Source: Rendering.js

  1. /**
  2. * The rendering object, which has two main functionalities:
  3. * - render the surface
  4. * - offer drag-able elements, to control light and seedline.
  5. * @class
  6. */
  7. function Rendering(canvas) {
  8. this.renderer = null;
  9. this.camera = null;
  10. this.controls = null;
  11. this.scene = null;
  12. this.cloudScene = null;
  13. this.mouse = new THREE.Vector2();
  14. this.raycaster = new THREE.Raycaster();
  15. this.offset = new THREE.Vector3();
  16. this.material = null;
  17. this.lightSource = null;
  18. this.materialDepth = null;
  19. this.INTERSECTED = null;
  20. this.SELECTED = null;
  21. this.plane = null;
  22. this.objects = [];
  23. this.canvas = canvas;
  24. this.rtAttribute = null;
  25. this.rtDepth = null;
  26. this.sceneSQ = null;
  27. this.cameraSQ = null;
  28. this.materialSQ = null;
  29. this.surface = null;
  30. this.seedLine = null;
  31. this.seedLineStartSphere = null;
  32. this.seedLineEndSphere = null;
  33. this.seedLineMesh = null;
  34. this.colored = false;
  35. this.animate = function() {
  36. requestAnimationFrame( this.animate.bind(this) );
  37. this.controls.update();
  38. }
  39. /**
  40. * Renders the the surface, the seed line and the scene.
  41. */
  42. this.render = function() {
  43. var needUpdate = false;
  44. if (!this.rtDepth ||
  45. this.rtDepth.width != this.canvas.clientWidth ||
  46. this.rtDepth.height != this.canvas.clientHeight) {
  47. if (this.rtDepth)
  48. this.rtDepth.dispose();
  49. this.rtDepth = new THREE.WebGLRenderTarget(
  50. this.canvas.clientWidth,
  51. this.canvas.clientHeight, {
  52. minFilter: THREE.NearestFilter,
  53. magFilter: THREE.NearestFilter,
  54. format: THREE.RGBAFormat,
  55. type: THREE.FloatType
  56. } );
  57. needUpdate = true;
  58. }
  59. if (!this.rtAttribute ||
  60. this.rtAttribute.width != this.canvas.clientWidth ||
  61. this.rtAttribute.height != this.canvas.clientHeight) {
  62. if (this.rtAttribute)
  63. this.rtAttribute.dispose();
  64. this.rtAttribute = new THREE.WebGLRenderTarget(
  65. this.canvas.clientWidth,
  66. this.canvas.clientHeight, {
  67. minFilter: THREE.NearestFilter,
  68. magFilter: THREE.NearestFilter,
  69. format: THREE.RGBAFormat,
  70. type: THREE.FloatType
  71. } );
  72. needUpdate = true;
  73. }
  74. this.renderer.clear();
  75. this.renderer.clearTarget(this.rtDepth, true, true, true);
  76. this.renderer.clearTarget(this.rtAttribute, true, true, true);
  77. // cloud to texture
  78. this.cloud.material = this.materialDepth;
  79. this.renderer.render(this.cloudScene, this.camera, this.rtDepth);
  80. this.cloud.material = this.material;
  81. this.material.uniforms.screenWidth.value = this.canvas.clientWidth;
  82. this.material.uniforms.screenHeight.value = this.canvas.clientHeight;
  83. this.material.uniforms.depthMap.value = this.rtDepth;
  84. this.material.uniforms.lightPosition.value = this.lightSource.position.clone();
  85. this.material.uniforms.colored.value = this.colored;
  86. this.renderer.render(this.cloudScene, this.camera, this.rtAttribute);
  87. this.materialSQ.uniforms.texture.value = this.rtAttribute;
  88. this.renderer.render(this.sceneSQ, this.cameraSQ);
  89. // rest
  90. this.renderer.render( this.scene, this.camera );
  91. }
  92. /**
  93. * Callback when the window is resized.
  94. */
  95. this.onWindowResize = function() {
  96. this.camera.aspect = window.innerWidth / window.innerHeight;
  97. this.camera.updateProjectionMatrix();
  98. this.renderer.setSize( window.innerWidth, window.innerHeight );
  99. this.render();
  100. }
  101. /**
  102. * Callback, when the mouse is moved. If there is a selected object, the object is moved.
  103. */
  104. this.onDocumentMouseMove = function(event) {
  105. event.preventDefault();
  106. this.mouse.x = ( event.clientX / window.innerWidth ) * 2 - 1;
  107. this.mouse.y = - ( event.clientY / window.innerHeight ) * 2 + 1;
  108. this.raycaster.setFromCamera(this.mouse, this.camera);
  109. if ( this.SELECTED ) {
  110. var intersects = this.raycaster.intersectObject( this.plane );
  111. var newPosition = intersects[ 0 ].point.sub( this.offset );
  112. newPosition.x = Math.max(newPosition.x, -0.5);
  113. newPosition.y = Math.max(newPosition.y, -0.5);
  114. newPosition.z = Math.max(newPosition.z, -0.5);
  115. newPosition.x = Math.min(newPosition.x, 0.5);
  116. newPosition.y = Math.min(newPosition.y, 0.5);
  117. newPosition.z = Math.min(newPosition.z, 0.5);
  118. this.SELECTED.position.copy( newPosition );
  119. if (this.SELECTED === this.seedLineStartSphere) {
  120. this.seedLine.start.copy(this.seedLineStartSphere.position);
  121. this.seedLine.start.addScalar(0.5);
  122. this.surface.calculate(this.surface.volume, this.seedLine, this.surface.numIt);
  123. this.refresh(this.surface);
  124. }
  125. if (this.SELECTED === this.seedLineEndSphere) {
  126. this.seedLine.end.copy(this.seedLineEndSphere.position);
  127. this.seedLine.end.addScalar(0.5);
  128. this.surface.calculate(this.surface.volume, this.seedLine, this.surface.numIt);
  129. this.refresh(this.surface);
  130. }
  131. this.render();
  132. return;
  133. }
  134. var intersects = this.raycaster.intersectObjects( this.objects );
  135. if ( intersects.length > 0 ) {
  136. if ( this.INTERSECTED != intersects[ 0 ].object ) {
  137. if ( this.INTERSECTED )
  138. this.INTERSECTED.material.color.setHex( this.INTERSECTED.currentHex );
  139. this.INTERSECTED = intersects[ 0 ].object;
  140. this.INTERSECTED.currentHex = this.INTERSECTED.material.color.getHex();
  141. this.plane.position.copy( this.INTERSECTED.position );
  142. this.plane.lookAt( this.camera.position );
  143. }
  144. this.canvas.style.cursor = 'pointer';
  145. } else {
  146. if ( this.INTERSECTED )
  147. this.INTERSECTED.material.color.setHex( this.INTERSECTED.currentHex );
  148. this.INTERSECTED = null;
  149. this.canvas.style.cursor = 'auto';
  150. }
  151. this.render();
  152. }
  153. /**
  154. * Callback for mouse down event. If there is an object under the mouse, it is marked
  155. * as selected, and will be moved, when the mouse is moved.
  156. */
  157. this.onDocumentMouseDown = function( event ) {
  158. event.preventDefault();
  159. var vector = new THREE.Vector3( this.mouse.x, this.mouse.y, 0.5 ).unproject( this.camera );
  160. var raycaster = new THREE.Raycaster( this.camera.position, vector.sub( this.camera.position ).normalize() );
  161. var intersects = raycaster.intersectObjects( this.objects );
  162. if ( intersects.length > 0 ) {
  163. this.controls.enabled = false;
  164. this.SELECTED = intersects[ 0 ].object;
  165. var intersects = raycaster.intersectObject( this.plane );
  166. this.offset.copy( intersects[ 0 ].point ).sub( this.plane.position );
  167. this.canvas.style.cursor = 'move';
  168. }
  169. }
  170. /**
  171. * Callback for mouse up event. Removes an object from being selected.
  172. */
  173. this.onDocumentMouseUp = function( event ) {
  174. event.preventDefault();
  175. this.controls.enabled = true;
  176. if ( this.INTERSECTED ) {
  177. this.plane.position.copy( this.INTERSECTED.position );
  178. this.SELECTED = null;
  179. }
  180. this.canvas.style.cursor = 'auto';
  181. }
  182. /**
  183. * This method creates quads for each particle, which are normal aligned to the surface normal.
  184. *
  185. * TODO replace by - not yet supported - geometry shader.
  186. */
  187. this.points2quads = function(positions, normals, d) {
  188. var result = {
  189. positions: new Float32Array(positions.length * 6 * 3),
  190. uvs: new Float32Array(positions.length * 6 * 2)
  191. };
  192. var up = new THREE.Vector3(0, 0, 1);
  193. var side = new THREE.Vector3(1, 0, 0);
  194. var size = d*2;
  195. for (var i = 0; i < positions.length; i++) {
  196. var normal = normals[i];
  197. var position = positions[i];
  198. var v1 = new THREE.Vector3();
  199. var v2 = new THREE.Vector3();
  200. if (normal.z <= normal.x || normal.z <= normal.y) {
  201. v1.crossVectors(normal, up);
  202. } else {
  203. v1.crossVectors(normal, side);
  204. }
  205. v1 = v1.normalize();
  206. v2.crossVectors(v1, normal);
  207. v2 = v2.normalize();
  208. v1.multiplyScalar(size);
  209. v2.multiplyScalar(size);
  210. var p1 = new THREE.Vector3();
  211. var p2 = new THREE.Vector3();
  212. var p3 = new THREE.Vector3();
  213. var p4 = new THREE.Vector3();
  214. p1.addVectors(position, v1);
  215. p2.addVectors(position, v2);
  216. p3.subVectors(position, v1);
  217. p4.subVectors(position, v2);
  218. result.positions[i*18 + 0] = p1.x;
  219. result.positions[i*18 + 1] = p1.y;
  220. result.positions[i*18 + 2] = p1.z;
  221. result.positions[i*18 + 3] = p2.x;
  222. result.positions[i*18 + 4] = p2.y;
  223. result.positions[i*18 + 5] = p2.z;
  224. result.positions[i*18 + 6] = p3.x;
  225. result.positions[i*18 + 7] = p3.y;
  226. result.positions[i*18 + 8] = p3.z;
  227. result.positions[i*18 + 9] = p1.x;
  228. result.positions[i*18 + 10] = p1.y;
  229. result.positions[i*18 + 11] = p1.z;
  230. result.positions[i*18 + 12] = p3.x;
  231. result.positions[i*18 + 13] = p3.y;
  232. result.positions[i*18 + 14] = p3.z;
  233. result.positions[i*18 + 15] = p4.x;
  234. result.positions[i*18 + 16] = p4.y;
  235. result.positions[i*18 + 17] = p4.z;
  236. result.uvs[i*12 + 0] = 0;
  237. result.uvs[i*12 + 1] = 1;
  238. result.uvs[i*12 + 2] = 1;
  239. result.uvs[i*12 + 3] = 1;
  240. result.uvs[i*12 + 4] = 1;
  241. result.uvs[i*12 + 5] = 0;
  242. result.uvs[i*12 + 6] = 1;
  243. result.uvs[i*12 + 7] = 1;
  244. result.uvs[i*12 + 8] = 0;
  245. result.uvs[i*12 + 9] = 0;
  246. result.uvs[i*12 + 10] = 0;
  247. result.uvs[i*12 + 11] = 1;
  248. }
  249. return result;
  250. }
  251. this.array2buffered = function(array, n) {
  252. var result = new Float32Array(array.length * n * 3);
  253. for (var i = 0; i < array.length; i++) {
  254. for (var j = 0; j < n; j++) {
  255. result[i*3*n + j*3 + 0] = array[i].x;
  256. result[i*3*n + j*3 + 1] = array[i].y;
  257. result[i*3*n + j*3 + 2] = array[i].z;
  258. }
  259. }
  260. return result;
  261. }
  262. /**
  263. * Initializes the rendering of the surface.
  264. */
  265. this.initSurface = function() {
  266. this.cloudScene = new THREE.Scene();
  267. this.materialDepth = new THREE.ShaderMaterial( {
  268. attributes: {
  269. customUV: { type: 'v2', value: [] }
  270. },
  271. vertexShader: document.getElementById( 'vertexShaderDepth' ).textContent,
  272. fragmentShader: document.getElementById( 'fragmentShaderDepth' ).textContent,
  273. side: THREE.DoubleSide,
  274. blending: THREE.NoBlending,
  275. transparent: false,
  276. depthWrite: true,
  277. depthTest: true
  278. } );
  279. this.material = new THREE.ShaderMaterial( {
  280. attributes: {
  281. customNormal: { type: 'v3', value: [] },
  282. customColor: { type: 'v3', value: [] },
  283. customUV: { type: 'v2', value: [] }
  284. },
  285. uniforms: {
  286. lightPosition: { type: 'v3', value: new THREE.Vector3() },
  287. depthMap: { type: 't', value: this.rtDepth },
  288. screenWidth: { type: 'f', value: null },
  289. screenHeight: { type: 'f', value: null },
  290. colored: { type: 'f', value: this.colored * 1.0 }
  291. },
  292. vertexShader: document.getElementById( 'vertexShader' ).textContent,
  293. fragmentShader: document.getElementById( 'fragmentShader' ).textContent,
  294. side: THREE.DoubleSide,
  295. blending: THREE.AdditiveBlending,
  296. transparent: true,
  297. depthWrite: false,
  298. depthTest: false
  299. } );
  300. var quadsResult = this.points2quads(this.surface.positions, this.surface.normals, this.seedLine.interval);
  301. var quadPositions = quadsResult.positions;
  302. var quadUVs = quadsResult.uvs;
  303. var quadNormals = this.array2buffered(this.surface.normals, 6);
  304. var quadColors = this.array2buffered(this.surface.colors, 6);
  305. var geometry = new THREE.BufferGeometry();
  306. geometry.addAttribute( 'position', new THREE.BufferAttribute(quadPositions, 3 ));
  307. geometry.addAttribute( 'customUV', new THREE.BufferAttribute(quadUVs, 2 ));
  308. geometry.addAttribute( 'customColor', new THREE.BufferAttribute(quadColors, 3 ));
  309. geometry.addAttribute( 'customNormal', new THREE.BufferAttribute(quadNormals, 3 ));
  310. this.cloud = new THREE.Mesh(geometry, this.material);
  311. this.cloud.position.addScalar(-0.5);
  312. this.cloudScene.add(this.cloud);
  313. }
  314. /**
  315. * Initializes the rendering of the seed line.
  316. */
  317. this.initSeedLine = function() {
  318. if(this.seedLineMesh)
  319. this.scene.remove( this.seedLineMesh );
  320. var seedLineGeometry = new THREE.Geometry();
  321. seedLineGeometry.vertices.push(
  322. this.seedLine.start,
  323. this.seedLine.end
  324. );
  325. var seedLineMaterial = new THREE.LineBasicMaterial({color: 0x00ff00, depthTest: false });
  326. this.seedLineMesh = new THREE.Line(seedLineGeometry, seedLineMaterial);
  327. this.seedLineMesh.position.addScalar(-0.5);
  328. this.scene.add(this.seedLineMesh);
  329. }
  330. /**
  331. * Initializes the scene (except surface and seed line).
  332. */
  333. this.initScene = function() {
  334. this.scene = new THREE.Scene();
  335. this.objects = [];
  336. // sq
  337. this.materialSQ = new THREE.ShaderMaterial({
  338. uniforms: {
  339. texture: { type: 't', value: this.rtAttribute }
  340. },
  341. vertexShader: document.getElementById( 'vertexShaderSQ' ).textContent,
  342. fragmentShader: document.getElementById( 'fragmentShaderSQ' ).textContent
  343. });
  344. var SQ = new THREE.Mesh(new THREE.PlaneBufferGeometry(2,2,0), this.materialSQ);
  345. this.sceneSQ = new THREE.Scene();
  346. this.sceneSQ.add(SQ);
  347. this.cameraSQ = new THREE.Camera();
  348. // box
  349. var boxGeometry = new THREE.BoxGeometry( 1, 1, 1 );
  350. var boxMesh = new THREE.Mesh( boxGeometry, new THREE.MeshBasicMaterial({color: 0xffffff, depthTest: false}) );
  351. var boxEdges = new THREE.EdgesHelper( boxMesh, 0xffffff );
  352. //boxEdges.material.linewidth = 2;
  353. boxEdges.material = boxMesh.material;
  354. this.scene.add( boxEdges );
  355. //plane
  356. this.plane = new THREE.Mesh(
  357. new THREE.PlaneBufferGeometry( 200, 200, 8, 8),
  358. new THREE.MeshBasicMaterial( { color: 0x000000, opacity: 0.25, transparent: true, side: THREE.DoubleSide } )
  359. );
  360. this.plane.visible = false;
  361. this.plane.rotation.x = -Math.PI/2;
  362. this.scene.add( this.plane );
  363. // light source
  364. if (!this.lightSource) {
  365. var lightSourceGeometry = new THREE.SphereGeometry(0.02,20,20);
  366. var lightSourceMaterial = new THREE.MeshBasicMaterial( {color: 0xffffff, depthTest: false} );
  367. this.lightSource = new THREE.Mesh(lightSourceGeometry, lightSourceMaterial);
  368. this.lightSource.position.x = -0.5;
  369. }
  370. this.scene.add(this.lightSource);
  371. this.objects.push(this.lightSource);
  372. // seedLine controls
  373. var seedLineStartSphereGeometry = new THREE.SphereGeometry(0.01,20,20);
  374. var seedLineStartSphereMaterial = new THREE.MeshBasicMaterial( {color: 0x00ff00, depthTest: false} );
  375. this.seedLineStartSphere = new THREE.Mesh(seedLineStartSphereGeometry, seedLineStartSphereMaterial);
  376. this.seedLineStartSphere.position.copy(this.seedLine.start).subScalar(0.5);
  377. this.scene.add(this.seedLineStartSphere);
  378. this.objects.push(this.seedLineStartSphere);
  379. var seedLineEndSphereGeometry = new THREE.SphereGeometry(0.01,20,20);
  380. var seedLineEndSphereMaterial = new THREE.MeshBasicMaterial( {color: 0x00ff00, depthTest: false} );
  381. this.seedLineEndSphere = new THREE.Mesh(seedLineEndSphereGeometry, seedLineEndSphereMaterial);
  382. this.seedLineEndSphere.position.copy(this.seedLine.end).subScalar(0.5);
  383. this.scene.add(this.seedLineEndSphere);
  384. this.objects.push(this.seedLineEndSphere);
  385. }
  386. /**
  387. * Initializes the renderer.
  388. * @param {StreamSurface} surface The surface to be rendered.
  389. * @param {SeedLine} seedLine The seed line, which was used to sample the particles (also rendered).
  390. */
  391. this.init = function(surface, seedLine) {
  392. this.surface = surface;
  393. this.seedLine = seedLine;
  394. this.resetCamera();
  395. this.initSurface();
  396. this.initScene();
  397. this.initSeedLine();
  398. // renderer
  399. this.renderer = new THREE.WebGLRenderer( { canvas: this.canvas, alpha: true, preserveDrawingBuffer: true } );
  400. this.renderer.autoClear = false;
  401. this.renderer.setPixelRatio( window.devicePixelRatio );
  402. this.renderer.setSize( this.canvas.width, this.canvas.height );
  403. this.renderer.sortObjects = false;
  404. this.render();
  405. window.addEventListener( 'resize', this.onWindowResize.bind(this), false );
  406. this.renderer.domElement.addEventListener( 'mousemove', this.onDocumentMouseMove.bind(this), false );
  407. this.renderer.domElement.addEventListener( 'mousedown', this.onDocumentMouseDown.bind(this), false );
  408. this.renderer.domElement.addEventListener( 'mouseup', this.onDocumentMouseUp.bind(this), false );
  409. }
  410. /**
  411. * Resets the scene according to the new surface.
  412. * @param {StreamSurface} surface The new surface.
  413. */
  414. this.refresh = function(surface) {
  415. this.initSurface();
  416. this.initSeedLine();
  417. this.render();
  418. }
  419. /**
  420. * Sets the camera back to its initial position.
  421. */
  422. this.resetCamera = function() {
  423. this.camera = new THREE.PerspectiveCamera( 60, this.canvas.width/this.canvas.height, 0.001, 1000 );
  424. this.camera.position.y = 2;
  425. this.camera.up = new THREE.Vector3(0,0,-1);
  426. this.controls = new THREE.TrackballControls( this.camera, this.canvas );
  427. this.controls.rotateSpeed = 1.0;
  428. this.controls.zoomSpeed = 1.2;
  429. this.controls.panSpeed = 0.8;
  430. this.controls.noZoom = false;
  431. this.controls.noPan = false;
  432. this.controls.staticMoving = true;
  433. this.controls.dynamicDampingFactor = 0.3;
  434. this.controls.keys = [ 65, 83, 68 ];
  435. this.controls.addEventListener( 'change', this.render.bind(this) );
  436. }
  437. }