GLTFExporter.tests.js 12 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340
  1. /**
  2. * @author Don McCurdy / https://www.donmccurdy.com
  3. */
  4. /* global QUnit */
  5. import * as GLTFExporter from '../../../../examples/js/exporters/GLTFExporter';
  6. import { AnimationClip } from '../../../../src/animation/AnimationClip';
  7. import { BufferAttribute } from '../../../../src/core/BufferAttribute';
  8. import { BufferGeometry } from '../../../../src/core/BufferGeometry';
  9. import { Mesh } from '../../../../src/objects/Mesh';
  10. import { Object3D } from '../../../../src/core/Object3D';
  11. import { NumberKeyframeTrack } from '../../../../src/animation/tracks/NumberKeyframeTrack';
  12. import { VectorKeyframeTrack } from '../../../../src/animation/tracks/VectorKeyframeTrack';
  13. import {
  14. InterpolateLinear,
  15. InterpolateSmooth,
  16. InterpolateDiscrete
  17. } from '../../../../src/constants.js';
  18. export default QUnit.module( 'Exporters', () => {
  19. QUnit.module( 'GLTFExporter', () => {
  20. QUnit.test( 'constructor', ( assert ) => {
  21. assert.ok( new THREE.GLTFExporter(), 'Can instantiate an exporter.' );
  22. } );
  23. QUnit.test( 'parse - metadata', ( assert ) => {
  24. var done = assert.async();
  25. var object = new THREE.Object3D();
  26. var exporter = new THREE.GLTFExporter();
  27. exporter.parse( object, function ( gltf ) {
  28. assert.equal( '2.0', gltf.asset.version, 'asset.version' );
  29. assert.equal( 'THREE.GLTFExporter', gltf.asset.generator, 'asset.generator' );
  30. done();
  31. } );
  32. } );
  33. QUnit.test( 'parse - basic', ( assert ) => {
  34. var done = assert.async();
  35. var box = new THREE.Mesh(
  36. new THREE.CubeGeometry( 1, 1, 1 ),
  37. new THREE.MeshStandardMaterial( { color: 0xFF0000 } )
  38. );
  39. var exporter = new THREE.GLTFExporter();
  40. exporter.parse( box, function ( gltf ) {
  41. assert.equal( 1, gltf.nodes.length, 'correct number of nodes' );
  42. assert.equal( 0, gltf.nodes[ 0 ].mesh, 'node references mesh' );
  43. assert.equal( 1, gltf.meshes[ 0 ].primitives.length, 'correct number of primitives' );
  44. var primitive = gltf.meshes[ 0 ].primitives[ 0 ];
  45. var material = gltf.materials[ primitive.material ];
  46. assert.equal( 4, primitive.mode, 'mesh uses TRIANGLES mode' );
  47. assert.ok( primitive.attributes.POSITION !== undefined, 'mesh contains position data' );
  48. assert.ok( primitive.attributes.NORMAL !== undefined, 'mesh contains normal data' );
  49. assert.smartEqual( {
  50. baseColorFactor: [ 1, 0, 0, 1 ],
  51. metallicFactor: 0.5,
  52. roughnessFactor: 0.5
  53. }, material.pbrMetallicRoughness, 'material' );
  54. done();
  55. } );
  56. } );
  57. QUnit.test( 'parse - animation', ( assert ) => {
  58. var done = assert.async();
  59. var mesh1 = new THREE.Mesh();
  60. mesh1.name = 'mesh1';
  61. var mesh2 = new THREE.Mesh();
  62. mesh2.name = 'mesh2';
  63. var mesh3 = new THREE.Mesh();
  64. mesh3.name = 'mesh3';
  65. var scene = new THREE.Scene();
  66. scene.add( mesh1, mesh2, mesh3 );
  67. var clip1 = new THREE.AnimationClip( 'clip1', undefined, [
  68. new THREE.VectorKeyframeTrack( 'mesh1.position', [ 0, 1, 2 ], [ 0, 0, 0, 30, 0, 0, 0, 0, 0 ] )
  69. ] );
  70. var clip2 = new THREE.AnimationClip( 'clip2', undefined, [
  71. new THREE.VectorKeyframeTrack( 'mesh3.scale', [ 0, 1, 2 ], [ 1, 1, 1, 2, 2, 2, 1, 1, 1 ] )
  72. ] );
  73. var exporter = new THREE.GLTFExporter();
  74. exporter.parse( scene, function ( gltf ) {
  75. assert.equal( 2, gltf.animations.length, 'one animation per clip' );
  76. var target1 = gltf.animations[ 0 ].channels[ 0 ].target;
  77. var target2 = gltf.animations[ 1 ].channels[ 0 ].target;
  78. assert.equal( 'mesh1', gltf.nodes[ target1.node ].name, 'clip1 node' );
  79. assert.equal( 'translation', target1.path, 'clip1 property' );
  80. assert.equal( 'mesh3', gltf.nodes[ target2.node ].name, 'clip2 node' );
  81. assert.equal( 'scale', target2.path, 'clip2 property' );
  82. done();
  83. }, { animations: [ clip1, clip2 ] } );
  84. } );
  85. QUnit.test( 'parse - empty buffergeometry', ( assert ) => {
  86. var done = assert.async();
  87. var scene = new THREE.Scene();
  88. var geometry = new THREE.BufferGeometry();
  89. var numElements = 6;
  90. var positions = new Float32Array( ( numElements ) * 3 );
  91. var colors = new Float32Array( ( numElements ) * 3 );
  92. geometry.addAttribute( 'position', new THREE.BufferAttribute( positions, 3 ) );
  93. geometry.addAttribute( 'color', new THREE.BufferAttribute( colors, 3 ) );
  94. geometry.setDrawRange( 0, 0 );
  95. var empty = new THREE.Mesh( geometry, new THREE.MeshBasicMaterial( { side: THREE.DoubleSide, vertexColors: THREE.VertexColors } ) );
  96. empty.name = 'Custom buffered empty (drawrange)';
  97. scene.add( empty );
  98. var exporter = new THREE.GLTFExporter();
  99. exporter.parse( scene, function ( gltf ) {
  100. assert.equal( gltf.meshes, undefined, 'empty meshes');
  101. assert.equal( gltf.materials, undefined, 'empty materials');
  102. assert.equal( gltf.bufferViews, undefined, 'empty bufferViews');
  103. assert.equal( gltf.buffers, undefined, 'buffers');
  104. assert.equal( gltf.accessors, undefined, 'accessors');
  105. assert.equal( gltf.nodes[0].mesh, undefined, 'nodes[0].mesh');
  106. done();
  107. });
  108. } );
  109. QUnit.test( 'parse - individual morph targets', ( assert ) => {
  110. var done = assert.async();
  111. // Creates a geometry with four (4) morph targets, three (3) of which are
  112. // animated by an animation clip. Because glTF requires all morph targets
  113. // to be animated in unison, the exporter should write an empty track for
  114. // the fourth target.
  115. var geometry = new THREE.BufferGeometry();
  116. var position = new THREE.BufferAttribute( new Float32Array( [ 0, 0, 0, 0, 0, 1, 1, 0, 1 ] ), 3 );
  117. geometry.addAttribute( 'position', position );
  118. geometry.morphAttributes.position = [ position, position, position, position ];
  119. var mesh = new THREE.Mesh( geometry );
  120. mesh.morphTargetDictionary.a = 0;
  121. mesh.morphTargetDictionary.b = 1;
  122. mesh.morphTargetDictionary.c = 2;
  123. mesh.morphTargetDictionary.d = 3;
  124. var timesA = [ 0, 1, 2 ];
  125. var timesB = [ 2, 3, 4 ];
  126. var timesC = [ 4, 5, 6 ];
  127. var valuesA = [ 0, 1, 0 ];
  128. var valuesB = [ 0, 1, 0 ];
  129. var valuesC = [ 0, 1, 0 ];
  130. var trackA = new THREE.VectorKeyframeTrack( '.morphTargetInfluences[a]', timesA, valuesA, THREE.InterpolateLinear );
  131. var trackB = new THREE.VectorKeyframeTrack( '.morphTargetInfluences[b]', timesB, valuesB, THREE.InterpolateLinear );
  132. var trackC = new THREE.VectorKeyframeTrack( '.morphTargetInfluences[c]', timesC, valuesC, THREE.InterpolateLinear );
  133. var clip = new THREE.AnimationClip( 'clip1', undefined, [ trackA, trackB, trackC ] );
  134. var exporter = new THREE.GLTFExporter();
  135. exporter.parse( mesh, function ( gltf ) {
  136. assert.equal( 1, gltf.animations.length, 'one animation' );
  137. assert.equal( 1, gltf.animations[ 0 ].channels.length, 'one channel' );
  138. assert.equal( 1, gltf.animations[ 0 ].samplers.length, 'one sampler' );
  139. var channel = gltf.animations[ 0 ].channels[ 0 ];
  140. var sampler = gltf.animations[ 0 ].samplers[ 0 ];
  141. assert.smartEqual( channel, { sampler: 0, target: { node: 0, path: 'weights' } } );
  142. assert.equal( sampler.interpolation, 'LINEAR' );
  143. var input = gltf.accessors[ sampler.input ];
  144. var output = gltf.accessors[ sampler.output ];
  145. assert.equal( input.count, 7 );
  146. assert.equal( input.type, 'SCALAR' );
  147. assert.smartEqual( input.min, [ 0 ] );
  148. assert.smartEqual( input.max, [ 6 ] );
  149. assert.equal( output.count, 28 ); // 4 targets * 7 frames
  150. assert.equal( output.type, 'SCALAR' );
  151. assert.smartEqual( output.min, [ 0 ] );
  152. assert.smartEqual( output.max, [ 1 ] );
  153. done();
  154. }, { animations: [ clip ] } );
  155. } );
  156. QUnit.test( 'utils - insertKeyframe', ( assert ) => {
  157. var track;
  158. var index;
  159. function createTrack () {
  160. return new VectorKeyframeTrack(
  161. 'foo.bar',
  162. [ 5, 10, 15, 20, 25, 30 ],
  163. [ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ],
  164. InterpolateLinear
  165. );
  166. }
  167. track = createTrack();
  168. index = THREE.GLTFExporter.Utils.insertKeyframe( track, 0 );
  169. assert.equal( index, 0, 'prepend - index' );
  170. assert.smartEqual( Array.from( track.times ), [ 0, 5, 10, 15, 20, 25, 30 ], 'prepend - time' );
  171. assert.smartEqual( Array.from( track.values ), [ 0, 5, 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ], 'prepend - value' );
  172. track = createTrack();
  173. index = THREE.GLTFExporter.Utils.insertKeyframe( track, 7.5 );
  174. assert.equal( index, 1, 'insert - index (linear)' );
  175. assert.smartEqual( Array.from( track.times ), [ 5, 7.5, 10, 15, 20, 25, 30 ], 'insert - time (linear)' );
  176. 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)' );
  177. track = createTrack();
  178. track.setInterpolation( InterpolateDiscrete );
  179. index = THREE.GLTFExporter.Utils.insertKeyframe( track, 16 );
  180. assert.equal( index, 3, 'insert - index (linear)' );
  181. assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 16, 20, 25, 30 ], 'insert - time (discrete)' );
  182. assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 2, 3, 3, 2, 4, 1, 5, 0 ], 'insert - value (discrete)' );
  183. track = createTrack();
  184. index = THREE.GLTFExporter.Utils.insertKeyframe( track, 100 );
  185. assert.equal( index, 6, 'append - index' );
  186. assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30, 100 ], 'append time' );
  187. assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0, 5, 0 ], 'append value' );
  188. track = createTrack();
  189. index = THREE.GLTFExporter.Utils.insertKeyframe( track, 15 );
  190. assert.equal( index, 2, 'existing - index' );
  191. assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30 ], 'existing - time' );
  192. assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ], 'existing - value' );
  193. track = createTrack();
  194. index = THREE.GLTFExporter.Utils.insertKeyframe( track, 20.000005 );
  195. assert.equal( index, 3, 'tolerance - index' );
  196. assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30 ], 'tolerance - time' );
  197. assert.smartEqual( Array.from( track.values ), [ 0, 5, 1, 4, 2, 3, 3, 2, 4, 1, 5, 0 ], 'tolerance - value' );
  198. } );
  199. QUnit.test( 'utils - mergeMorphTargetTracks', ( assert ) => {
  200. var trackA = new NumberKeyframeTrack(
  201. 'foo.morphTargetInfluences[a]',
  202. [ 5, 10, 15, 20, 25, 30 ],
  203. [ 0, 0.2, 0.4, 0.6, 0.8, 1.0 ],
  204. InterpolateLinear
  205. );
  206. var trackB = new NumberKeyframeTrack(
  207. 'foo.morphTargetInfluences[b]',
  208. [ 10, 50 ],
  209. [ 0.25, 0.75 ],
  210. InterpolateLinear
  211. );
  212. var geometry = new BufferGeometry();
  213. var position = new BufferAttribute( new Float32Array( [ 0, 0, 0, 0, 0, 1, 1, 0, 1 ] ), 3 );
  214. geometry.addAttribute( 'position', position );
  215. geometry.morphAttributes.position = [ position, position ];
  216. var mesh = new Mesh( geometry );
  217. mesh.name = 'foo';
  218. mesh.morphTargetDictionary.a = 0;
  219. mesh.morphTargetDictionary.b = 1;
  220. var root = new Object3D();
  221. root.add( mesh );
  222. var clip = new AnimationClip( 'waltz', undefined, [ trackA, trackB ] );
  223. clip = THREE.GLTFExporter.Utils.mergeMorphTargetTracks( clip, root );
  224. assert.equal( clip.tracks.length, 1, 'tracks are merged' );
  225. var track = clip.tracks[ 0 ];
  226. assert.smartEqual( Array.from( track.times ), [ 5, 10, 15, 20, 25, 30, 50 ], 'all keyframes are present' );
  227. 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 ];
  228. for ( var i = 0; i < track.values.length; i ++ ) {
  229. assert.numEqual( track.values[ i ], expectedValues[ i ], 'all values are merged or interpolated - ' + i );
  230. }
  231. } );
  232. } );
  233. } );