ThreeJsRenderer.js 15 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541
  1. /**
  2. * Not written as a NodeJs module yet, because this would require to use browserify
  3. * to make it available in the browser, while it is onlly useful in the browser anyway.
  4. *
  5. * @copyright Bart McLeod 2016, [email protected]
  6. * @author Bart McLeod / http://spaceweb.nl/
  7. */
  8. if ( 'undefined' === typeof VrmlParser ) {
  9. VrmlParser = {};
  10. }
  11. if ( 'undefined' === typeof VrmlParser.Renderer ) {
  12. VrmlParser.Renderer = {};
  13. }
  14. VrmlParser.Renderer.ThreeJsRenderer = function () {
  15. };
  16. VrmlParser.Renderer.ThreeJsRenderer.prototype = {
  17. REVISION: 1,
  18. constructor: VrmlParser.Renderer.ThreeJsRenderer,
  19. log: function () {
  20. console.log.apply(console, arguments);
  21. },
  22. warn: function () {
  23. console.warn.apply(console, arguments);
  24. },
  25. error: function () {
  26. console.error.apply(console, arguments);
  27. },
  28. /**
  29. * @param Object nodeTree
  30. * @param THREE.Scene scene
  31. */
  32. render: function (nodeTree, scene) {
  33. console.log('VrmlParser.Renderer.ThreeJsRenderer ' + this.REVISION);
  34. /**
  35. * Colors ar return by the parser as vector{x, y, z}.
  36. * We want them as color{r, g, b}.
  37. * @param vector
  38. */
  39. var convertVectorToColor = function (vector) {
  40. return {r: vector.x, g: vector.y, b: vector.z};
  41. }
  42. /**
  43. * Interpolates colors a and b following their relative distance
  44. * expressed by t.
  45. *
  46. * @param float a
  47. * @param float b
  48. * @param float t
  49. * @returns {Color}
  50. */
  51. var interpolateColors = function (a, b, t) {
  52. a = convertVectorToColor(a);
  53. b = convertVectorToColor(b);
  54. var deltaR = a.r - b.r;
  55. var deltaG = a.g - b.g;
  56. var deltaB = a.b - b.b;
  57. var c = new THREE.Color();
  58. c.r = a.r - t * deltaR;
  59. c.g = a.g - t * deltaG;
  60. c.b = a.b - t * deltaB;
  61. return c;
  62. };
  63. /**
  64. * Vertically paints the faces interpolating between the
  65. * specified colors at the specified angels. This is used for the Background
  66. * node, but could be applied to other nodes with multiple faces as well.
  67. *
  68. * When used with the Background node, default is directionIsDown is true if
  69. * interpolating the skyColor down from the Zenith. When interpolationg up from
  70. * the Nadir i.e. interpolating the groundColor, the directionIsDown is false.
  71. *
  72. * The first angle is never specified, it is the Zenith (0 rad). Angles are specified
  73. * in radians. The geometry is thought a sphere, but could be anything. The color interpolation
  74. * is linear along the Y axis in any case.
  75. *
  76. * You must specify one more color than you have angles at the beginning of the colors array.
  77. * This is the color of the Zenith (the top of the shape).
  78. *
  79. * @param geometry
  80. * @param radius
  81. * @param angles
  82. * @param colors
  83. * @param boolean directionIsDown Whether to work bottom up or top down.
  84. */
  85. var paintFaces = function (geometry, radius, angles, colors, directionIsDown) {
  86. // @todo: while this is all neat and jolly, we really should declare each variable on its own line
  87. var f, n, p, vertexIndex, color;
  88. var direction = directionIsDown ? 1 : -1;
  89. var faceIndices = ['a', 'b', 'c', 'd'];
  90. var coord = [], aColor, bColor, t = 1, A = {}, B = {}, applyColor = false, colorIndex;
  91. for ( var k = 0; k < angles.length; k++ ) {
  92. var vec = {};
  93. // push the vector at which the color changes
  94. vec.y = direction * ( Math.cos(angles[k]) * radius );
  95. vec.x = direction * ( Math.sin(angles[k]) * radius );
  96. coord.push(vec);
  97. }
  98. // painting the colors on the faces
  99. for ( var i = 0; i < geometry.faces.length; i++ ) {
  100. f = geometry.faces[i];
  101. n = ( f instanceof THREE.Face3 ) ? 3 : 4;
  102. for ( var j = 0; j < n; j++ ) {
  103. vertexIndex = f[faceIndices[j]];
  104. p = geometry.vertices[vertexIndex];
  105. for ( var index = 0; index < colors.length; index++ ) {
  106. // linear interpolation between aColor and bColor, calculate proportion
  107. // A is previous point (angle)
  108. if ( index === 0 ) {
  109. A.x = 0;
  110. A.y = directionIsDown ? radius : -1 * radius;
  111. } else {
  112. A.x = coord[index - 1].x;
  113. A.y = coord[index - 1].y;
  114. }
  115. // B is current point (angle)
  116. B = coord[index];
  117. if ( undefined !== B ) {
  118. // p has to be between the points A and B which we interpolate
  119. applyColor = directionIsDown ? p.y <= A.y && p.y > B.y : p.y >= A.y && p.y < B.y;
  120. if ( applyColor ) {
  121. bColor = colors[index + 1];
  122. aColor = colors[index];
  123. // below is simple linear interpolation
  124. t = Math.abs(p.y - A.y) / ( A.y - B.y );
  125. // to make it faster, you can only calculate this if the y coord changes, the color is the same for points with the same y
  126. color = interpolateColors(aColor, bColor, t);
  127. f.vertexColors[j] = color;
  128. }
  129. } else if ( undefined === f.vertexColors[j] ) {
  130. colorIndex = directionIsDown ? colors.length - 1 : 0;
  131. f.vertexColors[j] = convertVectorToColor(colors[colorIndex]);
  132. }
  133. }
  134. }
  135. }
  136. };
  137. /**
  138. * Utility to quickly and safely check if a given property is
  139. * present and set on a node.
  140. *
  141. * @param string property
  142. * @return boolean
  143. */
  144. var has = function (property) {
  145. // note that this pull the object the 'has' method is assigned to into this functions scope
  146. return ('undefined' !== typeof this[property] && null !== this[property]);
  147. };
  148. /**
  149. * Convert VRML node representation into a ThreeJS 3D object.
  150. *
  151. * @param object node VRML node as parsed by the VrmlParser.
  152. * @returns {THREE.Object3D}
  153. */
  154. var parseNode = function (node) {
  155. if ( undefined === node.node ) {
  156. // not a node, for now, ignore it
  157. return false;
  158. }
  159. // for syntactic sugar only:
  160. node.has = has;
  161. // this will be the returned ThreeJS object returned from parseNode, if not overwritten
  162. var object = new THREE.Object3D();
  163. switch ( node.node ) {
  164. case 'Group':
  165. case 'Transform':
  166. if ( node.has('children') ) {
  167. // sugar
  168. node.children.has = has;
  169. // children can be a node or an array
  170. if ( node.children.has('node') ) {
  171. // children is a node
  172. object.add(parseNode(node.children));
  173. } else if ( node.children.has('length') ) {
  174. // children should be an array
  175. for ( var i = 0; i < node.children.length; i++ ) {
  176. var child = node.children[i];
  177. var threeJsObj = parseNode(child);
  178. if ( false !== threeJsObj ) {
  179. object.add(threeJsObj);
  180. }
  181. }
  182. }
  183. }
  184. if ( node.has('translation') ) {
  185. var t = node.translation;
  186. object.position.set(t.x, t.y, t.z);
  187. }
  188. if ( node.has('rotation') ) {
  189. var r = node.rotation;
  190. object.quaternion.setFromAxisAngle(new THREE.Vector3(r.x, r.y, r.z), r.radians);
  191. }
  192. if ( node.has('scale') ) {
  193. var s = node.scale;
  194. object.scale.set(s.x, s.y, s.z);
  195. }
  196. break;
  197. case 'Shape':
  198. object = new THREE.Mesh();
  199. if ( node.has('geometry') ) {
  200. object.geometry = parseNode(node.geometry);
  201. }
  202. if ( node.has('appearance') ) {
  203. var appearance = node.appearance;
  204. // sugar
  205. appearance.has = has;
  206. if ( appearance.has('material') ) {
  207. var vrmlMaterial = appearance.material;
  208. // sugar
  209. vrmlMaterial.has = has;
  210. var material = new THREE.MeshPhongMaterial();
  211. if ( vrmlMaterial.has('diffuseColor') ) {
  212. var materialColor = convertVectorToColor(vrmlMaterial.diffuseColor);
  213. material.color.setRGB(materialColor.r, materialColor.g, materialColor.b);
  214. }
  215. if ( vrmlMaterial.has('emissiveColor') ) {
  216. var emissiveColor = convertVectorToColor(vrmlMaterial.emissiveColor);
  217. material.emissive.setRGB(emissiveColor.r, emissiveColor.g, emissiveColor.b);
  218. }
  219. if ( vrmlMaterial.has('specularColor') ) {
  220. var specularColor = convertVectorToColor(vrmlMaterial.specularColor);
  221. material.specular.setRGB(specularColor.r, specularColor.g, specularColor.b);
  222. }
  223. if ( vrmlMaterial.has('transparency') ) {
  224. // transparency is opposite of opacity
  225. material.opacity = Math.abs(1 - vrmlMaterial.transparency);
  226. material.transparent = true;
  227. }
  228. }
  229. object.material = material;
  230. if ( 'ImageTexture' === vrmlMaterial.node ) {
  231. var textureName = vrmlMaterial.textureName;
  232. if ( textureName ) {
  233. object.material.name = textureName[1];
  234. object.material.map = textureLoader.load(texturePath + textureName[1]);
  235. }
  236. }
  237. if ( 'IndexedFaceSet' === node.geometry.node ) {
  238. //if ( false === node.geometry.node.solid ) {
  239. object.material.side = THREE.DoubleSide;
  240. // }
  241. }
  242. }
  243. break;
  244. case 'Background':
  245. object = false;
  246. var segments = 20;
  247. // sky (full sphere):
  248. var radius = 2e4;
  249. var skyGeometry = new THREE.SphereGeometry(radius, segments, segments);
  250. var skyMaterial = new THREE.MeshBasicMaterial({fog: false, side: THREE.BackSide});
  251. if ( node.skyColor.length > 1 ) {
  252. paintFaces(skyGeometry, radius, node.skyAngle, node.skyColor, true);
  253. skyMaterial.vertexColors = THREE.VertexColors;
  254. } else {
  255. var color = convertVectorToColor(node.skyColor[0]);
  256. skyMaterial.color.setRGB(color.r, color.g, color.b);
  257. }
  258. scene.add(new THREE.Mesh(skyGeometry, skyMaterial));
  259. // ground (half sphere):
  260. if ( node.has('groundColor') ) {
  261. radius = 1.2e4;
  262. var groundGeometry = new THREE.SphereGeometry(radius, segments, segments, 0, 2 * Math.PI, 0.5 * Math.PI, 1.5 * Math.PI);
  263. var groundMaterial = new THREE.MeshBasicMaterial({
  264. fog: false,
  265. side: THREE.BackSide,
  266. vertexColors: THREE.VertexColors
  267. });
  268. paintFaces(groundGeometry, radius, node.groundAngle, node.groundColor, false);
  269. scene.add(new THREE.Mesh(groundGeometry, groundMaterial));
  270. }
  271. break;
  272. case 'Box':
  273. var s = node.size;
  274. object = new THREE.BoxGeometry(s.x, s.y, s.z);
  275. break;
  276. case 'Cylinder':
  277. object = new THREE.CylinderGeometry(node.radius, node.radius, node.height);
  278. break;
  279. case 'Cone':
  280. object = new THREE.CylinderGeometry(node.topRadius, node.bottomRadius, node.height);
  281. break;
  282. case 'Sphere':
  283. object = new THREE.SphereGeometry(node.radius);
  284. break;
  285. case 'IndexedFaceSet':
  286. object = new THREE.Geometry();
  287. var indexes, uvIndexes, uvs;
  288. var vec;
  289. if ( node.has('texCoord') ) {
  290. uvs = node.texCoord.points;
  291. }
  292. if ( node.has('coord') ) {
  293. for ( var k = 0, l = node.coord.point.length; k < l; k++ ) {
  294. var point = node.coord.point[k];
  295. vec = new THREE.Vector3(point.x, point.y, point.z);
  296. object.vertices.push(vec);
  297. }
  298. }
  299. var skip = 0;
  300. // some shapes only have vertices for use in other shapes
  301. if ( node.has('coordIndex') ) {
  302. // read this: http://math.hws.edu/eck/cs424/notes2013/16_Threejs_Advanced.html
  303. for ( var i = 0, j = node.coordIndex.length; i < j; i++ ) {
  304. indexes = node.coordIndex[i];
  305. if ( node.has('texCoordIndex') ) {
  306. uvIndexes = node.texCoordIndex[i];
  307. }
  308. // vrml support multipoint indexed face sets (more then 3 vertices). You must calculate the composing triangles here
  309. skip = 0;
  310. // Face3 only works with triangles, but IndexedFaceSet allows shapes with more then three vertices, build them of triangles
  311. while ( indexes.length >= 3 && skip < ( indexes.length - 2 ) ) {
  312. var face = new THREE.Face3(
  313. indexes[0],
  314. indexes[skip + (node.ccw ? 1 : 2)],
  315. indexes[skip + (node.ccw ? 2 : 1)],
  316. null // normal, will be added later
  317. // todo: pass in the color, if a color index is present
  318. );
  319. if ( uvs && uvIndexes ) {
  320. object.faceVertexUvs [0].push([
  321. new THREE.Vector2(
  322. uvs[uvIndexes[0]].x,
  323. uvs[uvIndexes[0]].y
  324. ),
  325. new THREE.Vector2(
  326. uvs[uvIndexes[skip + (node.ccw ? 1 : 2)]].x,
  327. uvs[uvIndexes[skip + (node.ccw ? 1 : 2)]].y
  328. ),
  329. new THREE.Vector2(
  330. uvs[uvIndexes[skip + (node.ccw ? 2 : 1)]].x,
  331. uvs[uvIndexes[skip + (node.ccw ? 2 : 1)]].y
  332. )
  333. ]);
  334. }
  335. skip++;
  336. object.faces.push(face);
  337. }
  338. }
  339. }
  340. object.computeFaceNormals();
  341. //object.computeVertexNormals(); // does not show
  342. object.computeBoundingSphere();
  343. break;
  344. }
  345. if ( false !== object ) {
  346. if ( undefined !== object.userData ) {
  347. // keep the original VRML node for reference
  348. object.userData.originalVrmlNode = node;
  349. }
  350. if ( node.has('name') ) {
  351. object.name = node.name;
  352. } else if ( node.has('node') ) {
  353. object.name = node.node;
  354. }
  355. object.castShadow = true;
  356. object.receiveShadow = true;
  357. }
  358. return object;
  359. };
  360. for ( var n = 0; n < nodeTree.length; n++ ) {
  361. var childNode = parseNode(nodeTree[n]);
  362. if ( false !== childNode ) {
  363. scene.add(childNode);
  364. }
  365. }
  366. console.log(scene);
  367. // @todo: parse nodeTree.nodeDefinitions
  368. // @todo: parse nodeTree.routes
  369. }
  370. };