VRMLLoader.js 18 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601602603604605606607608
  1. /**
  2. * @author mrdoob / http://mrdoob.com/
  3. */
  4. THREE.VRMLLoader = function () {};
  5. THREE.VRMLLoader.prototype = {
  6. constructor: THREE.VRMLLoader,
  7. isRecordingPoints: false,
  8. isRecordingFaces: false,
  9. points: [],
  10. indexes : [],
  11. load: function ( url, callback ) {
  12. var scope = this;
  13. var request = new XMLHttpRequest();
  14. request.addEventListener( 'load', function ( event ) {
  15. var object = scope.parse( event.target.responseText );
  16. scope.dispatchEvent( { type: 'load', content: object } );
  17. }, false );
  18. request.addEventListener( 'progress', function ( event ) {
  19. scope.dispatchEvent( { type: 'progress', loaded: event.loaded, total: event.total } );
  20. }, false );
  21. request.addEventListener( 'error', function () {
  22. scope.dispatchEvent( { type: 'error', message: 'Couldn\'t load URL [' + url + ']' } );
  23. }, false );
  24. request.open( 'GET', url, true );
  25. request.send( null );
  26. },
  27. parse: function ( data ) {
  28. var parseV1 = function ( lines, scene ) {
  29. console.warn( 'VRML V1.0 not supported yet' );
  30. };
  31. var parseV2 = function ( lines, scene ) {
  32. var defines = {};
  33. var float3_pattern = /([\d\.\+\-e]+),?\s+([\d\.\+\-e]+),?\s+([\d\.\+\-e]+)/;
  34. var parseProperty = function (node, line) {
  35. var parts = [];
  36. var part;
  37. var property = {};
  38. var fieldName;
  39. /**
  40. * Expression for matching relevant information, such as a name or value, but not the separators
  41. * @type {RegExp}
  42. */
  43. var regex = /[^\s,\[\]]+/g;
  44. var point;
  45. var index;
  46. while (null != ( part = regex.exec(line) ) ) {
  47. parts.push(part[0]);
  48. }
  49. fieldName = parts[0];
  50. if (fieldName === 'point') {
  51. // start recording points
  52. this.isRecordingPoints = true;
  53. this.points = [];
  54. }
  55. if (fieldName === 'coordIndex') {
  56. // start recording faces
  57. this.isRecordingFaces = true;
  58. this.indexes = [];
  59. }
  60. if (this.isRecordingFaces) {
  61. // the parts hold the indexes as strings
  62. if (parts.length > 0) {
  63. index = [];
  64. for (var ind = 0;ind < parts.length; ind++) {
  65. // the part should either be positive integer or -1
  66. if (!/(-?\d+)/.test( parts[ind]) ) {
  67. continue;
  68. }
  69. // end of current face
  70. if (parts[ind] === "-1") {
  71. if (index.length > 0) {
  72. this.indexes.push(index);
  73. }
  74. // start new one
  75. index = [];
  76. } else {
  77. index.push(parseInt( parts[ind]) );
  78. }
  79. }
  80. }
  81. // end
  82. if (/]/.exec(line)) {
  83. this.isRecordingFaces = false;
  84. node.coordIndex = this.indexes;
  85. }
  86. } else if (this.isRecordingPoints) {
  87. parts = float3_pattern.exec(line);
  88. // parts may be empty on first and last line
  89. if (null != parts) {
  90. point = {
  91. x: parseFloat(parts[1]),
  92. y: parseFloat(parts[2]),
  93. z: parseFloat(parts[3])
  94. };
  95. this.points.push(point);
  96. }
  97. // end
  98. if (/]/.exec(line)) {
  99. this.isRecordingPoints = false;
  100. node.points = this.points;
  101. }
  102. } else if ( parts[parts.length -1] !== 'NULL' && fieldName !== 'children') {
  103. switch (fieldName) {
  104. case 'diffuseColor':
  105. case 'emissiveColor':
  106. case 'specularColor':
  107. case 'color':
  108. if (parts.length != 4) {
  109. console.warn('Invalid color format detected for ' + fieldName );
  110. break;
  111. }
  112. property = {
  113. 'r' : parseFloat(parts[1]),
  114. 'g' : parseFloat(parts[2]),
  115. 'b' : parseFloat(parts[3])
  116. }
  117. break;
  118. case 'translation':
  119. case 'scale':
  120. case 'size':
  121. if (parts.length != 4) {
  122. console.warn('Invalid vector format detected for ' + fieldName);
  123. break;
  124. }
  125. property = {
  126. 'x' : parseFloat(parts[1]),
  127. 'y' : parseFloat(parts[2]),
  128. 'z' : parseFloat(parts[3])
  129. }
  130. break;
  131. case 'radius':
  132. case 'topRadius':
  133. case 'bottomRadius':
  134. case 'height':
  135. case 'transparency':
  136. case 'shininess':
  137. case 'ambientIntensity':
  138. if (parts.length != 2) {
  139. console.warn('Invalid single float value specification detected for ' + fieldName);
  140. break;
  141. }
  142. property = parseFloat(parts[1]);
  143. break;
  144. case 'rotation':
  145. if (parts.length != 5) {
  146. console.warn('Invalid quaternion format detected for ' + fieldName);
  147. break;
  148. }
  149. property = {
  150. 'x' : parseFloat(parts[1]),
  151. 'y' : parseFloat(parts[2]),
  152. 'z' : parseFloat(parts[3]),
  153. 'w' : parseFloat(parts[4])
  154. }
  155. break;
  156. case 'ccw':
  157. case 'solid':
  158. case 'colorPerVertex':
  159. case 'convex':
  160. if (parts.length != 2) {
  161. console.warn('Invalid format detected for ' + fieldName);
  162. break;
  163. }
  164. property = parts[1] === 'TRUE' ? true : false;
  165. break;
  166. }
  167. node[fieldName] = property;
  168. }
  169. return property;
  170. };
  171. var getTree = function ( lines ) {
  172. var tree = { 'string': 'Scene', children: [] };
  173. var current = tree;
  174. var matches;
  175. var specification;
  176. for ( var i = 0; i < lines.length; i ++ ) {
  177. var comment = '';
  178. var line = lines[ i ];
  179. // omit whitespace only lines
  180. if ( null !== ( result = /^\s+?$/g.exec( line ) ) ) {
  181. continue;
  182. }
  183. line = line.trim();
  184. // skip empty lines
  185. if (line === '') {
  186. continue;
  187. }
  188. if ( /#/.exec( line ) ) {
  189. var parts = line.split('#');
  190. // discard everything after the #, it is a comment
  191. line = parts[0];
  192. // well, let's also keep the comment
  193. comment = parts[1];
  194. }
  195. if ( matches = /([^\s]*){1}\s?{/.exec( line ) ) { // first subpattern should match the Node name
  196. var block = { 'nodeType' : matches[1], 'string': line, 'parent': current, 'children': [],'comment' : comment};
  197. current.children.push( block );
  198. current = block;
  199. if ( /}/.exec( line ) ) {
  200. // example: geometry Box { size 1 1 1 } # all on the same line
  201. specification = /{(.*)}/.exec( line )[ 1 ];
  202. // todo: remove once new parsing is complete?
  203. block.children.push( specification );
  204. parseProperty(current, specification);
  205. current = current.parent;
  206. }
  207. } else if ( /}/.exec( line ) ) {
  208. current = current.parent;
  209. } else if ( line !== '' ) {
  210. parseProperty(current, line);
  211. // todo: remove once new parsing is complete?
  212. current.children.push( line );
  213. }
  214. }
  215. return tree;
  216. }
  217. var parseNode = function ( data, parent ) {
  218. // console.log( data );
  219. if ( typeof data === 'string' ) {
  220. if ( /USE/.exec( data ) ) {
  221. var defineKey = /USE\s+?(\w+)/.exec( data )[ 1 ];
  222. if (undefined == defines[defineKey]) {
  223. console.warn(defineKey + ' is not defined.');
  224. } else {
  225. if ( /appearance/.exec( data ) && defineKey ) {
  226. parent.material = defines[ defineKey ].clone();
  227. } else if ( /geometry/.exec( data ) && defineKey ) {
  228. parent.geometry = defines[ defineKey ].clone();
  229. // the solid property is not cloned with clone(), is only needed for VRML loading, so we need to transfer it
  230. if (undefined !== defines[ defineKey ].solid && defines[ defineKey ].solid === false) {
  231. parent.geometry.solid = false;
  232. parent.material.side = THREE.DoubleSide;
  233. }
  234. } else if (defineKey){
  235. var object = defines[ defineKey ].clone();
  236. parent.add( object );
  237. }
  238. }
  239. }
  240. return;
  241. }
  242. var object = parent;
  243. if ( 'Transform' === data.nodeType || 'Group' === data.nodeType ) {
  244. object = new THREE.Object3D();
  245. if ( /DEF/.exec( data.string ) ) {
  246. object.name = /DEF\s+(\w+)/.exec( data.string )[ 1 ];
  247. defines[ object.name ] = object;
  248. }
  249. if ( undefined !== data['translation'] ) {
  250. var t = data.translation;
  251. object.position.set(t.x, t.y, t.z);
  252. }
  253. if ( undefined !== data.rotation ) {
  254. var r = data.rotation;
  255. object.quaternion.setFromAxisAngle( new THREE.Vector3( r.x, r.y, r.z ), r.w );
  256. }
  257. if ( undefined !== data.scale ) {
  258. var s = data.scale;
  259. object.scale.set( s.x, s.y, s.z );
  260. }
  261. parent.add( object );
  262. } else if ( 'Shape' === data.nodeType ) {
  263. object = new THREE.Mesh();
  264. if ( /DEF/.exec( data.string ) ) {
  265. object.name = /DEF (\w+)/.exec( data.string )[ 1 ];
  266. defines[ object.name ] = object;
  267. }
  268. parent.add( object );
  269. } else if ( 'Background' === data.nodeType ) {
  270. console.warn('Implement hemisphere light here');
  271. } else if ( /geometry/.exec( data.string ) ) {
  272. if ( 'Box' === data.nodeType ) {
  273. var s = data.size;
  274. parent.geometry = new THREE.CubeGeometry( s.x, s.y, s.z );
  275. } else if ( 'Cylinder' === data.nodeType ) {
  276. parent.geometry = new THREE.CylinderGeometry( data.radius, data.radius, data.height );
  277. } else if ( 'Cone' === data.nodeType ) {
  278. parent.geometry = new THREE.CylinderGeometry( data.topRadius, data.bottomRadius, data.height );
  279. } else if ( 'Sphere' === data.nodeType ) {
  280. parent.geometry = new THREE.SphereGeometry( data.radius );
  281. } else if ( 'IndexedFaceSet' === data.nodeType ) {
  282. var geometry = new THREE.Geometry();
  283. var indexes;
  284. for (var i = 0, j = data.children.length; i < j; i++) {
  285. var child = data.children[i];
  286. var vec;
  287. if ( 'Coordinate' === child.nodeType ) {
  288. for (var k = 0, l = child.points.length; k < l; k++) {
  289. var point = child.points[k];
  290. vec = new THREE.Vector3(point.x, point.y, point.z);
  291. geometry.vertices.push( vec );
  292. }
  293. break;
  294. }
  295. }
  296. var skip = 0;
  297. // read this: http://math.hws.edu/eck/cs424/notes2013/16_Threejs_Advanced.html
  298. for (var i = 0, j = data.coordIndex.length; i < j; i++) {
  299. indexes = data.coordIndex[i];
  300. // vrml support multipoint indexed face sets (more then 3 vertices). You must calculate the composing triangles here
  301. skip = 0;
  302. // todo: this is the time to check if the faces are ordered ccw or not (cw)
  303. // Face3 only works with triangles, but IndexedFaceSet allows shapes with more then three vertices, build them of triangles
  304. while ( indexes.length >= 3 && skip < (indexes.length -2) ) {
  305. var face = new THREE.Face3(
  306. indexes[0],
  307. indexes[skip + 1],
  308. indexes[skip + 2],
  309. null // normal, will be added later
  310. // todo: pass in the color, if a color index is present
  311. );
  312. skip++;
  313. geometry.faces.push(face);
  314. }
  315. }
  316. if (false === data.solid) {
  317. parent.material.side = THREE.DoubleSide;
  318. }
  319. // we need to store it on the geometry for use with defines
  320. geometry.solid = data.solid;
  321. geometry.computeFaceNormals();
  322. //geometry.computeVertexNormals(); // does not show
  323. geometry.computeBoundingSphere();
  324. // see if it's a define
  325. if ( /DEF/.exec( data.string ) ) {
  326. geometry.name = /DEF (\w+)/.exec( data.string )[ 1 ];
  327. defines[ geometry.name ] = geometry;
  328. }
  329. parent.geometry = geometry;
  330. }
  331. return;
  332. } else if ( /appearance/.exec( data.string ) ) {
  333. for ( var i = 0; i < data.children.length; i ++ ) {
  334. var child = data.children[ i ];
  335. if ( 'Material' === child.nodeType ) {
  336. var material = new THREE.MeshPhongMaterial();
  337. if ( undefined !== child.diffuseColor ) {
  338. var d = child.diffuseColor;
  339. material.color.setRGB( d.r, d.g, d.b );
  340. }
  341. if ( undefined !== child.emissiveColor ) {
  342. var e = child.emissiveColor;
  343. material.emissive.setRGB( e.r, e.g, e.b);
  344. }
  345. if ( undefined !== child.specularColor ) {
  346. var s = child.specularColor;
  347. material.specular.setRGB( s.r, s.g, s.b );
  348. }
  349. if ( undefined !== child.transparency ) {
  350. var t = child.transparency;
  351. // transparency is opposite of opacity
  352. material.opacity = Math.abs( 1 - t );
  353. material.transparent = true;
  354. }
  355. if ( /DEF/.exec( data.string ) ) {
  356. material.name = /DEF (\w+)/.exec( data.string )[ 1 ];
  357. defines[ material.name ] = material;
  358. }
  359. // todo: below does not work, but we should find a way to make it work (for USE(d) geometries (IndexedFaceSets) that are marked as solid)
  360. if (undefined !== parent.geometry.solid && false === parent.geometry.solid) {
  361. material.side = THREE.DoubleSide;
  362. }
  363. parent.material = material;
  364. // material found, stop looping
  365. break;
  366. }
  367. }
  368. return;
  369. }
  370. for ( var i = 0, l = data.children.length; i < l; i ++ ) {
  371. var child = data.children[ i ];
  372. parseNode( data.children[ i ], object );
  373. }
  374. }
  375. parseNode( getTree( lines ), scene );
  376. };
  377. var scene = new THREE.Scene();
  378. var lines = data.split( '\n' );
  379. var header = lines.shift();
  380. if ( /V1.0/.exec( header ) ) {
  381. parseV1( lines, scene );
  382. } else if ( /V2.0/.exec( header ) ) {
  383. parseV2( lines, scene );
  384. }
  385. return scene;
  386. }
  387. };
  388. THREE.EventDispatcher.prototype.apply( THREE.VRMLLoader.prototype );