PageRenderTime 68ms CodeModel.GetById 8ms RepoModel.GetById 0ms app.codeStats 0ms

/src/yage/scene/camera.d

https://bitbucket.org/JoeCoder/yage/
D | 469 lines | 271 code | 57 blank | 141 comment | 29 complexity | c9b91aefcb25635e96d00f19dc44e34c MD5 | raw file
Possible License(s): LGPL-2.0
  1. /**
  2. * Copyright: (c) 2005-2009 Eric Poggel
  3. * Authors: Eric Poggel
  4. * License: <a href="lgpl3.txt">LGPL v3</a>
  5. */
  6. module yage.scene.camera;
  7. import tango.math.Math;
  8. import tango.core.Thread;
  9. import tango.core.WeakRef;
  10. import yage.core.array;
  11. import yage.core.color;
  12. import yage.core.math.matrix;
  13. import yage.core.math.plane;
  14. import yage.core.math.vector;
  15. import yage.scene.light;
  16. import yage.scene.node;
  17. import yage.scene.scene;
  18. import yage.scene.visible;
  19. import yage.system.log;
  20. import yage.system.system;
  21. import yage.system.window;
  22. // new:
  23. import tango.time.Clock;
  24. import yage.resource.geometry;
  25. import yage.resource.material;
  26. import yage.scene.light;
  27. import yage.system.graphics.probe;
  28. import yage.scene.sound;
  29. import yage.resource.sound;
  30. import yage.scene.model; // temporary
  31. // TODO: Move these to Render?
  32. struct RenderCommand
  33. {
  34. Matrix transform;
  35. Geometry geometry;
  36. Material[] materialOverrides;
  37. private ubyte lightsLength;
  38. private LightNode[8] lights; // indices in the RenderScene's array of RenderLights
  39. LightNode[] getLights()
  40. { return lights[0..lightsLength];
  41. }
  42. void setLights(LightNode[] lights)
  43. { lightsLength = lights.length;
  44. for (int i=0; i<lights.length; i++)
  45. this.lights[i] = lights[i];
  46. }
  47. }
  48. // Everything in a scene seen by the Camera.
  49. struct RenderScene
  50. { Scene scene; // Is this used?
  51. ArrayBuilder!(RenderCommand) commands;
  52. ArrayBuilder!(LightNode) lights;
  53. }
  54. struct RenderList
  55. { RenderScene[] scenes; // one ArrayBuilder of commands for each scene to render.
  56. long timestamp;
  57. Matrix cameraInverse;
  58. }
  59. // Copies of SoundNode properties to provide lock-free access.
  60. struct SoundCommand
  61. { Sound sound;
  62. Vec3f worldPosition;
  63. Vec3f worldVelocity;
  64. float pitch;
  65. float volume;
  66. float radius;
  67. float intensity; // used internally for sorting
  68. float position; // playback position
  69. size_t id;
  70. SoundNode soundNode; // original SoundNode. Must be used behind lock!
  71. bool looping;
  72. bool reseek;
  73. }
  74. struct SoundList
  75. { ArrayBuilder!(SoundCommand) commands;
  76. long timestamp;
  77. Vec3f cameraPosition;
  78. Vec3f cameraRotation;
  79. Vec3f cameraVelocity;
  80. }
  81. /**
  82. * Without any rotation, the Camera looks in the direction of the -z axis.
  83. * TODO: More documentation. */
  84. class CameraNode : Node
  85. {
  86. float near = 1; /// The camera's near plane. Nothing closer than this will be rendered. The default is 1.
  87. float far = 100000; /// The camera's far plane. Nothing further away than this will be rendered. The default is 100,000.
  88. float fov = 45; /// The field of view of the camera, in degrees. The default is 45.
  89. float threshold = 1; /// Nodes must be at least this diameter in pixels or they won't be rendered.
  90. float aspectRatio = 1.25; /// The aspect ratio of the camera. This is normally set automatically in Render.scene() based on the size of the Render Target.
  91. package int currentXres; // Used internally for determining visibility
  92. package int currentYres;
  93. protected Plane[6] frustum;
  94. protected Vec3f frustumSphereCenter;
  95. protected float frustumSphereRadiusSquared;
  96. protected Plane[6] skyboxFrustum; // a special frustum with the camera centered at the origin of worldspace.
  97. protected static CameraNode listener; // Camera that plays audio.
  98. struct TripleBuffer(T)
  99. { T[3] lists;
  100. Object mutex;
  101. ubyte read=1;
  102. ubyte write=0;
  103. // Get a buffer for reading that is guaranteed to not currently being written.
  104. T getNextRead()
  105. { synchronized (mutex)
  106. { int next = 3-(read+write);
  107. if (lists[next].timestamp > lists[read].timestamp)
  108. read = next; // advance the read list only if what's available is newer.
  109. assert(read < 3);
  110. assert(read != write);
  111. return lists[read];
  112. }
  113. }
  114. // Get the next write buffer that is guaranteed to not currently being read
  115. private T* getNextWrite()
  116. { synchronized (mutex)
  117. { write = 3 - (read+write);
  118. assert(read < 3);
  119. assert(read != write);
  120. return &lists[write];
  121. }
  122. }
  123. }
  124. TripleBuffer!(SoundList) soundLists;
  125. TripleBuffer!(RenderList) renderLists;
  126. /**
  127. * Get a render list for the scene and each of the skyboxes this camera sees. */
  128. RenderList getRenderList()
  129. { return renderLists.getNextRead();
  130. }
  131. /**
  132. * List of SoundCommands that this camera can hear, in order from loudest to most quiet. */
  133. SoundList getSoundList()
  134. { return soundLists.getNextRead();
  135. }
  136. package void updateSoundCommands()
  137. {
  138. assert(Thread.getThis() == scene.getUpdateThread());
  139. assert(getListener() is this);
  140. SoundList* list = soundLists.getNextWrite();
  141. list.commands.reserveAndClear(); // reset content
  142. Vec3f wp = getWorldPosition();
  143. scope allSounds = scene.getAllSounds();
  144. int i;
  145. foreach (soundNode; allSounds) // Make a deep copy of the scene's sounds
  146. {
  147. if (!soundNode.paused() && soundNode.getSound())
  148. { //Log.write(2);
  149. SoundCommand command;
  150. command.intensity = soundNode.getVolumeAtPosition(wp);
  151. if (command.intensity > 0.002) // A very quiet sound, arbitrary number
  152. {
  153. command.sound = soundNode.getSound();
  154. command.worldPosition = soundNode.getWorldPosition();
  155. command.worldVelocity = soundNode.getWorldVelocity();
  156. command.pitch = soundNode.pitch;
  157. command.volume = soundNode.volume;
  158. command.radius = soundNode.radius;
  159. command.looping = soundNode.looping;
  160. command.position = soundNode.tell();
  161. command.soundNode = soundNode;
  162. command.reseek = soundNode.reseek;
  163. soundNode.reseek = false; // the value has been consumed
  164. addSorted!(SoundCommand, float)(list.commands, command, false, (SoundCommand s) { return s.intensity; }); // fails!!!
  165. }
  166. }
  167. i++;
  168. }
  169. //Log.write("camera ", list.commands.length, " ", allSounds.length);
  170. list.timestamp = Clock.now().ticks(); // 100-nanosecond precision
  171. list.cameraPosition = getWorldPosition();
  172. list.cameraRotation = getWorldRotation();
  173. list.cameraVelocity = getWorldVelocity();
  174. }
  175. /*
  176. * Cameras update a list of RenderCommands for every Scene they see.
  177. * This is typically one Scene and its Skybox. */
  178. package void updateRenderCommands()
  179. {
  180. assert(Thread.getThis() == scene.getUpdateThread());
  181. currentXres = Window.getInstance().getHeight(); // TODO Break dependance on Window.
  182. currentYres = Window.getInstance().getHeight();
  183. void writeCommands(Node root, Plane[] frustum, LightNode[] lights, ref ArrayBuilder!(RenderCommand) result)
  184. {
  185. // Test this node for visibility
  186. VisibleNode vnode = cast(VisibleNode)root;
  187. if (vnode && vnode.getVisible())
  188. {
  189. /* // inlining doesn't help performance any.
  190. ModelNode m = cast(ModelNode)vnode;
  191. if (m)
  192. { Vec3f wp = m.getWorldPosition();
  193. if (scene !is scene)
  194. wp += getWorldPosition();
  195. if (isVisible(wp, m.getRadius()))
  196. {
  197. RenderCommand rc;
  198. rc.transform = m.getWorldTransform().scale(m.getSize());
  199. rc.geometry = m.getModel();
  200. rc.materialOverrides = m.materialOverrides;
  201. rc.setLights(m.getLights(lights, 8));
  202. result.append(rc);
  203. }
  204. } else */
  205. vnode.getRenderCommands(this, lights, result);
  206. }
  207. // Recurse through and render children.
  208. foreach (Node c; root.getChildren())
  209. writeCommands(c, frustum, lights, result);
  210. }
  211. // TODO: If this takes 1ms, it's still 15ms longer until this is called a second time that the renderer
  212. // can use this info! Maybe we need a way to say we're done writing?
  213. // e.g. renderlists.performWrite(void (ref RenderList list) { ... });
  214. auto list = renderLists.getNextWrite();
  215. // Iterate through skyboxes, clearing out the RenderList commands and refilling them
  216. Scene currentScene = scene;
  217. int i=0;
  218. list.cameraInverse = getWorldTransform().inverse(); // must occur before the loop below
  219. list.timestamp = Clock.now().ticks(); // 100-nanosecond precision
  220. do {
  221. // Ensure we have a command set for this scene
  222. if (list.scenes.length <= i)
  223. list.scenes.length = i+1;
  224. else // clear out previous commands
  225. list.scenes[i].commands.reserveAndClear(); // reset content
  226. RenderScene* rs = &list.scenes[i];
  227. rs.scene = currentScene;
  228. // Add lights that affect what this camera can see.
  229. int j;
  230. scope allLights = currentScene.getAllLights();
  231. rs.lights.length = allLights.length;
  232. foreach (ref light; allLights) // Make a deep copy of the scene's lights
  233. { rs.lights.data[j] = light.clone(false, rs.lights.data[j]); // to prevent locking when the render thread uses them.
  234. rs.lights.data[j].setPosition(light.getWorldPosition());
  235. rs.lights.data[j].cameraSpacePosition = light.getWorldPosition().transform(list.cameraInverse);
  236. if (light.type == LightNode.Type.SPOT)
  237. rs.lights.data[j].setRotation(light.getWorldRotation());
  238. rs.lights.data[j].worldPosition = rs.lights.data[j].position;
  239. rs.lights.data[j].worldDirty = false; // hack to prevent it from being recalculated.
  240. j++;
  241. }
  242. writeCommands(currentScene, frustum, rs.lights.data, list.scenes[i].commands);
  243. i++;
  244. } while ((currentScene = currentScene.skyBox) !is null); // iterate through skyboxes
  245. }
  246. /**
  247. * Construct */
  248. this()
  249. { renderLists.mutex = new Object();
  250. soundLists.mutex = new Object();
  251. if (!listener)
  252. listener = this;
  253. }
  254. this(Node parent)
  255. { this();
  256. if (parent)
  257. { mixin(Sync!("scene"));
  258. parent.addChild(this);
  259. }
  260. }
  261. /**
  262. * Set the current listener to null if the listener is this CameraNode. */
  263. override void dispose()
  264. { if (listener && listener == this)
  265. listener = null;
  266. }
  267. /**
  268. * Sound playback can occur from only one camera at a time.
  269. * setListener() can be used to make this CameraNode the listener.
  270. * getListener() will return, from all CameraNodes, the CameraNode that is the current listener.
  271. * When there is no listener (listener is null), the first camera
  272. * added to a scene becomes the listener (not the first CameraNode created).
  273. * The listener is set to null when the current listener is removed from its scene. */
  274. static CameraNode getListener()
  275. { return listener;
  276. }
  277. void setListener() /// ditto
  278. { listener = this;
  279. }
  280. /*
  281. * Unfinished!
  282. * This function casts a ray from the Camera's view into the scene
  283. * and returns all Nodes that it collides with.
  284. * This will not return any Nodes from the Scene's skybox.
  285. * Params:
  286. * position = Coordinates between 0 and 1 in the camera's near view frustum.
  287. * includeBoundingSphere = If true, collision tests will only be performed against Object's bounding
  288. * sphere and not on a per-polygon basis. The bounding sphere is determined by VisibleNode.getRadius().
  289. * Returns: An unsorted array of matching Nodes. */
  290. VisibleNode[] getNodesAtCoordinate(Vec2f position, bool includeBoundingSphere=false)
  291. { mixin(Sync!("scene"));
  292. return null;
  293. }
  294. /*
  295. * Unfinished!
  296. * Get the 3d coordinate at the 2d screen coordinate at a distance of z from the camera.
  297. * Params:
  298. * x = screen coordinate between 0 and 1, where 0 is the left side of the camrea's view, and 1 is the right.
  299. * x = screen coordinate between 0 and 1, where 0 is the left side of the camrea's view, and 1 is the right. */
  300. Vec3f getWorldCoordinate(Vec2f screenCoordinate, float z)
  301. { mixin(Sync!("scene"));
  302. Matrix clip;
  303. Matrix modl;
  304. // TODO!
  305. return Vec3f();
  306. }
  307. /*
  308. * Calculate a 6-plane view frutum based on the orientation of the camera.*/
  309. Plane[] getFrustum(bool skybox=false)
  310. { mixin(Sync!("scene"));
  311. return skybox ? skyboxFrustum : frustum;
  312. }
  313. /**
  314. * Is this point/sphere within the view area of the camera and large enough to be drawn?
  315. * Params:
  316. * point = Point in 3d space, in world coordinates
  317. * radius = Radius of this point (sphere).
  318. * frustum = Use this array of 6 planes as the view frustum instead of recalculating it.*/
  319. bool isVisible(Vec3f point, float radius, bool skybox=false)
  320. { mixin(Sync!("scene"));
  321. Plane[] frustum = skybox ? skyboxFrustum : this.frustum;
  322. /*
  323. // See if it's inside the sphere. Doesn't provide any benefit.
  324. if (!skybox)
  325. if ((point - frustumSphereCenter).length2() > frustumSphereRadiusSquared)
  326. { //Log.write("early escape!");
  327. return false;
  328. }
  329. */
  330. // See if it's inside the frustum
  331. float nr = -radius;
  332. foreach_reverse (f; frustum)
  333. if (f.x*point.x +f.y*point.y + f.z*point.z + f.d < nr) // plane distance-to-point function, expanded in-line.
  334. return false;
  335. // See if it's large enough to be drawn
  336. //Vec3f* cameraPosition = cast(Vec3f*)transform_abs.v[12..15].ptr;
  337. float distance2 = (getWorldPosition() - point).length2();
  338. return radius*radius*currentYres*currentYres > distance2*threshold*threshold;
  339. }
  340. /*
  341. * Update the scene's list of cameras and add/remove listener reference to this CameraNode
  342. * This should be protected, but making it anything but public causes it not to be called.
  343. * Most likely a D bug. */
  344. override public void ancestorChange(Node old_ancestor)
  345. { super.ancestorChange(old_ancestor); // must be called first so scene is set.
  346. Scene old_scene = old_ancestor ? old_ancestor.getScene() : null;
  347. if (scene !is old_scene)
  348. { if (old_scene)
  349. old_scene.removeCamera(this);
  350. if (scene) // if scene changed.
  351. { scene.addCamera(this);
  352. if (!listener)
  353. listener = this;
  354. }
  355. }
  356. // no scene or scene didn't change
  357. if (!scene && listener == this)
  358. listener = null;
  359. }
  360. /*
  361. * Update the frustums when the camera moves. */
  362. override protected void calcWorld()
  363. { //Log.write(rotation);
  364. //Log.write(worldRotation);
  365. super.calcWorld();
  366. // Create the clipping matrix from the modelview and projection matrices
  367. Matrix projection = Matrix.createProjection(fov*3.1415927/180f, aspectRatio, near, far);
  368. //Log.write("camera ", worldRotation);
  369. Matrix model = Matrix.compose(worldPosition, worldRotation, worldScale).inverse();
  370. (model*projection).getFrustum(frustum);
  371. model = worldRotation.toMatrix().inverse(); // shed all but the rotation values
  372. (model*projection).getFrustum(skyboxFrustum);
  373. /*
  374. // TODO: Use frustum optimizations from: http://www.flipcode.com/archives/Frustum_Culling.shtml
  375. // calculate the radius of the frustum sphere
  376. float fViewLen = far - near;
  377. // use some trig to find the height of the frustum at the far plane
  378. float fHeight = fViewLen * tan(fov*3.1415927/180 * 0.5f);
  379. // with an aspect ratio of 1, the width will be the same
  380. float fWidth = fHeight;
  381. // halfway point between near/far planes starting at the origin and extending along the z axis
  382. Vec3f P = Vec3f(0.0f, 0.0f, near + fViewLen * 0.5f);
  383. // the calculate far corner of the frustum
  384. Vec3f Q = Vec3f(fWidth, fHeight, fViewLen);
  385. // the vector between P and Q
  386. Vec3f vDiff = P - Q;
  387. // the radius becomes the length of this vector
  388. frustumSphereRadiusSquared = vDiff.length() * .5;
  389. frustumSphereRadiusSquared *= frustumSphereRadiusSquared;
  390. // get the look vector of the camera from the view matrix
  391. Vec3f vLookVector = Vec3f(0, 0, -1);
  392. auto worldTransform = Matrix.compose(worldPosition, worldRotation, worldScale);
  393. vLookVector = vLookVector.rotate(worldTransform);
  394. //m_mxView.LookVector(&vLookVector);
  395. // calculate the center of the sphere
  396. frustumSphereCenter = worldPosition + (vLookVector * (fViewLen * 0.5f) + near);
  397. //Log.write(frustumSphereCenter, frustumSphereRadiusSquared);
  398. */
  399. }
  400. }