GLTFExporter.tests.js 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428
  1. /* global QUnit */
  2. import { GLTFExporter } from '../../../../examples/jsm/exporters/GLTFExporter';
  3. import { AnimationClip } from '../../../../src/animation/AnimationClip';
  4. import { BoxGeometry } from '../../../../src/geometries/BoxGeometry';
  5. import { BufferAttribute } from '../../../../src/core/BufferAttribute';
  6. import { BufferGeometry } from '../../../../src/core/BufferGeometry';
  7. import { DirectionalLight } from '../../../../src/lights/DirectionalLight';
  8. import { Mesh } from '../../../../src/objects/Mesh';
  9. import { MeshBasicMaterial } from '../../../../src/materials/MeshBasicMaterial';
  10. import { MeshStandardMaterial } from '../../../../src/materials/MeshStandardMaterial';
  11. import { Object3D } from '../../../../src/core/Object3D';
  12. import { NumberKeyframeTrack } from '../../../../src/animation/tracks/NumberKeyframeTrack';
  13. import { Scene } from '../../../../src/scenes/Scene';
  14. import { VectorKeyframeTrack } from '../../../../src/animation/tracks/VectorKeyframeTrack';
  15. import {
  16. DoubleSide,
  17. InterpolateLinear,
  18. InterpolateDiscrete
  19. } from '../../../../src/constants.js';
  20. export default QUnit.module( 'Exporters', () => {
  21. QUnit.module( 'GLTFExporter', () => {
  22. QUnit.test( 'constructor', ( assert ) => {
  23. assert.ok( new GLTFExporter(), 'Can instantiate an exporter.' );
  24. } );
  25. QUnit.test( 'utils - insertKeyframe', ( assert ) => {
  26. var track;
  27. var index;
  28. function createTrack() {
  29. return new VectorKeyframeTrack(
  30. 'foo.bar',
  31. [ 5, 10, 15, 20, 25, 30 ],
  32. [ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ],
  33. InterpolateLinear
  34. );
  35. }
  36. track = createTrack();
  37. index = GLTFExporter.Utils.insertKeyframe( track, 0 );
  38. assert.equal( index, 0, 'prepend - index' );
  39. assert.smartEqual( Array.from( track.times ), [ 0, 5, 10, 15, 20, 25, 30 ], 'prepend - time' );
  40. assert.smartEqual( Array.from( track.values ), [ 0, 5, 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ], 'prepend - value' );
  41. track = createTrack();
  42. index = GLTFExporter.Utils.insertKeyframe( track, 7.5 );
  43. assert.equal( index, 1, 'insert - index (linear)' );
  44. assert.smartEqual( Array.from( track.times ), [ 5, 7.5, 10, 15, 20, 25, 30 ], 'insert - time (linear)' );
  45. assert.smartEqual( Array.from( track.values ), [ 0, 5, 0.5, 4.5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ], 'insert - value (linear)' );
  46. track = createTrack();
  47. track.setInterpolation( InterpolateDiscrete );
  48. index = GLTFExporter.Utils.insertKeyframe( track, 16 );
  49. assert.equal( index, 3, 'insert - index (linear)' );
  50. assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 16, 20, 25, 30 ], 'insert - time (discrete)' );
  51. assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 2, 3, 3, 2, 4, 1, 5, 0 ], 'insert - value (discrete)' );
  52. track = createTrack();
  53. index = GLTFExporter.Utils.insertKeyframe( track, 100 );
  54. assert.equal( index, 6, 'append - index' );
  55. assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30, 100 ], 'append time' );
  56. assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0, 5, 0 ], 'append value' );
  57. track = createTrack();
  58. index = GLTFExporter.Utils.insertKeyframe( track, 15 );
  59. assert.equal( index, 2, 'existing - index' );
  60. assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30 ], 'existing - time' );
  61. assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ], 'existing - value' );
  62. track = createTrack();
  63. index = GLTFExporter.Utils.insertKeyframe( track, 20.000005 );
  64. assert.equal( index, 3, 'tolerance - index' );
  65. assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30 ], 'tolerance - time' );
  66. assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ], 'tolerance - value' );
  67. } );
  68. QUnit.test( 'utils - mergeMorphTargetTracks', ( assert ) => {
  69. var trackA = new NumberKeyframeTrack(
  70. 'foo.morphTargetInfluences[a]',
  71. [ 5, 10, 15, 20, 25, 30 ],
  72. [ 0, 0.2, 0.4, 0.6, 0.8, 1.0 ],
  73. InterpolateLinear
  74. );
  75. var trackB = new NumberKeyframeTrack(
  76. 'foo.morphTargetInfluences[b]',
  77. [ 10, 50 ],
  78. [ 0.25, 0.75 ],
  79. InterpolateLinear
  80. );
  81. var geometry = new BufferGeometry();
  82. var position = new BufferAttribute( new Float32Array( [ 0, 0, 0, 0, 0, 1, 1, 0, 1 ] ), 3 );
  83. geometry.setAttribute( 'position', position );
  84. geometry.morphAttributes.position = [ position, position ];
  85. var mesh = new Mesh( geometry );
  86. mesh.name = 'foo';
  87. mesh.morphTargetDictionary.a = 0;
  88. mesh.morphTargetDictionary.b = 1;
  89. var root = new Object3D();
  90. root.add( mesh );
  91. var clip = new AnimationClip( 'waltz', undefined, [ trackA, trackB ] );
  92. clip = GLTFExporter.Utils.mergeMorphTargetTracks( clip, root );
  93. assert.equal( clip.tracks.length, 1, 'tracks are merged' );
  94. var track = clip.tracks[ 0 ];
  95. assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30, 50 ], 'all keyframes are present' );
  96. var expectedValues = [ 0, 0.25, 0.2, 0.25, 0.4, 0.3125, 0.6, 0.375, 0.8, 0.4375, 1.0, 0.5, 1.0, 0.75 ];
  97. for ( var i = 0; i < track.values.length; i ++ ) {
  98. assert.numEqual( track.values[ i ], expectedValues[ i ], 'all values are merged or interpolated - ' + i );
  99. }
  100. } );
  101. } );
  102. QUnit.module( 'GLTFExporter-webonly', () => {
  103. QUnit.test( 'parse - metadata', ( assert ) => {
  104. var done = assert.async();
  105. var object = new Object3D();
  106. var exporter = new GLTFExporter();
  107. exporter.parse( object, function ( gltf ) {
  108. assert.equal( '2.0', gltf.asset.version, 'asset.version' );
  109. assert.equal( 'THREE.GLTFExporter', gltf.asset.generator, 'asset.generator' );
  110. done();
  111. } );
  112. } );
  113. QUnit.test( 'parse - basic', ( assert ) => {
  114. var done = assert.async();
  115. var box = new Mesh(
  116. new BoxGeometry( 1, 1, 1 ),
  117. new MeshStandardMaterial( { color: 0xFF0000 } )
  118. );
  119. var exporter = new GLTFExporter();
  120. exporter.parse( box, function ( gltf ) {
  121. assert.equal( 1, gltf.nodes.length, 'correct number of nodes' );
  122. assert.equal( 0, gltf.nodes[ 0 ].mesh, 'node references mesh' );
  123. assert.equal( 1, gltf.meshes[ 0 ].primitives.length, 'correct number of primitives' );
  124. var primitive = gltf.meshes[ 0 ].primitives[ 0 ];
  125. var material = gltf.materials[ primitive.material ];
  126. assert.equal( 4, primitive.mode, 'mesh uses TRIANGLES mode' );
  127. assert.ok( primitive.attributes.POSITION !== undefined, 'mesh contains position data' );
  128. assert.ok( primitive.attributes.NORMAL !== undefined, 'mesh contains normal data' );
  129. assert.smartEqual( {
  130. baseColorFactor: [ 1, 0, 0, 1 ],
  131. metallicFactor: 0.0,
  132. roughnessFactor: 1.0
  133. }, material.pbrMetallicRoughness, 'material' );
  134. done();
  135. } );
  136. } );
  137. QUnit.test( 'parse - animation', ( assert ) => {
  138. var done = assert.async();
  139. var mesh1 = new Mesh();
  140. mesh1.name = 'mesh1';
  141. var mesh2 = new Mesh();
  142. mesh2.name = 'mesh2';
  143. var mesh3 = new Mesh();
  144. mesh3.name = 'mesh3';
  145. var scene = new Scene();
  146. scene.add( mesh1, mesh2, mesh3 );
  147. var clip1 = new AnimationClip( 'clip1', undefined, [
  148. new VectorKeyframeTrack( 'mesh1.position', [ 0, 1, 2 ], [ 0, 0, 0, 30, 0, 0, 0, 0, 0 ] )
  149. ] );
  150. var clip2 = new AnimationClip( 'clip2', undefined, [
  151. new VectorKeyframeTrack( 'mesh3.scale', [ 0, 1, 2 ], [ 1, 1, 1, 2, 2, 2, 1, 1, 1 ] )
  152. ] );
  153. var exporter = new GLTFExporter();
  154. exporter.parse( scene, function ( gltf ) {
  155. assert.equal( 2, gltf.animations.length, 'one animation per clip' );
  156. var target1 = gltf.animations[ 0 ].channels[ 0 ].target;
  157. var target2 = gltf.animations[ 1 ].channels[ 0 ].target;
  158. assert.equal( 'mesh1', gltf.nodes[ target1.node ].name, 'clip1 node' );
  159. assert.equal( 'translation', target1.path, 'clip1 property' );
  160. assert.equal( 'mesh3', gltf.nodes[ target2.node ].name, 'clip2 node' );
  161. assert.equal( 'scale', target2.path, 'clip2 property' );
  162. done();
  163. }, { animations: [ clip1, clip2 ] } );
  164. } );
  165. QUnit.test( 'parse - empty buffergeometry', ( assert ) => {
  166. var done = assert.async();
  167. var scene = new Scene();
  168. var geometry = new BufferGeometry();
  169. var numElements = 6;
  170. var positions = new Float32Array( ( numElements ) * 3 );
  171. var colors = new Float32Array( ( numElements ) * 3 );
  172. geometry.setAttribute( 'position', new BufferAttribute( positions, 3 ) );
  173. geometry.setAttribute( 'color', new BufferAttribute( colors, 3 ) );
  174. geometry.setDrawRange( 0, 0 );
  175. var empty = new Mesh( geometry, new MeshBasicMaterial( { side: DoubleSide, vertexColors: true } ) );
  176. empty.name = 'Custom buffered empty (drawrange)';
  177. scene.add( empty );
  178. var exporter = new GLTFExporter();
  179. exporter.parse( scene, function ( gltf ) {
  180. assert.equal( gltf.meshes, undefined, 'empty meshes' );
  181. assert.equal( gltf.materials, undefined, 'empty materials' );
  182. assert.equal( gltf.bufferViews, undefined, 'empty bufferViews' );
  183. assert.equal( gltf.buffers, undefined, 'buffers' );
  184. assert.equal( gltf.accessors, undefined, 'accessors' );
  185. assert.equal( gltf.nodes[ 0 ].mesh, undefined, 'nodes[0].mesh' );
  186. done();
  187. } );
  188. } );
  189. QUnit.test( 'parse - individual morph targets', ( assert ) => {
  190. var done = assert.async();
  191. // Creates a geometry with four (4) morph targets, three (3) of which are
  192. // animated by an animation clip. Because glTF requires all morph targets
  193. // to be animated in unison, the exporter should write an empty track for
  194. // the fourth target.
  195. var geometry = new BufferGeometry();
  196. var position = new BufferAttribute( new Float32Array( [ 0, 0, 0, 0, 0, 1, 1, 0, 1 ] ), 3 );
  197. geometry.setAttribute( 'position', position );
  198. geometry.morphAttributes.position = [ position, position, position, position ];
  199. var mesh = new Mesh( geometry );
  200. mesh.morphTargetDictionary.a = 0;
  201. mesh.morphTargetDictionary.b = 1;
  202. mesh.morphTargetDictionary.c = 2;
  203. mesh.morphTargetDictionary.d = 3;
  204. var timesA = [ 0, 1, 2 ];
  205. var timesB = [ 2, 3, 4 ];
  206. var timesC = [ 4, 5, 6 ];
  207. var valuesA = [ 0, 1, 0 ];
  208. var valuesB = [ 0, 1, 0 ];
  209. var valuesC = [ 0, 1, 0 ];
  210. var trackA = new VectorKeyframeTrack( '.morphTargetInfluences[a]', timesA, valuesA, InterpolateLinear );
  211. var trackB = new VectorKeyframeTrack( '.morphTargetInfluences[b]', timesB, valuesB, InterpolateLinear );
  212. var trackC = new VectorKeyframeTrack( '.morphTargetInfluences[c]', timesC, valuesC, InterpolateLinear );
  213. var clip = new AnimationClip( 'clip1', undefined, [ trackA, trackB, trackC ] );
  214. var exporter = new GLTFExporter();
  215. exporter.parse( mesh, function ( gltf ) {
  216. assert.equal( 1, gltf.animations.length, 'one animation' );
  217. assert.equal( 1, gltf.animations[ 0 ].channels.length, 'one channel' );
  218. assert.equal( 1, gltf.animations[ 0 ].samplers.length, 'one sampler' );
  219. var channel = gltf.animations[ 0 ].channels[ 0 ];
  220. var sampler = gltf.animations[ 0 ].samplers[ 0 ];
  221. assert.smartEqual( channel, { sampler: 0, target: { node: 0, path: 'weights' } } );
  222. assert.equal( sampler.interpolation, 'LINEAR' );
  223. var input = gltf.accessors[ sampler.input ];
  224. var output = gltf.accessors[ sampler.output ];
  225. assert.equal( input.count, 7 );
  226. assert.equal( input.type, 'SCALAR' );
  227. assert.smartEqual( input.min, [ 0 ] );
  228. assert.smartEqual( input.max, [ 6 ] );
  229. assert.equal( output.count, 28 ); // 4 targets * 7 frames
  230. assert.equal( output.type, 'SCALAR' );
  231. assert.smartEqual( output.min, [ 0 ] );
  232. assert.smartEqual( output.max, [ 1 ] );
  233. done();
  234. }, { animations: [ clip ] } );
  235. } );
  236. QUnit.test( 'parse - KHR_lights_punctual extension', ( assert ) => {
  237. const done = assert.async();
  238. const scene = new Scene();
  239. const light = new DirectionalLight( 0xffffff );
  240. light.position.set( 1, 2, 3 );
  241. scene.add( light );
  242. scene.updateMatrixWorld();
  243. const exporter = new GLTFExporter();
  244. exporter.parse( scene, gltf => {
  245. const extensionName = 'KHR_lights_punctual';
  246. const extensionsUsed = gltf.extensionsUsed || [];
  247. const extensions = gltf.extensions || {};
  248. const lightsDef = extensions[ extensionName ] || {};
  249. const lights = lightsDef.lights || [];
  250. const lightDef = lights[ 0 ] || {};
  251. const nodes = gltf.nodes || [];
  252. const lightNodeDefsNum = nodes.filter( nodeDef => nodeDef.extensions && nodeDef.extensions[ extensionName ] ).length;
  253. const lightNodeDef = nodes.find( nodeDef => nodeDef.extensions && nodeDef.extensions[ extensionName ] ) || {};
  254. const lightNodeExtensions = lightNodeDef.extensions || {};
  255. const lightNodeExtensionDef = lightNodeExtensions[ extensionName ] || {};
  256. assert.ok( extensionsUsed.indexOf( extensionName ) >= 0, `${extensionName} exists in extensionsUsed` );
  257. assert.equal( 1, lights.length, 'one light' );
  258. assert.smartEqual( [ 1, 1, 1 ], lightDef.color, 'correct color' );
  259. assert.equal( light.intensity, lightDef.intensity, 'correct intensity' );
  260. assert.equal( 'directional', lightDef.type, 'correct type' );
  261. assert.equal( 1, lightNodeDefsNum, `one node having ${extensionName} extension` );
  262. assert.equal( 0, lightNodeExtensionDef.light, 'correct light node index' );
  263. assert.smartEqual( light.matrix.elements,
  264. lightNodeDef.matrix, 'correct light node transform' );
  265. // @TODO: Add PointLight and SpotLight tests
  266. done();
  267. } );
  268. } );
  269. QUnit.test( 'parse - KHR_materials_unlit extension', ( assert ) => {
  270. const done = assert.async();
  271. const scene = new Scene();
  272. const mesh = new Mesh(
  273. new BoxBufferGeometry( 1, 1, 1 ),
  274. new MeshBasicMaterial()
  275. );
  276. scene.add( mesh );
  277. const exporter = new GLTFExporter();
  278. exporter.parse( scene, gltf => {
  279. const extensionName = 'KHR_materials_unlit';
  280. const extensionsUsed = gltf.extensionsUsed || [];
  281. const materials = gltf.materials || [];
  282. const materialDef = materials[ 0 ] || {};
  283. const pbrMetallicRoughness = materialDef.pbrMetallicRoughness || {};
  284. const extensions = materialDef.extensions || {};
  285. assert.ok( extensionsUsed.indexOf( extensionName ) >= 0, `${extensionName} exists in extensionsUsed` );
  286. assert.equal( 1, materials.length, 'one material' );
  287. assert.ok( extensions[ extensionName ], `material has ${extensionName} extension` );
  288. assert.equal( 0.0, pbrMetallicRoughness.metallicFactor, 'correct metallicFactor' );
  289. assert.equal( 0.9, pbrMetallicRoughness.roughnessFactor, 'correct roughnessFactor' );
  290. done();
  291. } );
  292. } );
  293. } );
  294. } );