Scene.hx 13 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470
  1. package hide.comp;
  2. class Scene extends Component implements h3d.IDrawable {
  3. static var UID = 0;
  4. var id = ++UID;
  5. var window : hxd.Window;
  6. public var canvas : js.html.CanvasElement;
  7. var hmdCache = new Map<String, hxd.fmt.hmd.Library>();
  8. var texCache = new Map<String, h3d.mat.Texture>();
  9. var pathsMap = new Map<String, String>();
  10. var cleanup = new Array<Void->Void>();
  11. var defaultCamera : h3d.Camera;
  12. var listeners = new Array<Float -> Void>();
  13. public var config : hide.Config;
  14. public var engine : h3d.Engine;
  15. public var width(get, never) : Int;
  16. public var height(get, never) : Int;
  17. public var s2d : h2d.Scene;
  18. public var s3d : h3d.scene.Scene;
  19. public var sevents : hxd.SceneEvents;
  20. public var speed : Float = 1.0;
  21. public var visible(default, null) : Bool = true;
  22. public var editor : hide.comp.SceneEditor;
  23. var unFocusedTime = 0.;
  24. public function new(config, parent, el) {
  25. super(parent,el);
  26. this.config = config;
  27. element.addClass("hide-scene-container");
  28. canvas = cast new Element("<canvas class='hide-scene' style='width:100%;height:100%'/>").appendTo(element)[0];
  29. canvas.addEventListener("mousemove",function(_) canvas.focus());
  30. canvas.addEventListener("mouseleave",function(_) canvas.blur());
  31. canvas.oncontextmenu = function(e){
  32. e.stopPropagation();
  33. e.preventDefault();
  34. return false;
  35. };
  36. untyped canvas.__scene = this;
  37. haxe.Timer.delay(delayedInit,0); // wait canvas added to window
  38. }
  39. public function dispose() {
  40. for( c in cleanup )
  41. c();
  42. cleanup = [];
  43. ide.unregisterUpdate(sync);
  44. @:privateAccess s2d.window.removeResizeEvent(s2d.checkResize);
  45. engine.dispose();
  46. @:privateAccess engine.driver = null;
  47. untyped canvas.__scene = null;
  48. canvas = null;
  49. if( h3d.Engine.getCurrent() == engine ) @:privateAccess h3d.Engine.CURRENT = null;
  50. untyped js.Browser.window.$_ = null; // jquery can sometimes leak s2d
  51. @:privateAccess haxe.NativeStackTrace.lastError = null; // possible leak there
  52. window.dispose();
  53. }
  54. public function addListener(f) {
  55. listeners.push(f);
  56. }
  57. public function removeListener(f) {
  58. for( f2 in listeners )
  59. if( Reflect.compareMethods(f,f2) ) {
  60. listeners.remove(f2);
  61. break;
  62. }
  63. }
  64. function delayedInit() {
  65. canvas.id = "webgl";
  66. window = @:privateAccess new hxd.Window(canvas);
  67. window.propagateKeyEvents = true;
  68. window.setCurrent();
  69. engine = @:privateAccess new h3d.Engine();
  70. @:privateAccess engine.resCache.set(Scene, this);
  71. engine.backgroundColor = 0xFF111111;
  72. canvas.id = null;
  73. engine.onResized = function() {
  74. if( s2d == null ) return;
  75. setCurrent();
  76. visible = engine.width > 32 && engine.height > 32; // 32x32 when hidden !
  77. s2d.scaleMode = Resize; // setter call
  78. onResize();
  79. };
  80. engine.onReady = function() {
  81. if( engine.driver == null ) return;
  82. new Element(canvas).on("resize", function() {
  83. window.setCurrent();
  84. @:privateAccess window.checkResize();
  85. });
  86. setCurrent();
  87. hxd.Key.initialize();
  88. s2d = new h2d.Scene();
  89. s3d = new h3d.scene.Scene();
  90. sevents = new hxd.SceneEvents(window);
  91. sevents.addScene(s2d);
  92. sevents.addScene(s3d);
  93. @:privateAccess window.checkResize();
  94. onReady();
  95. onResize();
  96. sync();
  97. ide.registerUpdate(sync);
  98. };
  99. engine.init();
  100. }
  101. function get_width() {
  102. return engine.width;
  103. }
  104. function get_height() {
  105. return engine.height;
  106. }
  107. public function init( ?root : h3d.scene.Object ) {
  108. var autoHide : Array<String> = config.get("scene.autoHide");
  109. function initRec( obj : h3d.scene.Object ) {
  110. for(n in autoHide)
  111. if(obj.name != null && obj.name.indexOf(n) == 0)
  112. obj.visible = false;
  113. for( o in obj )
  114. initRec(o);
  115. }
  116. if( root == null ) {
  117. root = s3d;
  118. engine.backgroundColor = Std.parseInt("0x"+config.get("scene.backgroundColor").substr(1)) | 0xFF000000;
  119. }
  120. initRec(root);
  121. }
  122. public function setCurrent() {
  123. engine.setCurrent();
  124. window.setCurrent();
  125. }
  126. function checkCurrent() {
  127. if( h3d.Engine.getCurrent() != engine )
  128. throw "Invalid current engine : use setCurrent() first";
  129. }
  130. function sync() {
  131. if( new Element(canvas).parents("html").length == 0 ) {
  132. dispose();
  133. return;
  134. }
  135. if( !visible || pendingCount > 0)
  136. return;
  137. var dt = hxd.Timer.tmod * speed / 60;
  138. if( !Ide.inst.isFocused ) {
  139. // refresh at 1FPS
  140. unFocusedTime += dt;
  141. if( unFocusedTime < 1 ) return;
  142. unFocusedTime -= 1;
  143. dt = 1;
  144. } else
  145. unFocusedTime = 0;
  146. setCurrent();
  147. sevents.checkEvents();
  148. s2d.setElapsedTime(dt);
  149. s3d.setElapsedTime(dt);
  150. for( f in listeners )
  151. f(dt);
  152. onUpdate(dt);
  153. engine.render(this);
  154. }
  155. var loadQueue : Array<Void->Void> = [];
  156. var pendingCount : Int = 0;
  157. function loadTextureData( img : hxd.res.Image, onReady : h3d.mat.Texture -> Void, ?target : h3d.mat.Texture ) {
  158. if( !img.getFormat().useAsyncDecode ) {
  159. // immediate read
  160. if( target == null )
  161. target = img.toTexture();
  162. else {
  163. var pix = img.getPixels();
  164. target.resize(pix.width, pix.height);
  165. target.uploadPixels(pix);
  166. }
  167. if( onReady != null ) {
  168. onReady(target);
  169. onReady = null;
  170. }
  171. return target;
  172. }
  173. if( target == null ) {
  174. var size = img.getSize();
  175. target = new h3d.mat.Texture(size.width,size.height);
  176. target.clear(0x102030);
  177. target.flags.set(Loading);
  178. }
  179. if( pendingCount < 10 ) {
  180. pendingCount++;
  181. _loadTextureData(img,function() {
  182. target.flags.unset(Loading);
  183. pendingCount--;
  184. var f = loadQueue.shift();
  185. if( f != null ) f();
  186. onReady(target);
  187. }, target);
  188. } else {
  189. loadQueue.push(loadTextureData.bind(img,onReady,target));
  190. }
  191. return target;
  192. }
  193. function _loadTextureData( img : hxd.res.Image, onReady : Void -> Void, t : h3d.mat.Texture ) {
  194. var path = ide.getPath(img.entry.path);
  195. var img = new Element('<img src="${ide.getUnCachedUrl(path)}" crossorigin="anonymous"/>');
  196. function onLoaded() {
  197. if( engine.driver == null ) return;
  198. setCurrent();
  199. var bmp : js.html.ImageElement = cast img[0];
  200. t.resize(bmp.width, bmp.height);
  201. untyped bmp.ctx = { getImageData : function(_) return bmp, canvas : { width : 0, height : 0 } };
  202. engine.driver.uploadTextureBitmap(t, cast bmp, 0, 0);
  203. t.realloc = onLoaded;
  204. t.flags.unset(Loading);
  205. @:privateAccess if( t.waitLoads != null ) {
  206. var arr = t.waitLoads;
  207. t.waitLoads = null;
  208. for( f in arr ) f();
  209. }
  210. if( onReady != null ) {
  211. onReady();
  212. onReady = null;
  213. }
  214. }
  215. img.on("load", onLoaded);
  216. function onChange() {
  217. img.attr("src", ide.getUnCachedUrl(path));
  218. }
  219. ide.fileWatcher.register( path, onChange, true, element );
  220. cleanup.push(function() { ide.fileWatcher.unregister( path, onChange ); });
  221. }
  222. public function listAnims( path : String ) {
  223. var config = hide.Config.loadForFile(ide, path);
  224. var dirs : Array<String> = config.get("hmd.animPaths");
  225. if( dirs == null ) dirs = [];
  226. dirs = [for( d in dirs ) ide.resourceDir + d];
  227. var parts = path.split("/");
  228. parts.pop();
  229. dirs.unshift(ide.getPath(parts.join("/")));
  230. var anims = [];
  231. var lib = loadHMD(path, false);
  232. if( lib.header.animations.length > 0 )
  233. anims.push(ide.getPath(path));
  234. for( dir in dirs ) {
  235. var dir = dir;
  236. if( StringTools.endsWith(dir, "/") ) dir = dir.substr(0,-1);
  237. for( f in try sys.FileSystem.readDirectory(dir) catch( e : Dynamic ) [] ) {
  238. var file = f.toLowerCase();
  239. if( StringTools.startsWith(f,"Anim_") && (StringTools.endsWith(file,".hmd") || StringTools.endsWith(file,".fbx")) )
  240. anims.push(dir+"/"+f);
  241. }
  242. }
  243. return anims;
  244. }
  245. public function animationName( path : String ) {
  246. var name = path.split("/").pop();
  247. if( StringTools.startsWith(name, "Anim_") )
  248. name = name.substr(5);
  249. name = name.substr(0, -4);
  250. if( StringTools.endsWith(name,"_loop") )
  251. name = name.substr(0,-5);
  252. return name;
  253. }
  254. public function loadModel( path : String, mainScene = false, reload = false ) {
  255. checkCurrent();
  256. var lib = loadHMD(path, false, reload);
  257. return lib.makeObject(loadTexture.bind(path));
  258. }
  259. public function loadAnimation( path : String ) {
  260. var lib = loadHMD(path,true);
  261. return lib.loadAnimation();
  262. }
  263. function resolvePathImpl( modelPath : String, filePath : String ) {
  264. inline function exists(path) return sys.FileSystem.exists(path);
  265. var fullPath = ide.getPath(filePath);
  266. if( exists(fullPath) )
  267. return fullPath;
  268. // swap drive letter
  269. if( fullPath.charAt(1) == ":" && fullPath.charAt(0) != ide.projectDir.charAt(0) ) {
  270. fullPath = ide.projectDir.charAt(0) + fullPath.substr(1);
  271. if( exists(fullPath) )
  272. return fullPath;
  273. }
  274. if( modelPath == null )
  275. return null;
  276. filePath = filePath.split("\\").join("/");
  277. modelPath = ide.getPath(modelPath);
  278. var path = modelPath.split("/");
  279. path.pop();
  280. var relToModel = path.join("/") + "/" + filePath.split("/").pop();
  281. if( exists(relToModel) )
  282. return relToModel;
  283. return null;
  284. }
  285. function resolvePath(modelPath : String, filePath : String) {
  286. var key = modelPath + ":" + filePath;
  287. var p = pathsMap.get(key);
  288. if(p != null)
  289. return p;
  290. p = resolvePathImpl(modelPath, filePath);
  291. pathsMap.set(key, p);
  292. return p;
  293. }
  294. public function loadTextureDotPath( path : String, ?onReady ) {
  295. var path = path.split(".").join("/");
  296. var t = resolvePath(null, path + ".png");
  297. if( t == null )
  298. t = resolvePath(null, path + ".jpg");
  299. if( t == null )
  300. t = resolvePath(null, path + ".jpeg");
  301. if( t == null )
  302. t = path;
  303. return loadTexture("", t, onReady);
  304. }
  305. public function loadTexture( modelPath : String, texturePath : String, ?onReady : h3d.mat.Texture -> Void ) {
  306. checkCurrent();
  307. var path = resolvePath(modelPath, texturePath);
  308. if( path == null ) {
  309. ide.error("Could not load texture " + { modelPath : modelPath, texturePath : texturePath });
  310. return null;
  311. }
  312. var t = texCache.get(path);
  313. if( t != null ) {
  314. if( onReady != null ) haxe.Timer.delay(onReady.bind(t), 1);
  315. return t;
  316. }
  317. var relPath = StringTools.startsWith(path, ide.resourceDir) ? path.substr(ide.resourceDir.length+1) : path;
  318. var res = try hxd.res.Loader.currentInstance.load(relPath) catch( e : hxd.res.NotFound ) {
  319. var bytes = sys.io.File.getBytes(path);
  320. hxd.res.Any.fromBytes(path, bytes);
  321. };
  322. if( onReady == null ) onReady = function(_) {};
  323. try {
  324. t = loadTextureData(res.toImage(), onReady, t);
  325. t.setName( ide.makeRelative(path));
  326. texCache.set(path, t);
  327. } catch( error : Dynamic ) {
  328. throw "Could not load texure " + texturePath + ":\n" + Std.string(error);
  329. };
  330. return t;
  331. }
  332. function loadHMD( path : String, isAnimation : Bool, reload = false ) {
  333. checkCurrent();
  334. var fullPath = ide.getPath(path);
  335. var key = fullPath;
  336. var hmd = hmdCache.get(key);
  337. if( !reload && hmd != null )
  338. return hmd;
  339. var relPath = StringTools.startsWith(path, ide.resourceDir) ? path.substr(ide.resourceDir.length+1) : path;
  340. var e;
  341. if( reload )
  342. @:privateAccess hxd.res.Loader.currentInstance.cache.remove(path);
  343. if( ide.isDebugger )
  344. e = hxd.res.Loader.currentInstance.load(relPath);
  345. else
  346. e = try hxd.res.Loader.currentInstance.load(relPath) catch( e : hxd.res.NotFound ) null;
  347. if( e == null ) {
  348. var data = sys.io.File.getBytes(fullPath);
  349. if( data.get(0) != 'H'.code ) {
  350. var hmdOut = new hxd.fmt.fbx.HMDOut(fullPath);
  351. hmdOut.absoluteTexturePath = (e == null);
  352. hmdOut.loadFile(data);
  353. var hmd = hmdOut.toHMD(null, !isAnimation);
  354. var out = new haxe.io.BytesOutput();
  355. new hxd.fmt.hmd.Writer(out).write(hmd);
  356. data = out.getBytes();
  357. }
  358. e = hxd.res.Any.fromBytes(path, data);
  359. }
  360. hmd = e.toModel().toHmd();
  361. if (!reload && e != null) {
  362. e.watch(function() {
  363. if (sys.FileSystem.exists(ide.getPath(e.entry.path))) {
  364. var lib = e.toModel().toHmd();
  365. hmdCache.set(key, lib);
  366. editor.onResourceChanged(lib);
  367. }
  368. });
  369. cleanup.push(function() {
  370. e.watch(null);
  371. });
  372. }
  373. hmdCache.set(key, hmd);
  374. return hmd;
  375. }
  376. public function resetCamera( ?obj : h3d.scene.Object, distanceFactor = 1. ) {
  377. if( defaultCamera != null ) {
  378. s3d.camera.load(defaultCamera);
  379. return;
  380. }
  381. if( obj == null ) obj = s3d;
  382. var b = obj.getBounds();
  383. if( b.isEmpty() )
  384. return;
  385. var dx = Math.max(Math.abs(b.xMax),Math.abs(b.xMin));
  386. var dy = Math.max(Math.abs(b.yMax),Math.abs(b.yMin));
  387. var dz = Math.max(Math.abs(b.zMax),Math.abs(b.zMin));
  388. var dist = Math.max(Math.max(dx * 6, dy * 6), dz * 4) * distanceFactor;
  389. var ang = Math.PI / 4;
  390. var zang = Math.PI * 0.4;
  391. s3d.camera.pos.set(Math.sin(zang) * Math.cos(ang) * dist, Math.sin(zang) * Math.sin(ang) * dist, Math.cos(zang) * dist);
  392. s3d.camera.target.set(0, 0, (b.zMax + b.zMin) * 0.5);
  393. }
  394. public function render( e : h3d.Engine ) {
  395. s3d.render(e);
  396. s2d.render(e);
  397. }
  398. public dynamic function onUpdate(dt:Float) {
  399. }
  400. public dynamic function onReady() {
  401. }
  402. public dynamic function onResize() {
  403. }
  404. public static function getNearest( e : Element ) : Scene {
  405. while( e.length > 0 ) {
  406. var c : Dynamic = e.find("canvas")[0];
  407. if( c != null && c.__scene != null )
  408. return c.__scene;
  409. e = e.parent();
  410. }
  411. return null;
  412. }
  413. public static function getCurrent() : Scene {
  414. return @:privateAccess h3d.Engine.getCurrent().resCache.get(Scene);
  415. }
  416. }