Scene.hx 11 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447
  1. package h3d.scene;
  2. /**
  3. h3d.scene.Scene is the root class for a 3D scene. All root objects are added to it before being drawn on screen.
  4. **/
  5. class Scene extends Object implements h3d.IDrawable implements hxd.SceneEvents.InteractiveScene {
  6. /**
  7. The scene current camera.
  8. **/
  9. public var camera : h3d.Camera;
  10. /**
  11. The scene light system. Can be customized.
  12. **/
  13. public var lightSystem : LightSystem;
  14. /**
  15. The scene renderer. Can be customized.
  16. **/
  17. public var renderer(default,set) : Renderer;
  18. public var offsetX : Float = 0;
  19. public var offsetY : Float = 0;
  20. public var ratioX : Float = 1;
  21. public var ratioY : Float = 1;
  22. /**
  23. Adjust the position of the ray used to handle interactives.
  24. **/
  25. public var interactiveOffset : Float = 0;
  26. var ctx : RenderContext;
  27. var interactives : Array<Interactive>;
  28. @:allow(h3d.scene.Interactive)
  29. var events : hxd.SceneEvents;
  30. var hitInteractives : Array<Interactive>;
  31. var eventListeners : Array<hxd.Event -> Void>;
  32. var window : hxd.Window;
  33. #if debug
  34. public var checkPasses = true;
  35. #end
  36. /**
  37. Create a new scene. A default 3D scene is already available in `hxd.App.s3d`
  38. **/
  39. public function new( ?createRenderer = true, ?createLightSystem = true ) {
  40. super(null);
  41. window = hxd.Window.getInstance();
  42. eventListeners = [];
  43. hitInteractives = [];
  44. interactives = [];
  45. camera = new h3d.Camera();
  46. // update ratio before render (prevent first-frame difference)
  47. var engine = h3d.Engine.getCurrent();
  48. if( engine != null )
  49. camera.screenRatio = engine.width / engine.height;
  50. ctx = new RenderContext(this);
  51. if( createRenderer ) renderer = h3d.mat.MaterialSetup.current.createRenderer();
  52. if( createLightSystem ) lightSystem = h3d.mat.MaterialSetup.current.createLightSystem();
  53. }
  54. @:noCompletion @:dox(hide) public function setEvents(events) {
  55. this.events = events;
  56. }
  57. /**
  58. Add an event listener that will capture all events not caught by an h2d.Interactive
  59. **/
  60. public function addEventListener( f : hxd.Event -> Void ) {
  61. eventListeners.push(f);
  62. }
  63. /**
  64. Remove a previously added event listener, return false it was not part of our event listeners.
  65. **/
  66. public function removeEventListener( f : hxd.Event -> Void ) {
  67. for( e in eventListeners )
  68. if( Reflect.compareMethods(e, f) ) {
  69. eventListeners.remove(e);
  70. return true;
  71. }
  72. return false;
  73. }
  74. @:dox(hide) @:noCompletion
  75. public function dispatchListeners(event:hxd.Event) {
  76. for( l in eventListeners ) {
  77. l(event);
  78. if( !event.propagate ) break;
  79. }
  80. }
  81. function set_renderer(r) {
  82. renderer = r;
  83. if( r != null ) @:privateAccess r.ctx = ctx;
  84. return r;
  85. }
  86. function sortHitPointByCameraDistance( i1 : Interactive, i2 : Interactive ) {
  87. var z1 = i1.hitPoint.w;
  88. var z2 = i2.hitPoint.w;
  89. if( z1 > z2 )
  90. return -1;
  91. return 1;
  92. }
  93. @:dox(hide) @:noCompletion
  94. public function dispatchEvent( event : hxd.Event, to : hxd.SceneEvents.Interactive ) {
  95. var i : Interactive = cast to;
  96. // TODO : compute relX/Y/Z
  97. i.handleEvent(event);
  98. }
  99. @:dox(hide) @:noCompletion
  100. public function isInteractiveVisible( i : hxd.SceneEvents.Interactive ) {
  101. var o : Object = cast i;
  102. while( o != this ) {
  103. if( o == null || !o.visible ) return false;
  104. o = o.parent;
  105. }
  106. return true;
  107. }
  108. @:dox(hide) @:noCompletion
  109. public function handleEvent( event : hxd.Event, last : hxd.SceneEvents.Interactive ) {
  110. if( interactives.length == 0 )
  111. return null;
  112. if( hitInteractives.length == 0 ) {
  113. var x = event.relX - offsetX;
  114. var y = event.relY - offsetY;
  115. var width = ratioX * window.width;
  116. var height = ratioY * window.height;
  117. var screenX = (x / width - 0.5) * 2;
  118. var screenY = -(y / height - 0.5) * 2;
  119. var p0 = camera.unproject(screenX, screenY, 0);
  120. var p1 = camera.unproject(screenX, screenY, 1);
  121. var r = h3d.col.Ray.fromPoints(p0.toPoint(), p1.toPoint());
  122. if( interactiveOffset != 0 ) {
  123. r.px += r.lx * interactiveOffset;
  124. r.py += r.ly * interactiveOffset;
  125. r.pz += r.lz * interactiveOffset;
  126. }
  127. var saveR = r.clone();
  128. var priority = 0x80000000;
  129. for( i in interactives ) {
  130. if( i.priority < priority ) continue;
  131. var p : h3d.scene.Object = i;
  132. while( p != null && p.visible )
  133. p = p.parent;
  134. if( p != null ) continue;
  135. if( !i.isAbsoluteShape ) {
  136. var minv = i.getInvPos();
  137. r.transform(minv);
  138. }
  139. // check for NaN
  140. if( r.lx != r.lx ) {
  141. r.load(saveR);
  142. continue;
  143. }
  144. var hit = i.shape.rayIntersection(r, i.bestMatch);
  145. if( hit < 0 ) {
  146. r.load(saveR);
  147. continue;
  148. }
  149. var hitPoint = r.getPoint(hit);
  150. r.load(saveR);
  151. i.hitPoint.x = hitPoint.x;
  152. i.hitPoint.y = hitPoint.y;
  153. i.hitPoint.z = hitPoint.z;
  154. if( i.priority > priority ) {
  155. while( hitInteractives.length > 0 ) hitInteractives.pop();
  156. priority = i.priority;
  157. }
  158. hitInteractives.push(i);
  159. }
  160. if( hitInteractives.length == 0 )
  161. return null;
  162. if( hitInteractives.length > 1 ) {
  163. for( i in hitInteractives ) {
  164. var m = i.invPos;
  165. var wfactor = 0.;
  166. // adjust result with better precision
  167. if( i.preciseShape != null || !i.bestMatch ) {
  168. if( !i.isAbsoluteShape )
  169. r.transform(m);
  170. var hit = (i.preciseShape ?? i.shape).rayIntersection(r, true);
  171. if( hit > 0 ) {
  172. var hitPoint = r.getPoint(hit);
  173. i.hitPoint.x = hitPoint.x;
  174. i.hitPoint.y = hitPoint.y;
  175. i.hitPoint.z = hitPoint.z;
  176. } else
  177. wfactor = 1.;
  178. r.load(saveR);
  179. }
  180. var p = i.hitPoint.clone();
  181. p.w = 1;
  182. if( !i.isAbsoluteShape )
  183. p.transform3x4(i.absPos);
  184. p.project(camera.m);
  185. i.hitPoint.w = p.z + wfactor;
  186. }
  187. hitInteractives.sort(sortHitPointByCameraDistance);
  188. }
  189. hitInteractives.unshift(null);
  190. }
  191. while( hitInteractives.length > 0 ) {
  192. var i = hitInteractives.pop();
  193. if( i == null )
  194. return null;
  195. event.relX = i.hitPoint.x;
  196. event.relY = i.hitPoint.y;
  197. event.relZ = i.hitPoint.z;
  198. i.handleEvent(event);
  199. if( event.cancel ) {
  200. event.cancel = false;
  201. event.propagate = false;
  202. continue;
  203. }
  204. if( !event.propagate ) {
  205. while( hitInteractives.length > 0 ) hitInteractives.pop();
  206. }
  207. return i;
  208. }
  209. return null;
  210. }
  211. override function clone( ?o : Object ) {
  212. var s = o == null ? new Scene() : cast o;
  213. s.camera = camera.clone();
  214. super.clone(s);
  215. return s;
  216. }
  217. /**
  218. Free the GPU memory for this Scene and its children
  219. **/
  220. public function dispose() {
  221. if ( allocated )
  222. onRemove();
  223. ctx.dispose();
  224. if(renderer != null) {
  225. renderer.dispose();
  226. renderer = new Renderer();
  227. }
  228. }
  229. @:allow(h3d)
  230. function addEventTarget(i:Interactive) {
  231. if( interactives.indexOf(i) >= 0 ) throw "assert";
  232. interactives.push(i);
  233. }
  234. @:allow(h3d)
  235. function removeEventTarget(i:Interactive) {
  236. if( interactives.remove(i) ) {
  237. if( events != null ) @:privateAccess events.onRemove(i);
  238. hitInteractives.remove(i);
  239. }
  240. }
  241. /**
  242. Before render() or sync() are called, allow to set how much time has elapsed (in seconds) since the last frame in order to update scene animations.
  243. This is managed automatically by hxd.App
  244. **/
  245. public function setElapsedTime( elapsedTime ) {
  246. ctx.elapsedTime = elapsedTime;
  247. }
  248. /**
  249. Synchronize the scene without rendering, updating all objects and animations by the given amount of time, in seconds.
  250. **/
  251. public function syncOnly( et : Float ) {
  252. var engine = h3d.Engine.getCurrent();
  253. setElapsedTime(et);
  254. var t = engine.getCurrentTarget();
  255. if( t == null )
  256. camera.screenRatio = engine.width / engine.height;
  257. else
  258. camera.screenRatio = t.width / t.height;
  259. camera.update();
  260. ctx.start();
  261. syncRec(ctx);
  262. ctx.done();
  263. }
  264. /**
  265. Perform a rendering with `RendererContext.computingStatic=true`, allowing the computation of static shadow maps, etc.
  266. **/
  267. public function computeStatic() {
  268. var old = ctx.elapsedTime;
  269. ctx.elapsedTime = 0;
  270. ctx.computingStatic = true;
  271. render(h3d.Engine.getCurrent());
  272. ctx.computingStatic = false;
  273. ctx.elapsedTime = old;
  274. }
  275. /**
  276. Automatically called when the 3D context is lost
  277. **/
  278. public function onContextLost() {
  279. ctx.wasContextLost = true;
  280. }
  281. /**
  282. Render the scene on screen. Internal usage only.
  283. **/
  284. @:access(h3d.mat.Pass)
  285. @:access(h3d.scene.RenderContext)
  286. public function render( engine : h3d.Engine ) {
  287. if( !allocated )
  288. onAdd();
  289. var t = engine.getCurrentTarget();
  290. if( t == null )
  291. camera.screenRatio = engine.width / engine.height;
  292. else
  293. camera.screenRatio = t.width / t.height;
  294. camera.update();
  295. if( camera.rightHanded )
  296. engine.driver.setRenderFlag(CameraHandness,1);
  297. ctx.start();
  298. renderer.start();
  299. renderer.startEffects();
  300. #if sceneprof h3d.impl.SceneProf.begin("sync", ctx.frame); #end
  301. mark("sync");
  302. syncRec(ctx);
  303. #if sceneprof
  304. h3d.impl.SceneProf.end();
  305. h3d.impl.SceneProf.begin("emit", ctx.frame);
  306. #end
  307. mark("emit");
  308. emitRec(ctx);
  309. #if sceneprof h3d.impl.SceneProf.end(); #end
  310. var passes = [];
  311. var passIndex = -1;
  312. for ( passId in 0...ctx.passes.length ) {
  313. var curPass = ctx.passes[passId];
  314. if ( curPass == null )
  315. continue;
  316. var pobjs = ctx.cachedPassObjects[++passIndex];
  317. if( pobjs == null ) {
  318. pobjs = new Renderer.PassObjects();
  319. ctx.cachedPassObjects[passIndex] = pobjs;
  320. }
  321. pobjs.name = curPass.pass.name;
  322. pobjs.passes.init(curPass);
  323. passes.push(pobjs);
  324. }
  325. // send to rendered
  326. if( lightSystem != null ) {
  327. ctx.lightSystem = lightSystem;
  328. lightSystem.initLights(ctx);
  329. }
  330. renderer.process(passes);
  331. // check that passes have been rendered
  332. #if (debug && !editor)
  333. if( !ctx.computingStatic && checkPasses)
  334. for( p in passes )
  335. if( !p.rendered )
  336. trace("Pass " + p.name+" has not been rendered : don't know how to handle.");
  337. #end
  338. if( camera.rightHanded )
  339. engine.driver.setRenderFlag(CameraHandness,0);
  340. ctx.done();
  341. ctx.wasContextLost = false;
  342. for( i in 0...passIndex ) {
  343. var p = ctx.cachedPassObjects[i];
  344. p.name = null;
  345. p.passes.init(null);
  346. }
  347. }
  348. public dynamic function mark(name : String) {
  349. @:privateAccess renderer.mark(name);
  350. }
  351. var prevDB : h3d.mat.Texture;
  352. var prevEngine = null;
  353. /**
  354. Temporarily overrides the output render target. This is useful for picture-in-picture rendering,
  355. where the output render target has a different size from the window.
  356. `tex` must have a matching depthBuffer attached.
  357. Call `setOutputTarget()` after `render()` has been called.
  358. **/
  359. public function setOutputTarget( ?engine: h3d.Engine, ?tex : h3d.mat.Texture ) @:privateAccess {
  360. if(tex != null) {
  361. if(prevDB != null) throw "missing setOutputTarget()";
  362. engine.pushTarget(tex);
  363. engine.width = tex.width;
  364. engine.height = tex.height;
  365. prevDB = ctx.textures.defaultDepthBuffer;
  366. prevEngine = engine;
  367. ctx.textures.defaultDepthBuffer = tex.depthBuffer;
  368. }
  369. else {
  370. prevEngine.popTarget();
  371. prevEngine.width = prevDB.width;
  372. prevEngine.height = prevDB.height;
  373. ctx.textures.defaultDepthBuffer = prevDB;
  374. prevDB = null;
  375. prevEngine = null;
  376. }
  377. }
  378. public function getRenderCamera() {
  379. return ctx.camera;
  380. }
  381. }