glTFExporter.ts 73 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192939495969798991001011021031041051061071081091101111121131141151161171181191201211221231241251261271281291301311321331341351361371381391401411421431441451461471481491501511521531541551561571581591601611621631641651661671681691701711721731741751761771781791801811821831841851861871881891901911921931941951961971981992002012022032042052062072082092102112122132142152162172182192202212222232242252262272282292302312322332342352362372382392402412422432442452462472482492502512522532542552562572582592602612622632642652662672682692702712722732742752762772782792802812822832842852862872882892902912922932942952962972982993003013023033043053063073083093103113123133143153163173183193203213223233243253263273283293303313323333343353363373383393403413423433443453463473483493503513523533543553563573583593603613623633643653663673683693703713723733743753763773783793803813823833843853863873883893903913923933943953963973983994004014024034044054064074084094104114124134144154164174184194204214224234244254264274284294304314324334344354364374384394404414424434444454464474484494504514524534544554564574584594604614624634644654664674684694704714724734744754764774784794804814824834844854864874884894904914924934944954964974984995005015025035045055065075085095105115125135145155165175185195205215225235245255265275285295305315325335345355365375385395405415425435445455465475485495505515525535545555565575585595605615625635645655665675685695705715725735745755765775785795805815825835845855865875885895905915925935945955965975985996006016026036046056066076086096106116126136146156166176186196206216226236246256266276286296306316326336346356366376386396406416426436446456466476486496506516526536546556566576586596606616626636646656666676686696706716726736746756766776786796806816826836846856866876886896906916926936946956966976986997007017027037047057067077087097107117127137147157167177187197207217227237247257267277287297307317327337347357367377387397407417427437447457467477487497507517527537547557567577587597607617627637647657667677687697707717727737747757767777787797807817827837847857867877887897907917927937947957967977987998008018028038048058068078088098108118128138148158168178188198208218228238248258268278288298308318328338348358368378388398408418428438448458468478488498508518528538548558568578588598608618628638648658668678688698708718728738748758768778788798808818828838848858868878888898908918928938948958968978988999009019029039049059069079089099109119129139149159169179189199209219229239249259269279289299309319329339349359369379389399409419429439449459469479489499509519529539549559569579589599609619629639649659669679689699709719729739749759769779789799809819829839849859869879889899909919929939949959969979989991000100110021003100410051006100710081009101010111012101310141015101610171018101910201021102210231024102510261027102810291030103110321033103410351036103710381039104010411042104310441045104610471048104910501051105210531054105510561057105810591060106110621063106410651066106710681069107010711072107310741075107610771078107910801081108210831084108510861087108810891090109110921093109410951096109710981099110011011102110311041105110611071108110911101111111211131114111511161117111811191120112111221123112411251126112711281129113011311132113311341135113611371138113911401141114211431144114511461147114811491150115111521153115411551156115711581159116011611162116311641165116611671168116911701171117211731174117511761177117811791180118111821183118411851186118711881189119011911192119311941195119611971198119912001201120212031204120512061207120812091210121112121213121412151216121712181219122012211222122312241225122612271228122912301231123212331234123512361237123812391240124112421243124412451246124712481249125012511252125312541255125612571258125912601261126212631264126512661267126812691270127112721273127412751276127712781279128012811282128312841285128612871288128912901291129212931294129512961297129812991300130113021303130413051306130713081309131013111312131313141315131613171318131913201321132213231324132513261327132813291330133113321333133413351336133713381339134013411342134313441345134613471348134913501351135213531354135513561357135813591360136113621363136413651366136713681369137013711372137313741375137613771378137913801381138213831384138513861387138813891390139113921393139413951396139713981399140014011402140314041405140614071408140914101411141214131414141514161417141814191420142114221423142414251426142714281429143014311432143314341435143614371438143914401441144214431444144514461447144814491450145114521453145414551456145714581459146014611462146314641465146614671468146914701471147214731474147514761477147814791480148114821483148414851486148714881489149014911492149314941495149614971498149915001501150215031504150515061507150815091510151115121513151415151516151715181519152015211522152315241525152615271528152915301531153215331534153515361537153815391540154115421543154415451546154715481549155015511552155315541555155615571558155915601561156215631564156515661567156815691570157115721573157415751576157715781579158015811582158315841585158615871588158915901591159215931594
  1. import { AccessorType, IBufferView, IAccessor, INode, IScene, IMesh, IMaterial, ITexture, IImage, ISampler, IAnimation, ImageMimeType, IMeshPrimitive, IBuffer, IGLTF, MeshPrimitiveMode, AccessorComponentType } from "babylonjs-gltf2interface";
  2. import { FloatArray, Nullable, IndicesArray } from "babylonjs/types";
  3. import { Viewport, Color3, Vector2, Vector3, Vector4, Quaternion } from "babylonjs/Maths/math";
  4. import { Tools } from "babylonjs/Misc/tools";
  5. import { VertexBuffer } from "babylonjs/Meshes/buffer";
  6. import { Node } from "babylonjs/node";
  7. import { TransformNode } from "babylonjs/Meshes/transformNode";
  8. import { AbstractMesh } from "babylonjs/Meshes/abstractMesh";
  9. import { SubMesh } from "babylonjs/Meshes/subMesh";
  10. import { Mesh } from "babylonjs/Meshes/mesh";
  11. import { LinesMesh } from "babylonjs/Meshes/linesMesh";
  12. import { InstancedMesh } from "babylonjs/Meshes/instancedMesh";
  13. import { BaseTexture } from "babylonjs/Materials/Textures/baseTexture";
  14. import { Texture } from "babylonjs/Materials/Textures/texture";
  15. import { Material } from "babylonjs/Materials/material";
  16. import { MultiMaterial } from "babylonjs/Materials/multiMaterial";
  17. import { Engine } from "babylonjs/Engines/engine";
  18. import { Scene } from "babylonjs/scene";
  19. import { IGLTFExporterExtensionV2 } from "./glTFExporterExtension";
  20. import { _GLTFMaterialExporter } from "./glTFMaterialExporter";
  21. import { IExportOptions } from "./glTFSerializer";
  22. import { _GLTFUtilities } from "./glTFUtilities";
  23. import { GLTFData } from "./glTFData";
  24. import { _GLTFAnimation } from "./glTFAnimation";
  25. /**
  26. * Utility interface for storing vertex attribute data
  27. * @hidden
  28. */
  29. interface _IVertexAttributeData {
  30. /**
  31. * Specifies the Babylon Vertex Buffer Type (Position, Normal, Color, etc.)
  32. */
  33. kind: string;
  34. /**
  35. * Specifies the glTF Accessor Type (VEC2, VEC3, etc.)
  36. */
  37. accessorType: AccessorType;
  38. /**
  39. * Specifies the BufferView index for the vertex attribute data
  40. */
  41. bufferViewIndex?: number;
  42. byteStride?: number;
  43. }
  44. /**
  45. * Converts Babylon Scene into glTF 2.0.
  46. * @hidden
  47. */
  48. export class _Exporter {
  49. /**
  50. * Stores the glTF to export
  51. */
  52. public _glTF: IGLTF;
  53. /**
  54. * Stores all generated buffer views, which represents views into the main glTF buffer data
  55. */
  56. public _bufferViews: IBufferView[];
  57. /**
  58. * Stores all the generated accessors, which is used for accessing the data within the buffer views in glTF
  59. */
  60. public _accessors: IAccessor[];
  61. /**
  62. * Stores all the generated nodes, which contains transform and/or mesh information per node
  63. */
  64. private _nodes: INode[];
  65. /**
  66. * Stores all the generated glTF scenes, which stores multiple node hierarchies
  67. */
  68. private _scenes: IScene[];
  69. /**
  70. * Stores all the generated mesh information, each containing a set of primitives to render in glTF
  71. */
  72. private _meshes: IMesh[];
  73. /**
  74. * Stores all the generated material information, which represents the appearance of each primitive
  75. */
  76. public _materials: IMaterial[];
  77. public _materialMap: { [materialID: number]: number };
  78. /**
  79. * Stores all the generated texture information, which is referenced by glTF materials
  80. */
  81. public _textures: ITexture[];
  82. /**
  83. * Stores all the generated image information, which is referenced by glTF textures
  84. */
  85. public _images: IImage[];
  86. /**
  87. * Stores all the texture samplers
  88. */
  89. public _samplers: ISampler[];
  90. /**
  91. * Stores all the generated animation samplers, which is referenced by glTF animations
  92. */
  93. /**
  94. * Stores the animations for glTF models
  95. */
  96. private _animations: IAnimation[];
  97. /**
  98. * Stores the total amount of bytes stored in the glTF buffer
  99. */
  100. private _totalByteLength: number;
  101. /**
  102. * Stores a reference to the Babylon scene containing the source geometry and material information
  103. */
  104. public _babylonScene: Scene;
  105. /**
  106. * Stores a map of the image data, where the key is the file name and the value
  107. * is the image data
  108. */
  109. public _imageData: { [fileName: string]: { data: Uint8Array, mimeType: ImageMimeType } };
  110. /**
  111. * Stores a map of the unique id of a node to its index in the node array
  112. */
  113. private _nodeMap: { [key: number]: number };
  114. /**
  115. * Specifies if the Babylon scene should be converted to right-handed on export
  116. */
  117. public _convertToRightHandedSystem: boolean;
  118. /**
  119. * Baked animation sample rate
  120. */
  121. private _animationSampleRate: number;
  122. private _options: IExportOptions;
  123. private _localEngine: Engine;
  124. public _glTFMaterialExporter: _GLTFMaterialExporter;
  125. private _extensions: { [name: string]: IGLTFExporterExtensionV2 } = {};
  126. private static _ExtensionNames = new Array<string>();
  127. private static _ExtensionFactories: { [name: string]: (exporter: _Exporter) => IGLTFExporterExtensionV2 } = {};
  128. private _applyExtensions<T>(property: any, actionAsync: (extension: IGLTFExporterExtensionV2) => Nullable<T> | undefined): Nullable<T> {
  129. for (const name of _Exporter._ExtensionNames) {
  130. const extension = this._extensions[name];
  131. if (extension.enabled) {
  132. const exporterProperty = property as any;
  133. exporterProperty._activeLoaderExtensions = exporterProperty._activeLoaderExtensions || {};
  134. const activeLoaderExtensions = exporterProperty._activeLoaderExtensions;
  135. if (!activeLoaderExtensions[name]) {
  136. activeLoaderExtensions[name] = true;
  137. try {
  138. const result = actionAsync(extension);
  139. if (result) {
  140. return result;
  141. }
  142. }
  143. finally {
  144. delete activeLoaderExtensions[name];
  145. delete exporterProperty._activeLoaderExtensions;
  146. }
  147. }
  148. }
  149. }
  150. return null;
  151. }
  152. public _extensionsPreExportTextureAsync(context: string, babylonTexture: Texture, mimeType: ImageMimeType): Nullable<Promise<BaseTexture>> {
  153. return this._applyExtensions(babylonTexture, (extension) => extension.preExportTextureAsync && extension.preExportTextureAsync(context, babylonTexture, mimeType));
  154. }
  155. public _extensionsPostExportMeshPrimitiveAsync(context: string, meshPrimitive: IMeshPrimitive, babylonSubMesh: SubMesh, binaryWriter: _BinaryWriter): Nullable<Promise<IMeshPrimitive>> {
  156. return this._applyExtensions(meshPrimitive, (extension) => extension.postExportMeshPrimitiveAsync && extension.postExportMeshPrimitiveAsync(context, meshPrimitive, babylonSubMesh, binaryWriter));
  157. }
  158. public _extensionsPostExportNodeAsync(context: string, node: INode, babylonNode: Node): Nullable<Promise<INode>> {
  159. return this._applyExtensions(node, (extension) => extension.postExportNodeAsync && extension.postExportNodeAsync(context, node, babylonNode));
  160. }
  161. private _forEachExtensions(action: (extension: IGLTFExporterExtensionV2) => void): void {
  162. for (const name of _Exporter._ExtensionNames) {
  163. const extension = this._extensions[name];
  164. if (extension.enabled) {
  165. action(extension);
  166. }
  167. }
  168. }
  169. private _extensionsOnExporting(): void {
  170. this._forEachExtensions((extension) => extension.onExporting && extension.onExporting());
  171. }
  172. /**
  173. * Load glTF serializer extensions
  174. */
  175. private _loadExtensions(): void {
  176. for (const name of _Exporter._ExtensionNames) {
  177. const extension = _Exporter._ExtensionFactories[name](this);
  178. this._extensions[name] = extension;
  179. }
  180. }
  181. /**
  182. * Creates a glTF Exporter instance, which can accept optional exporter options
  183. * @param babylonScene Babylon scene object
  184. * @param options Options to modify the behavior of the exporter
  185. */
  186. public constructor(babylonScene: Scene, options?: IExportOptions) {
  187. this._glTF = {
  188. asset: { generator: "BabylonJS", version: "2.0" }
  189. };
  190. this._babylonScene = babylonScene;
  191. this._bufferViews = [];
  192. this._accessors = [];
  193. this._meshes = [];
  194. this._scenes = [];
  195. this._nodes = [];
  196. this._images = [];
  197. this._materials = [];
  198. this._materialMap = [];
  199. this._textures = [];
  200. this._samplers = [];
  201. this._animations = [];
  202. this._imageData = {};
  203. this._convertToRightHandedSystem = this._babylonScene.useRightHandedSystem ? false : true;
  204. this._options = options || {};
  205. this._animationSampleRate = options && options.animationSampleRate ? options.animationSampleRate : 1 / 60;
  206. this._glTFMaterialExporter = new _GLTFMaterialExporter(this);
  207. this._loadExtensions();
  208. }
  209. /**
  210. * Registers a glTF exporter extension
  211. * @param name Name of the extension to export
  212. * @param factory The factory function that creates the exporter extension
  213. */
  214. public static RegisterExtension(name: string, factory: (exporter: _Exporter) => IGLTFExporterExtensionV2): void {
  215. if (_Exporter.UnregisterExtension(name)) {
  216. Tools.Warn(`Extension with the name ${name} already exists`);
  217. }
  218. _Exporter._ExtensionFactories[name] = factory;
  219. _Exporter._ExtensionNames.push(name);
  220. }
  221. /**
  222. * Un-registers an exporter extension
  223. * @param name The name fo the exporter extension
  224. * @returns A boolean indicating whether the extension has been un-registered
  225. */
  226. public static UnregisterExtension(name: string): boolean {
  227. if (!_Exporter._ExtensionFactories[name]) {
  228. return false;
  229. }
  230. delete _Exporter._ExtensionFactories[name];
  231. const index = _Exporter._ExtensionNames.indexOf(name);
  232. if (index !== -1) {
  233. _Exporter._ExtensionNames.splice(index, 1);
  234. }
  235. return true;
  236. }
  237. /**
  238. * Lazy load a local engine with premultiplied alpha set to false
  239. */
  240. public _getLocalEngine(): Engine {
  241. if (!this._localEngine) {
  242. const localCanvas = document.createElement('canvas');
  243. localCanvas.id = "WriteCanvas";
  244. localCanvas.width = 2048;
  245. localCanvas.height = 2048;
  246. this._localEngine = new Engine(localCanvas, true, { premultipliedAlpha: false, preserveDrawingBuffer: true });
  247. this._localEngine.setViewport(new Viewport(0, 0, 1, 1));
  248. }
  249. return this._localEngine;
  250. }
  251. private reorderIndicesBasedOnPrimitiveMode(submesh: SubMesh, primitiveMode: number, babylonIndices: IndicesArray, byteOffset: number, binaryWriter: _BinaryWriter) {
  252. switch (primitiveMode) {
  253. case Material.TriangleFillMode: {
  254. if (!byteOffset) { byteOffset = 0; }
  255. for (let i = submesh.indexStart, length = submesh.indexStart + submesh.indexCount; i < length; i = i + 3) {
  256. const index = byteOffset + i * 4;
  257. // swap the second and third indices
  258. const secondIndex = binaryWriter.getUInt32(index + 4);
  259. const thirdIndex = binaryWriter.getUInt32(index + 8);
  260. binaryWriter.setUInt32(thirdIndex, index + 4);
  261. binaryWriter.setUInt32(secondIndex, index + 8);
  262. }
  263. break;
  264. }
  265. case Material.TriangleFanDrawMode: {
  266. for (let i = submesh.indexStart + submesh.indexCount - 1, start = submesh.indexStart; i >= start; --i) {
  267. binaryWriter.setUInt32(babylonIndices[i], byteOffset);
  268. byteOffset += 4;
  269. }
  270. break;
  271. }
  272. case Material.TriangleStripDrawMode: {
  273. if (submesh.indexCount >= 3) {
  274. binaryWriter.setUInt32(babylonIndices[submesh.indexStart + 2], byteOffset + 4);
  275. binaryWriter.setUInt32(babylonIndices[submesh.indexStart + 1], byteOffset + 8);
  276. }
  277. break;
  278. }
  279. }
  280. }
  281. /**
  282. * Reorders the vertex attribute data based on the primitive mode. This is necessary when indices are not available and the winding order is
  283. * clock-wise during export to glTF
  284. * @param submesh BabylonJS submesh
  285. * @param primitiveMode Primitive mode of the mesh
  286. * @param sideOrientation the winding order of the submesh
  287. * @param vertexBufferKind The type of vertex attribute
  288. * @param meshAttributeArray The vertex attribute data
  289. * @param byteOffset The offset to the binary data
  290. * @param binaryWriter The binary data for the glTF file
  291. */
  292. private reorderVertexAttributeDataBasedOnPrimitiveMode(submesh: SubMesh, primitiveMode: number, sideOrientation: number, vertexBufferKind: string, meshAttributeArray: FloatArray, byteOffset: number, binaryWriter: _BinaryWriter): void {
  293. if (this._convertToRightHandedSystem && sideOrientation === Material.ClockWiseSideOrientation) {
  294. switch (primitiveMode) {
  295. case Material.TriangleFillMode: {
  296. this.reorderTriangleFillMode(submesh, primitiveMode, sideOrientation, vertexBufferKind, meshAttributeArray, byteOffset, binaryWriter);
  297. break;
  298. }
  299. case Material.TriangleStripDrawMode: {
  300. this.reorderTriangleStripDrawMode(submesh, primitiveMode, sideOrientation, vertexBufferKind, meshAttributeArray, byteOffset, binaryWriter);
  301. break;
  302. }
  303. case Material.TriangleFanDrawMode: {
  304. this.reorderTriangleFanMode(submesh, primitiveMode, sideOrientation, vertexBufferKind, meshAttributeArray, byteOffset, binaryWriter);
  305. break;
  306. }
  307. }
  308. }
  309. }
  310. /**
  311. * Reorders the vertex attributes in the correct triangle mode order . This is necessary when indices are not available and the winding order is
  312. * clock-wise during export to glTF
  313. * @param submesh BabylonJS submesh
  314. * @param primitiveMode Primitive mode of the mesh
  315. * @param sideOrientation the winding order of the submesh
  316. * @param vertexBufferKind The type of vertex attribute
  317. * @param meshAttributeArray The vertex attribute data
  318. * @param byteOffset The offset to the binary data
  319. * @param binaryWriter The binary data for the glTF file
  320. */
  321. private reorderTriangleFillMode(submesh: SubMesh, primitiveMode: number, sideOrientation: number, vertexBufferKind: string, meshAttributeArray: FloatArray, byteOffset: number, binaryWriter: _BinaryWriter) {
  322. const vertexBuffer = this.getVertexBufferFromMesh(vertexBufferKind, submesh.getMesh() as Mesh);
  323. if (vertexBuffer) {
  324. let stride = vertexBuffer.byteStride / VertexBuffer.GetTypeByteLength(vertexBuffer.type);
  325. if (submesh.verticesCount % 3 !== 0) {
  326. Tools.Error('The submesh vertices for the triangle fill mode is not divisible by 3!');
  327. }
  328. else {
  329. let vertexData: Vector2[] | Vector3[] | Vector4[] = [];
  330. let index = 0;
  331. switch (vertexBufferKind) {
  332. case VertexBuffer.PositionKind:
  333. case VertexBuffer.NormalKind: {
  334. for (let x = submesh.verticesStart; x < submesh.verticesStart + submesh.verticesCount; x = x + 3) {
  335. index = x * stride;
  336. (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index));
  337. (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index + 2 * stride));
  338. (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index + stride));
  339. }
  340. break;
  341. }
  342. case VertexBuffer.TangentKind: {
  343. for (let x = submesh.verticesStart; x < submesh.verticesStart + submesh.verticesCount; x = x + 3) {
  344. index = x * stride;
  345. (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index));
  346. (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index + 2 * stride));
  347. (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index + stride));
  348. }
  349. break;
  350. }
  351. case VertexBuffer.ColorKind: {
  352. const size = vertexBuffer.getSize();
  353. for (let x = submesh.verticesStart; x < submesh.verticesStart + submesh.verticesCount; x = x + size) {
  354. index = x * stride;
  355. if (size === 4) {
  356. (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index));
  357. (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index + 2 * stride));
  358. (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index + stride));
  359. }
  360. else {
  361. (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index));
  362. (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index + 2 * stride));
  363. (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index + stride));
  364. }
  365. }
  366. break;
  367. }
  368. case VertexBuffer.UVKind:
  369. case VertexBuffer.UV2Kind: {
  370. for (let x = submesh.verticesStart; x < submesh.verticesStart + submesh.verticesCount; x = x + 3) {
  371. index = x * stride;
  372. (vertexData as Vector2[]).push(Vector2.FromArray(meshAttributeArray, index));
  373. (vertexData as Vector2[]).push(Vector2.FromArray(meshAttributeArray, index + 2 * stride));
  374. (vertexData as Vector2[]).push(Vector2.FromArray(meshAttributeArray, index + stride));
  375. }
  376. break;
  377. }
  378. default: {
  379. Tools.Error(`Unsupported Vertex Buffer type: ${vertexBufferKind}`);
  380. }
  381. }
  382. this.writeVertexAttributeData(vertexData, byteOffset, vertexBufferKind, meshAttributeArray, binaryWriter);
  383. }
  384. }
  385. else {
  386. Tools.Warn(`reorderTriangleFillMode: Vertex Buffer Kind ${vertexBufferKind} not present!`);
  387. }
  388. }
  389. /**
  390. * Reorders the vertex attributes in the correct triangle strip order. This is necessary when indices are not available and the winding order is
  391. * clock-wise during export to glTF
  392. * @param submesh BabylonJS submesh
  393. * @param primitiveMode Primitive mode of the mesh
  394. * @param sideOrientation the winding order of the submesh
  395. * @param vertexBufferKind The type of vertex attribute
  396. * @param meshAttributeArray The vertex attribute data
  397. * @param byteOffset The offset to the binary data
  398. * @param binaryWriter The binary data for the glTF file
  399. */
  400. private reorderTriangleStripDrawMode(submesh: SubMesh, primitiveMode: number, sideOrientation: number, vertexBufferKind: string, meshAttributeArray: FloatArray, byteOffset: number, binaryWriter: _BinaryWriter) {
  401. const vertexBuffer = this.getVertexBufferFromMesh(vertexBufferKind, submesh.getMesh() as Mesh);
  402. if (vertexBuffer) {
  403. const stride = vertexBuffer.byteStride / VertexBuffer.GetTypeByteLength(vertexBuffer.type);
  404. let vertexData: Vector2[] | Vector3[] | Vector4[] = [];
  405. let index = 0;
  406. switch (vertexBufferKind) {
  407. case VertexBuffer.PositionKind:
  408. case VertexBuffer.NormalKind: {
  409. index = submesh.verticesStart;
  410. (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index + 2 * stride));
  411. (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index + stride));
  412. break;
  413. }
  414. case VertexBuffer.TangentKind: {
  415. for (let x = submesh.verticesStart + submesh.verticesCount - 1; x >= submesh.verticesStart; --x) {
  416. index = x * stride;
  417. (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index));
  418. }
  419. break;
  420. }
  421. case VertexBuffer.ColorKind: {
  422. for (let x = submesh.verticesStart + submesh.verticesCount - 1; x >= submesh.verticesStart; --x) {
  423. index = x * stride;
  424. vertexBuffer.getSize() === 4 ? (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index)) : (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index));
  425. }
  426. break;
  427. }
  428. case VertexBuffer.UVKind:
  429. case VertexBuffer.UV2Kind: {
  430. for (let x = submesh.verticesStart + submesh.verticesCount - 1; x >= submesh.verticesStart; --x) {
  431. index = x * stride;
  432. (vertexData as Vector2[]).push(Vector2.FromArray(meshAttributeArray, index));
  433. }
  434. break;
  435. }
  436. default: {
  437. Tools.Error(`Unsupported Vertex Buffer type: ${vertexBufferKind}`);
  438. }
  439. }
  440. this.writeVertexAttributeData(vertexData, byteOffset + 12, vertexBufferKind, meshAttributeArray, binaryWriter);
  441. }
  442. else {
  443. Tools.Warn(`reorderTriangleStripDrawMode: Vertex buffer kind ${vertexBufferKind} not present!`);
  444. }
  445. }
  446. /**
  447. * Reorders the vertex attributes in the correct triangle fan order. This is necessary when indices are not available and the winding order is
  448. * clock-wise during export to glTF
  449. * @param submesh BabylonJS submesh
  450. * @param primitiveMode Primitive mode of the mesh
  451. * @param sideOrientation the winding order of the submesh
  452. * @param vertexBufferKind The type of vertex attribute
  453. * @param meshAttributeArray The vertex attribute data
  454. * @param byteOffset The offset to the binary data
  455. * @param binaryWriter The binary data for the glTF file
  456. */
  457. private reorderTriangleFanMode(submesh: SubMesh, primitiveMode: number, sideOrientation: number, vertexBufferKind: string, meshAttributeArray: FloatArray, byteOffset: number, binaryWriter: _BinaryWriter) {
  458. const vertexBuffer = this.getVertexBufferFromMesh(vertexBufferKind, submesh.getMesh() as Mesh);
  459. if (vertexBuffer) {
  460. let stride = vertexBuffer.byteStride / VertexBuffer.GetTypeByteLength(vertexBuffer.type);
  461. let vertexData: Vector2[] | Vector3[] | Vector4[] = [];
  462. let index = 0;
  463. switch (vertexBufferKind) {
  464. case VertexBuffer.PositionKind:
  465. case VertexBuffer.NormalKind: {
  466. for (let x = submesh.verticesStart + submesh.verticesCount - 1; x >= submesh.verticesStart; --x) {
  467. index = x * stride;
  468. (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index));
  469. }
  470. break;
  471. }
  472. case VertexBuffer.TangentKind: {
  473. for (let x = submesh.verticesStart + submesh.verticesCount - 1; x >= submesh.verticesStart; --x) {
  474. index = x * stride;
  475. (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index));
  476. }
  477. break;
  478. }
  479. case VertexBuffer.ColorKind: {
  480. for (let x = submesh.verticesStart + submesh.verticesCount - 1; x >= submesh.verticesStart; --x) {
  481. index = x * stride;
  482. (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index));
  483. vertexBuffer.getSize() === 4 ? (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index)) : (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index));
  484. }
  485. break;
  486. }
  487. case VertexBuffer.UVKind:
  488. case VertexBuffer.UV2Kind: {
  489. for (let x = submesh.verticesStart + submesh.verticesCount - 1; x >= submesh.verticesStart; --x) {
  490. index = x * stride;
  491. (vertexData as Vector2[]).push(Vector2.FromArray(meshAttributeArray, index));
  492. }
  493. break;
  494. }
  495. default: {
  496. Tools.Error(`Unsupported Vertex Buffer type: ${vertexBufferKind}`);
  497. }
  498. }
  499. this.writeVertexAttributeData(vertexData, byteOffset, vertexBufferKind, meshAttributeArray, binaryWriter);
  500. }
  501. else {
  502. Tools.Warn(`reorderTriangleFanMode: Vertex buffer kind ${vertexBufferKind} not present!`);
  503. }
  504. }
  505. /**
  506. * Writes the vertex attribute data to binary
  507. * @param vertices The vertices to write to the binary writer
  508. * @param byteOffset The offset into the binary writer to overwrite binary data
  509. * @param vertexAttributeKind The vertex attribute type
  510. * @param meshAttributeArray The vertex attribute data
  511. * @param binaryWriter The writer containing the binary data
  512. */
  513. private writeVertexAttributeData(vertices: Vector2[] | Vector3[] | Vector4[], byteOffset: number, vertexAttributeKind: string, meshAttributeArray: FloatArray, binaryWriter: _BinaryWriter) {
  514. for (let vertex of vertices) {
  515. if (this._convertToRightHandedSystem && !(vertexAttributeKind === VertexBuffer.ColorKind) && !(vertex instanceof Vector2)) {
  516. if (vertex instanceof Vector3) {
  517. if (vertexAttributeKind === VertexBuffer.NormalKind) {
  518. _GLTFUtilities._GetRightHandedNormalVector3FromRef(vertex);
  519. }
  520. else if (vertexAttributeKind === VertexBuffer.PositionKind) {
  521. _GLTFUtilities._GetRightHandedPositionVector3FromRef(vertex);
  522. }
  523. else {
  524. Tools.Error('Unsupported vertex attribute kind!');
  525. }
  526. }
  527. else {
  528. _GLTFUtilities._GetRightHandedVector4FromRef(vertex);
  529. }
  530. }
  531. if (vertexAttributeKind === VertexBuffer.NormalKind) {
  532. vertex.normalize();
  533. }
  534. else if (vertexAttributeKind === VertexBuffer.TangentKind && vertex instanceof Vector4) {
  535. _GLTFUtilities._NormalizeTangentFromRef(vertex);
  536. }
  537. for (let component of vertex.asArray()) {
  538. binaryWriter.setFloat32(component, byteOffset);
  539. byteOffset += 4;
  540. }
  541. }
  542. }
  543. /**
  544. * Writes mesh attribute data to a data buffer
  545. * Returns the bytelength of the data
  546. * @param vertexBufferKind Indicates what kind of vertex data is being passed in
  547. * @param meshAttributeArray Array containing the attribute data
  548. * @param binaryWriter The buffer to write the binary data to
  549. * @param indices Used to specify the order of the vertex data
  550. */
  551. public writeAttributeData(vertexBufferKind: string, meshAttributeArray: FloatArray, byteStride: number, binaryWriter: _BinaryWriter) {
  552. const stride = byteStride / 4;
  553. let vertexAttributes: number[][] = [];
  554. let index: number;
  555. switch (vertexBufferKind) {
  556. case VertexBuffer.PositionKind: {
  557. for (let k = 0, length = meshAttributeArray.length / stride; k < length; ++k) {
  558. index = k * stride;
  559. const vertexData = Vector3.FromArray(meshAttributeArray, index);
  560. if (this._convertToRightHandedSystem) {
  561. _GLTFUtilities._GetRightHandedPositionVector3FromRef(vertexData);
  562. }
  563. vertexAttributes.push(vertexData.asArray());
  564. }
  565. break;
  566. }
  567. case VertexBuffer.NormalKind: {
  568. for (let k = 0, length = meshAttributeArray.length / stride; k < length; ++k) {
  569. index = k * stride;
  570. const vertexData = Vector3.FromArray(meshAttributeArray, index);
  571. if (this._convertToRightHandedSystem) {
  572. _GLTFUtilities._GetRightHandedNormalVector3FromRef(vertexData);
  573. }
  574. vertexData.normalize();
  575. vertexAttributes.push(vertexData.asArray());
  576. }
  577. break;
  578. }
  579. case VertexBuffer.TangentKind: {
  580. for (let k = 0, length = meshAttributeArray.length / stride; k < length; ++k) {
  581. index = k * stride;
  582. const vertexData = Vector4.FromArray(meshAttributeArray, index);
  583. if (this._convertToRightHandedSystem) {
  584. _GLTFUtilities._GetRightHandedVector4FromRef(vertexData);
  585. }
  586. _GLTFUtilities._NormalizeTangentFromRef(vertexData);
  587. vertexAttributes.push(vertexData.asArray());
  588. }
  589. break;
  590. }
  591. case VertexBuffer.ColorKind: {
  592. for (let k = 0, length = meshAttributeArray.length / stride; k < length; ++k) {
  593. index = k * stride;
  594. const vertexData = stride === 3 ? Vector3.FromArray(meshAttributeArray, index) : Vector4.FromArray(meshAttributeArray, index);
  595. vertexAttributes.push(vertexData.asArray());
  596. }
  597. break;
  598. }
  599. case VertexBuffer.UVKind:
  600. case VertexBuffer.UV2Kind: {
  601. for (let k = 0, length = meshAttributeArray.length / stride; k < length; ++k) {
  602. index = k * stride;
  603. vertexAttributes.push(this._convertToRightHandedSystem ? [meshAttributeArray[index], meshAttributeArray[index + 1]] : [meshAttributeArray[index], meshAttributeArray[index + 1]]);
  604. }
  605. break;
  606. }
  607. default: {
  608. Tools.Warn("Unsupported Vertex Buffer Type: " + vertexBufferKind);
  609. vertexAttributes = [];
  610. }
  611. }
  612. for (let vertexAttribute of vertexAttributes) {
  613. for (let component of vertexAttribute) {
  614. binaryWriter.setFloat32(component);
  615. }
  616. }
  617. }
  618. /**
  619. * Generates glTF json data
  620. * @param shouldUseGlb Indicates whether the json should be written for a glb file
  621. * @param glTFPrefix Text to use when prefixing a glTF file
  622. * @param prettyPrint Indicates whether the json file should be pretty printed (true) or not (false)
  623. * @returns json data as string
  624. */
  625. private generateJSON(shouldUseGlb: boolean, glTFPrefix?: string, prettyPrint?: boolean): string {
  626. let buffer: IBuffer = { byteLength: this._totalByteLength };
  627. let imageName: string;
  628. let imageData: { data: Uint8Array, mimeType: ImageMimeType };
  629. let bufferView: IBufferView;
  630. let byteOffset: number = this._totalByteLength;
  631. if (buffer.byteLength) {
  632. this._glTF.buffers = [buffer];
  633. }
  634. if (this._nodes && this._nodes.length) {
  635. this._glTF.nodes = this._nodes;
  636. }
  637. if (this._meshes && this._meshes.length) {
  638. this._glTF.meshes = this._meshes;
  639. }
  640. if (this._scenes && this._scenes.length) {
  641. this._glTF.scenes = this._scenes;
  642. this._glTF.scene = 0;
  643. }
  644. if (this._bufferViews && this._bufferViews.length) {
  645. this._glTF.bufferViews = this._bufferViews;
  646. }
  647. if (this._accessors && this._accessors.length) {
  648. this._glTF.accessors = this._accessors;
  649. }
  650. if (this._animations && this._animations.length) {
  651. this._glTF.animations = this._animations;
  652. }
  653. if (this._materials && this._materials.length) {
  654. this._glTF.materials = this._materials;
  655. }
  656. if (this._textures && this._textures.length) {
  657. this._glTF.textures = this._textures;
  658. }
  659. if (this._samplers && this._samplers.length) {
  660. this._glTF.samplers = this._samplers;
  661. }
  662. if (this._images && this._images.length) {
  663. if (!shouldUseGlb) {
  664. this._glTF.images = this._images;
  665. }
  666. else {
  667. this._glTF.images = [];
  668. this._images.forEach((image) => {
  669. if (image.uri) {
  670. imageData = this._imageData[image.uri];
  671. imageName = image.uri.split('.')[0] + " image";
  672. bufferView = _GLTFUtilities._CreateBufferView(0, byteOffset, imageData.data.length, undefined, imageName);
  673. byteOffset += imageData.data.buffer.byteLength;
  674. this._bufferViews.push(bufferView);
  675. image.bufferView = this._bufferViews.length - 1;
  676. image.name = imageName;
  677. image.mimeType = imageData.mimeType;
  678. image.uri = undefined;
  679. if (!this._glTF.images) {
  680. this._glTF.images = [];
  681. }
  682. this._glTF.images.push(image);
  683. }
  684. });
  685. // Replace uri with bufferview and mime type for glb
  686. buffer.byteLength = byteOffset;
  687. }
  688. }
  689. if (!shouldUseGlb) {
  690. buffer.uri = glTFPrefix + ".bin";
  691. }
  692. const jsonText = prettyPrint ? JSON.stringify(this._glTF, null, 2) : JSON.stringify(this._glTF);
  693. return jsonText;
  694. }
  695. /**
  696. * Generates data for .gltf and .bin files based on the glTF prefix string
  697. * @param glTFPrefix Text to use when prefixing a glTF file
  698. * @returns GLTFData with glTF file data
  699. */
  700. public _generateGLTFAsync(glTFPrefix: string): Promise<GLTFData> {
  701. return this._generateBinaryAsync().then((binaryBuffer) => {
  702. this._extensionsOnExporting();
  703. const jsonText = this.generateJSON(false, glTFPrefix, true);
  704. const bin = new Blob([binaryBuffer], { type: 'application/octet-stream' });
  705. const glTFFileName = glTFPrefix + '.gltf';
  706. const glTFBinFile = glTFPrefix + '.bin';
  707. const container = new GLTFData();
  708. container.glTFFiles[glTFFileName] = jsonText;
  709. container.glTFFiles[glTFBinFile] = bin;
  710. if (this._imageData) {
  711. for (let image in this._imageData) {
  712. container.glTFFiles[image] = new Blob([this._imageData[image].data], { type: this._imageData[image].mimeType });
  713. }
  714. }
  715. return container;
  716. });
  717. }
  718. /**
  719. * Creates a binary buffer for glTF
  720. * @returns array buffer for binary data
  721. */
  722. private _generateBinaryAsync(): Promise<ArrayBuffer> {
  723. let binaryWriter = new _BinaryWriter(4);
  724. return this.createSceneAsync(this._babylonScene, binaryWriter).then(() => {
  725. if (this._localEngine) {
  726. this._localEngine.dispose();
  727. }
  728. return binaryWriter.getArrayBuffer();
  729. });
  730. }
  731. /**
  732. * Pads the number to a multiple of 4
  733. * @param num number to pad
  734. * @returns padded number
  735. */
  736. private _getPadding(num: number): number {
  737. let remainder = num % 4;
  738. let padding = remainder === 0 ? remainder : 4 - remainder;
  739. return padding;
  740. }
  741. /**
  742. * Generates a glb file from the json and binary data
  743. * Returns an object with the glb file name as the key and data as the value
  744. * @param glTFPrefix
  745. * @returns object with glb filename as key and data as value
  746. */
  747. public _generateGLBAsync(glTFPrefix: string): Promise<GLTFData> {
  748. return this._generateBinaryAsync().then((binaryBuffer) => {
  749. this._extensionsOnExporting();
  750. const jsonText = this.generateJSON(true);
  751. const glbFileName = glTFPrefix + '.glb';
  752. const headerLength = 12;
  753. const chunkLengthPrefix = 8;
  754. const jsonLength = jsonText.length;
  755. let imageByteLength = 0;
  756. for (let key in this._imageData) {
  757. imageByteLength += this._imageData[key].data.byteLength;
  758. }
  759. const jsonPadding = this._getPadding(jsonLength);
  760. const binPadding = this._getPadding(binaryBuffer.byteLength);
  761. const imagePadding = this._getPadding(imageByteLength);
  762. const byteLength = headerLength + (2 * chunkLengthPrefix) + jsonLength + jsonPadding + binaryBuffer.byteLength + binPadding + imageByteLength + imagePadding;
  763. //header
  764. const headerBuffer = new ArrayBuffer(headerLength);
  765. const headerBufferView = new DataView(headerBuffer);
  766. headerBufferView.setUint32(0, 0x46546C67, true); //glTF
  767. headerBufferView.setUint32(4, 2, true); // version
  768. headerBufferView.setUint32(8, byteLength, true); // total bytes in file
  769. //json chunk
  770. const jsonChunkBuffer = new ArrayBuffer(chunkLengthPrefix + jsonLength + jsonPadding);
  771. const jsonChunkBufferView = new DataView(jsonChunkBuffer);
  772. jsonChunkBufferView.setUint32(0, jsonLength + jsonPadding, true);
  773. jsonChunkBufferView.setUint32(4, 0x4E4F534A, true);
  774. //json chunk bytes
  775. const jsonData = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix);
  776. for (let i = 0; i < jsonLength; ++i) {
  777. jsonData[i] = jsonText.charCodeAt(i);
  778. }
  779. //json padding
  780. const jsonPaddingView = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix + jsonLength);
  781. for (let i = 0; i < jsonPadding; ++i) {
  782. jsonPaddingView[i] = 0x20;
  783. }
  784. //binary chunk
  785. const binaryChunkBuffer = new ArrayBuffer(chunkLengthPrefix);
  786. const binaryChunkBufferView = new DataView(binaryChunkBuffer);
  787. binaryChunkBufferView.setUint32(0, binaryBuffer.byteLength + imageByteLength + imagePadding, true);
  788. binaryChunkBufferView.setUint32(4, 0x004E4942, true);
  789. // binary padding
  790. const binPaddingBuffer = new ArrayBuffer(binPadding);
  791. const binPaddingView = new Uint8Array(binPaddingBuffer);
  792. for (let i = 0; i < binPadding; ++i) {
  793. binPaddingView[i] = 0;
  794. }
  795. const imagePaddingBuffer = new ArrayBuffer(imagePadding);
  796. const imagePaddingView = new Uint8Array(imagePaddingBuffer);
  797. for (let i = 0; i < imagePadding; ++i) {
  798. imagePaddingView[i] = 0;
  799. }
  800. const glbData = [headerBuffer, jsonChunkBuffer, binaryChunkBuffer, binaryBuffer];
  801. // binary data
  802. for (let key in this._imageData) {
  803. glbData.push(this._imageData[key].data.buffer);
  804. }
  805. glbData.push(binPaddingBuffer);
  806. glbData.push(imagePaddingBuffer);
  807. const glbFile = new Blob(glbData, { type: 'application/octet-stream' });
  808. const container = new GLTFData();
  809. container.glTFFiles[glbFileName] = glbFile;
  810. if (this._localEngine != null) {
  811. this._localEngine.dispose();
  812. }
  813. return container;
  814. });
  815. }
  816. /**
  817. * Sets the TRS for each node
  818. * @param node glTF Node for storing the transformation data
  819. * @param babylonTransformNode Babylon mesh used as the source for the transformation data
  820. */
  821. private setNodeTransformation(node: INode, babylonTransformNode: TransformNode): void {
  822. if (!babylonTransformNode.getPivotPoint().equalsToFloats(0, 0, 0)) {
  823. Tools.Warn("Pivot points are not supported in the glTF serializer");
  824. }
  825. if (!babylonTransformNode.position.equalsToFloats(0, 0, 0)) {
  826. node.translation = this._convertToRightHandedSystem ? _GLTFUtilities._GetRightHandedPositionVector3(babylonTransformNode.position).asArray() : babylonTransformNode.position.asArray();
  827. }
  828. if (!babylonTransformNode.scaling.equalsToFloats(1, 1, 1)) {
  829. node.scale = babylonTransformNode.scaling.asArray();
  830. }
  831. let rotationQuaternion = Quaternion.RotationYawPitchRoll(babylonTransformNode.rotation.y, babylonTransformNode.rotation.x, babylonTransformNode.rotation.z);
  832. if (babylonTransformNode.rotationQuaternion) {
  833. rotationQuaternion.multiplyInPlace(babylonTransformNode.rotationQuaternion);
  834. }
  835. if (!(rotationQuaternion.x === 0 && rotationQuaternion.y === 0 && rotationQuaternion.z === 0 && rotationQuaternion.w === 1)) {
  836. if (this._convertToRightHandedSystem) {
  837. _GLTFUtilities._GetRightHandedQuaternionFromRef(rotationQuaternion);
  838. }
  839. node.rotation = rotationQuaternion.normalize().asArray();
  840. }
  841. }
  842. private getVertexBufferFromMesh(attributeKind: string, bufferMesh: Mesh): Nullable<VertexBuffer> {
  843. if (bufferMesh.isVerticesDataPresent(attributeKind)) {
  844. const vertexBuffer = bufferMesh.getVertexBuffer(attributeKind);
  845. if (vertexBuffer) {
  846. return vertexBuffer;
  847. }
  848. }
  849. return null;
  850. }
  851. /**
  852. * Creates a bufferview based on the vertices type for the Babylon mesh
  853. * @param kind Indicates the type of vertices data
  854. * @param babylonTransformNode The Babylon mesh to get the vertices data from
  855. * @param binaryWriter The buffer to write the bufferview data to
  856. */
  857. private createBufferViewKind(kind: string, babylonTransformNode: TransformNode, binaryWriter: _BinaryWriter, byteStride: number) {
  858. const bufferMesh = babylonTransformNode instanceof Mesh ?
  859. babylonTransformNode as Mesh : babylonTransformNode instanceof InstancedMesh ?
  860. (babylonTransformNode as InstancedMesh).sourceMesh : null;
  861. if (bufferMesh) {
  862. const vertexData = bufferMesh.getVerticesData(kind);
  863. if (vertexData) {
  864. const byteLength = vertexData.length * 4;
  865. const bufferView = _GLTFUtilities._CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, byteStride, kind + " - " + bufferMesh.name);
  866. this._bufferViews.push(bufferView);
  867. this.writeAttributeData(
  868. kind,
  869. vertexData,
  870. byteStride,
  871. binaryWriter
  872. );
  873. }
  874. }
  875. }
  876. /**
  877. * The primitive mode of the Babylon mesh
  878. * @param babylonMesh The BabylonJS mesh
  879. */
  880. private getMeshPrimitiveMode(babylonMesh: AbstractMesh): number {
  881. if (babylonMesh instanceof LinesMesh) {
  882. return Material.LineListDrawMode;
  883. }
  884. return babylonMesh.material ? babylonMesh.material.fillMode : Material.TriangleFillMode;
  885. }
  886. /**
  887. * Sets the primitive mode of the glTF mesh primitive
  888. * @param meshPrimitive glTF mesh primitive
  889. * @param primitiveMode The primitive mode
  890. */
  891. private setPrimitiveMode(meshPrimitive: IMeshPrimitive, primitiveMode: number) {
  892. switch (primitiveMode) {
  893. case Material.TriangleFillMode: {
  894. // glTF defaults to using Triangle Mode
  895. break;
  896. }
  897. case Material.TriangleStripDrawMode: {
  898. meshPrimitive.mode = MeshPrimitiveMode.TRIANGLE_STRIP;
  899. break;
  900. }
  901. case Material.TriangleFanDrawMode: {
  902. meshPrimitive.mode = MeshPrimitiveMode.TRIANGLE_FAN;
  903. break;
  904. }
  905. case Material.PointListDrawMode: {
  906. meshPrimitive.mode = MeshPrimitiveMode.POINTS;
  907. }
  908. case Material.PointFillMode: {
  909. meshPrimitive.mode = MeshPrimitiveMode.POINTS;
  910. break;
  911. }
  912. case Material.LineLoopDrawMode: {
  913. meshPrimitive.mode = MeshPrimitiveMode.LINE_LOOP;
  914. break;
  915. }
  916. case Material.LineListDrawMode: {
  917. meshPrimitive.mode = MeshPrimitiveMode.LINES;
  918. break;
  919. }
  920. case Material.LineStripDrawMode: {
  921. meshPrimitive.mode = MeshPrimitiveMode.LINE_STRIP;
  922. break;
  923. }
  924. }
  925. }
  926. /**
  927. * Sets the vertex attribute accessor based of the glTF mesh primitive
  928. * @param meshPrimitive glTF mesh primitive
  929. * @param attributeKind vertex attribute
  930. * @returns boolean specifying if uv coordinates are present
  931. */
  932. private setAttributeKind(meshPrimitive: IMeshPrimitive, attributeKind: string): void {
  933. switch (attributeKind) {
  934. case VertexBuffer.PositionKind: {
  935. meshPrimitive.attributes.POSITION = this._accessors.length - 1;
  936. break;
  937. }
  938. case VertexBuffer.NormalKind: {
  939. meshPrimitive.attributes.NORMAL = this._accessors.length - 1;
  940. break;
  941. }
  942. case VertexBuffer.ColorKind: {
  943. meshPrimitive.attributes.COLOR_0 = this._accessors.length - 1;
  944. break;
  945. }
  946. case VertexBuffer.TangentKind: {
  947. meshPrimitive.attributes.TANGENT = this._accessors.length - 1;
  948. break;
  949. }
  950. case VertexBuffer.UVKind: {
  951. meshPrimitive.attributes.TEXCOORD_0 = this._accessors.length - 1;
  952. break;
  953. }
  954. case VertexBuffer.UV2Kind: {
  955. meshPrimitive.attributes.TEXCOORD_1 = this._accessors.length - 1;
  956. break;
  957. }
  958. default: {
  959. Tools.Warn("Unsupported Vertex Buffer Type: " + attributeKind);
  960. }
  961. }
  962. }
  963. /**
  964. * Sets data for the primitive attributes of each submesh
  965. * @param mesh glTF Mesh object to store the primitive attribute information
  966. * @param babylonTransformNode Babylon mesh to get the primitive attribute data from
  967. * @param binaryWriter Buffer to write the attribute data to
  968. */
  969. private setPrimitiveAttributesAsync(mesh: IMesh, babylonTransformNode: TransformNode, binaryWriter: _BinaryWriter): Promise<void> {
  970. let promises: Promise<IMeshPrimitive>[] = [];
  971. let bufferMesh: Nullable<Mesh> = null;
  972. let bufferView: IBufferView;
  973. let minMax: { min: Nullable<number[]>, max: Nullable<number[]> };
  974. if (babylonTransformNode instanceof Mesh) {
  975. bufferMesh = (babylonTransformNode as Mesh);
  976. }
  977. else if (babylonTransformNode instanceof InstancedMesh) {
  978. bufferMesh = (babylonTransformNode as InstancedMesh).sourceMesh;
  979. }
  980. const attributeData: _IVertexAttributeData[] = [
  981. { kind: VertexBuffer.PositionKind, accessorType: AccessorType.VEC3, byteStride: 12 },
  982. { kind: VertexBuffer.NormalKind, accessorType: AccessorType.VEC3, byteStride: 12 },
  983. { kind: VertexBuffer.ColorKind, accessorType: AccessorType.VEC4, byteStride: 16 },
  984. { kind: VertexBuffer.TangentKind, accessorType: AccessorType.VEC4, byteStride: 16 },
  985. { kind: VertexBuffer.UVKind, accessorType: AccessorType.VEC2, byteStride: 8 },
  986. { kind: VertexBuffer.UV2Kind, accessorType: AccessorType.VEC2, byteStride: 8 },
  987. ];
  988. if (bufferMesh) {
  989. let indexBufferViewIndex: Nullable<number> = null;
  990. const primitiveMode = this.getMeshPrimitiveMode(bufferMesh);
  991. let vertexAttributeBufferViews: { [attributeKind: string]: number } = {};
  992. // For each BabylonMesh, create bufferviews for each 'kind'
  993. for (const attribute of attributeData) {
  994. const attributeKind = attribute.kind;
  995. if (bufferMesh.isVerticesDataPresent(attributeKind)) {
  996. const vertexBuffer = this.getVertexBufferFromMesh(attributeKind, bufferMesh);
  997. attribute.byteStride = vertexBuffer ? vertexBuffer.getSize() * 4 : VertexBuffer.DeduceStride(attributeKind) * 4;
  998. if (attribute.byteStride === 12) {
  999. attribute.accessorType = AccessorType.VEC3;
  1000. }
  1001. this.createBufferViewKind(attributeKind, babylonTransformNode, binaryWriter, attribute.byteStride);
  1002. attribute.bufferViewIndex = this._bufferViews.length - 1;
  1003. vertexAttributeBufferViews[attributeKind] = attribute.bufferViewIndex;
  1004. }
  1005. }
  1006. if (bufferMesh.getTotalIndices()) {
  1007. const indices = bufferMesh.getIndices();
  1008. if (indices) {
  1009. const byteLength = indices.length * 4;
  1010. bufferView = _GLTFUtilities._CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, "Indices - " + bufferMesh.name);
  1011. this._bufferViews.push(bufferView);
  1012. indexBufferViewIndex = this._bufferViews.length - 1;
  1013. for (let k = 0, length = indices.length; k < length; ++k) {
  1014. binaryWriter.setUInt32(indices[k]);
  1015. }
  1016. }
  1017. }
  1018. if (bufferMesh.subMeshes) {
  1019. // go through all mesh primitives (submeshes)
  1020. for (const submesh of bufferMesh.subMeshes) {
  1021. let babylonMaterial = submesh.getMaterial() || bufferMesh.getScene().defaultMaterial;
  1022. let materialIndex: Nullable<number> = null;
  1023. if (babylonMaterial) {
  1024. if (bufferMesh instanceof LinesMesh) {
  1025. // get the color from the lines mesh and set it in the material
  1026. const material: IMaterial = {
  1027. name: bufferMesh.name + ' material'
  1028. };
  1029. if (!bufferMesh.color.equals(Color3.White()) || bufferMesh.alpha < 1) {
  1030. material.pbrMetallicRoughness = {
  1031. baseColorFactor: bufferMesh.color.asArray().concat([bufferMesh.alpha])
  1032. };
  1033. }
  1034. this._materials.push(material);
  1035. materialIndex = this._materials.length - 1;
  1036. }
  1037. else if (babylonMaterial instanceof MultiMaterial) {
  1038. const subMaterial = babylonMaterial.subMaterials[submesh.materialIndex];
  1039. if (subMaterial) {
  1040. babylonMaterial = subMaterial;
  1041. materialIndex = this._materialMap[babylonMaterial.uniqueId];
  1042. }
  1043. }
  1044. else {
  1045. materialIndex = this._materialMap[babylonMaterial.uniqueId];
  1046. }
  1047. }
  1048. let glTFMaterial: Nullable<IMaterial> = materialIndex != null ? this._materials[materialIndex] : null;
  1049. const meshPrimitive: IMeshPrimitive = { attributes: {} };
  1050. this.setPrimitiveMode(meshPrimitive, primitiveMode);
  1051. for (const attribute of attributeData) {
  1052. const attributeKind = attribute.kind;
  1053. if (attributeKind === VertexBuffer.UVKind || attributeKind === VertexBuffer.UV2Kind) {
  1054. if (glTFMaterial && !this._glTFMaterialExporter._hasTexturesPresent(glTFMaterial)) {
  1055. continue;
  1056. }
  1057. }
  1058. let vertexData = bufferMesh.getVerticesData(attributeKind);
  1059. if (vertexData) {
  1060. const vertexBuffer = this.getVertexBufferFromMesh(attributeKind, bufferMesh);
  1061. if (vertexBuffer) {
  1062. const stride = vertexBuffer.getSize();
  1063. const bufferViewIndex = attribute.bufferViewIndex;
  1064. if (bufferViewIndex != undefined) { // check to see if bufferviewindex has a numeric value assigned.
  1065. minMax = { min: null, max: null };
  1066. if (attributeKind == VertexBuffer.PositionKind) {
  1067. minMax = _GLTFUtilities._CalculateMinMaxPositions(vertexData, 0, vertexData.length / stride, this._convertToRightHandedSystem);
  1068. }
  1069. const accessor = _GLTFUtilities._CreateAccessor(bufferViewIndex, attributeKind + " - " + babylonTransformNode.name, attribute.accessorType, AccessorComponentType.FLOAT, vertexData.length / stride, 0, minMax.min, minMax.max);
  1070. this._accessors.push(accessor);
  1071. this.setAttributeKind(meshPrimitive, attributeKind);
  1072. }
  1073. }
  1074. }
  1075. }
  1076. if (indexBufferViewIndex) {
  1077. // Create accessor
  1078. const accessor = _GLTFUtilities._CreateAccessor(indexBufferViewIndex, "indices - " + babylonTransformNode.name, AccessorType.SCALAR, AccessorComponentType.UNSIGNED_INT, submesh.indexCount, submesh.indexStart * 4, null, null);
  1079. this._accessors.push(accessor);
  1080. meshPrimitive.indices = this._accessors.length - 1;
  1081. }
  1082. if (materialIndex != null && Object.keys(meshPrimitive.attributes).length > 0) {
  1083. let sideOrientation = babylonMaterial.sideOrientation;
  1084. // Only reverse the winding if we have a clockwise winding
  1085. if (sideOrientation === Material.ClockWiseSideOrientation) {
  1086. let byteOffset = indexBufferViewIndex != null ? this._bufferViews[indexBufferViewIndex].byteOffset : null;
  1087. if (byteOffset == null) { byteOffset = 0; }
  1088. let babylonIndices: Nullable<IndicesArray> = null;
  1089. if (indexBufferViewIndex != null) {
  1090. babylonIndices = bufferMesh.getIndices();
  1091. }
  1092. if (babylonIndices) {
  1093. this.reorderIndicesBasedOnPrimitiveMode(submesh, primitiveMode, babylonIndices, byteOffset, binaryWriter);
  1094. }
  1095. else {
  1096. for (let attribute of attributeData) {
  1097. let vertexData = bufferMesh.getVerticesData(attribute.kind);
  1098. if (vertexData) {
  1099. let byteOffset = this._bufferViews[vertexAttributeBufferViews[attribute.kind]].byteOffset;
  1100. if (!byteOffset) {
  1101. byteOffset = 0;
  1102. }
  1103. this.reorderVertexAttributeDataBasedOnPrimitiveMode(submesh, primitiveMode, sideOrientation, attribute.kind, vertexData, byteOffset, binaryWriter);
  1104. }
  1105. }
  1106. }
  1107. }
  1108. meshPrimitive.material = materialIndex;
  1109. }
  1110. mesh.primitives.push(meshPrimitive);
  1111. const promise = this._extensionsPostExportMeshPrimitiveAsync("postExport", meshPrimitive, submesh, binaryWriter);
  1112. if (promise) {
  1113. promises.push();
  1114. }
  1115. }
  1116. }
  1117. }
  1118. return Promise.all(promises).then(() => {
  1119. /* do nothing */
  1120. });
  1121. }
  1122. /**
  1123. * Creates a glTF scene based on the array of meshes
  1124. * Returns the the total byte offset
  1125. * @param babylonScene Babylon scene to get the mesh data from
  1126. * @param binaryWriter Buffer to write binary data to
  1127. */
  1128. private createSceneAsync(babylonScene: Scene, binaryWriter: _BinaryWriter): Promise<void> {
  1129. const scene: IScene = { nodes: [] };
  1130. let glTFNodeIndex: number;
  1131. let glTFNode: INode;
  1132. let directDescendents: Node[];
  1133. const nodes: Node[] = [...babylonScene.transformNodes, ...babylonScene.meshes, ...babylonScene.lights];
  1134. return this._glTFMaterialExporter._convertMaterialsToGLTFAsync(babylonScene.materials, ImageMimeType.PNG, true).then(() => {
  1135. return this.createNodeMapAndAnimationsAsync(babylonScene, nodes, binaryWriter).then((nodeMap) => {
  1136. this._nodeMap = nodeMap;
  1137. this._totalByteLength = binaryWriter.getByteOffset();
  1138. if (this._totalByteLength == undefined) {
  1139. throw new Error("undefined byte length!");
  1140. }
  1141. // Build Hierarchy with the node map.
  1142. for (let babylonNode of nodes) {
  1143. glTFNodeIndex = this._nodeMap[babylonNode.uniqueId];
  1144. if (glTFNodeIndex !== undefined) {
  1145. glTFNode = this._nodes[glTFNodeIndex];
  1146. if (babylonNode.metadata) {
  1147. if (this._options.metadataSelector) {
  1148. glTFNode.extras = this._options.metadataSelector(babylonNode.metadata);
  1149. } else if (babylonNode.metadata.gltf) {
  1150. glTFNode.extras = babylonNode.metadata.gltf.extras;
  1151. }
  1152. }
  1153. if (!babylonNode.parent) {
  1154. if (this._options.shouldExportNode && !this._options.shouldExportNode(babylonNode)) {
  1155. Tools.Log("Omitting " + babylonNode.name + " from scene.");
  1156. }
  1157. else {
  1158. if (this._convertToRightHandedSystem) {
  1159. if (glTFNode.translation) {
  1160. glTFNode.translation[2] *= -1;
  1161. glTFNode.translation[0] *= -1;
  1162. }
  1163. glTFNode.rotation = glTFNode.rotation ? Quaternion.FromArray([0, 1, 0, 0]).multiply(Quaternion.FromArray(glTFNode.rotation)).asArray() : (Quaternion.FromArray([0, 1, 0, 0])).asArray();
  1164. }
  1165. scene.nodes.push(glTFNodeIndex);
  1166. }
  1167. }
  1168. directDescendents = babylonNode.getDescendants(true);
  1169. if (!glTFNode.children && directDescendents && directDescendents.length) {
  1170. const children: number[] = [];
  1171. for (let descendent of directDescendents) {
  1172. if (this._nodeMap[descendent.uniqueId] != null) {
  1173. children.push(this._nodeMap[descendent.uniqueId]);
  1174. }
  1175. }
  1176. if (children.length) {
  1177. glTFNode.children = children;
  1178. }
  1179. }
  1180. }
  1181. }
  1182. if (scene.nodes.length) {
  1183. this._scenes.push(scene);
  1184. }
  1185. });
  1186. });
  1187. }
  1188. /**
  1189. * Creates a mapping of Node unique id to node index and handles animations
  1190. * @param babylonScene Babylon Scene
  1191. * @param nodes Babylon transform nodes
  1192. * @param binaryWriter Buffer to write binary data to
  1193. * @returns Node mapping of unique id to index
  1194. */
  1195. private createNodeMapAndAnimationsAsync(babylonScene: Scene, nodes: Node[], binaryWriter: _BinaryWriter): Promise<{ [key: number]: number }> {
  1196. let promiseChain = Promise.resolve();
  1197. const nodeMap: { [key: number]: number } = {};
  1198. let nodeIndex: number;
  1199. let runtimeGLTFAnimation: IAnimation = {
  1200. name: 'runtime animations',
  1201. channels: [],
  1202. samplers: []
  1203. };
  1204. let idleGLTFAnimations: IAnimation[] = [];
  1205. for (let babylonNode of nodes) {
  1206. if (this._options.shouldExportNode && this._options.shouldExportNode(babylonNode)) {
  1207. promiseChain = promiseChain.then(() => {
  1208. return this.createNodeAsync(babylonNode, binaryWriter).then((node) => {
  1209. const promise = this._extensionsPostExportNodeAsync("createNodeAsync", node, babylonNode);
  1210. if (promise == null) {
  1211. Tools.Warn(`Not exporting node ${babylonNode.name}`);
  1212. return Promise.resolve();
  1213. }
  1214. else {
  1215. return promise.then((node) => {
  1216. const directDescendents = babylonNode.getDescendants(true, (node: Node) => { return (node instanceof Node); });
  1217. if (directDescendents.length || node.mesh != null || (node.extensions)) {
  1218. this._nodes.push(node);
  1219. nodeIndex = this._nodes.length - 1;
  1220. nodeMap[babylonNode.uniqueId] = nodeIndex;
  1221. }
  1222. if (!babylonScene.animationGroups.length && babylonNode.animations.length) {
  1223. _GLTFAnimation._CreateNodeAnimationFromNodeAnimations(babylonNode, runtimeGLTFAnimation, idleGLTFAnimations, nodeMap, this._nodes, binaryWriter, this._bufferViews, this._accessors, this._convertToRightHandedSystem, this._animationSampleRate);
  1224. }
  1225. });
  1226. }
  1227. });
  1228. });
  1229. }
  1230. else {
  1231. `Excluding node ${babylonNode.name}`;
  1232. }
  1233. }
  1234. return promiseChain.then(() => {
  1235. if (runtimeGLTFAnimation.channels.length && runtimeGLTFAnimation.samplers.length) {
  1236. this._animations.push(runtimeGLTFAnimation);
  1237. }
  1238. idleGLTFAnimations.forEach((idleGLTFAnimation) => {
  1239. if (idleGLTFAnimation.channels.length && idleGLTFAnimation.samplers.length) {
  1240. this._animations.push(idleGLTFAnimation);
  1241. }
  1242. });
  1243. if (babylonScene.animationGroups.length) {
  1244. _GLTFAnimation._CreateNodeAnimationFromAnimationGroups(babylonScene, this._animations, nodeMap, this._nodes, binaryWriter, this._bufferViews, this._accessors, this._convertToRightHandedSystem, this._animationSampleRate);
  1245. }
  1246. return nodeMap;
  1247. });
  1248. }
  1249. /**
  1250. * Creates a glTF node from a Babylon mesh
  1251. * @param babylonMesh Source Babylon mesh
  1252. * @param binaryWriter Buffer for storing geometry data
  1253. * @returns glTF node
  1254. */
  1255. private createNodeAsync(babylonNode: Node, binaryWriter: _BinaryWriter): Promise<INode> {
  1256. return Promise.resolve().then(() => {
  1257. // create node to hold translation/rotation/scale and the mesh
  1258. const node: INode = {};
  1259. // create mesh
  1260. const mesh: IMesh = { primitives: [] };
  1261. if (babylonNode.name) {
  1262. node.name = babylonNode.name;
  1263. }
  1264. if (babylonNode instanceof TransformNode) {
  1265. // Set transformation
  1266. this.setNodeTransformation(node, babylonNode);
  1267. return this.setPrimitiveAttributesAsync(mesh, babylonNode, binaryWriter).then(() => {
  1268. if (mesh.primitives.length) {
  1269. this._meshes.push(mesh);
  1270. node.mesh = this._meshes.length - 1;
  1271. }
  1272. return node;
  1273. });
  1274. }
  1275. else {
  1276. return node;
  1277. }
  1278. });
  1279. }
  1280. }
  1281. /**
  1282. * @hidden
  1283. *
  1284. * Stores glTF binary data. If the array buffer byte length is exceeded, it doubles in size dynamically
  1285. */
  1286. export class _BinaryWriter {
  1287. /**
  1288. * Array buffer which stores all binary data
  1289. */
  1290. private _arrayBuffer: ArrayBuffer;
  1291. /**
  1292. * View of the array buffer
  1293. */
  1294. private _dataView: DataView;
  1295. /**
  1296. * byte offset of data in array buffer
  1297. */
  1298. private _byteOffset: number;
  1299. /**
  1300. * Initialize binary writer with an initial byte length
  1301. * @param byteLength Initial byte length of the array buffer
  1302. */
  1303. constructor(byteLength: number) {
  1304. this._arrayBuffer = new ArrayBuffer(byteLength);
  1305. this._dataView = new DataView(this._arrayBuffer);
  1306. this._byteOffset = 0;
  1307. }
  1308. /**
  1309. * Resize the array buffer to the specified byte length
  1310. * @param byteLength
  1311. */
  1312. private resizeBuffer(byteLength: number): ArrayBuffer {
  1313. let newBuffer = new ArrayBuffer(byteLength);
  1314. let oldUint8Array = new Uint8Array(this._arrayBuffer);
  1315. let newUint8Array = new Uint8Array(newBuffer);
  1316. for (let i = 0, length = newUint8Array.byteLength; i < length; ++i) {
  1317. newUint8Array[i] = oldUint8Array[i];
  1318. }
  1319. this._arrayBuffer = newBuffer;
  1320. this._dataView = new DataView(this._arrayBuffer);
  1321. return newBuffer;
  1322. }
  1323. /**
  1324. * Get an array buffer with the length of the byte offset
  1325. * @returns ArrayBuffer resized to the byte offset
  1326. */
  1327. public getArrayBuffer(): ArrayBuffer {
  1328. return this.resizeBuffer(this.getByteOffset());
  1329. }
  1330. /**
  1331. * Get the byte offset of the array buffer
  1332. * @returns byte offset
  1333. */
  1334. public getByteOffset(): number {
  1335. if (this._byteOffset == undefined) {
  1336. throw new Error("Byte offset is undefined!");
  1337. }
  1338. return this._byteOffset;
  1339. }
  1340. /**
  1341. * Stores an UInt8 in the array buffer
  1342. * @param entry
  1343. * @param byteOffset If defined, specifies where to set the value as an offset.
  1344. */
  1345. public setUInt8(entry: number, byteOffset?: number) {
  1346. if (byteOffset != null) {
  1347. if (byteOffset < this._byteOffset) {
  1348. this._dataView.setUint8(byteOffset, entry);
  1349. }
  1350. else {
  1351. Tools.Error('BinaryWriter: byteoffset is greater than the current binary buffer length!');
  1352. }
  1353. }
  1354. else {
  1355. if (this._byteOffset + 1 > this._arrayBuffer.byteLength) {
  1356. this.resizeBuffer(this._arrayBuffer.byteLength * 2);
  1357. }
  1358. this._dataView.setUint8(this._byteOffset++, entry);
  1359. }
  1360. }
  1361. /**
  1362. * Gets an UInt32 in the array buffer
  1363. * @param entry
  1364. * @param byteOffset If defined, specifies where to set the value as an offset.
  1365. */
  1366. public getUInt32(byteOffset: number): number {
  1367. if (byteOffset < this._byteOffset) {
  1368. return this._dataView.getUint32(byteOffset, true);
  1369. }
  1370. else {
  1371. Tools.Error('BinaryWriter: byteoffset is greater than the current binary buffer length!');
  1372. throw new Error('BinaryWriter: byteoffset is greater than the current binary buffer length!');
  1373. }
  1374. }
  1375. public getVector3Float32FromRef(vector3: Vector3, byteOffset: number): void {
  1376. if (byteOffset + 8 > this._byteOffset) {
  1377. Tools.Error(`BinaryWriter: byteoffset is greater than the current binary buffer length!`);
  1378. }
  1379. else {
  1380. vector3.x = this._dataView.getFloat32(byteOffset, true);
  1381. vector3.y = this._dataView.getFloat32(byteOffset + 4, true);
  1382. vector3.z = this._dataView.getFloat32(byteOffset + 8, true);
  1383. }
  1384. }
  1385. public setVector3Float32FromRef(vector3: Vector3, byteOffset: number): void {
  1386. if (byteOffset + 8 > this._byteOffset) {
  1387. Tools.Error(`BinaryWriter: byteoffset is greater than the current binary buffer length!`);
  1388. }
  1389. else {
  1390. this._dataView.setFloat32(byteOffset, vector3.x, true);
  1391. this._dataView.setFloat32(byteOffset + 4, vector3.y, true);
  1392. this._dataView.setFloat32(byteOffset + 8, vector3.z, true);
  1393. }
  1394. }
  1395. public getVector4Float32FromRef(vector4: Vector4, byteOffset: number): void {
  1396. if (byteOffset + 12 > this._byteOffset) {
  1397. Tools.Error(`BinaryWriter: byteoffset is greater than the current binary buffer length!`);
  1398. }
  1399. else {
  1400. vector4.x = this._dataView.getFloat32(byteOffset, true);
  1401. vector4.y = this._dataView.getFloat32(byteOffset + 4, true);
  1402. vector4.z = this._dataView.getFloat32(byteOffset + 8, true);
  1403. vector4.w = this._dataView.getFloat32(byteOffset + 12, true);
  1404. }
  1405. }
  1406. public setVector4Float32FromRef(vector4: Vector4, byteOffset: number): void {
  1407. if (byteOffset + 12 > this._byteOffset) {
  1408. Tools.Error(`BinaryWriter: byteoffset is greater than the current binary buffer length!`);
  1409. }
  1410. else {
  1411. this._dataView.setFloat32(byteOffset, vector4.x, true);
  1412. this._dataView.setFloat32(byteOffset + 4, vector4.y, true);
  1413. this._dataView.setFloat32(byteOffset + 8, vector4.z, true);
  1414. this._dataView.setFloat32(byteOffset + 12, vector4.w, true);
  1415. }
  1416. }
  1417. /**
  1418. * Stores a Float32 in the array buffer
  1419. * @param entry
  1420. */
  1421. public setFloat32(entry: number, byteOffset?: number) {
  1422. if (isNaN(entry)) {
  1423. Tools.Error('Invalid data being written!');
  1424. }
  1425. if (byteOffset != null) {
  1426. if (byteOffset < this._byteOffset) {
  1427. this._dataView.setFloat32(byteOffset, entry, true);
  1428. }
  1429. else {
  1430. Tools.Error('BinaryWriter: byteoffset is greater than the current binary length!');
  1431. }
  1432. }
  1433. if (this._byteOffset + 4 > this._arrayBuffer.byteLength) {
  1434. this.resizeBuffer(this._arrayBuffer.byteLength * 2);
  1435. }
  1436. this._dataView.setFloat32(this._byteOffset, entry, true);
  1437. this._byteOffset += 4;
  1438. }
  1439. /**
  1440. * Stores an UInt32 in the array buffer
  1441. * @param entry
  1442. * @param byteOffset If defined, specifies where to set the value as an offset.
  1443. */
  1444. public setUInt32(entry: number, byteOffset?: number) {
  1445. if (byteOffset != null) {
  1446. if (byteOffset < this._byteOffset) {
  1447. this._dataView.setUint32(byteOffset, entry, true);
  1448. }
  1449. else {
  1450. Tools.Error('BinaryWriter: byteoffset is greater than the current binary buffer length!');
  1451. }
  1452. }
  1453. else {
  1454. if (this._byteOffset + 4 > this._arrayBuffer.byteLength) {
  1455. this.resizeBuffer(this._arrayBuffer.byteLength * 2);
  1456. }
  1457. this._dataView.setUint32(this._byteOffset, entry, true);
  1458. this._byteOffset += 4;
  1459. }
  1460. }
  1461. }