From 6b903c99327312b3d9739b2e6e9c8d8a9ba195ca Mon Sep 17 00:00:00 2001 From: Gary Hsu Date: Wed, 2 Oct 2024 09:35:28 -0700 Subject: [PATCH 001/133] WIP --- packages/dev/core/src/Buffers/buffer.ts | 144 +- packages/dev/core/src/Buffers/bufferUtils.ts | 267 +- packages/dev/core/src/Meshes/geometry.ts | 4 +- .../actionTabs/tabs/toolsTabComponent.tsx | 12 +- .../dev/loaders/src/glTF/2.0/glTFLoader.ts | 11 +- .../2.0/Extensions/EXT_mesh_gpu_instancing.ts | 370 +- .../2.0/Extensions/KHR_lights_punctual.ts | 380 +- .../Extensions/KHR_materials_anisotropy.ts | 166 +- .../2.0/Extensions/KHR_materials_clearcoat.ts | 218 +- .../KHR_materials_diffuse_transmission.ts | 244 +- .../Extensions/KHR_materials_dispersion.ts | 126 +- .../KHR_materials_emissive_strength.ts | 140 +- .../glTF/2.0/Extensions/KHR_materials_ior.ts | 110 +- .../Extensions/KHR_materials_iridescence.ts | 182 +- .../2.0/Extensions/KHR_materials_sheen.ts | 174 +- .../2.0/Extensions/KHR_materials_specular.ts | 236 +- .../Extensions/KHR_materials_transmission.ts | 214 +- .../2.0/Extensions/KHR_materials_unlit.ts | 90 +- .../2.0/Extensions/KHR_materials_volume.ts | 238 +- .../2.0/Extensions/KHR_texture_transform.ts | 206 +- .../src/glTF/2.0/Extensions/index.ts | 30 +- .../serializers/src/glTF/2.0/dataWriter.ts | 68 + .../serializers/src/glTF/2.0/glTFAnimation.ts | 1996 +++++----- .../dev/serializers/src/glTF/2.0/glTFData.ts | 63 +- .../serializers/src/glTF/2.0/glTFExporter.ts | 3206 +++++++---------- .../src/glTF/2.0/glTFExporterExtension.ts | 8 +- .../src/glTF/2.0/glTFMaterialExporter.ts | 805 ++--- .../src/glTF/2.0/glTFSerializer.ts | 69 +- .../serializers/src/glTF/2.0/glTFUtilities.ts | 439 ++- .../dev/serializers/src/glTF/2.0/index.ts | 7 - .../babylon.glTF2Interface.d.ts | 5 - .../tests/test/visualization/config.json | 14 +- 32 files changed, 4875 insertions(+), 5367 deletions(-) create mode 100644 packages/dev/serializers/src/glTF/2.0/dataWriter.ts diff --git a/packages/dev/core/src/Buffers/buffer.ts b/packages/dev/core/src/Buffers/buffer.ts index 0d3c2ae82c9..18cf5d31054 100644 --- a/packages/dev/core/src/Buffers/buffer.ts +++ b/packages/dev/core/src/Buffers/buffer.ts @@ -4,6 +4,7 @@ import { DataBuffer } from "./dataBuffer"; import type { Mesh } from "../Meshes/mesh"; import { Logger } from "../Misc/logger"; import { Constants } from "../Engines/constants"; +import { enumerateFloatValues, getFloatData, getTypeByteLength } from "./bufferUtils"; /** * Class used to store data that will be store in GPU memory @@ -566,7 +567,7 @@ export class VertexBuffer { this.type = type; } - const typeByteLength = VertexBuffer.GetTypeByteLength(this.type); + const typeByteLength = getTypeByteLength(this.type); if (useBytes) { this._size = size || (stride ? stride / typeByteLength : VertexBuffer.DeduceStride(kind)); @@ -641,7 +642,7 @@ export class VertexBuffer { return null; } - return VertexBuffer.GetFloatData(data, this._size, this.type, this.byteOffset, this.byteStride, this.normalized, totalVertices, forceCopy); + return getFloatData(data, this._size, this.type, this.byteOffset, this.byteStride, this.normalized, totalVertices, forceCopy); } /** @@ -667,7 +668,7 @@ export class VertexBuffer { * @deprecated Please use byteStride instead. */ public getStrideSize(): number { - return this.byteStride / VertexBuffer.GetTypeByteLength(this.type); + return this.byteStride / getTypeByteLength(this.type); } /** @@ -676,7 +677,7 @@ export class VertexBuffer { * @deprecated Please use byteOffset instead. */ public getOffset(): number { - return this.byteOffset / VertexBuffer.GetTypeByteLength(this.type); + return this.byteOffset / getTypeByteLength(this.type); } /** @@ -685,7 +686,7 @@ export class VertexBuffer { * @returns the number of components */ public getSize(sizeInBytes = false): number { - return sizeInBytes ? this._size * VertexBuffer.GetTypeByteLength(this.type) : this._size; + return sizeInBytes ? this._size * getTypeByteLength(this.type) : this._size; } /** @@ -754,7 +755,11 @@ export class VertexBuffer { * @param callback the callback function called for each value */ public forEach(count: number, callback: (value: number, index: number) => void): void { - VertexBuffer.ForEach(this._buffer.getData()!, this.byteOffset, this.byteStride, this._size, this.type, count, this.normalized, callback); + enumerateFloatValues(this._buffer.getData()!, this.byteOffset, this.byteStride, this._size, this.type, count, this.normalized, (values, index) => { + for (let i = 0; i < this._size; i++) { + callback(values[i], index + i); + } + }); } /** @internal */ @@ -879,22 +884,10 @@ export class VertexBuffer { * Gets the byte length of the given type. * @param type the type * @returns the number of bytes + * @deprecated Use `getTypeByteLength` from `bufferUtils` instead */ public static GetTypeByteLength(type: number): number { - switch (type) { - case VertexBuffer.BYTE: - case VertexBuffer.UNSIGNED_BYTE: - return 1; - case VertexBuffer.SHORT: - case VertexBuffer.UNSIGNED_SHORT: - return 2; - case VertexBuffer.INT: - case VertexBuffer.UNSIGNED_INT: - case VertexBuffer.FLOAT: - return 4; - default: - throw new Error(`Invalid type '${type}'`); - } + return getTypeByteLength(type); } /** @@ -907,6 +900,7 @@ export class VertexBuffer { * @param count the number of values to enumerate * @param normalized whether the data is normalized * @param callback the callback function called for each value + * @deprecated Use `enumerateFloatValues` from `bufferUtils` instead */ public static ForEach( data: DataArray, @@ -918,73 +912,11 @@ export class VertexBuffer { normalized: boolean, callback: (value: number, index: number) => void ): void { - if (data instanceof Array) { - let offset = byteOffset / 4; - const stride = byteStride / 4; - for (let index = 0; index < count; index += componentCount) { - for (let componentIndex = 0; componentIndex < componentCount; componentIndex++) { - callback(data[offset + componentIndex], index + componentIndex); - } - offset += stride; - } - } else { - const dataView = data instanceof ArrayBuffer ? new DataView(data) : new DataView(data.buffer, data.byteOffset, data.byteLength); - const componentByteLength = VertexBuffer.GetTypeByteLength(componentType); - for (let index = 0; index < count; index += componentCount) { - let componentByteOffset = byteOffset; - for (let componentIndex = 0; componentIndex < componentCount; componentIndex++) { - const value = VertexBuffer._GetFloatValue(dataView, componentType, componentByteOffset, normalized); - callback(value, index + componentIndex); - componentByteOffset += componentByteLength; - } - byteOffset += byteStride; - } - } - } - - private static _GetFloatValue(dataView: DataView, type: number, byteOffset: number, normalized: boolean): number { - switch (type) { - case VertexBuffer.BYTE: { - let value = dataView.getInt8(byteOffset); - if (normalized) { - value = Math.max(value / 127, -1); - } - return value; - } - case VertexBuffer.UNSIGNED_BYTE: { - let value = dataView.getUint8(byteOffset); - if (normalized) { - value = value / 255; - } - return value; - } - case VertexBuffer.SHORT: { - let value = dataView.getInt16(byteOffset, true); - if (normalized) { - value = Math.max(value / 32767, -1); - } - return value; - } - case VertexBuffer.UNSIGNED_SHORT: { - let value = dataView.getUint16(byteOffset, true); - if (normalized) { - value = value / 65535; - } - return value; - } - case VertexBuffer.INT: { - return dataView.getInt32(byteOffset, true); - } - case VertexBuffer.UNSIGNED_INT: { - return dataView.getUint32(byteOffset, true); - } - case VertexBuffer.FLOAT: { - return dataView.getFloat32(byteOffset, true); - } - default: { - throw new Error(`Invalid component type ${type}`); + enumerateFloatValues(data, byteOffset, byteStride, componentCount, componentType, count, normalized, (values, index) => { + for (let componentIndex = 0; componentIndex < componentCount; componentIndex++) { + callback(values[componentIndex], index + componentIndex); } - } + }); } /** @@ -998,6 +930,7 @@ export class VertexBuffer { * @param totalVertices number of vertices in the buffer to take into account * @param forceCopy defines a boolean indicating that the returned array must be cloned upon returning it * @returns a float array containing vertex data + * @deprecated Use `getFloatData` from `bufferUtils` instead */ public static GetFloatData( data: DataArray, @@ -1009,43 +942,6 @@ export class VertexBuffer { totalVertices: number, forceCopy?: boolean ): FloatArray { - const tightlyPackedByteStride = size * VertexBuffer.GetTypeByteLength(type); - const count = totalVertices * size; - - if (type !== VertexBuffer.FLOAT || byteStride !== tightlyPackedByteStride) { - const copy = new Float32Array(count); - VertexBuffer.ForEach(data, byteOffset, byteStride, size, type, count, normalized, (value, index) => (copy[index] = value)); - return copy; - } - - if (!(data instanceof Array || data instanceof Float32Array) || byteOffset !== 0 || data.length !== count) { - if (data instanceof Array) { - const offset = byteOffset / 4; - return data.slice(offset, offset + count); - } else if (data instanceof ArrayBuffer) { - return new Float32Array(data, byteOffset, count); - } else { - const offset = data.byteOffset + byteOffset; - if ((offset & 3) !== 0) { - Logger.Warn("Float array must be aligned to 4-bytes border"); - forceCopy = true; - } - - if (forceCopy) { - const result = new Uint8Array(count * Float32Array.BYTES_PER_ELEMENT); - const source = new Uint8Array(data.buffer, offset, result.length); - result.set(source); - return new Float32Array(result.buffer); - } else { - return new Float32Array(data.buffer, offset, count); - } - } - } - - if (forceCopy) { - return data.slice(); - } - - return data; + return getFloatData(data, size, type, byteOffset, byteStride, normalized, totalVertices, forceCopy); } } diff --git a/packages/dev/core/src/Buffers/bufferUtils.ts b/packages/dev/core/src/Buffers/bufferUtils.ts index b224e3f2a3a..2b9476cf14a 100644 --- a/packages/dev/core/src/Buffers/bufferUtils.ts +++ b/packages/dev/core/src/Buffers/bufferUtils.ts @@ -1,6 +1,247 @@ -import type { DataArray } from "../types"; -import { VertexBuffer } from "./buffer"; +import { Constants } from "../Engines/constants"; import { Logger } from "../Misc/logger"; +import type { DataArray, FloatArray } from "../types"; + +function getFloatValue(dataView: DataView, type: number, byteOffset: number, normalized: boolean): number { + switch (type) { + case Constants.BYTE: { + let value = dataView.getInt8(byteOffset); + if (normalized) { + value = Math.max(value / 127, -1); + } + return value; + } + case Constants.UNSIGNED_BYTE: { + let value = dataView.getUint8(byteOffset); + if (normalized) { + value = value / 255; + } + return value; + } + case Constants.SHORT: { + let value = dataView.getInt16(byteOffset, true); + if (normalized) { + value = Math.max(value / 32767, -1); + } + return value; + } + case Constants.UNSIGNED_SHORT: { + let value = dataView.getUint16(byteOffset, true); + if (normalized) { + value = value / 65535; + } + return value; + } + case Constants.INT: { + return dataView.getInt32(byteOffset, true); + } + case Constants.UNSIGNED_INT: { + return dataView.getUint32(byteOffset, true); + } + case Constants.FLOAT: { + return dataView.getFloat32(byteOffset, true); + } + default: { + throw new Error(`Invalid component type ${type}`); + } + } +} + +function setFloatValue(dataView: DataView, type: number, byteOffset: number, normalized: boolean, value: number): void { + switch (type) { + case Constants.BYTE: { + if (normalized) { + value = Math.round(value * 127.0); + } + dataView.setInt8(byteOffset, value); + break; + } + case Constants.UNSIGNED_BYTE: { + if (normalized) { + value = Math.round(value * 255); + } + dataView.setUint8(byteOffset, value); + break; + } + case Constants.SHORT: { + if (normalized) { + value = Math.round(value * 32767); + } + dataView.setInt16(byteOffset, value, true); + break; + } + case Constants.UNSIGNED_SHORT: { + if (normalized) { + value = Math.round(value * 65535); + } + dataView.setUint16(byteOffset, value, true); + break; + } + case Constants.INT: { + dataView.setInt32(byteOffset, value, true); + break; + } + case Constants.UNSIGNED_INT: { + dataView.setUint32(byteOffset, value, true); + break; + } + case Constants.FLOAT: { + dataView.setFloat32(byteOffset, value, true); + break; + } + default: { + throw new Error(`Invalid component type ${type}`); + } + } +} + +/** + * Gets the byte length of the given type. + * @param type the type + * @returns the number of bytes + */ +export function getTypeByteLength(type: number): number { + switch (type) { + case Constants.BYTE: + case Constants.UNSIGNED_BYTE: + return 1; + case Constants.SHORT: + case Constants.UNSIGNED_SHORT: + return 2; + case Constants.INT: + case Constants.UNSIGNED_INT: + case Constants.FLOAT: + return 4; + default: + throw new Error(`Invalid type '${type}'`); + } +} + +/** + * Enumerates each value of the data array and calls the given callback. + * @param data the data to enumerate + * @param byteOffset the byte offset of the data + * @param byteStride the byte stride of the data + * @param componentCount the number of components per element + * @param componentType the type of the component + * @param count the number of values to enumerate + * @param normalized whether the data is normalized + * @param callback the callback function called for each group of component values + */ +export function enumerateFloatValues( + data: DataArray, + byteOffset: number, + byteStride: number, + componentCount: number, + componentType: number, + count: number, + normalized: boolean, + callback: (values: number[], index: number) => void +): void { + const oldValues = new Array(componentCount); + const newValues = new Array(componentCount); + + if (data instanceof Array) { + let offset = byteOffset / 4; + const stride = byteStride / 4; + for (let index = 0; index < count; index += componentCount) { + for (let componentIndex = 0; componentIndex < componentCount; componentIndex++) { + oldValues[componentIndex] = newValues[componentIndex] = data[offset + componentIndex]; + } + + callback(newValues, index); + + for (let componentIndex = 0; componentIndex < componentCount; componentIndex++) { + if (oldValues[componentIndex] !== newValues[componentIndex]) { + data[offset + componentIndex] = newValues[componentIndex]; + } + } + + offset += stride; + } + } else { + const dataView = data instanceof ArrayBuffer ? new DataView(data) : new DataView(data.buffer, data.byteOffset, data.byteLength); + const componentByteLength = getTypeByteLength(componentType); + for (let index = 0; index < count; index += componentCount) { + for (let componentIndex = 0, componentByteOffset = byteOffset; componentIndex < componentCount; componentIndex++, componentByteOffset += componentByteLength) { + oldValues[componentIndex] = newValues[componentIndex] = getFloatValue(dataView, componentType, componentByteOffset, normalized); + } + + callback(newValues, index); + + for (let componentIndex = 0, componentByteOffset = byteOffset; componentIndex < componentCount; componentIndex++, componentByteOffset += componentByteLength) { + if (oldValues[componentIndex] !== newValues[componentIndex]) { + setFloatValue(dataView, componentType, componentByteOffset, normalized, newValues[componentIndex]); + } + } + + byteOffset += byteStride; + } + } +} + +/** + * Gets the given data array as a float array. Float data is constructed if the data array cannot be returned directly. + * @param data the input data array + * @param size the number of components + * @param type the component type + * @param byteOffset the byte offset of the data + * @param byteStride the byte stride of the data + * @param normalized whether the data is normalized + * @param totalVertices number of vertices in the buffer to take into account + * @param forceCopy defines a boolean indicating that the returned array must be cloned upon returning it + * @returns a float array containing vertex data + */ +export function getFloatData( + data: DataArray, + size: number, + type: number, + byteOffset: number, + byteStride: number, + normalized: boolean, + totalVertices: number, + forceCopy?: boolean +): FloatArray { + const tightlyPackedByteStride = size * getTypeByteLength(type); + const count = totalVertices * size; + + if (type !== Constants.FLOAT || byteStride !== tightlyPackedByteStride) { + const copy = new Float32Array(count); + enumerateFloatValues(data, byteOffset, byteStride, size, type, count, normalized, (values, index) => { + for (let i = 0; i < size; i++) { + copy[index + i] = values[i]; + } + }); + return copy; + } + + if (!(data instanceof Array || data instanceof Float32Array) || byteOffset !== 0 || data.length !== count) { + if (data instanceof Array) { + const offset = byteOffset / 4; + return data.slice(offset, offset + count); + } else if (data instanceof ArrayBuffer) { + return new Float32Array(data, byteOffset, count); + } else { + const offset = data.byteOffset + byteOffset; + if ((offset & 3) !== 0) { + Logger.Warn("Float array must be aligned to 4-bytes border"); + forceCopy = true; + } + + if (forceCopy) { + return new Float32Array(data.buffer.slice(offset, offset + count * Float32Array.BYTES_PER_ELEMENT)); + } else { + return new Float32Array(data.buffer, offset, count); + } + } + } + + if (forceCopy) { + return data.slice(); + } + + return data; +} /** * Copies the given data array to the given float array. @@ -13,7 +254,7 @@ import { Logger } from "../Misc/logger"; * @param totalVertices number of vertices in the buffer to take into account * @param output the output float array */ -export function CopyFloatData( +export function copyFloatData( input: DataArray, size: number, type: number, @@ -23,15 +264,19 @@ export function CopyFloatData( totalVertices: number, output: Float32Array ): void { - const tightlyPackedByteStride = size * VertexBuffer.GetTypeByteLength(type); + const tightlyPackedByteStride = size * getTypeByteLength(type); const count = totalVertices * size; if (output.length !== count) { throw new Error("Output length is not valid"); } - if (type !== VertexBuffer.FLOAT || byteStride !== tightlyPackedByteStride) { - VertexBuffer.ForEach(input, byteOffset, byteStride, size, type, count, normalized, (value, index) => (output[index] = value)); + if (type !== Constants.FLOAT || byteStride !== tightlyPackedByteStride) { + enumerateFloatValues(input, byteOffset, byteStride, size, type, count, normalized, (values, index) => { + for (let i = 0; i < size; i++) { + output[index + i] = values[i]; + } + }); return; } @@ -43,13 +288,9 @@ export function CopyFloatData( output.set(floatData); } else { const offset = input.byteOffset + byteOffset; - - // Protect against bad data - const remainder = offset % 4; - if (remainder) { - Logger.Warn("CopyFloatData: copied misaligned data."); - // If not aligned, copy the data to aligned buffer - output.set(new Float32Array(input.buffer.slice(offset, offset + count * 4))); + if ((offset & 3) !== 0) { + Logger.Warn("Float array must be aligned to 4-bytes border"); + output.set(new Float32Array(input.buffer.slice(offset, offset + count * Float32Array.BYTES_PER_ELEMENT))); return; } diff --git a/packages/dev/core/src/Meshes/geometry.ts b/packages/dev/core/src/Meshes/geometry.ts index e8f584f7bbf..27a514a90cb 100644 --- a/packages/dev/core/src/Meshes/geometry.ts +++ b/packages/dev/core/src/Meshes/geometry.ts @@ -24,7 +24,7 @@ import type { Mesh } from "../Meshes/mesh"; import type { Buffer } from "../Buffers/buffer"; import type { AbstractEngine } from "../Engines/abstractEngine"; import type { ThinEngine } from "../Engines/thinEngine"; -import { CopyFloatData } from "../Buffers/bufferUtils"; +import { copyFloatData } from "../Buffers/bufferUtils"; /** * Class used to store geometry data (vertex buffers + index buffer) @@ -472,7 +472,7 @@ export class Geometry implements IGetSetVerticesData { vertexData[kind] ||= new Float32Array(this._totalVertices * vertexBuffer.getSize()); const data = vertexBuffer.getData(); if (data) { - CopyFloatData( + copyFloatData( data, vertexBuffer.getSize(), vertexBuffer.type, diff --git a/packages/dev/inspector/src/components/actionTabs/tabs/toolsTabComponent.tsx b/packages/dev/inspector/src/components/actionTabs/tabs/toolsTabComponent.tsx index 76c3c80dc8f..ea8a12720f0 100644 --- a/packages/dev/inspector/src/components/actionTabs/tabs/toolsTabComponent.tsx +++ b/packages/dev/inspector/src/components/actionTabs/tabs/toolsTabComponent.tsx @@ -283,17 +283,17 @@ export class ToolsTabComponent extends PaneComponent { return true; }; - GLTF2Export.GLBAsync(scene, "scene", { shouldExportNode: (node) => shouldExport(node) }).then( - (glb: GLTFData) => { + GLTF2Export.GLBAsync(scene, "scene", { shouldExportNode: (node) => shouldExport(node) }) + .then((glb: GLTFData) => { this._isExportingGltf = false; this.forceUpdate(); glb.downloadFiles(); - }, - () => { + }) + .catch((reason) => { + Logger.Error(`Failed to export GLB: ${reason}`); this._isExportingGltf = false; this.forceUpdate(); - } - ); + }); } exportBabylon() { diff --git a/packages/dev/loaders/src/glTF/2.0/glTFLoader.ts b/packages/dev/loaders/src/glTF/2.0/glTFLoader.ts index d4aa957f4d7..9655c115024 100644 --- a/packages/dev/loaders/src/glTF/2.0/glTFLoader.ts +++ b/packages/dev/loaders/src/glTF/2.0/glTFLoader.ts @@ -1245,7 +1245,7 @@ export class GLTFLoader implements IGLTFLoader { loadAttribute("POSITION", VertexBuffer.PositionKind, (babylonVertexBuffer, data) => { const positions = new Float32Array(data.length); - babylonVertexBuffer.forEach(data.length, (value, index) => { + babylonVertexBuffer.forEach(positions.length, (value, index) => { positions[index] = data[index] + value; }); @@ -1264,7 +1264,7 @@ export class GLTFLoader implements IGLTFLoader { loadAttribute("TANGENT", VertexBuffer.TangentKind, (babylonVertexBuffer, data) => { const tangents = new Float32Array((data.length / 3) * 4); let dataIndex = 0; - babylonVertexBuffer.forEach((data.length / 3) * 4, (value, index) => { + babylonVertexBuffer.forEach(tangents.length, (value, index) => { // Tangent data for morph targets is stored as xyz delta. // The vertexData.tangent is stored as xyzw. // So we need to skip every fourth vertexData.tangent. @@ -2160,7 +2160,6 @@ export class GLTFLoader implements IGLTFLoader { const babylonMaterial = new PBRMaterial(name, this._babylonScene); babylonMaterial._parentContainer = this._assetContainer; this._babylonScene._blockEntityCollection = false; - // Moved to mesh so user can change materials on gltf meshes: babylonMaterial.sideOrientation = this._babylonScene.useRightHandedSystem ? Material.CounterClockWiseSideOrientation : Material.ClockWiseSideOrientation; babylonMaterial.fillMode = babylonDrawMode; babylonMaterial.enableSpecularAntiAliasing = true; babylonMaterial.useRadianceOverAlpha = !this._parent.transparencyAsCoverage; @@ -2520,11 +2519,7 @@ export class GLTFLoader implements IGLTFLoader { * @param pointer the JSON pointer */ public static AddPointerMetadata(babylonObject: IWithMetadata, pointer: string): void { - babylonObject.metadata = babylonObject.metadata || {}; - const metadata = (babylonObject._internalMetadata = babylonObject._internalMetadata || {}); - const gltf = (metadata.gltf = metadata.gltf || {}); - const pointers = (gltf.pointers = gltf.pointers || []); - pointers.push(pointer); + (((babylonObject._internalMetadata ||= {}).gltf ||= {}).pointers ||= []).push(pointer); } private static _GetTextureWrapMode(context: string, mode: TextureWrapMode | undefined): number { diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts index 080dbdddf9c..a14818e2498 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts @@ -1,185 +1,185 @@ -import type { IBufferView, IAccessor, INode, IEXTMeshGpuInstancing } from "babylonjs-gltf2interface"; -import { AccessorType, AccessorComponentType } from "babylonjs-gltf2interface"; -import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -import type { _BinaryWriter } from "../glTFExporter"; -import { _Exporter } from "../glTFExporter"; -import type { Nullable } from "core/types"; -import type { Node } from "core/node"; -import { Mesh } from "core/Meshes/mesh"; -import "core/Meshes/thinInstanceMesh"; -import { TmpVectors, Quaternion, Vector3 } from "core/Maths/math.vector"; -import { VertexBuffer } from "core/Buffers/buffer"; - -const NAME = "EXT_mesh_gpu_instancing"; - -/** - * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_mesh_gpu_instancing/README.md) - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export class EXT_mesh_gpu_instancing implements IGLTFExporterExtensionV2 { - /** Name of this extension */ - public readonly name = NAME; - - /** Defines whether this extension is enabled */ - public enabled = true; - - /** Defines whether this extension is required */ - public required = false; - - private _exporter: _Exporter; - - private _wasUsed = false; - - constructor(exporter: _Exporter) { - this._exporter = exporter; - } - - public dispose() {} - - /** @internal */ - public get wasUsed() { - return this._wasUsed; - } - - /** - * After node is exported - * @param context the GLTF context when loading the asset - * @param node the node exported - * @param babylonNode the corresponding babylon node - * @param nodeMap map from babylon node id to node index - * @param binaryWriter binary writer - * @returns nullable promise, resolves with the node - */ - public postExportNodeAsync( - context: string, - node: Nullable, - babylonNode: Node, - nodeMap: { [key: number]: number }, - binaryWriter: _BinaryWriter - ): Promise> { - return new Promise((resolve) => { - if (node && babylonNode instanceof Mesh) { - if (babylonNode.hasThinInstances && binaryWriter) { - this._wasUsed = true; - - const noTranslation = Vector3.Zero(); - const noRotation = Quaternion.Identity(); - const noScale = Vector3.One(); - - // retrieve all the instance world matrix - const matrix = babylonNode.thinInstanceGetWorldMatrices(); - - const iwt = TmpVectors.Vector3[2]; - const iwr = TmpVectors.Quaternion[1]; - const iws = TmpVectors.Vector3[3]; - - let hasAnyInstanceWorldTranslation = false; - let hasAnyInstanceWorldRotation = false; - let hasAnyInstanceWorldScale = false; - - // prepare temp buffers - const translationBuffer = new Float32Array(babylonNode.thinInstanceCount * 3); - const rotationBuffer = new Float32Array(babylonNode.thinInstanceCount * 4); - const scaleBuffer = new Float32Array(babylonNode.thinInstanceCount * 3); - - let i = 0; - for (const m of matrix) { - m.decompose(iws, iwr, iwt); - - // fill the temp buffer - translationBuffer.set(iwt.asArray(), i * 3); - rotationBuffer.set(iwr.normalize().asArray(), i * 4); // ensure the quaternion is normalized - scaleBuffer.set(iws.asArray(), i * 3); - - // this is where we decide if there is any transformation - hasAnyInstanceWorldTranslation = hasAnyInstanceWorldTranslation || !iwt.equalsWithEpsilon(noTranslation); - hasAnyInstanceWorldRotation = hasAnyInstanceWorldRotation || !iwr.equalsWithEpsilon(noRotation); - hasAnyInstanceWorldScale = hasAnyInstanceWorldScale || !iws.equalsWithEpsilon(noScale); - - i++; - } - - const extension: IEXTMeshGpuInstancing = { - attributes: {}, - }; - - // do we need to write TRANSLATION ? - if (hasAnyInstanceWorldTranslation) { - extension.attributes["TRANSLATION"] = this._buildAccessor( - translationBuffer, - AccessorType.VEC3, - babylonNode.thinInstanceCount, - binaryWriter, - AccessorComponentType.FLOAT - ); - } - // do we need to write ROTATION ? - if (hasAnyInstanceWorldRotation) { - const componentType = AccessorComponentType.FLOAT; // we decided to stay on FLOAT for now see https://github.com/BabylonJS/Babylon.js/pull/12495 - extension.attributes["ROTATION"] = this._buildAccessor(rotationBuffer, AccessorType.VEC4, babylonNode.thinInstanceCount, binaryWriter, componentType); - } - // do we need to write SCALE ? - if (hasAnyInstanceWorldScale) { - extension.attributes["SCALE"] = this._buildAccessor( - scaleBuffer, - AccessorType.VEC3, - babylonNode.thinInstanceCount, - binaryWriter, - AccessorComponentType.FLOAT - ); - } - - /* eslint-enable @typescript-eslint/naming-convention*/ - node.extensions = node.extensions || {}; - node.extensions[NAME] = extension; - } - } - resolve(node); - }); - } - - private _buildAccessor(buffer: Float32Array, type: AccessorType, count: number, binaryWriter: _BinaryWriter, componentType: AccessorComponentType): number { - // write the buffer - const bufferOffset = binaryWriter.getByteOffset(); - switch (componentType) { - case AccessorComponentType.FLOAT: { - for (let i = 0; i != buffer.length; i++) { - binaryWriter.setFloat32(buffer[i]); - } - break; - } - case AccessorComponentType.BYTE: { - for (let i = 0; i != buffer.length; i++) { - binaryWriter.setByte(buffer[i] * 127); - } - break; - } - case AccessorComponentType.SHORT: { - for (let i = 0; i != buffer.length; i++) { - binaryWriter.setInt16(buffer[i] * 32767); - } - - break; - } - } - // build the buffer view - const bv: IBufferView = { buffer: 0, byteOffset: bufferOffset, byteLength: buffer.length * VertexBuffer.GetTypeByteLength(componentType) }; - const bufferViewIndex = this._exporter._bufferViews.length; - this._exporter._bufferViews.push(bv); - - // finally build the accessor - const accessorIndex = this._exporter._accessors.length; - const accessor: IAccessor = { - bufferView: bufferViewIndex, - componentType: componentType, - count: count, - type: type, - normalized: componentType == AccessorComponentType.BYTE || componentType == AccessorComponentType.SHORT, - }; - this._exporter._accessors.push(accessor); - return accessorIndex; - } -} - -// eslint-disable-next-line @typescript-eslint/no-unused-vars -_Exporter.RegisterExtension(NAME, (exporter) => new EXT_mesh_gpu_instancing(exporter)); +// import type { IBufferView, IAccessor, INode, IEXTMeshGpuInstancing } from "babylonjs-gltf2interface"; +// import { AccessorType, AccessorComponentType } from "babylonjs-gltf2interface"; +// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +// import type { _BinaryWriter } from "../glTFExporter"; +// import { _Exporter } from "../glTFExporter"; +// import type { Nullable } from "core/types"; +// import type { Node } from "core/node"; +// import { Mesh } from "core/Meshes/mesh"; +// import "core/Meshes/thinInstanceMesh"; +// import { TmpVectors, Quaternion, Vector3 } from "core/Maths/math.vector"; +// import { VertexBuffer } from "core/Buffers/buffer"; + +// const NAME = "EXT_mesh_gpu_instancing"; + +// /** +// * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_mesh_gpu_instancing/README.md) +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export class EXT_mesh_gpu_instancing implements IGLTFExporterExtensionV2 { +// /** Name of this extension */ +// public readonly name = NAME; + +// /** Defines whether this extension is enabled */ +// public enabled = true; + +// /** Defines whether this extension is required */ +// public required = false; + +// private _exporter: _Exporter; + +// private _wasUsed = false; + +// constructor(exporter: _Exporter) { +// this._exporter = exporter; +// } + +// public dispose() {} + +// /** @internal */ +// public get wasUsed() { +// return this._wasUsed; +// } + +// /** +// * After node is exported +// * @param context the GLTF context when loading the asset +// * @param node the node exported +// * @param babylonNode the corresponding babylon node +// * @param nodeMap map from babylon node id to node index +// * @param binaryWriter binary writer +// * @returns nullable promise, resolves with the node +// */ +// public postExportNodeAsync( +// context: string, +// node: Nullable, +// babylonNode: Node, +// nodeMap: { [key: number]: number }, +// binaryWriter: _BinaryWriter +// ): Promise> { +// return new Promise((resolve) => { +// if (node && babylonNode instanceof Mesh) { +// if (babylonNode.hasThinInstances && binaryWriter) { +// this._wasUsed = true; + +// const noTranslation = Vector3.Zero(); +// const noRotation = Quaternion.Identity(); +// const noScale = Vector3.One(); + +// // retrieve all the instance world matrix +// const matrix = babylonNode.thinInstanceGetWorldMatrices(); + +// const iwt = TmpVectors.Vector3[2]; +// const iwr = TmpVectors.Quaternion[1]; +// const iws = TmpVectors.Vector3[3]; + +// let hasAnyInstanceWorldTranslation = false; +// let hasAnyInstanceWorldRotation = false; +// let hasAnyInstanceWorldScale = false; + +// // prepare temp buffers +// const translationBuffer = new Float32Array(babylonNode.thinInstanceCount * 3); +// const rotationBuffer = new Float32Array(babylonNode.thinInstanceCount * 4); +// const scaleBuffer = new Float32Array(babylonNode.thinInstanceCount * 3); + +// let i = 0; +// for (const m of matrix) { +// m.decompose(iws, iwr, iwt); + +// // fill the temp buffer +// translationBuffer.set(iwt.asArray(), i * 3); +// rotationBuffer.set(iwr.normalize().asArray(), i * 4); // ensure the quaternion is normalized +// scaleBuffer.set(iws.asArray(), i * 3); + +// // this is where we decide if there is any transformation +// hasAnyInstanceWorldTranslation = hasAnyInstanceWorldTranslation || !iwt.equalsWithEpsilon(noTranslation); +// hasAnyInstanceWorldRotation = hasAnyInstanceWorldRotation || !iwr.equalsWithEpsilon(noRotation); +// hasAnyInstanceWorldScale = hasAnyInstanceWorldScale || !iws.equalsWithEpsilon(noScale); + +// i++; +// } + +// const extension: IEXTMeshGpuInstancing = { +// attributes: {}, +// }; + +// // do we need to write TRANSLATION ? +// if (hasAnyInstanceWorldTranslation) { +// extension.attributes["TRANSLATION"] = this._buildAccessor( +// translationBuffer, +// AccessorType.VEC3, +// babylonNode.thinInstanceCount, +// binaryWriter, +// AccessorComponentType.FLOAT +// ); +// } +// // do we need to write ROTATION ? +// if (hasAnyInstanceWorldRotation) { +// const componentType = AccessorComponentType.FLOAT; // we decided to stay on FLOAT for now see https://github.com/BabylonJS/Babylon.js/pull/12495 +// extension.attributes["ROTATION"] = this._buildAccessor(rotationBuffer, AccessorType.VEC4, babylonNode.thinInstanceCount, binaryWriter, componentType); +// } +// // do we need to write SCALE ? +// if (hasAnyInstanceWorldScale) { +// extension.attributes["SCALE"] = this._buildAccessor( +// scaleBuffer, +// AccessorType.VEC3, +// babylonNode.thinInstanceCount, +// binaryWriter, +// AccessorComponentType.FLOAT +// ); +// } + +// /* eslint-enable @typescript-eslint/naming-convention*/ +// node.extensions = node.extensions || {}; +// node.extensions[NAME] = extension; +// } +// } +// resolve(node); +// }); +// } + +// private _buildAccessor(buffer: Float32Array, type: AccessorType, count: number, binaryWriter: _BinaryWriter, componentType: AccessorComponentType): number { +// // write the buffer +// const bufferOffset = binaryWriter.getByteOffset(); +// switch (componentType) { +// case AccessorComponentType.FLOAT: { +// for (let i = 0; i != buffer.length; i++) { +// binaryWriter.setFloat32(buffer[i]); +// } +// break; +// } +// case AccessorComponentType.BYTE: { +// for (let i = 0; i != buffer.length; i++) { +// binaryWriter.setByte(buffer[i] * 127); +// } +// break; +// } +// case AccessorComponentType.SHORT: { +// for (let i = 0; i != buffer.length; i++) { +// binaryWriter.setInt16(buffer[i] * 32767); +// } + +// break; +// } +// } +// // build the buffer view +// const bv: IBufferView = { buffer: 0, byteOffset: bufferOffset, byteLength: buffer.length * VertexBuffer.GetTypeByteLength(componentType) }; +// const bufferViewIndex = this._exporter._bufferViews.length; +// this._exporter._bufferViews.push(bv); + +// // finally build the accessor +// const accessorIndex = this._exporter._accessors.length; +// const accessor: IAccessor = { +// bufferView: bufferViewIndex, +// componentType: componentType, +// count: count, +// type: type, +// normalized: componentType == AccessorComponentType.BYTE || componentType == AccessorComponentType.SHORT, +// }; +// this._exporter._accessors.push(accessor); +// return accessorIndex; +// } +// } + +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// _Exporter.RegisterExtension(NAME, (exporter) => new EXT_mesh_gpu_instancing(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index 780a50febf8..64575a5b8b2 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -1,190 +1,190 @@ -import type { SpotLight } from "core/Lights/spotLight"; -import type { Nullable } from "core/types"; -import { Vector3, Quaternion, TmpVectors, Matrix } from "core/Maths/math.vector"; -import { Color3 } from "core/Maths/math.color"; -import { Light } from "core/Lights/light"; -import type { Node } from "core/node"; -import { ShadowLight } from "core/Lights/shadowLight"; -import type { INode, IKHRLightsPunctual_LightReference, IKHRLightsPunctual_Light, IKHRLightsPunctual } from "babylonjs-gltf2interface"; -import { KHRLightsPunctual_LightType } from "babylonjs-gltf2interface"; -import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -import { _Exporter } from "../glTFExporter"; -import { Logger } from "core/Misc/logger"; -import { _GLTFUtilities } from "../glTFUtilities"; - -const NAME = "KHR_lights_punctual"; - -/** - * [Specification](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md) - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { - /** The name of this extension. */ - public readonly name = NAME; - - /** Defines whether this extension is enabled. */ - public enabled = true; - - /** Defines whether this extension is required */ - public required = false; - - /** Reference to the glTF exporter */ - private _exporter: _Exporter; - - private _lights: IKHRLightsPunctual; - - /** - * @internal - */ - constructor(exporter: _Exporter) { - this._exporter = exporter; - } - - /** @internal */ - public dispose() { - (this._lights as any) = null; - } - - /** @internal */ - public get wasUsed() { - return !!this._lights; - } - - /** @internal */ - public onExporting(): void { - this._exporter!._glTF.extensions![NAME] = this._lights; - } - /** - * Define this method to modify the default behavior when exporting a node - * @param context The context when exporting the node - * @param node glTF node - * @param babylonNode BabylonJS node - * @param nodeMap Node mapping of unique id to glTF node index - * @returns nullable INode promise - */ - public postExportNodeAsync(context: string, node: Nullable, babylonNode: Node, nodeMap: { [key: number]: number }): Promise> { - return new Promise((resolve) => { - if (node && babylonNode instanceof ShadowLight) { - let light: IKHRLightsPunctual_Light; - - const lightType = - babylonNode.getTypeID() == Light.LIGHTTYPEID_POINTLIGHT - ? KHRLightsPunctual_LightType.POINT - : babylonNode.getTypeID() == Light.LIGHTTYPEID_DIRECTIONALLIGHT - ? KHRLightsPunctual_LightType.DIRECTIONAL - : babylonNode.getTypeID() == Light.LIGHTTYPEID_SPOTLIGHT - ? KHRLightsPunctual_LightType.SPOT - : null; - if (lightType == null) { - Logger.Warn(`${context}: Light ${babylonNode.name} is not supported in ${NAME}`); - } else { - if (!babylonNode.position.equalsToFloats(0, 0, 0)) { - node.translation = babylonNode.position.asArray(); - } - if (lightType !== KHRLightsPunctual_LightType.POINT) { - const localAxis = babylonNode.direction; - const yaw = -Math.atan2(localAxis.z, localAxis.x) + Math.PI / 2; - const len = Math.sqrt(localAxis.x * localAxis.x + localAxis.z * localAxis.z); - const pitch = -Math.atan2(localAxis.y, len); - const lightRotationQuaternion = Quaternion.RotationYawPitchRoll(yaw + Math.PI, pitch, 0); - if (!Quaternion.IsIdentity(lightRotationQuaternion)) { - node.rotation = lightRotationQuaternion.asArray(); - } - } - - if (babylonNode.falloffType !== Light.FALLOFF_GLTF) { - Logger.Warn(`${context}: Light falloff for ${babylonNode.name} does not match the ${NAME} specification!`); - } - light = { - type: lightType, - }; - if (!babylonNode.diffuse.equals(Color3.White())) { - light.color = babylonNode.diffuse.asArray(); - } - if (babylonNode.intensity !== 1.0) { - light.intensity = babylonNode.intensity; - } - if (babylonNode.range !== Number.MAX_VALUE) { - light.range = babylonNode.range; - } - - if (lightType === KHRLightsPunctual_LightType.SPOT) { - const babylonSpotLight = babylonNode as SpotLight; - if (babylonSpotLight.angle !== Math.PI / 2.0) { - if (light.spot == null) { - light.spot = {}; - } - light.spot.outerConeAngle = babylonSpotLight.angle / 2.0; - } - if (babylonSpotLight.innerAngle !== 0) { - if (light.spot == null) { - light.spot = {}; - } - light.spot.innerConeAngle = babylonSpotLight.innerAngle / 2.0; - } - } - - this._lights ||= { - lights: [], - }; - - this._lights.lights.push(light); - - const lightReference: IKHRLightsPunctual_LightReference = { - light: this._lights.lights.length - 1, - }; - - // Avoid duplicating the Light's parent node if possible. - const parentBabylonNode = babylonNode.parent; - if (parentBabylonNode && parentBabylonNode.getChildren().length == 1) { - const parentNode = this._exporter._nodes[nodeMap[parentBabylonNode.uniqueId]]; - if (parentNode) { - const parentTranslation = Vector3.FromArrayToRef(parentNode.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); - const parentRotation = Quaternion.FromArrayToRef(parentNode.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); - const parentScale = Vector3.FromArrayToRef(parentNode.scale || [1, 1, 1], 0, TmpVectors.Vector3[1]); - const parentMatrix = Matrix.ComposeToRef(parentScale, parentRotation, parentTranslation, TmpVectors.Matrix[0]); - - const translation = Vector3.FromArrayToRef(node.translation || [0, 0, 0], 0, TmpVectors.Vector3[2]); - const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); - const matrix = Matrix.ComposeToRef(Vector3.OneReadOnly, rotation, translation, TmpVectors.Matrix[1]); - - parentMatrix.multiplyToRef(matrix, matrix); - matrix.decompose(parentScale, parentRotation, parentTranslation); - - if (parentTranslation.equalsToFloats(0, 0, 0)) { - delete parentNode.translation; - } else { - parentNode.translation = parentTranslation.asArray(); - } - - if (Quaternion.IsIdentity(parentRotation)) { - delete parentNode.rotation; - } else { - parentNode.rotation = parentRotation.asArray(); - } - - if (parentScale.equalsToFloats(1, 1, 1)) { - delete parentNode.scale; - } else { - parentNode.scale = parentScale.asArray(); - } - - parentNode.extensions ||= {}; - parentNode.extensions[NAME] = lightReference; - - // Do not export the original node - resolve(null); - return; - } - } - - node.extensions ||= {}; - node.extensions[NAME] = lightReference; - } - } - resolve(node); - }); - } -} - -_Exporter.RegisterExtension(NAME, (exporter) => new KHR_lights_punctual(exporter)); +// import type { SpotLight } from "core/Lights/spotLight"; +// import type { Nullable } from "core/types"; +// import { Vector3, Quaternion, TmpVectors, Matrix } from "core/Maths/math.vector"; +// import { Color3 } from "core/Maths/math.color"; +// import { Light } from "core/Lights/light"; +// import type { Node } from "core/node"; +// import { ShadowLight } from "core/Lights/shadowLight"; +// import type { INode, IKHRLightsPunctual_LightReference, IKHRLightsPunctual_Light, IKHRLightsPunctual } from "babylonjs-gltf2interface"; +// import { KHRLightsPunctual_LightType } from "babylonjs-gltf2interface"; +// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +// import { GLTFExporter } from "../glTFExporter"; +// import { Logger } from "core/Misc/logger"; +// import { _GLTFUtilities } from "../glTFUtilities"; + +// const NAME = "KHR_lights_punctual"; + +// /** +// * [Specification](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md) +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { +// /** The name of this extension. */ +// public readonly name = NAME; + +// /** Defines whether this extension is enabled. */ +// public enabled = true; + +// /** Defines whether this extension is required */ +// public required = false; + +// /** Reference to the glTF exporter */ +// private _exporter: GLTFExporter; + +// private _lights: IKHRLightsPunctual; + +// /** +// * @internal +// */ +// constructor(exporter: GLTFExporter) { +// this._exporter = exporter; +// } + +// /** @internal */ +// public dispose() { +// (this._lights as any) = null; +// } + +// /** @internal */ +// public get wasUsed() { +// return !!this._lights; +// } + +// /** @internal */ +// public onExporting(): void { +// this._exporter!._glTF.extensions![NAME] = this._lights; +// } +// /** +// * Define this method to modify the default behavior when exporting a node +// * @param context The context when exporting the node +// * @param node glTF node +// * @param babylonNode BabylonJS node +// * @param nodeMap Node mapping of unique id to glTF node index +// * @returns nullable INode promise +// */ +// public postExportNodeAsync(context: string, node: Nullable, babylonNode: Node, nodeMap: { [key: number]: number }): Promise> { +// return new Promise((resolve) => { +// if (node && babylonNode instanceof ShadowLight) { +// let light: IKHRLightsPunctual_Light; + +// const lightType = +// babylonNode.getTypeID() == Light.LIGHTTYPEID_POINTLIGHT +// ? KHRLightsPunctual_LightType.POINT +// : babylonNode.getTypeID() == Light.LIGHTTYPEID_DIRECTIONALLIGHT +// ? KHRLightsPunctual_LightType.DIRECTIONAL +// : babylonNode.getTypeID() == Light.LIGHTTYPEID_SPOTLIGHT +// ? KHRLightsPunctual_LightType.SPOT +// : null; +// if (lightType == null) { +// Logger.Warn(`${context}: Light ${babylonNode.name} is not supported in ${NAME}`); +// } else { +// if (!babylonNode.position.equalsToFloats(0, 0, 0)) { +// node.translation = babylonNode.position.asArray(); +// } +// if (lightType !== KHRLightsPunctual_LightType.POINT) { +// const localAxis = babylonNode.direction; +// const yaw = -Math.atan2(localAxis.z, localAxis.x) + Math.PI / 2; +// const len = Math.sqrt(localAxis.x * localAxis.x + localAxis.z * localAxis.z); +// const pitch = -Math.atan2(localAxis.y, len); +// const lightRotationQuaternion = Quaternion.RotationYawPitchRoll(yaw + Math.PI, pitch, 0); +// if (!Quaternion.IsIdentity(lightRotationQuaternion)) { +// node.rotation = lightRotationQuaternion.asArray(); +// } +// } + +// if (babylonNode.falloffType !== Light.FALLOFF_GLTF) { +// Logger.Warn(`${context}: Light falloff for ${babylonNode.name} does not match the ${NAME} specification!`); +// } +// light = { +// type: lightType, +// }; +// if (!babylonNode.diffuse.equals(Color3.White())) { +// light.color = babylonNode.diffuse.asArray(); +// } +// if (babylonNode.intensity !== 1.0) { +// light.intensity = babylonNode.intensity; +// } +// if (babylonNode.range !== Number.MAX_VALUE) { +// light.range = babylonNode.range; +// } + +// if (lightType === KHRLightsPunctual_LightType.SPOT) { +// const babylonSpotLight = babylonNode as SpotLight; +// if (babylonSpotLight.angle !== Math.PI / 2.0) { +// if (light.spot == null) { +// light.spot = {}; +// } +// light.spot.outerConeAngle = babylonSpotLight.angle / 2.0; +// } +// if (babylonSpotLight.innerAngle !== 0) { +// if (light.spot == null) { +// light.spot = {}; +// } +// light.spot.innerConeAngle = babylonSpotLight.innerAngle / 2.0; +// } +// } + +// this._lights ||= { +// lights: [], +// }; + +// this._lights.lights.push(light); + +// const lightReference: IKHRLightsPunctual_LightReference = { +// light: this._lights.lights.length - 1, +// }; + +// // Avoid duplicating the Light's parent node if possible. +// const parentBabylonNode = babylonNode.parent; +// if (parentBabylonNode && parentBabylonNode.getChildren().length == 1) { +// const parentNode = this._exporter._nodes[nodeMap[parentBabylonNode.uniqueId]]; +// if (parentNode) { +// const parentTranslation = Vector3.FromArrayToRef(parentNode.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); +// const parentRotation = Quaternion.FromArrayToRef(parentNode.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); +// const parentScale = Vector3.FromArrayToRef(parentNode.scale || [1, 1, 1], 0, TmpVectors.Vector3[1]); +// const parentMatrix = Matrix.ComposeToRef(parentScale, parentRotation, parentTranslation, TmpVectors.Matrix[0]); + +// const translation = Vector3.FromArrayToRef(node.translation || [0, 0, 0], 0, TmpVectors.Vector3[2]); +// const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); +// const matrix = Matrix.ComposeToRef(Vector3.OneReadOnly, rotation, translation, TmpVectors.Matrix[1]); + +// parentMatrix.multiplyToRef(matrix, matrix); +// matrix.decompose(parentScale, parentRotation, parentTranslation); + +// if (parentTranslation.equalsToFloats(0, 0, 0)) { +// delete parentNode.translation; +// } else { +// parentNode.translation = parentTranslation.asArray(); +// } + +// if (Quaternion.IsIdentity(parentRotation)) { +// delete parentNode.rotation; +// } else { +// parentNode.rotation = parentRotation.asArray(); +// } + +// if (parentScale.equalsToFloats(1, 1, 1)) { +// delete parentNode.scale; +// } else { +// parentNode.scale = parentScale.asArray(); +// } + +// parentNode.extensions ||= {}; +// parentNode.extensions[NAME] = lightReference; + +// // Do not export the original node +// resolve(null); +// return; +// } +// } + +// node.extensions ||= {}; +// node.extensions[NAME] = lightReference; +// } +// } +// resolve(node); +// }); +// } +// } + +// GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_lights_punctual(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_anisotropy.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_anisotropy.ts index 16f0f80d382..14418fd7486 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_anisotropy.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_anisotropy.ts @@ -1,83 +1,83 @@ -import type { IMaterial, IKHRMaterialsAnisotropy } from "babylonjs-gltf2interface"; -import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -import { _Exporter } from "../glTFExporter"; -import type { Material } from "core/Materials/material"; -import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; -import type { BaseTexture } from "core/Materials/Textures/baseTexture"; - -const NAME = "KHR_materials_anisotropy"; - -/** - * @internal - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export class KHR_materials_anisotropy implements IGLTFExporterExtensionV2 { - /** Name of this extension */ - public readonly name = NAME; - - /** Defines whether this extension is enabled */ - public enabled = true; - - /** Defines whether this extension is required */ - public required = false; - - private _exporter: _Exporter; - - private _wasUsed = false; - - constructor(exporter: _Exporter) { - this._exporter = exporter; - } - - public dispose() {} - - /** @internal */ - public get wasUsed() { - return this._wasUsed; - } - - public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { - const additionalTextures: BaseTexture[] = []; - if (babylonMaterial instanceof PBRBaseMaterial) { - if (babylonMaterial.anisotropy.isEnabled && !babylonMaterial.anisotropy.legacy) { - if (babylonMaterial.anisotropy.texture) { - additionalTextures.push(babylonMaterial.anisotropy.texture); - } - return additionalTextures; - } - } - - return []; - } - - public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { - return new Promise((resolve) => { - if (babylonMaterial instanceof PBRBaseMaterial) { - if (!babylonMaterial.anisotropy.isEnabled || babylonMaterial.anisotropy.legacy) { - resolve(node); - return; - } - - this._wasUsed = true; - - node.extensions = node.extensions || {}; - - const anisotropyTextureInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.anisotropy.texture); - - const anisotropyInfo: IKHRMaterialsAnisotropy = { - anisotropyStrength: babylonMaterial.anisotropy.intensity, - anisotropyRotation: babylonMaterial.anisotropy.angle, - anisotropyTexture: anisotropyTextureInfo ?? undefined, - hasTextures: () => { - return anisotropyInfo.anisotropyTexture !== null; - }, - }; - - node.extensions[NAME] = anisotropyInfo; - } - resolve(node); - }); - } -} - -_Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_anisotropy(exporter)); +// import type { IMaterial, IKHRMaterialsAnisotropy } from "babylonjs-gltf2interface"; +// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +// import { GLTFExporter } from "../glTFExporter"; +// import type { Material } from "core/Materials/material"; +// import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; +// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; + +// const NAME = "KHR_materials_anisotropy"; + +// /** +// * @internal +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export class KHR_materials_anisotropy implements IGLTFExporterExtensionV2 { +// /** Name of this extension */ +// public readonly name = NAME; + +// /** Defines whether this extension is enabled */ +// public enabled = true; + +// /** Defines whether this extension is required */ +// public required = false; + +// private _exporter: GLTFExporter; + +// private _wasUsed = false; + +// constructor(exporter: GLTFExporter) { +// this._exporter = exporter; +// } + +// public dispose() {} + +// /** @internal */ +// public get wasUsed() { +// return this._wasUsed; +// } + +// public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { +// const additionalTextures: BaseTexture[] = []; +// if (babylonMaterial instanceof PBRBaseMaterial) { +// if (babylonMaterial.anisotropy.isEnabled && !babylonMaterial.anisotropy.legacy) { +// if (babylonMaterial.anisotropy.texture) { +// additionalTextures.push(babylonMaterial.anisotropy.texture); +// } +// return additionalTextures; +// } +// } + +// return []; +// } + +// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { +// return new Promise((resolve) => { +// if (babylonMaterial instanceof PBRBaseMaterial) { +// if (!babylonMaterial.anisotropy.isEnabled || babylonMaterial.anisotropy.legacy) { +// resolve(node); +// return; +// } + +// this._wasUsed = true; + +// node.extensions = node.extensions || {}; + +// const anisotropyTextureInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.anisotropy.texture); + +// const anisotropyInfo: IKHRMaterialsAnisotropy = { +// anisotropyStrength: babylonMaterial.anisotropy.intensity, +// anisotropyRotation: babylonMaterial.anisotropy.angle, +// anisotropyTexture: anisotropyTextureInfo ?? undefined, +// hasTextures: () => { +// return anisotropyInfo.anisotropyTexture !== null; +// }, +// }; + +// node.extensions[NAME] = anisotropyInfo; +// } +// resolve(node); +// }); +// } +// } + +// GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_anisotropy(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat.ts index 2d0419af55d..5b765c6e386 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat.ts @@ -1,109 +1,109 @@ -import type { IMaterial, IKHRMaterialsClearcoat } from "babylonjs-gltf2interface"; -import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -import { _Exporter } from "../glTFExporter"; -import type { Material } from "core/Materials/material"; -import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; -import type { BaseTexture } from "core/Materials/Textures/baseTexture"; - -import { Tools } from "core/Misc/tools"; - -const NAME = "KHR_materials_clearcoat"; - -/** - * @internal - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export class KHR_materials_clearcoat implements IGLTFExporterExtensionV2 { - /** Name of this extension */ - public readonly name = NAME; - - /** Defines whether this extension is enabled */ - public enabled = true; - - /** Defines whether this extension is required */ - public required = false; - - private _exporter: _Exporter; - - private _wasUsed = false; - - constructor(exporter: _Exporter) { - this._exporter = exporter; - } - - public dispose() {} - - /** @internal */ - public get wasUsed() { - return this._wasUsed; - } - - public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { - const additionalTextures: BaseTexture[] = []; - if (babylonMaterial instanceof PBRBaseMaterial) { - if (babylonMaterial.clearCoat.isEnabled) { - if (babylonMaterial.clearCoat.texture) { - additionalTextures.push(babylonMaterial.clearCoat.texture); - } - if (!babylonMaterial.clearCoat.useRoughnessFromMainTexture && babylonMaterial.clearCoat.textureRoughness) { - additionalTextures.push(babylonMaterial.clearCoat.textureRoughness); - } - if (babylonMaterial.clearCoat.bumpTexture) { - additionalTextures.push(babylonMaterial.clearCoat.bumpTexture); - } - return additionalTextures; - } - } - - return []; - } - - public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { - return new Promise((resolve) => { - if (babylonMaterial instanceof PBRBaseMaterial) { - if (!babylonMaterial.clearCoat.isEnabled) { - resolve(node); - return; - } - - this._wasUsed = true; - - node.extensions = node.extensions || {}; - - const clearCoatTextureInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.clearCoat.texture); - let clearCoatTextureRoughnessInfo; - if (babylonMaterial.clearCoat.useRoughnessFromMainTexture) { - clearCoatTextureRoughnessInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.clearCoat.texture); - } else { - clearCoatTextureRoughnessInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.clearCoat.textureRoughness); - } - - if (babylonMaterial.clearCoat.isTintEnabled) { - Tools.Warn(`Clear Color tint is not supported for glTF export. Ignoring for: ${babylonMaterial.name}`); - } - - if (babylonMaterial.clearCoat.remapF0OnInterfaceChange) { - Tools.Warn(`Clear Color F0 remapping is not supported for glTF export. Ignoring for: ${babylonMaterial.name}`); - } - - const clearCoatNormalTextureInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.clearCoat.bumpTexture); - - const clearCoatInfo: IKHRMaterialsClearcoat = { - clearcoatFactor: babylonMaterial.clearCoat.intensity, - clearcoatTexture: clearCoatTextureInfo ?? undefined, - clearcoatRoughnessFactor: babylonMaterial.clearCoat.roughness, - clearcoatRoughnessTexture: clearCoatTextureRoughnessInfo ?? undefined, - clearcoatNormalTexture: clearCoatNormalTextureInfo ?? undefined, - hasTextures: () => { - return clearCoatInfo.clearcoatTexture !== null || clearCoatInfo.clearcoatRoughnessTexture !== null || clearCoatInfo.clearcoatRoughnessTexture !== null; - }, - }; - - node.extensions[NAME] = clearCoatInfo; - } - resolve(node); - }); - } -} - -_Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_clearcoat(exporter)); +// import type { IMaterial, IKHRMaterialsClearcoat } from "babylonjs-gltf2interface"; +// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +// import { GLTFExporter } from "../glTFExporter"; +// import type { Material } from "core/Materials/material"; +// import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; +// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; + +// import { Tools } from "core/Misc/tools"; + +// const NAME = "KHR_materials_clearcoat"; + +// /** +// * @internal +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export class KHR_materials_clearcoat implements IGLTFExporterExtensionV2 { +// /** Name of this extension */ +// public readonly name = NAME; + +// /** Defines whether this extension is enabled */ +// public enabled = true; + +// /** Defines whether this extension is required */ +// public required = false; + +// private _exporter: GLTFExporter; + +// private _wasUsed = false; + +// constructor(exporter: GLTFExporter) { +// this._exporter = exporter; +// } + +// public dispose() {} + +// /** @internal */ +// public get wasUsed() { +// return this._wasUsed; +// } + +// public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { +// const additionalTextures: BaseTexture[] = []; +// if (babylonMaterial instanceof PBRBaseMaterial) { +// if (babylonMaterial.clearCoat.isEnabled) { +// if (babylonMaterial.clearCoat.texture) { +// additionalTextures.push(babylonMaterial.clearCoat.texture); +// } +// if (!babylonMaterial.clearCoat.useRoughnessFromMainTexture && babylonMaterial.clearCoat.textureRoughness) { +// additionalTextures.push(babylonMaterial.clearCoat.textureRoughness); +// } +// if (babylonMaterial.clearCoat.bumpTexture) { +// additionalTextures.push(babylonMaterial.clearCoat.bumpTexture); +// } +// return additionalTextures; +// } +// } + +// return []; +// } + +// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { +// return new Promise((resolve) => { +// if (babylonMaterial instanceof PBRBaseMaterial) { +// if (!babylonMaterial.clearCoat.isEnabled) { +// resolve(node); +// return; +// } + +// this._wasUsed = true; + +// node.extensions = node.extensions || {}; + +// const clearCoatTextureInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.clearCoat.texture); +// let clearCoatTextureRoughnessInfo; +// if (babylonMaterial.clearCoat.useRoughnessFromMainTexture) { +// clearCoatTextureRoughnessInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.clearCoat.texture); +// } else { +// clearCoatTextureRoughnessInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.clearCoat.textureRoughness); +// } + +// if (babylonMaterial.clearCoat.isTintEnabled) { +// Tools.Warn(`Clear Color tint is not supported for glTF export. Ignoring for: ${babylonMaterial.name}`); +// } + +// if (babylonMaterial.clearCoat.remapF0OnInterfaceChange) { +// Tools.Warn(`Clear Color F0 remapping is not supported for glTF export. Ignoring for: ${babylonMaterial.name}`); +// } + +// const clearCoatNormalTextureInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.clearCoat.bumpTexture); + +// const clearCoatInfo: IKHRMaterialsClearcoat = { +// clearcoatFactor: babylonMaterial.clearCoat.intensity, +// clearcoatTexture: clearCoatTextureInfo ?? undefined, +// clearcoatRoughnessFactor: babylonMaterial.clearCoat.roughness, +// clearcoatRoughnessTexture: clearCoatTextureRoughnessInfo ?? undefined, +// clearcoatNormalTexture: clearCoatNormalTextureInfo ?? undefined, +// hasTextures: () => { +// return clearCoatInfo.clearcoatTexture !== null || clearCoatInfo.clearcoatRoughnessTexture !== null || clearCoatInfo.clearcoatRoughnessTexture !== null; +// }, +// }; + +// node.extensions[NAME] = clearCoatInfo; +// } +// resolve(node); +// }); +// } +// } + +// GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_clearcoat(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.ts index d5a18fda883..756c107501f 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.ts @@ -1,122 +1,122 @@ -import type { IMaterial, IKHRMaterialsDiffuseTransmission } from "babylonjs-gltf2interface"; -import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -import { _Exporter } from "../glTFExporter"; -import type { Material } from "core/Materials/material"; -import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -import type { BaseTexture } from "core/Materials/Textures/baseTexture"; - -const NAME = "KHR_materials_diffuse_transmission"; - -/** - * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1825) - * !!! Experimental Extension Subject to Changes !!! - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export class KHR_materials_diffuse_transmission implements IGLTFExporterExtensionV2 { - /** Name of this extension */ - public readonly name = NAME; - - /** Defines whether this extension is enabled */ - public enabled = true; - - /** Defines whether this extension is required */ - public required = false; - - private _exporter: _Exporter; - - private _wasUsed = false; - - constructor(exporter: _Exporter) { - this._exporter = exporter; - } - - public dispose() {} - - /** @internal */ - public get wasUsed() { - return this._wasUsed; - } - - /** - * After exporting a material, deal with additional textures - * @param context GLTF context of the material - * @param node exported GLTF node - * @param babylonMaterial corresponding babylon material - * @returns array of additional textures to export - */ - public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { - const additionalTextures: BaseTexture[] = []; - - if (babylonMaterial instanceof PBRMaterial) { - if (this._isExtensionEnabled(babylonMaterial)) { - if (babylonMaterial.subSurface.thicknessTexture) { - additionalTextures.push(babylonMaterial.subSurface.thicknessTexture); - } - return additionalTextures; - } - } - - return additionalTextures; - } - - private _isExtensionEnabled(mat: PBRMaterial): boolean { - // This extension must not be used on a material that also uses KHR_materials_unlit - if (mat.unlit) { - return false; - } - const subs = mat.subSurface; - if (!subs.isTranslucencyEnabled) { - return false; - } - - return ( - !mat.unlit && - !subs.useAlbedoToTintTranslucency && - subs.useGltfStyleTextures && - subs.volumeIndexOfRefraction === 1 && - subs.minimumThickness === 0 && - subs.maximumThickness === 0 - ); - } - - private _hasTexturesExtension(mat: PBRMaterial): boolean { - return mat.subSurface.translucencyIntensityTexture != null || mat.subSurface.translucencyColorTexture != null; - } - - /** - * After exporting a material - * @param context GLTF context of the material - * @param node exported GLTF node - * @param babylonMaterial corresponding babylon material - * @returns promise that resolves with the updated node - */ - public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { - return new Promise((resolve) => { - if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { - this._wasUsed = true; - - const subs = babylonMaterial.subSurface; - - const diffuseTransmissionFactor = subs.translucencyIntensity == 1 ? undefined : subs.translucencyIntensity; - const diffuseTransmissionTexture = this._exporter._glTFMaterialExporter._getTextureInfo(subs.translucencyIntensityTexture) ?? undefined; - const diffuseTransmissionColorFactor = !subs.translucencyColor || subs.translucencyColor.equalsFloats(1.0, 1.0, 1.0) ? undefined : subs.translucencyColor.asArray(); - const diffuseTransmissionColorTexture = this._exporter._glTFMaterialExporter._getTextureInfo(subs.translucencyColorTexture) ?? undefined; - - const diffuseTransmissionInfo: IKHRMaterialsDiffuseTransmission = { - diffuseTransmissionFactor, - diffuseTransmissionTexture, - diffuseTransmissionColorFactor, - diffuseTransmissionColorTexture, - hasTextures: () => { - return this._hasTexturesExtension(babylonMaterial); - }, - }; - node.extensions = node.extensions || {}; - node.extensions[NAME] = diffuseTransmissionInfo; - } - resolve(node); - }); - } -} - -_Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_diffuse_transmission(exporter)); +// import type { IMaterial, IKHRMaterialsDiffuseTransmission } from "babylonjs-gltf2interface"; +// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +// import { _Exporter } from "../glTFExporter"; +// import type { Material } from "core/Materials/material"; +// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; + +// const NAME = "KHR_materials_diffuse_transmission"; + +// /** +// * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1825) +// * !!! Experimental Extension Subject to Changes !!! +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export class KHR_materials_diffuse_transmission implements IGLTFExporterExtensionV2 { +// /** Name of this extension */ +// public readonly name = NAME; + +// /** Defines whether this extension is enabled */ +// public enabled = true; + +// /** Defines whether this extension is required */ +// public required = false; + +// private _exporter: _Exporter; + +// private _wasUsed = false; + +// constructor(exporter: _Exporter) { +// this._exporter = exporter; +// } + +// public dispose() {} + +// /** @internal */ +// public get wasUsed() { +// return this._wasUsed; +// } + +// /** +// * After exporting a material, deal with additional textures +// * @param context GLTF context of the material +// * @param node exported GLTF node +// * @param babylonMaterial corresponding babylon material +// * @returns array of additional textures to export +// */ +// public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { +// const additionalTextures: BaseTexture[] = []; + +// if (babylonMaterial instanceof PBRMaterial) { +// if (this._isExtensionEnabled(babylonMaterial)) { +// if (babylonMaterial.subSurface.thicknessTexture) { +// additionalTextures.push(babylonMaterial.subSurface.thicknessTexture); +// } +// return additionalTextures; +// } +// } + +// return additionalTextures; +// } + +// private _isExtensionEnabled(mat: PBRMaterial): boolean { +// // This extension must not be used on a material that also uses KHR_materials_unlit +// if (mat.unlit) { +// return false; +// } +// const subs = mat.subSurface; +// if (!subs.isTranslucencyEnabled) { +// return false; +// } + +// return ( +// !mat.unlit && +// !subs.useAlbedoToTintTranslucency && +// subs.useGltfStyleTextures && +// subs.volumeIndexOfRefraction === 1 && +// subs.minimumThickness === 0 && +// subs.maximumThickness === 0 +// ); +// } + +// private _hasTexturesExtension(mat: PBRMaterial): boolean { +// return mat.subSurface.translucencyIntensityTexture != null || mat.subSurface.translucencyColorTexture != null; +// } + +// /** +// * After exporting a material +// * @param context GLTF context of the material +// * @param node exported GLTF node +// * @param babylonMaterial corresponding babylon material +// * @returns promise that resolves with the updated node +// */ +// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { +// return new Promise((resolve) => { +// if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { +// this._wasUsed = true; + +// const subs = babylonMaterial.subSurface; + +// const diffuseTransmissionFactor = subs.translucencyIntensity == 1 ? undefined : subs.translucencyIntensity; +// const diffuseTransmissionTexture = this._exporter._glTFMaterialExporter._getTextureInfo(subs.translucencyIntensityTexture) ?? undefined; +// const diffuseTransmissionColorFactor = !subs.translucencyColor || subs.translucencyColor.equalsFloats(1.0, 1.0, 1.0) ? undefined : subs.translucencyColor.asArray(); +// const diffuseTransmissionColorTexture = this._exporter._glTFMaterialExporter._getTextureInfo(subs.translucencyColorTexture) ?? undefined; + +// const diffuseTransmissionInfo: IKHRMaterialsDiffuseTransmission = { +// diffuseTransmissionFactor, +// diffuseTransmissionTexture, +// diffuseTransmissionColorFactor, +// diffuseTransmissionColorTexture, +// hasTextures: () => { +// return this._hasTexturesExtension(babylonMaterial); +// }, +// }; +// node.extensions = node.extensions || {}; +// node.extensions[NAME] = diffuseTransmissionInfo; +// } +// resolve(node); +// }); +// } +// } + +// _Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_diffuse_transmission(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_dispersion.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_dispersion.ts index 242d37dd893..42722a2400a 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_dispersion.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_dispersion.ts @@ -1,76 +1,76 @@ -import type { IMaterial, IKHRMaterialsDispersion } from "babylonjs-gltf2interface"; -import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -import { _Exporter } from "../glTFExporter"; -import type { Material } from "core/Materials/material"; -import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +// import type { IMaterial, IKHRMaterialsDispersion } from "babylonjs-gltf2interface"; +// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +// import { _Exporter } from "../glTFExporter"; +// import type { Material } from "core/Materials/material"; +// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -const NAME = "KHR_materials_dispersion"; +// const NAME = "KHR_materials_dispersion"; -/** - * [Specification](https://github.com/KhronosGroup/glTF/blob/87bd64a7f5e23c84b6aef2e6082069583ed0ddb4/extensions/2.0/Khronos/KHR_materials_dispersion/README.md) - * @experimental - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export class KHR_materials_dispersion implements IGLTFExporterExtensionV2 { - /** Name of this extension */ - public readonly name = NAME; +// /** +// * [Specification](https://github.com/KhronosGroup/glTF/blob/87bd64a7f5e23c84b6aef2e6082069583ed0ddb4/extensions/2.0/Khronos/KHR_materials_dispersion/README.md) +// * @experimental +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export class KHR_materials_dispersion implements IGLTFExporterExtensionV2 { +// /** Name of this extension */ +// public readonly name = NAME; - /** Defines whether this extension is enabled */ - public enabled = true; +// /** Defines whether this extension is enabled */ +// public enabled = true; - /** Defines whether this extension is required */ - public required = false; +// /** Defines whether this extension is required */ +// public required = false; - private _wasUsed = false; +// private _wasUsed = false; - /** Constructor */ - constructor() {} +// /** Constructor */ +// constructor() {} - /** Dispose */ - public dispose() {} +// /** Dispose */ +// public dispose() {} - /** @internal */ - public get wasUsed() { - return this._wasUsed; - } +// /** @internal */ +// public get wasUsed() { +// return this._wasUsed; +// } - private _isExtensionEnabled(mat: PBRMaterial): boolean { - // This extension must not be used on a material that also uses KHR_materials_unlit - if (mat.unlit) { - return false; - } - const subs = mat.subSurface; - // this extension requires refraction to be enabled. - if (!subs.isRefractionEnabled && !subs.isDispersionEnabled) { - return false; - } - return true; - } +// private _isExtensionEnabled(mat: PBRMaterial): boolean { +// // This extension must not be used on a material that also uses KHR_materials_unlit +// if (mat.unlit) { +// return false; +// } +// const subs = mat.subSurface; +// // this extension requires refraction to be enabled. +// if (!subs.isRefractionEnabled && !subs.isDispersionEnabled) { +// return false; +// } +// return true; +// } - /** - * After exporting a material - * @param context GLTF context of the material - * @param node exported GLTF node - * @param babylonMaterial corresponding babylon material - * @returns promise, resolves with the material - */ - public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { - return new Promise((resolve) => { - if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { - this._wasUsed = true; +// /** +// * After exporting a material +// * @param context GLTF context of the material +// * @param node exported GLTF node +// * @param babylonMaterial corresponding babylon material +// * @returns promise, resolves with the material +// */ +// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { +// return new Promise((resolve) => { +// if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { +// this._wasUsed = true; - const subs = babylonMaterial.subSurface; - const dispersion = subs.dispersion; +// const subs = babylonMaterial.subSurface; +// const dispersion = subs.dispersion; - const dispersionInfo: IKHRMaterialsDispersion = { - dispersion: dispersion, - }; - node.extensions = node.extensions || {}; - node.extensions[NAME] = dispersionInfo; - } - resolve(node); - }); - } -} +// const dispersionInfo: IKHRMaterialsDispersion = { +// dispersion: dispersion, +// }; +// node.extensions = node.extensions || {}; +// node.extensions[NAME] = dispersionInfo; +// } +// resolve(node); +// }); +// } +// } -_Exporter.RegisterExtension(NAME, () => new KHR_materials_dispersion()); +// _Exporter.RegisterExtension(NAME, () => new KHR_materials_dispersion()); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_emissive_strength.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_emissive_strength.ts index 0dd69ff6c4e..edaff975fa0 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_emissive_strength.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_emissive_strength.ts @@ -1,70 +1,70 @@ -import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -import { _Exporter } from "../glTFExporter"; -import type { Material } from "core/Materials/material"; -import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -import type { IMaterial, IKHRMaterialsEmissiveStrength } from "babylonjs-gltf2interface"; - -const NAME = "KHR_materials_emissive_strength"; - -/** - * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md) - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export class KHR_materials_emissive_strength implements IGLTFExporterExtensionV2 { - /** Name of this extension */ - public readonly name = NAME; - - /** Defines whether this extension is enabled */ - public enabled = true; - - /** Defines whether this extension is required */ - public required = false; - - private _wasUsed = false; - - /** Dispose */ - public dispose() {} - - /** @internal */ - public get wasUsed() { - return this._wasUsed; - } - - /** - * After exporting a material - * @param context GLTF context of the material - * @param node exported GLTF node - * @param babylonMaterial corresponding babylon material - * @returns promise, resolves with the material - */ - public postExportMaterialAsync(context: string, node: IMaterial, babylonMaterial: Material): Promise { - return new Promise((resolve) => { - if (!(babylonMaterial instanceof PBRMaterial)) { - return resolve(node); - } - - const emissiveColor = babylonMaterial.emissiveColor.asArray(); - const tempEmissiveStrength = Math.max(...emissiveColor); - - if (tempEmissiveStrength > 1) { - this._wasUsed = true; - - node.extensions ||= {}; - - const emissiveStrengthInfo: IKHRMaterialsEmissiveStrength = { - emissiveStrength: tempEmissiveStrength, - }; - - // Normalize each value of the emissive factor to have a max value of 1 - const newEmissiveFactor = babylonMaterial.emissiveColor.scale(1 / emissiveStrengthInfo.emissiveStrength); - - node.emissiveFactor = newEmissiveFactor.asArray(); - node.extensions[NAME] = emissiveStrengthInfo; - } - - return resolve(node); - }); - } -} - -_Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_emissive_strength()); +// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +// import { _Exporter } from "../glTFExporter"; +// import type { Material } from "core/Materials/material"; +// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +// import type { IMaterial, IKHRMaterialsEmissiveStrength } from "babylonjs-gltf2interface"; + +// const NAME = "KHR_materials_emissive_strength"; + +// /** +// * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md) +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export class KHR_materials_emissive_strength implements IGLTFExporterExtensionV2 { +// /** Name of this extension */ +// public readonly name = NAME; + +// /** Defines whether this extension is enabled */ +// public enabled = true; + +// /** Defines whether this extension is required */ +// public required = false; + +// private _wasUsed = false; + +// /** Dispose */ +// public dispose() {} + +// /** @internal */ +// public get wasUsed() { +// return this._wasUsed; +// } + +// /** +// * After exporting a material +// * @param context GLTF context of the material +// * @param node exported GLTF node +// * @param babylonMaterial corresponding babylon material +// * @returns promise, resolves with the material +// */ +// public postExportMaterialAsync(context: string, node: IMaterial, babylonMaterial: Material): Promise { +// return new Promise((resolve) => { +// if (!(babylonMaterial instanceof PBRMaterial)) { +// return resolve(node); +// } + +// const emissiveColor = babylonMaterial.emissiveColor.asArray(); +// const tempEmissiveStrength = Math.max(...emissiveColor); + +// if (tempEmissiveStrength > 1) { +// this._wasUsed = true; + +// node.extensions ||= {}; + +// const emissiveStrengthInfo: IKHRMaterialsEmissiveStrength = { +// emissiveStrength: tempEmissiveStrength, +// }; + +// // Normalize each value of the emissive factor to have a max value of 1 +// const newEmissiveFactor = babylonMaterial.emissiveColor.scale(1 / emissiveStrengthInfo.emissiveStrength); + +// node.emissiveFactor = newEmissiveFactor.asArray(); +// node.extensions[NAME] = emissiveStrengthInfo; +// } + +// return resolve(node); +// }); +// } +// } + +// _Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_emissive_strength()); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_ior.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_ior.ts index 77c4d7cc0d1..bc5bb85b43d 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_ior.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_ior.ts @@ -1,67 +1,67 @@ -import type { IMaterial, IKHRMaterialsIor } from "babylonjs-gltf2interface"; -import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -import { _Exporter } from "../glTFExporter"; -import type { Material } from "core/Materials/material"; -import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +// import type { IMaterial, IKHRMaterialsIor } from "babylonjs-gltf2interface"; +// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +// import { _Exporter } from "../glTFExporter"; +// import type { Material } from "core/Materials/material"; +// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -const NAME = "KHR_materials_ior"; +// const NAME = "KHR_materials_ior"; -/** - * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_ior/README.md) - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export class KHR_materials_ior implements IGLTFExporterExtensionV2 { - /** Name of this extension */ - public readonly name = NAME; +// /** +// * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_ior/README.md) +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export class KHR_materials_ior implements IGLTFExporterExtensionV2 { +// /** Name of this extension */ +// public readonly name = NAME; - /** Defines whether this extension is enabled */ - public enabled = true; +// /** Defines whether this extension is enabled */ +// public enabled = true; - /** Defines whether this extension is required */ - public required = false; +// /** Defines whether this extension is required */ +// public required = false; - private _wasUsed = false; +// private _wasUsed = false; - constructor() {} +// constructor() {} - /** Dispose */ - public dispose() {} +// /** Dispose */ +// public dispose() {} - /** @internal */ - public get wasUsed() { - return this._wasUsed; - } +// /** @internal */ +// public get wasUsed() { +// return this._wasUsed; +// } - private _isExtensionEnabled(mat: PBRMaterial): boolean { - // This extension must not be used on a material that also uses KHR_materials_unlit - if (mat.unlit) { - return false; - } - return mat.indexOfRefraction != undefined && mat.indexOfRefraction != 1.5; // 1.5 is normative default value. - } +// private _isExtensionEnabled(mat: PBRMaterial): boolean { +// // This extension must not be used on a material that also uses KHR_materials_unlit +// if (mat.unlit) { +// return false; +// } +// return mat.indexOfRefraction != undefined && mat.indexOfRefraction != 1.5; // 1.5 is normative default value. +// } - /** - * After exporting a material - * @param context GLTF context of the material - * @param node exported GLTF node - * @param babylonMaterial corresponding babylon material - * @returns promise, resolves with the material - */ - public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { - return new Promise((resolve) => { - if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { - this._wasUsed = true; +// /** +// * After exporting a material +// * @param context GLTF context of the material +// * @param node exported GLTF node +// * @param babylonMaterial corresponding babylon material +// * @returns promise, resolves with the material +// */ +// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { +// return new Promise((resolve) => { +// if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { +// this._wasUsed = true; - const iorInfo: IKHRMaterialsIor = { - ior: babylonMaterial.indexOfRefraction, - }; - node.extensions = node.extensions || {}; - node.extensions[NAME] = iorInfo; - } - resolve(node); - }); - } -} +// const iorInfo: IKHRMaterialsIor = { +// ior: babylonMaterial.indexOfRefraction, +// }; +// node.extensions = node.extensions || {}; +// node.extensions[NAME] = iorInfo; +// } +// resolve(node); +// }); +// } +// } -// eslint-disable-next-line @typescript-eslint/no-unused-vars -_Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_ior()); +// // eslint-disable-next-line @typescript-eslint/no-unused-vars +// _Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_ior()); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_iridescence.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_iridescence.ts index 4b354c22c77..599a4c307aa 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_iridescence.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_iridescence.ts @@ -1,91 +1,91 @@ -import type { IMaterial, IKHRMaterialsIridescence } from "babylonjs-gltf2interface"; -import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -import { _Exporter } from "../glTFExporter"; -import type { Material } from "core/Materials/material"; -import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; -import type { BaseTexture } from "core/Materials/Textures/baseTexture"; - -const NAME = "KHR_materials_iridescence"; - -/** - * @internal - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export class KHR_materials_iridescence implements IGLTFExporterExtensionV2 { - /** Name of this extension */ - public readonly name = NAME; - - /** Defines whether this extension is enabled */ - public enabled = true; - - /** Defines whether this extension is required */ - public required = false; - - private _exporter: _Exporter; - - private _wasUsed = false; - - constructor(exporter: _Exporter) { - this._exporter = exporter; - } - - public dispose() {} - - /** @internal */ - public get wasUsed() { - return this._wasUsed; - } - - public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { - const additionalTextures: BaseTexture[] = []; - if (babylonMaterial instanceof PBRBaseMaterial) { - if (babylonMaterial.iridescence.isEnabled) { - if (babylonMaterial.iridescence.texture) { - additionalTextures.push(babylonMaterial.iridescence.texture); - } - if (babylonMaterial.iridescence.thicknessTexture && babylonMaterial.iridescence.thicknessTexture !== babylonMaterial.iridescence.texture) { - additionalTextures.push(babylonMaterial.iridescence.thicknessTexture); - } - return additionalTextures; - } - } - - return []; - } - - public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { - return new Promise((resolve) => { - if (babylonMaterial instanceof PBRBaseMaterial) { - if (!babylonMaterial.iridescence.isEnabled) { - resolve(node); - return; - } - - this._wasUsed = true; - - node.extensions = node.extensions || {}; - - const iridescenceTextureInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.iridescence.texture); - const iridescenceThicknessTextureInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.iridescence.thicknessTexture); - - const iridescenceInfo: IKHRMaterialsIridescence = { - iridescenceFactor: babylonMaterial.iridescence.intensity, - iridescenceIor: babylonMaterial.iridescence.indexOfRefraction, - iridescenceThicknessMinimum: babylonMaterial.iridescence.minimumThickness, - iridescenceThicknessMaximum: babylonMaterial.iridescence.maximumThickness, - - iridescenceTexture: iridescenceTextureInfo ?? undefined, - iridescenceThicknessTexture: iridescenceThicknessTextureInfo ?? undefined, - hasTextures: () => { - return iridescenceInfo.iridescenceTexture !== null || iridescenceInfo.iridescenceThicknessTexture !== null; - }, - }; - - node.extensions[NAME] = iridescenceInfo; - } - resolve(node); - }); - } -} - -_Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_iridescence(exporter)); +// import type { IMaterial, IKHRMaterialsIridescence } from "babylonjs-gltf2interface"; +// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +// import { GLTFExporter } from "../glTFExporter"; +// import type { Material } from "core/Materials/material"; +// import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; +// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; + +// const NAME = "KHR_materials_iridescence"; + +// /** +// * @internal +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export class KHR_materials_iridescence implements IGLTFExporterExtensionV2 { +// /** Name of this extension */ +// public readonly name = NAME; + +// /** Defines whether this extension is enabled */ +// public enabled = true; + +// /** Defines whether this extension is required */ +// public required = false; + +// private _exporter: GLTFExporter; + +// private _wasUsed = false; + +// constructor(exporter: GLTFExporter) { +// this._exporter = exporter; +// } + +// public dispose() {} + +// /** @internal */ +// public get wasUsed() { +// return this._wasUsed; +// } + +// public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { +// const additionalTextures: BaseTexture[] = []; +// if (babylonMaterial instanceof PBRBaseMaterial) { +// if (babylonMaterial.iridescence.isEnabled) { +// if (babylonMaterial.iridescence.texture) { +// additionalTextures.push(babylonMaterial.iridescence.texture); +// } +// if (babylonMaterial.iridescence.thicknessTexture && babylonMaterial.iridescence.thicknessTexture !== babylonMaterial.iridescence.texture) { +// additionalTextures.push(babylonMaterial.iridescence.thicknessTexture); +// } +// return additionalTextures; +// } +// } + +// return []; +// } + +// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { +// return new Promise((resolve) => { +// if (babylonMaterial instanceof PBRBaseMaterial) { +// if (!babylonMaterial.iridescence.isEnabled) { +// resolve(node); +// return; +// } + +// this._wasUsed = true; + +// node.extensions = node.extensions || {}; + +// const iridescenceTextureInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.iridescence.texture); +// const iridescenceThicknessTextureInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.iridescence.thicknessTexture); + +// const iridescenceInfo: IKHRMaterialsIridescence = { +// iridescenceFactor: babylonMaterial.iridescence.intensity, +// iridescenceIor: babylonMaterial.iridescence.indexOfRefraction, +// iridescenceThicknessMinimum: babylonMaterial.iridescence.minimumThickness, +// iridescenceThicknessMaximum: babylonMaterial.iridescence.maximumThickness, + +// iridescenceTexture: iridescenceTextureInfo ?? undefined, +// iridescenceThicknessTexture: iridescenceThicknessTextureInfo ?? undefined, +// hasTextures: () => { +// return iridescenceInfo.iridescenceTexture !== null || iridescenceInfo.iridescenceThicknessTexture !== null; +// }, +// }; + +// node.extensions[NAME] = iridescenceInfo; +// } +// resolve(node); +// }); +// } +// } + +// GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_iridescence(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_sheen.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_sheen.ts index abc4d285fd2..a26dd3f2a52 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_sheen.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_sheen.ts @@ -1,87 +1,87 @@ -import type { IMaterial, IKHRMaterialsSheen } from "babylonjs-gltf2interface"; -import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -import { _Exporter } from "../glTFExporter"; -import type { Material } from "core/Materials/material"; -import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -import type { BaseTexture } from "core/Materials/Textures/baseTexture"; - -const NAME = "KHR_materials_sheen"; - -/** - * @internal - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export class KHR_materials_sheen implements IGLTFExporterExtensionV2 { - /** Name of this extension */ - public readonly name = NAME; - - /** Defines whether this extension is enabled */ - public enabled = true; - - /** Defines whether this extension is required */ - public required = false; - - private _wasUsed = false; - - private _exporter: _Exporter; - - constructor(exporter: _Exporter) { - this._exporter = exporter; - } - - public dispose() {} - - /** @internal */ - public get wasUsed() { - return this._wasUsed; - } - - public postExportMaterialAdditionalTextures(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { - if (babylonMaterial instanceof PBRMaterial) { - if (babylonMaterial.sheen.isEnabled && babylonMaterial.sheen.texture) { - return [babylonMaterial.sheen.texture]; - } - } - - return []; - } - - public postExportMaterialAsync(context: string, node: IMaterial, babylonMaterial: Material): Promise { - return new Promise((resolve) => { - if (babylonMaterial instanceof PBRMaterial) { - if (!babylonMaterial.sheen.isEnabled) { - resolve(node); - return; - } - - this._wasUsed = true; - - if (node.extensions == null) { - node.extensions = {}; - } - const sheenInfo: IKHRMaterialsSheen = { - sheenColorFactor: babylonMaterial.sheen.color.asArray(), - sheenRoughnessFactor: babylonMaterial.sheen.roughness ?? 0, - hasTextures: () => { - return sheenInfo.sheenColorTexture !== null || sheenInfo.sheenRoughnessTexture !== null; - }, - }; - - if (babylonMaterial.sheen.texture) { - sheenInfo.sheenColorTexture = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.sheen.texture) ?? undefined; - } - - if (babylonMaterial.sheen.textureRoughness && !babylonMaterial.sheen.useRoughnessFromMainTexture) { - sheenInfo.sheenRoughnessTexture = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.sheen.textureRoughness) ?? undefined; - } else if (babylonMaterial.sheen.texture && babylonMaterial.sheen.useRoughnessFromMainTexture) { - sheenInfo.sheenRoughnessTexture = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.sheen.texture) ?? undefined; - } - - node.extensions[NAME] = sheenInfo; - } - resolve(node); - }); - } -} - -_Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_sheen(exporter)); +// import type { IMaterial, IKHRMaterialsSheen } from "babylonjs-gltf2interface"; +// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +// import { GLTFExporter } from "../glTFExporter"; +// import type { Material } from "core/Materials/material"; +// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; + +// const NAME = "KHR_materials_sheen"; + +// /** +// * @internal +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export class KHR_materials_sheen implements IGLTFExporterExtensionV2 { +// /** Name of this extension */ +// public readonly name = NAME; + +// /** Defines whether this extension is enabled */ +// public enabled = true; + +// /** Defines whether this extension is required */ +// public required = false; + +// private _wasUsed = false; + +// private _exporter: GLTFExporter; + +// constructor(exporter: GLTFExporter) { +// this._exporter = exporter; +// } + +// public dispose() {} + +// /** @internal */ +// public get wasUsed() { +// return this._wasUsed; +// } + +// public postExportMaterialAdditionalTextures(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { +// if (babylonMaterial instanceof PBRMaterial) { +// if (babylonMaterial.sheen.isEnabled && babylonMaterial.sheen.texture) { +// return [babylonMaterial.sheen.texture]; +// } +// } + +// return []; +// } + +// public postExportMaterialAsync(context: string, node: IMaterial, babylonMaterial: Material): Promise { +// return new Promise((resolve) => { +// if (babylonMaterial instanceof PBRMaterial) { +// if (!babylonMaterial.sheen.isEnabled) { +// resolve(node); +// return; +// } + +// this._wasUsed = true; + +// if (node.extensions == null) { +// node.extensions = {}; +// } +// const sheenInfo: IKHRMaterialsSheen = { +// sheenColorFactor: babylonMaterial.sheen.color.asArray(), +// sheenRoughnessFactor: babylonMaterial.sheen.roughness ?? 0, +// hasTextures: () => { +// return sheenInfo.sheenColorTexture !== null || sheenInfo.sheenRoughnessTexture !== null; +// }, +// }; + +// if (babylonMaterial.sheen.texture) { +// sheenInfo.sheenColorTexture = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.sheen.texture) ?? undefined; +// } + +// if (babylonMaterial.sheen.textureRoughness && !babylonMaterial.sheen.useRoughnessFromMainTexture) { +// sheenInfo.sheenRoughnessTexture = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.sheen.textureRoughness) ?? undefined; +// } else if (babylonMaterial.sheen.texture && babylonMaterial.sheen.useRoughnessFromMainTexture) { +// sheenInfo.sheenRoughnessTexture = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.sheen.texture) ?? undefined; +// } + +// node.extensions[NAME] = sheenInfo; +// } +// resolve(node); +// }); +// } +// } + +// GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_sheen(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_specular.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_specular.ts index d2b929cb941..9221c9ac3b8 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_specular.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_specular.ts @@ -1,118 +1,118 @@ -import type { IMaterial, IKHRMaterialsSpecular } from "babylonjs-gltf2interface"; -import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -import { _Exporter } from "../glTFExporter"; -import type { Material } from "core/Materials/material"; -import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -import type { BaseTexture } from "core/Materials/Textures/baseTexture"; - -const NAME = "KHR_materials_specular"; - -/** - * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_specular/README.md) - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export class KHR_materials_specular implements IGLTFExporterExtensionV2 { - /** Name of this extension */ - public readonly name = NAME; - - /** Defines whether this extension is enabled */ - public enabled = true; - - /** Defines whether this extension is required */ - public required = false; - - private _exporter: _Exporter; - - private _wasUsed = false; - - constructor(exporter: _Exporter) { - this._exporter = exporter; - } - - /** Dispose */ - public dispose() {} - - /** @internal */ - public get wasUsed() { - return this._wasUsed; - } - - /** - * After exporting a material, deal with the additional textures - * @param context GLTF context of the material - * @param node exported GLTF node - * @param babylonMaterial corresponding babylon material - * @returns array of additional textures to export - */ - public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { - const additionalTextures: BaseTexture[] = []; - - if (babylonMaterial instanceof PBRMaterial) { - if (this._isExtensionEnabled(babylonMaterial)) { - if (babylonMaterial.metallicReflectanceTexture) { - additionalTextures.push(babylonMaterial.metallicReflectanceTexture); - } - if (babylonMaterial.reflectanceTexture) { - additionalTextures.push(babylonMaterial.reflectanceTexture); - } - return additionalTextures; - } - } - - return additionalTextures; - } - - private _isExtensionEnabled(mat: PBRMaterial): boolean { - // This extension must not be used on a material that also uses KHR_materials_unlit - if (mat.unlit) { - return false; - } - return ( - (mat.metallicF0Factor != undefined && mat.metallicF0Factor != 1.0) || - (mat.metallicReflectanceColor != undefined && !mat.metallicReflectanceColor.equalsFloats(1.0, 1.0, 1.0)) || - this._hasTexturesExtension(mat) - ); - } - - private _hasTexturesExtension(mat: PBRMaterial): boolean { - return mat.metallicReflectanceTexture != null || mat.reflectanceTexture != null; - } - - /** - * After exporting a material - * @param context GLTF context of the material - * @param node exported GLTF node - * @param babylonMaterial corresponding babylon material - * @returns promise, resolves with the material - */ - public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { - return new Promise((resolve) => { - if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { - this._wasUsed = true; - - node.extensions = node.extensions || {}; - - const metallicReflectanceTexture = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.metallicReflectanceTexture) ?? undefined; - const reflectanceTexture = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.reflectanceTexture) ?? undefined; - const metallicF0Factor = babylonMaterial.metallicF0Factor == 1.0 ? undefined : babylonMaterial.metallicF0Factor; - const metallicReflectanceColor = babylonMaterial.metallicReflectanceColor.equalsFloats(1.0, 1.0, 1.0) - ? undefined - : babylonMaterial.metallicReflectanceColor.asArray(); - - const specularInfo: IKHRMaterialsSpecular = { - specularFactor: metallicF0Factor, - specularTexture: metallicReflectanceTexture, - specularColorFactor: metallicReflectanceColor, - specularColorTexture: reflectanceTexture, - hasTextures: () => { - return this._hasTexturesExtension(babylonMaterial); - }, - }; - node.extensions[NAME] = specularInfo; - } - resolve(node); - }); - } -} - -_Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_specular(exporter)); +// import type { IMaterial, IKHRMaterialsSpecular } from "babylonjs-gltf2interface"; +// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +// import { _Exporter } from "../glTFExporter"; +// import type { Material } from "core/Materials/material"; +// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; + +// const NAME = "KHR_materials_specular"; + +// /** +// * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_specular/README.md) +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export class KHR_materials_specular implements IGLTFExporterExtensionV2 { +// /** Name of this extension */ +// public readonly name = NAME; + +// /** Defines whether this extension is enabled */ +// public enabled = true; + +// /** Defines whether this extension is required */ +// public required = false; + +// private _exporter: _Exporter; + +// private _wasUsed = false; + +// constructor(exporter: _Exporter) { +// this._exporter = exporter; +// } + +// /** Dispose */ +// public dispose() {} + +// /** @internal */ +// public get wasUsed() { +// return this._wasUsed; +// } + +// /** +// * After exporting a material, deal with the additional textures +// * @param context GLTF context of the material +// * @param node exported GLTF node +// * @param babylonMaterial corresponding babylon material +// * @returns array of additional textures to export +// */ +// public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { +// const additionalTextures: BaseTexture[] = []; + +// if (babylonMaterial instanceof PBRMaterial) { +// if (this._isExtensionEnabled(babylonMaterial)) { +// if (babylonMaterial.metallicReflectanceTexture) { +// additionalTextures.push(babylonMaterial.metallicReflectanceTexture); +// } +// if (babylonMaterial.reflectanceTexture) { +// additionalTextures.push(babylonMaterial.reflectanceTexture); +// } +// return additionalTextures; +// } +// } + +// return additionalTextures; +// } + +// private _isExtensionEnabled(mat: PBRMaterial): boolean { +// // This extension must not be used on a material that also uses KHR_materials_unlit +// if (mat.unlit) { +// return false; +// } +// return ( +// (mat.metallicF0Factor != undefined && mat.metallicF0Factor != 1.0) || +// (mat.metallicReflectanceColor != undefined && !mat.metallicReflectanceColor.equalsFloats(1.0, 1.0, 1.0)) || +// this._hasTexturesExtension(mat) +// ); +// } + +// private _hasTexturesExtension(mat: PBRMaterial): boolean { +// return mat.metallicReflectanceTexture != null || mat.reflectanceTexture != null; +// } + +// /** +// * After exporting a material +// * @param context GLTF context of the material +// * @param node exported GLTF node +// * @param babylonMaterial corresponding babylon material +// * @returns promise, resolves with the material +// */ +// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { +// return new Promise((resolve) => { +// if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { +// this._wasUsed = true; + +// node.extensions = node.extensions || {}; + +// const metallicReflectanceTexture = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.metallicReflectanceTexture) ?? undefined; +// const reflectanceTexture = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.reflectanceTexture) ?? undefined; +// const metallicF0Factor = babylonMaterial.metallicF0Factor == 1.0 ? undefined : babylonMaterial.metallicF0Factor; +// const metallicReflectanceColor = babylonMaterial.metallicReflectanceColor.equalsFloats(1.0, 1.0, 1.0) +// ? undefined +// : babylonMaterial.metallicReflectanceColor.asArray(); + +// const specularInfo: IKHRMaterialsSpecular = { +// specularFactor: metallicF0Factor, +// specularTexture: metallicReflectanceTexture, +// specularColorFactor: metallicReflectanceColor, +// specularColorTexture: reflectanceTexture, +// hasTextures: () => { +// return this._hasTexturesExtension(babylonMaterial); +// }, +// }; +// node.extensions[NAME] = specularInfo; +// } +// resolve(node); +// }); +// } +// } + +// _Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_specular(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_transmission.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_transmission.ts index f7735b79161..39a26b199e6 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_transmission.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_transmission.ts @@ -1,107 +1,107 @@ -import type { IMaterial, IKHRMaterialsTransmission } from "babylonjs-gltf2interface"; -import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -import { _Exporter } from "../glTFExporter"; -import type { Material } from "core/Materials/material"; -import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -import type { BaseTexture } from "core/Materials/Textures/baseTexture"; - -const NAME = "KHR_materials_transmission"; - -/** - * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_transmission/README.md) - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export class KHR_materials_transmission implements IGLTFExporterExtensionV2 { - /** Name of this extension */ - public readonly name = NAME; - - /** Defines whether this extension is enabled */ - public enabled = true; - - /** Defines whether this extension is required */ - public required = false; - - private _exporter: _Exporter; - - private _wasUsed = false; - - constructor(exporter: _Exporter) { - this._exporter = exporter; - } - - /** Dispose */ - public dispose() {} - - /** @internal */ - public get wasUsed() { - return this._wasUsed; - } - - /** - * After exporting a material, deal with additional textures - * @param context GLTF context of the material - * @param node exported GLTF node - * @param babylonMaterial corresponding babylon material - * @returns array of additional textures to export - */ - public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { - const additionalTextures: BaseTexture[] = []; - - if (babylonMaterial instanceof PBRMaterial) { - if (this._isExtensionEnabled(babylonMaterial)) { - if (babylonMaterial.subSurface.thicknessTexture) { - additionalTextures.push(babylonMaterial.subSurface.thicknessTexture); - } - return additionalTextures; - } - } - - return additionalTextures; - } - - private _isExtensionEnabled(mat: PBRMaterial): boolean { - // This extension must not be used on a material that also uses KHR_materials_unlit - if (mat.unlit) { - return false; - } - const subs = mat.subSurface; - return (subs.isRefractionEnabled && subs.refractionIntensity != undefined && subs.refractionIntensity != 0) || this._hasTexturesExtension(mat); - } - - private _hasTexturesExtension(mat: PBRMaterial): boolean { - return mat.subSurface.refractionIntensityTexture != null; - } - - /** - * After exporting a material - * @param context GLTF context of the material - * @param node exported GLTF node - * @param babylonMaterial corresponding babylon material - * @returns true if successful - */ - public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { - return new Promise((resolve) => { - if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { - this._wasUsed = true; - - const subs = babylonMaterial.subSurface; - const transmissionFactor = subs.refractionIntensity === 0 ? undefined : subs.refractionIntensity; - - const transmissionTexture = this._exporter._glTFMaterialExporter._getTextureInfo(subs.refractionIntensityTexture) ?? undefined; - - const volumeInfo: IKHRMaterialsTransmission = { - transmissionFactor: transmissionFactor, - transmissionTexture: transmissionTexture, - hasTextures: () => { - return this._hasTexturesExtension(babylonMaterial); - }, - }; - node.extensions = node.extensions || {}; - node.extensions[NAME] = volumeInfo; - } - resolve(node); - }); - } -} - -_Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_transmission(exporter)); +// import type { IMaterial, IKHRMaterialsTransmission } from "babylonjs-gltf2interface"; +// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +// import { _Exporter } from "../glTFExporter"; +// import type { Material } from "core/Materials/material"; +// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; + +// const NAME = "KHR_materials_transmission"; + +// /** +// * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_transmission/README.md) +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export class KHR_materials_transmission implements IGLTFExporterExtensionV2 { +// /** Name of this extension */ +// public readonly name = NAME; + +// /** Defines whether this extension is enabled */ +// public enabled = true; + +// /** Defines whether this extension is required */ +// public required = false; + +// private _exporter: _Exporter; + +// private _wasUsed = false; + +// constructor(exporter: _Exporter) { +// this._exporter = exporter; +// } + +// /** Dispose */ +// public dispose() {} + +// /** @internal */ +// public get wasUsed() { +// return this._wasUsed; +// } + +// /** +// * After exporting a material, deal with additional textures +// * @param context GLTF context of the material +// * @param node exported GLTF node +// * @param babylonMaterial corresponding babylon material +// * @returns array of additional textures to export +// */ +// public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { +// const additionalTextures: BaseTexture[] = []; + +// if (babylonMaterial instanceof PBRMaterial) { +// if (this._isExtensionEnabled(babylonMaterial)) { +// if (babylonMaterial.subSurface.thicknessTexture) { +// additionalTextures.push(babylonMaterial.subSurface.thicknessTexture); +// } +// return additionalTextures; +// } +// } + +// return additionalTextures; +// } + +// private _isExtensionEnabled(mat: PBRMaterial): boolean { +// // This extension must not be used on a material that also uses KHR_materials_unlit +// if (mat.unlit) { +// return false; +// } +// const subs = mat.subSurface; +// return (subs.isRefractionEnabled && subs.refractionIntensity != undefined && subs.refractionIntensity != 0) || this._hasTexturesExtension(mat); +// } + +// private _hasTexturesExtension(mat: PBRMaterial): boolean { +// return mat.subSurface.refractionIntensityTexture != null; +// } + +// /** +// * After exporting a material +// * @param context GLTF context of the material +// * @param node exported GLTF node +// * @param babylonMaterial corresponding babylon material +// * @returns true if successful +// */ +// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { +// return new Promise((resolve) => { +// if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { +// this._wasUsed = true; + +// const subs = babylonMaterial.subSurface; +// const transmissionFactor = subs.refractionIntensity === 0 ? undefined : subs.refractionIntensity; + +// const transmissionTexture = this._exporter._glTFMaterialExporter._getTextureInfo(subs.refractionIntensityTexture) ?? undefined; + +// const volumeInfo: IKHRMaterialsTransmission = { +// transmissionFactor: transmissionFactor, +// transmissionTexture: transmissionTexture, +// hasTextures: () => { +// return this._hasTexturesExtension(babylonMaterial); +// }, +// }; +// node.extensions = node.extensions || {}; +// node.extensions[NAME] = volumeInfo; +// } +// resolve(node); +// }); +// } +// } + +// _Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_transmission(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_unlit.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_unlit.ts index 17c9e909e67..fcce751059f 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_unlit.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_unlit.ts @@ -1,60 +1,60 @@ -import type { IMaterial } from "babylonjs-gltf2interface"; -import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -import { _Exporter } from "../glTFExporter"; -import type { Material } from "core/Materials/material"; -import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -import { StandardMaterial } from "core/Materials/standardMaterial"; +// import type { IMaterial } from "babylonjs-gltf2interface"; +// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +// import { GLTFExporter } from "../glTFExporter"; +// import type { Material } from "core/Materials/material"; +// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +// import { StandardMaterial } from "core/Materials/standardMaterial"; -const NAME = "KHR_materials_unlit"; +// const NAME = "KHR_materials_unlit"; -/** - * @internal - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export class KHR_materials_unlit implements IGLTFExporterExtensionV2 { - /** Name of this extension */ - public readonly name = NAME; +// /** +// * @internal +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export class KHR_materials_unlit implements IGLTFExporterExtensionV2 { +// /** Name of this extension */ +// public readonly name = NAME; - /** Defines whether this extension is enabled */ - public enabled = true; +// /** Defines whether this extension is enabled */ +// public enabled = true; - /** Defines whether this extension is required */ - public required = false; +// /** Defines whether this extension is required */ +// public required = false; - private _wasUsed = false; +// private _wasUsed = false; - constructor() {} +// constructor() {} - /** @internal */ - public get wasUsed() { - return this._wasUsed; - } +// /** @internal */ +// public get wasUsed() { +// return this._wasUsed; +// } - public dispose() {} +// public dispose() {} - public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { - return new Promise((resolve) => { - let unlitMaterial = false; +// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { +// return new Promise((resolve) => { +// let unlitMaterial = false; - if (babylonMaterial instanceof PBRMaterial) { - unlitMaterial = babylonMaterial.unlit; - } else if (babylonMaterial instanceof StandardMaterial) { - unlitMaterial = babylonMaterial.disableLighting; - } +// if (babylonMaterial instanceof PBRMaterial) { +// unlitMaterial = babylonMaterial.unlit; +// } else if (babylonMaterial instanceof StandardMaterial) { +// unlitMaterial = babylonMaterial.disableLighting; +// } - if (unlitMaterial) { - this._wasUsed = true; +// if (unlitMaterial) { +// this._wasUsed = true; - if (node.extensions == null) { - node.extensions = {}; - } +// if (node.extensions == null) { +// node.extensions = {}; +// } - node.extensions[NAME] = {}; - } +// node.extensions[NAME] = {}; +// } - resolve(node); - }); - } -} +// resolve(node); +// }); +// } +// } -_Exporter.RegisterExtension(NAME, () => new KHR_materials_unlit()); +// GLTFExporter.RegisterExtension(NAME, () => new KHR_materials_unlit()); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_volume.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_volume.ts index c932c965079..ef0ad58abfc 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_volume.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_volume.ts @@ -1,119 +1,119 @@ -import type { IMaterial, IKHRMaterialsVolume } from "babylonjs-gltf2interface"; -import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -import { _Exporter } from "../glTFExporter"; -import type { Material } from "core/Materials/material"; -import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -import type { BaseTexture } from "core/Materials/Textures/baseTexture"; -import { Color3 } from "core/Maths/math.color"; - -const NAME = "KHR_materials_volume"; - -/** - * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_volume/README.md) - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export class KHR_materials_volume implements IGLTFExporterExtensionV2 { - /** Name of this extension */ - public readonly name = NAME; - - /** Defines whether this extension is enabled */ - public enabled = true; - - /** Defines whether this extension is required */ - public required = false; - - private _exporter: _Exporter; - - private _wasUsed = false; - - constructor(exporter: _Exporter) { - this._exporter = exporter; - } - - public dispose() {} - - /** @internal */ - public get wasUsed() { - return this._wasUsed; - } - - /** - * After exporting a material, deal with additional textures - * @param context GLTF context of the material - * @param node exported GLTF node - * @param babylonMaterial corresponding babylon material - * @returns array of additional textures to export - */ - public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { - const additionalTextures: BaseTexture[] = []; - - if (babylonMaterial instanceof PBRMaterial) { - if (this._isExtensionEnabled(babylonMaterial)) { - if (babylonMaterial.subSurface.thicknessTexture) { - additionalTextures.push(babylonMaterial.subSurface.thicknessTexture); - } - return additionalTextures; - } - } - - return additionalTextures; - } - - private _isExtensionEnabled(mat: PBRMaterial): boolean { - // This extension must not be used on a material that also uses KHR_materials_unlit - if (mat.unlit) { - return false; - } - const subs = mat.subSurface; - // this extension requires either the KHR_materials_transmission or KHR_materials_diffuse_transmission extensions. - if (!subs.isRefractionEnabled && !subs.isTranslucencyEnabled) { - return false; - } - return ( - (subs.maximumThickness != undefined && subs.maximumThickness != 0) || - (subs.tintColorAtDistance != undefined && subs.tintColorAtDistance != Number.POSITIVE_INFINITY) || - (subs.tintColor != undefined && subs.tintColor != Color3.White()) || - this._hasTexturesExtension(mat) - ); - } - - private _hasTexturesExtension(mat: PBRMaterial): boolean { - return mat.subSurface.thicknessTexture != null; - } - - /** - * After exporting a material - * @param context GLTF context of the material - * @param node exported GLTF node - * @param babylonMaterial corresponding babylon material - * @returns promise that resolves with the updated node - */ - public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { - return new Promise((resolve) => { - if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { - this._wasUsed = true; - - const subs = babylonMaterial.subSurface; - const thicknessFactor = subs.maximumThickness == 0 ? undefined : subs.maximumThickness; - const thicknessTexture = this._exporter._glTFMaterialExporter._getTextureInfo(subs.thicknessTexture) ?? undefined; - const attenuationDistance = subs.tintColorAtDistance == Number.POSITIVE_INFINITY ? undefined : subs.tintColorAtDistance; - const attenuationColor = subs.tintColor.equalsFloats(1.0, 1.0, 1.0) ? undefined : subs.tintColor.asArray(); - - const volumeInfo: IKHRMaterialsVolume = { - thicknessFactor: thicknessFactor, - thicknessTexture: thicknessTexture, - attenuationDistance: attenuationDistance, - attenuationColor: attenuationColor, - hasTextures: () => { - return this._hasTexturesExtension(babylonMaterial); - }, - }; - node.extensions = node.extensions || {}; - node.extensions[NAME] = volumeInfo; - } - resolve(node); - }); - } -} - -_Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_volume(exporter)); +// import type { IMaterial, IKHRMaterialsVolume } from "babylonjs-gltf2interface"; +// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +// import { _Exporter } from "../glTFExporter"; +// import type { Material } from "core/Materials/material"; +// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; +// import { Color3 } from "core/Maths/math.color"; + +// const NAME = "KHR_materials_volume"; + +// /** +// * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_volume/README.md) +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export class KHR_materials_volume implements IGLTFExporterExtensionV2 { +// /** Name of this extension */ +// public readonly name = NAME; + +// /** Defines whether this extension is enabled */ +// public enabled = true; + +// /** Defines whether this extension is required */ +// public required = false; + +// private _exporter: _Exporter; + +// private _wasUsed = false; + +// constructor(exporter: _Exporter) { +// this._exporter = exporter; +// } + +// public dispose() {} + +// /** @internal */ +// public get wasUsed() { +// return this._wasUsed; +// } + +// /** +// * After exporting a material, deal with additional textures +// * @param context GLTF context of the material +// * @param node exported GLTF node +// * @param babylonMaterial corresponding babylon material +// * @returns array of additional textures to export +// */ +// public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { +// const additionalTextures: BaseTexture[] = []; + +// if (babylonMaterial instanceof PBRMaterial) { +// if (this._isExtensionEnabled(babylonMaterial)) { +// if (babylonMaterial.subSurface.thicknessTexture) { +// additionalTextures.push(babylonMaterial.subSurface.thicknessTexture); +// } +// return additionalTextures; +// } +// } + +// return additionalTextures; +// } + +// private _isExtensionEnabled(mat: PBRMaterial): boolean { +// // This extension must not be used on a material that also uses KHR_materials_unlit +// if (mat.unlit) { +// return false; +// } +// const subs = mat.subSurface; +// // this extension requires either the KHR_materials_transmission or KHR_materials_diffuse_transmission extensions. +// if (!subs.isRefractionEnabled && !subs.isTranslucencyEnabled) { +// return false; +// } +// return ( +// (subs.maximumThickness != undefined && subs.maximumThickness != 0) || +// (subs.tintColorAtDistance != undefined && subs.tintColorAtDistance != Number.POSITIVE_INFINITY) || +// (subs.tintColor != undefined && subs.tintColor != Color3.White()) || +// this._hasTexturesExtension(mat) +// ); +// } + +// private _hasTexturesExtension(mat: PBRMaterial): boolean { +// return mat.subSurface.thicknessTexture != null; +// } + +// /** +// * After exporting a material +// * @param context GLTF context of the material +// * @param node exported GLTF node +// * @param babylonMaterial corresponding babylon material +// * @returns promise that resolves with the updated node +// */ +// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { +// return new Promise((resolve) => { +// if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { +// this._wasUsed = true; + +// const subs = babylonMaterial.subSurface; +// const thicknessFactor = subs.maximumThickness == 0 ? undefined : subs.maximumThickness; +// const thicknessTexture = this._exporter._glTFMaterialExporter._getTextureInfo(subs.thicknessTexture) ?? undefined; +// const attenuationDistance = subs.tintColorAtDistance == Number.POSITIVE_INFINITY ? undefined : subs.tintColorAtDistance; +// const attenuationColor = subs.tintColor.equalsFloats(1.0, 1.0, 1.0) ? undefined : subs.tintColor.asArray(); + +// const volumeInfo: IKHRMaterialsVolume = { +// thicknessFactor: thicknessFactor, +// thicknessTexture: thicknessTexture, +// attenuationDistance: attenuationDistance, +// attenuationColor: attenuationColor, +// hasTextures: () => { +// return this._hasTexturesExtension(babylonMaterial); +// }, +// }; +// node.extensions = node.extensions || {}; +// node.extensions[NAME] = volumeInfo; +// } +// resolve(node); +// }); +// } +// } + +// _Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_volume(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_texture_transform.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_texture_transform.ts index 093c5352973..6983646ae7f 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_texture_transform.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_texture_transform.ts @@ -1,103 +1,103 @@ -import type { ITextureInfo, IKHRTextureTransform } from "babylonjs-gltf2interface"; -import { Tools } from "core/Misc/tools"; -import type { Texture } from "core/Materials/Textures/texture"; -import type { Nullable } from "core/types"; -import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -import { _Exporter } from "../glTFExporter"; - -const NAME = "KHR_texture_transform"; - -/** - * @internal - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export class KHR_texture_transform implements IGLTFExporterExtensionV2 { - /** Name of this extension */ - public readonly name = NAME; - - /** Defines whether this extension is enabled */ - public enabled = true; - - /** Defines whether this extension is required */ - public required = false; - - /** Reference to the glTF exporter */ - private _wasUsed = false; - - constructor() {} - - public dispose() {} - - /** @internal */ - public get wasUsed() { - return this._wasUsed; - } - - public postExportTexture?(context: string, textureInfo: ITextureInfo, babylonTexture: Texture): void { - const canUseExtension = - babylonTexture && - ((babylonTexture.uAng === 0 && babylonTexture.wAng === 0 && babylonTexture.vAng === 0) || - (babylonTexture.uRotationCenter === 0 && babylonTexture.vRotationCenter === 0)); - - if (canUseExtension) { - const textureTransform: IKHRTextureTransform = {}; - let transformIsRequired = false; - - if (babylonTexture.uOffset !== 0 || babylonTexture.vOffset !== 0) { - textureTransform.offset = [babylonTexture.uOffset, babylonTexture.vOffset]; - transformIsRequired = true; - } - - if (babylonTexture.uScale !== 1 || babylonTexture.vScale !== 1) { - textureTransform.scale = [babylonTexture.uScale, babylonTexture.vScale]; - transformIsRequired = true; - } - - if (babylonTexture.wAng !== 0) { - textureTransform.rotation = -babylonTexture.wAng; - transformIsRequired = true; - } - - if (babylonTexture.coordinatesIndex !== 0) { - textureTransform.texCoord = babylonTexture.coordinatesIndex; - transformIsRequired = true; - } - - if (!transformIsRequired) { - return; - } - - this._wasUsed = true; - if (!textureInfo.extensions) { - textureInfo.extensions = {}; - } - textureInfo.extensions[NAME] = textureTransform; - } - } - - public preExportTextureAsync(context: string, babylonTexture: Texture): Promise> { - return new Promise((resolve, reject) => { - const scene = babylonTexture.getScene(); - if (!scene) { - reject(`${context}: "scene" is not defined for Babylon texture ${babylonTexture.name}!`); - return; - } - - /* - * The KHR_texture_transform schema only supports w rotation around the origin. - * See https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform#gltf-schema-updates. - */ - if (babylonTexture.uAng !== 0 || babylonTexture.vAng !== 0) { - Tools.Warn(`${context}: Texture ${babylonTexture.name} with rotation in the u or v axis is not supported in glTF.`); - resolve(null); - } else if (babylonTexture.wAng !== 0 && (babylonTexture.uRotationCenter !== 0 || babylonTexture.vRotationCenter !== 0)) { - Tools.Warn(`${context}: Texture ${babylonTexture.name} with rotation not centered at the origin cannot be exported with ${NAME}`); - resolve(null); - } else { - resolve(babylonTexture); - } - }); - } -} - -_Exporter.RegisterExtension(NAME, () => new KHR_texture_transform()); +// import type { ITextureInfo, IKHRTextureTransform } from "babylonjs-gltf2interface"; +// import { Tools } from "core/Misc/tools"; +// import type { Texture } from "core/Materials/Textures/texture"; +// import type { Nullable } from "core/types"; +// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +// import { GLTFExporter } from "../glTFExporter"; + +// const NAME = "KHR_texture_transform"; + +// /** +// * @internal +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export class KHR_texture_transform implements IGLTFExporterExtensionV2 { +// /** Name of this extension */ +// public readonly name = NAME; + +// /** Defines whether this extension is enabled */ +// public enabled = true; + +// /** Defines whether this extension is required */ +// public required = false; + +// /** Reference to the glTF exporter */ +// private _wasUsed = false; + +// constructor() {} + +// public dispose() {} + +// /** @internal */ +// public get wasUsed() { +// return this._wasUsed; +// } + +// public postExportTexture?(context: string, textureInfo: ITextureInfo, babylonTexture: Texture): void { +// const canUseExtension = +// babylonTexture && +// ((babylonTexture.uAng === 0 && babylonTexture.wAng === 0 && babylonTexture.vAng === 0) || +// (babylonTexture.uRotationCenter === 0 && babylonTexture.vRotationCenter === 0)); + +// if (canUseExtension) { +// const textureTransform: IKHRTextureTransform = {}; +// let transformIsRequired = false; + +// if (babylonTexture.uOffset !== 0 || babylonTexture.vOffset !== 0) { +// textureTransform.offset = [babylonTexture.uOffset, babylonTexture.vOffset]; +// transformIsRequired = true; +// } + +// if (babylonTexture.uScale !== 1 || babylonTexture.vScale !== 1) { +// textureTransform.scale = [babylonTexture.uScale, babylonTexture.vScale]; +// transformIsRequired = true; +// } + +// if (babylonTexture.wAng !== 0) { +// textureTransform.rotation = -babylonTexture.wAng; +// transformIsRequired = true; +// } + +// if (babylonTexture.coordinatesIndex !== 0) { +// textureTransform.texCoord = babylonTexture.coordinatesIndex; +// transformIsRequired = true; +// } + +// if (!transformIsRequired) { +// return; +// } + +// this._wasUsed = true; +// if (!textureInfo.extensions) { +// textureInfo.extensions = {}; +// } +// textureInfo.extensions[NAME] = textureTransform; +// } +// } + +// public preExportTextureAsync(context: string, babylonTexture: Texture): Promise> { +// return new Promise((resolve, reject) => { +// const scene = babylonTexture.getScene(); +// if (!scene) { +// reject(`${context}: "scene" is not defined for Babylon texture ${babylonTexture.name}!`); +// return; +// } + +// /* +// * The KHR_texture_transform schema only supports w rotation around the origin. +// * See https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform#gltf-schema-updates. +// */ +// if (babylonTexture.uAng !== 0 || babylonTexture.vAng !== 0) { +// Tools.Warn(`${context}: Texture ${babylonTexture.name} with rotation in the u or v axis is not supported in glTF.`); +// resolve(null); +// } else if (babylonTexture.wAng !== 0 && (babylonTexture.uRotationCenter !== 0 || babylonTexture.vRotationCenter !== 0)) { +// Tools.Warn(`${context}: Texture ${babylonTexture.name} with rotation not centered at the origin cannot be exported with ${NAME}`); +// resolve(null); +// } else { +// resolve(babylonTexture); +// } +// }); +// } +// } + +// GLTFExporter.RegisterExtension(NAME, () => new KHR_texture_transform()); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts index cd981e5901e..897a9e2d5f5 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts @@ -1,15 +1,15 @@ -export * from "./KHR_texture_transform"; -export * from "./KHR_lights_punctual"; -export * from "./KHR_materials_clearcoat"; -export * from "./KHR_materials_iridescence"; -export * from "./KHR_materials_anisotropy"; -export * from "./KHR_materials_sheen"; -export * from "./KHR_materials_unlit"; -export * from "./KHR_materials_ior"; -export * from "./KHR_materials_specular"; -export * from "./KHR_materials_volume"; -export * from "./KHR_materials_dispersion"; -export * from "./KHR_materials_transmission"; -export * from "./EXT_mesh_gpu_instancing"; -export * from "./KHR_materials_emissive_strength"; -export * from "./KHR_materials_diffuse_transmission"; +// export * from "./EXT_mesh_gpu_instancing"; +// export * from "./KHR_lights_punctual"; +// export * from "./KHR_materials_anisotropy"; +// export * from "./KHR_materials_clearcoat"; +// export * from "./KHR_materials_diffuse_transmission"; +// export * from "./KHR_materials_dispersion"; +// export * from "./KHR_materials_emissive_strength"; +// export * from "./KHR_materials_ior"; +// export * from "./KHR_materials_iridescence"; +// export * from "./KHR_materials_sheen"; +// export * from "./KHR_materials_specular"; +// export * from "./KHR_materials_transmission"; +// export * from "./KHR_materials_unlit"; +// export * from "./KHR_materials_volume"; +// export * from "./KHR_texture_transform"; diff --git a/packages/dev/serializers/src/glTF/2.0/dataWriter.ts b/packages/dev/serializers/src/glTF/2.0/dataWriter.ts new file mode 100644 index 00000000000..2e9d8945a4b --- /dev/null +++ b/packages/dev/serializers/src/glTF/2.0/dataWriter.ts @@ -0,0 +1,68 @@ +/* eslint-disable babylonjs/available */ + +/** @internal */ +export class DataWriter { + private _data: Uint8Array; + private _dataView: DataView; + private _byteOffset: number; + + public constructor(byteLength: number) { + this._data = new Uint8Array(byteLength); + this._dataView = new DataView(this._data.buffer); + this._byteOffset = 0; + } + + public get byteOffset(): number { + return this._byteOffset; + } + + public getOutputData(): Uint8Array { + return new Uint8Array(this._data.buffer, 0, this._byteOffset); + } + + public writeUInt8(value: number): void { + this._checkGrowBuffer(1); + this._dataView.setUint8(this._byteOffset, value); + this._byteOffset++; + } + + public writeInt16(entry: number): void { + this._checkGrowBuffer(2); + this._dataView.setInt16(this._byteOffset, entry, true); + this._byteOffset += 2; + } + + public writeUInt16(value: number): void { + this._checkGrowBuffer(2); + this._dataView.setUint16(this._byteOffset, value, true); + this._byteOffset += 2; + } + + public writeUInt32(entry: number): void { + this._checkGrowBuffer(4); + this._dataView.setUint32(this._byteOffset, entry, true); + this._byteOffset += 4; + } + + public writeFloat32(value: number): void { + this._checkGrowBuffer(4); + this._dataView.setFloat32(this._byteOffset, value, true); + this._byteOffset += 4; + } + + public writeUint8Array(value: Uint8Array): void { + this._checkGrowBuffer(value.byteLength); + this._data.set(value, this._byteOffset); + this._byteOffset += value.byteLength; + } + + private _checkGrowBuffer(byteLength: number): void { + const newByteLength = this.byteOffset + byteLength; + if (newByteLength > this._data.byteLength) { + const newData = new Uint8Array(newByteLength * 2); + newData.set(this._data); + this._data = newData; + this._dataView = new DataView(this._data.buffer); + } + } +} diff --git a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts index 9e9c6e13b0d..28f78043e69 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts @@ -1,1052 +1,1052 @@ -import type { IAnimation, INode, IBufferView, IAccessor, IAnimationSampler, IAnimationChannel } from "babylonjs-gltf2interface"; -import { AnimationSamplerInterpolation, AnimationChannelTargetPath, AccessorType, AccessorComponentType } from "babylonjs-gltf2interface"; -import type { Node } from "core/node"; -import type { Nullable } from "core/types"; -import { Vector3, Quaternion } from "core/Maths/math.vector"; -import { Tools } from "core/Misc/tools"; -import { Animation } from "core/Animations/animation"; -import { TransformNode } from "core/Meshes/transformNode"; -import type { Scene } from "core/scene"; -import { MorphTarget } from "core/Morph/morphTarget"; -import { Mesh } from "core/Meshes/mesh"; +// import type { IAnimation, INode, IBufferView, IAccessor, IAnimationSampler, IAnimationChannel } from "babylonjs-gltf2interface"; +// import { AnimationSamplerInterpolation, AnimationChannelTargetPath, AccessorType, AccessorComponentType } from "babylonjs-gltf2interface"; +// import type { Node } from "core/node"; +// import type { Nullable } from "core/types"; +// import { Vector3, Quaternion } from "core/Maths/math.vector"; +// import { Tools } from "core/Misc/tools"; +// import { Animation } from "core/Animations/animation"; +// import { TransformNode } from "core/Meshes/transformNode"; +// import type { Scene } from "core/scene"; +// import { MorphTarget } from "core/Morph/morphTarget"; +// import { Mesh } from "core/Meshes/mesh"; -import type { _BinaryWriter } from "./glTFExporter"; -import { _GLTFUtilities } from "./glTFUtilities"; -import type { IAnimationKey } from "core/Animations/animationKey"; -import { AnimationKeyInterpolation } from "core/Animations/animationKey"; +// import type { _BinaryWriter } from "./glTFExporter"; +// import { _GLTFUtilities } from "./glTFUtilities"; +// import type { IAnimationKey } from "core/Animations/animationKey"; +// import { AnimationKeyInterpolation } from "core/Animations/animationKey"; -import { Camera } from "core/Cameras/camera"; -import { Light } from "core/Lights/light"; +// import { Camera } from "core/Cameras/camera"; +// import { Light } from "core/Lights/light"; -/** - * @internal - * Interface to store animation data. - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export interface _IAnimationData { - /** - * Keyframe data. - */ - inputs: number[]; - /** - * Value data. - */ - outputs: number[][]; - /** - * Animation interpolation data. - */ - samplerInterpolation: AnimationSamplerInterpolation; - /** - * Minimum keyframe value. - */ - inputsMin: number; - /** - * Maximum keyframe value. - */ - inputsMax: number; -} +// /** +// * @internal +// * Interface to store animation data. +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export interface _IAnimationData { +// /** +// * Keyframe data. +// */ +// inputs: number[]; +// /** +// * Value data. +// */ +// outputs: number[][]; +// /** +// * Animation interpolation data. +// */ +// samplerInterpolation: AnimationSamplerInterpolation; +// /** +// * Minimum keyframe value. +// */ +// inputsMin: number; +// /** +// * Maximum keyframe value. +// */ +// inputsMax: number; +// } -/** - * @internal - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -export interface _IAnimationInfo { - /** - * The target channel for the animation - */ - animationChannelTargetPath: AnimationChannelTargetPath; - /** - * The glTF accessor type for the data. - */ - dataAccessorType: AccessorType.VEC3 | AccessorType.VEC4 | AccessorType.SCALAR; - /** - * Specifies if quaternions should be used. - */ - useQuaternion: boolean; -} +// /** +// * @internal +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// export interface _IAnimationInfo { +// /** +// * The target channel for the animation +// */ +// animationChannelTargetPath: AnimationChannelTargetPath; +// /** +// * The glTF accessor type for the data. +// */ +// dataAccessorType: AccessorType.VEC3 | AccessorType.VEC4 | AccessorType.SCALAR; +// /** +// * Specifies if quaternions should be used. +// */ +// useQuaternion: boolean; +// } -/** - * @internal - * Enum for handling in tangent and out tangent. - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -enum _TangentType { - /** - * Specifies that input tangents are used. - */ - INTANGENT, - /** - * Specifies that output tangents are used. - */ - OUTTANGENT, -} +// /** +// * @internal +// * Enum for handling in tangent and out tangent. +// */ +// // eslint-disable-next-line @typescript-eslint/naming-convention +// enum _TangentType { +// /** +// * Specifies that input tangents are used. +// */ +// INTANGENT, +// /** +// * Specifies that output tangents are used. +// */ +// OUTTANGENT, +// } -/** - * @internal - * Utility class for generating glTF animation data from BabylonJS. - */ -export class _GLTFAnimation { - /** - * Determine if a node is transformable - ie has properties it should be part of animation of transformation. - * @param babylonNode the node to test - * @returns true if can be animated, false otherwise. False if the parameter is null or undefined. - */ - private static _IsTransformable(babylonNode: Node): boolean { - return babylonNode && (babylonNode instanceof TransformNode || babylonNode instanceof Camera || babylonNode instanceof Light); - } +// /** +// * @internal +// * Utility class for generating glTF animation data from BabylonJS. +// */ +// export class _GLTFAnimation { +// /** +// * Determine if a node is transformable - ie has properties it should be part of animation of transformation. +// * @param babylonNode the node to test +// * @returns true if can be animated, false otherwise. False if the parameter is null or undefined. +// */ +// private static _IsTransformable(babylonNode: Node): boolean { +// return babylonNode && (babylonNode instanceof TransformNode || babylonNode instanceof Camera || babylonNode instanceof Light); +// } - /** - * @ignore - * - * Creates glTF channel animation from BabylonJS animation. - * @param babylonTransformNode - BabylonJS mesh. - * @param animation - animation. - * @param animationChannelTargetPath - The target animation channel. - * @param useQuaternion - Specifies if quaternions are used. - * @returns nullable IAnimationData - */ - public static _CreateNodeAnimation( - babylonTransformNode: Node, - animation: Animation, - animationChannelTargetPath: AnimationChannelTargetPath, - useQuaternion: boolean, - animationSampleRate: number - ): Nullable<_IAnimationData> { - if (this._IsTransformable(babylonTransformNode)) { - const inputs: number[] = []; - const outputs: number[][] = []; - const keyFrames = animation.getKeys(); - const minMaxKeyFrames = _GLTFAnimation._CalculateMinMaxKeyFrames(keyFrames); - const interpolationOrBake = _GLTFAnimation._DeduceInterpolation(keyFrames, animationChannelTargetPath, useQuaternion); +// /** +// * @ignore +// * +// * Creates glTF channel animation from BabylonJS animation. +// * @param babylonTransformNode - BabylonJS mesh. +// * @param animation - animation. +// * @param animationChannelTargetPath - The target animation channel. +// * @param useQuaternion - Specifies if quaternions are used. +// * @returns nullable IAnimationData +// */ +// public static _CreateNodeAnimation( +// babylonTransformNode: Node, +// animation: Animation, +// animationChannelTargetPath: AnimationChannelTargetPath, +// useQuaternion: boolean, +// animationSampleRate: number +// ): Nullable<_IAnimationData> { +// if (this._IsTransformable(babylonTransformNode)) { +// const inputs: number[] = []; +// const outputs: number[][] = []; +// const keyFrames = animation.getKeys(); +// const minMaxKeyFrames = _GLTFAnimation._CalculateMinMaxKeyFrames(keyFrames); +// const interpolationOrBake = _GLTFAnimation._DeduceInterpolation(keyFrames, animationChannelTargetPath, useQuaternion); - const interpolation = interpolationOrBake.interpolationType; - const shouldBakeAnimation = interpolationOrBake.shouldBakeAnimation; +// const interpolation = interpolationOrBake.interpolationType; +// const shouldBakeAnimation = interpolationOrBake.shouldBakeAnimation; - if (shouldBakeAnimation) { - _GLTFAnimation._CreateBakedAnimation( - babylonTransformNode, - animation, - animationChannelTargetPath, - minMaxKeyFrames.min, - minMaxKeyFrames.max, - animation.framePerSecond, - animationSampleRate, - inputs, - outputs, - minMaxKeyFrames, - useQuaternion - ); - } else { - if (interpolation === AnimationSamplerInterpolation.LINEAR || interpolation === AnimationSamplerInterpolation.STEP) { - _GLTFAnimation._CreateLinearOrStepAnimation(babylonTransformNode, animation, animationChannelTargetPath, inputs, outputs, useQuaternion); - } else if (interpolation === AnimationSamplerInterpolation.CUBICSPLINE) { - _GLTFAnimation._CreateCubicSplineAnimation(babylonTransformNode, animation, animationChannelTargetPath, inputs, outputs, useQuaternion); - } else { - _GLTFAnimation._CreateBakedAnimation( - babylonTransformNode, - animation, - animationChannelTargetPath, - minMaxKeyFrames.min, - minMaxKeyFrames.max, - animation.framePerSecond, - animationSampleRate, - inputs, - outputs, - minMaxKeyFrames, - useQuaternion - ); - } - } +// if (shouldBakeAnimation) { +// _GLTFAnimation._CreateBakedAnimation( +// babylonTransformNode, +// animation, +// animationChannelTargetPath, +// minMaxKeyFrames.min, +// minMaxKeyFrames.max, +// animation.framePerSecond, +// animationSampleRate, +// inputs, +// outputs, +// minMaxKeyFrames, +// useQuaternion +// ); +// } else { +// if (interpolation === AnimationSamplerInterpolation.LINEAR || interpolation === AnimationSamplerInterpolation.STEP) { +// _GLTFAnimation._CreateLinearOrStepAnimation(babylonTransformNode, animation, animationChannelTargetPath, inputs, outputs, useQuaternion); +// } else if (interpolation === AnimationSamplerInterpolation.CUBICSPLINE) { +// _GLTFAnimation._CreateCubicSplineAnimation(babylonTransformNode, animation, animationChannelTargetPath, inputs, outputs, useQuaternion); +// } else { +// _GLTFAnimation._CreateBakedAnimation( +// babylonTransformNode, +// animation, +// animationChannelTargetPath, +// minMaxKeyFrames.min, +// minMaxKeyFrames.max, +// animation.framePerSecond, +// animationSampleRate, +// inputs, +// outputs, +// minMaxKeyFrames, +// useQuaternion +// ); +// } +// } - if (inputs.length && outputs.length) { - const result: _IAnimationData = { - inputs: inputs, - outputs: outputs, - samplerInterpolation: interpolation, - inputsMin: shouldBakeAnimation ? minMaxKeyFrames.min : Tools.FloatRound(minMaxKeyFrames.min / animation.framePerSecond), - inputsMax: shouldBakeAnimation ? minMaxKeyFrames.max : Tools.FloatRound(minMaxKeyFrames.max / animation.framePerSecond), - }; +// if (inputs.length && outputs.length) { +// const result: _IAnimationData = { +// inputs: inputs, +// outputs: outputs, +// samplerInterpolation: interpolation, +// inputsMin: shouldBakeAnimation ? minMaxKeyFrames.min : Tools.FloatRound(minMaxKeyFrames.min / animation.framePerSecond), +// inputsMax: shouldBakeAnimation ? minMaxKeyFrames.max : Tools.FloatRound(minMaxKeyFrames.max / animation.framePerSecond), +// }; - return result; - } - } +// return result; +// } +// } - return null; - } +// return null; +// } - private static _DeduceAnimationInfo(animation: Animation): Nullable<_IAnimationInfo> { - let animationChannelTargetPath: Nullable = null; - let dataAccessorType = AccessorType.VEC3; - let useQuaternion: boolean = false; - const property = animation.targetProperty.split("."); - switch (property[0]) { - case "scaling": { - animationChannelTargetPath = AnimationChannelTargetPath.SCALE; - break; - } - case "position": { - animationChannelTargetPath = AnimationChannelTargetPath.TRANSLATION; - break; - } - case "rotation": { - dataAccessorType = AccessorType.VEC4; - animationChannelTargetPath = AnimationChannelTargetPath.ROTATION; - break; - } - case "rotationQuaternion": { - dataAccessorType = AccessorType.VEC4; - useQuaternion = true; - animationChannelTargetPath = AnimationChannelTargetPath.ROTATION; - break; - } - case "influence": { - dataAccessorType = AccessorType.SCALAR; - animationChannelTargetPath = AnimationChannelTargetPath.WEIGHTS; - break; - } - default: { - Tools.Error(`Unsupported animatable property ${property[0]}`); - } - } - if (animationChannelTargetPath) { - return { animationChannelTargetPath: animationChannelTargetPath, dataAccessorType: dataAccessorType, useQuaternion: useQuaternion }; - } else { - Tools.Error("animation channel target path and data accessor type could be deduced"); - } - return null; - } +// private static _DeduceAnimationInfo(animation: Animation): Nullable<_IAnimationInfo> { +// let animationChannelTargetPath: Nullable = null; +// let dataAccessorType = AccessorType.VEC3; +// let useQuaternion: boolean = false; +// const property = animation.targetProperty.split("."); +// switch (property[0]) { +// case "scaling": { +// animationChannelTargetPath = AnimationChannelTargetPath.SCALE; +// break; +// } +// case "position": { +// animationChannelTargetPath = AnimationChannelTargetPath.TRANSLATION; +// break; +// } +// case "rotation": { +// dataAccessorType = AccessorType.VEC4; +// animationChannelTargetPath = AnimationChannelTargetPath.ROTATION; +// break; +// } +// case "rotationQuaternion": { +// dataAccessorType = AccessorType.VEC4; +// useQuaternion = true; +// animationChannelTargetPath = AnimationChannelTargetPath.ROTATION; +// break; +// } +// case "influence": { +// dataAccessorType = AccessorType.SCALAR; +// animationChannelTargetPath = AnimationChannelTargetPath.WEIGHTS; +// break; +// } +// default: { +// Tools.Error(`Unsupported animatable property ${property[0]}`); +// } +// } +// if (animationChannelTargetPath) { +// return { animationChannelTargetPath: animationChannelTargetPath, dataAccessorType: dataAccessorType, useQuaternion: useQuaternion }; +// } else { +// Tools.Error("animation channel target path and data accessor type could be deduced"); +// } +// return null; +// } - /** - * @ignore - * Create node animations from the transform node animations - * @param babylonNode - * @param runtimeGLTFAnimation - * @param idleGLTFAnimations - * @param nodeMap - * @param nodes - * @param binaryWriter - * @param bufferViews - * @param accessors - * @param animationSampleRate - */ - public static _CreateNodeAnimationFromNodeAnimations( - babylonNode: Node, - runtimeGLTFAnimation: IAnimation, - idleGLTFAnimations: IAnimation[], - nodeMap: { [key: number]: number }, - nodes: INode[], - binaryWriter: _BinaryWriter, - bufferViews: IBufferView[], - accessors: IAccessor[], - animationSampleRate: number, - shouldExportAnimation?: (animation: Animation) => boolean - ) { - let glTFAnimation: IAnimation; - if (_GLTFAnimation._IsTransformable(babylonNode)) { - if (babylonNode.animations) { - for (const animation of babylonNode.animations) { - if (shouldExportAnimation && !shouldExportAnimation(animation)) { - continue; - } - const animationInfo = _GLTFAnimation._DeduceAnimationInfo(animation); - if (animationInfo) { - glTFAnimation = { - name: animation.name, - samplers: [], - channels: [], - }; - _GLTFAnimation._AddAnimation( - `${animation.name}`, - animation.hasRunningRuntimeAnimations ? runtimeGLTFAnimation : glTFAnimation, - babylonNode, - animation, - animationInfo.dataAccessorType, - animationInfo.animationChannelTargetPath, - nodeMap, - binaryWriter, - bufferViews, - accessors, - animationInfo.useQuaternion, - animationSampleRate - ); - if (glTFAnimation.samplers.length && glTFAnimation.channels.length) { - idleGLTFAnimations.push(glTFAnimation); - } - } - } - } - } - } +// /** +// * @ignore +// * Create node animations from the transform node animations +// * @param babylonNode +// * @param runtimeGLTFAnimation +// * @param idleGLTFAnimations +// * @param nodeMap +// * @param nodes +// * @param binaryWriter +// * @param bufferViews +// * @param accessors +// * @param animationSampleRate +// */ +// public static _CreateNodeAnimationFromNodeAnimations( +// babylonNode: Node, +// runtimeGLTFAnimation: IAnimation, +// idleGLTFAnimations: IAnimation[], +// nodeMap: { [key: number]: number }, +// nodes: INode[], +// binaryWriter: _BinaryWriter, +// bufferViews: IBufferView[], +// accessors: IAccessor[], +// animationSampleRate: number, +// shouldExportAnimation?: (animation: Animation) => boolean +// ) { +// let glTFAnimation: IAnimation; +// if (_GLTFAnimation._IsTransformable(babylonNode)) { +// if (babylonNode.animations) { +// for (const animation of babylonNode.animations) { +// if (shouldExportAnimation && !shouldExportAnimation(animation)) { +// continue; +// } +// const animationInfo = _GLTFAnimation._DeduceAnimationInfo(animation); +// if (animationInfo) { +// glTFAnimation = { +// name: animation.name, +// samplers: [], +// channels: [], +// }; +// _GLTFAnimation._AddAnimation( +// `${animation.name}`, +// animation.hasRunningRuntimeAnimations ? runtimeGLTFAnimation : glTFAnimation, +// babylonNode, +// animation, +// animationInfo.dataAccessorType, +// animationInfo.animationChannelTargetPath, +// nodeMap, +// binaryWriter, +// bufferViews, +// accessors, +// animationInfo.useQuaternion, +// animationSampleRate +// ); +// if (glTFAnimation.samplers.length && glTFAnimation.channels.length) { +// idleGLTFAnimations.push(glTFAnimation); +// } +// } +// } +// } +// } +// } - /** - * @ignore - * Create individual morph animations from the mesh's morph target animation tracks - * @param babylonNode - * @param runtimeGLTFAnimation - * @param idleGLTFAnimations - * @param nodeMap - * @param nodes - * @param binaryWriter - * @param bufferViews - * @param accessors - * @param animationSampleRate - */ - public static _CreateMorphTargetAnimationFromMorphTargetAnimations( - babylonNode: Node, - runtimeGLTFAnimation: IAnimation, - idleGLTFAnimations: IAnimation[], - nodeMap: { [key: number]: number }, - nodes: INode[], - binaryWriter: _BinaryWriter, - bufferViews: IBufferView[], - accessors: IAccessor[], - animationSampleRate: number, - shouldExportAnimation?: (animation: Animation) => boolean - ) { - let glTFAnimation: IAnimation; - if (babylonNode instanceof Mesh) { - const morphTargetManager = babylonNode.morphTargetManager; - if (morphTargetManager) { - for (let i = 0; i < morphTargetManager.numTargets; ++i) { - const morphTarget = morphTargetManager.getTarget(i); - for (const animation of morphTarget.animations) { - if (shouldExportAnimation && !shouldExportAnimation(animation)) { - continue; - } - const combinedAnimation = new Animation( - `${animation.name}`, - "influence", - animation.framePerSecond, - animation.dataType, - animation.loopMode, - animation.enableBlending - ); - const combinedAnimationKeys: IAnimationKey[] = []; - const animationKeys = animation.getKeys(); +// /** +// * @ignore +// * Create individual morph animations from the mesh's morph target animation tracks +// * @param babylonNode +// * @param runtimeGLTFAnimation +// * @param idleGLTFAnimations +// * @param nodeMap +// * @param nodes +// * @param binaryWriter +// * @param bufferViews +// * @param accessors +// * @param animationSampleRate +// */ +// public static _CreateMorphTargetAnimationFromMorphTargetAnimations( +// babylonNode: Node, +// runtimeGLTFAnimation: IAnimation, +// idleGLTFAnimations: IAnimation[], +// nodeMap: { [key: number]: number }, +// nodes: INode[], +// binaryWriter: _BinaryWriter, +// bufferViews: IBufferView[], +// accessors: IAccessor[], +// animationSampleRate: number, +// shouldExportAnimation?: (animation: Animation) => boolean +// ) { +// let glTFAnimation: IAnimation; +// if (babylonNode instanceof Mesh) { +// const morphTargetManager = babylonNode.morphTargetManager; +// if (morphTargetManager) { +// for (let i = 0; i < morphTargetManager.numTargets; ++i) { +// const morphTarget = morphTargetManager.getTarget(i); +// for (const animation of morphTarget.animations) { +// if (shouldExportAnimation && !shouldExportAnimation(animation)) { +// continue; +// } +// const combinedAnimation = new Animation( +// `${animation.name}`, +// "influence", +// animation.framePerSecond, +// animation.dataType, +// animation.loopMode, +// animation.enableBlending +// ); +// const combinedAnimationKeys: IAnimationKey[] = []; +// const animationKeys = animation.getKeys(); - for (let j = 0; j < animationKeys.length; ++j) { - const animationKey = animationKeys[j]; - for (let k = 0; k < morphTargetManager.numTargets; ++k) { - if (k == i) { - combinedAnimationKeys.push(animationKey); - } else { - combinedAnimationKeys.push({ frame: animationKey.frame, value: 0 }); - } - } - } - combinedAnimation.setKeys(combinedAnimationKeys); - const animationInfo = _GLTFAnimation._DeduceAnimationInfo(combinedAnimation); - if (animationInfo) { - glTFAnimation = { - name: combinedAnimation.name, - samplers: [], - channels: [], - }; - _GLTFAnimation._AddAnimation( - animation.name, - animation.hasRunningRuntimeAnimations ? runtimeGLTFAnimation : glTFAnimation, - babylonNode, - combinedAnimation, - animationInfo.dataAccessorType, - animationInfo.animationChannelTargetPath, - nodeMap, - binaryWriter, - bufferViews, - accessors, - animationInfo.useQuaternion, - animationSampleRate, - morphTargetManager.numTargets - ); - if (glTFAnimation.samplers.length && glTFAnimation.channels.length) { - idleGLTFAnimations.push(glTFAnimation); - } - } - } - } - } - } - } +// for (let j = 0; j < animationKeys.length; ++j) { +// const animationKey = animationKeys[j]; +// for (let k = 0; k < morphTargetManager.numTargets; ++k) { +// if (k == i) { +// combinedAnimationKeys.push(animationKey); +// } else { +// combinedAnimationKeys.push({ frame: animationKey.frame, value: 0 }); +// } +// } +// } +// combinedAnimation.setKeys(combinedAnimationKeys); +// const animationInfo = _GLTFAnimation._DeduceAnimationInfo(combinedAnimation); +// if (animationInfo) { +// glTFAnimation = { +// name: combinedAnimation.name, +// samplers: [], +// channels: [], +// }; +// _GLTFAnimation._AddAnimation( +// animation.name, +// animation.hasRunningRuntimeAnimations ? runtimeGLTFAnimation : glTFAnimation, +// babylonNode, +// combinedAnimation, +// animationInfo.dataAccessorType, +// animationInfo.animationChannelTargetPath, +// nodeMap, +// binaryWriter, +// bufferViews, +// accessors, +// animationInfo.useQuaternion, +// animationSampleRate, +// morphTargetManager.numTargets +// ); +// if (glTFAnimation.samplers.length && glTFAnimation.channels.length) { +// idleGLTFAnimations.push(glTFAnimation); +// } +// } +// } +// } +// } +// } +// } - /** - * @internal - * Create node and morph animations from the animation groups - * @param babylonScene - * @param glTFAnimations - * @param nodeMap - * @param nodes - * @param binaryWriter - * @param bufferViews - * @param accessors - * @param animationSampleRate - */ - public static _CreateNodeAndMorphAnimationFromAnimationGroups( - babylonScene: Scene, - glTFAnimations: IAnimation[], - nodeMap: { [key: number]: number }, - binaryWriter: _BinaryWriter, - bufferViews: IBufferView[], - accessors: IAccessor[], - animationSampleRate: number, - shouldExportAnimation?: (animation: Animation) => boolean - ) { - let glTFAnimation: IAnimation; - if (babylonScene.animationGroups) { - const animationGroups = babylonScene.animationGroups; - for (const animationGroup of animationGroups) { - const morphAnimations: Map> = new Map(); - const sampleAnimations: Map = new Map(); - const morphAnimationMeshes: Set = new Set(); - const animationGroupFrameDiff = animationGroup.to - animationGroup.from; - glTFAnimation = { - name: animationGroup.name, - channels: [], - samplers: [], - }; - for (let i = 0; i < animationGroup.targetedAnimations.length; ++i) { - const targetAnimation = animationGroup.targetedAnimations[i]; - const target = targetAnimation.target; - const animation = targetAnimation.animation; - if (shouldExportAnimation && !shouldExportAnimation(animation)) { - continue; - } - if (this._IsTransformable(target) || (target.length === 1 && this._IsTransformable(target[0]))) { - const animationInfo = _GLTFAnimation._DeduceAnimationInfo(targetAnimation.animation); - if (animationInfo) { - const babylonTransformNode = this._IsTransformable(target) ? target : this._IsTransformable(target[0]) ? target[0] : null; - if (babylonTransformNode) { - _GLTFAnimation._AddAnimation( - `${animation.name}`, - glTFAnimation, - babylonTransformNode, - animation, - animationInfo.dataAccessorType, - animationInfo.animationChannelTargetPath, - nodeMap, - binaryWriter, - bufferViews, - accessors, - animationInfo.useQuaternion, - animationSampleRate - ); - } - } - } else if (target instanceof MorphTarget || (target.length === 1 && target[0] instanceof MorphTarget)) { - const animationInfo = _GLTFAnimation._DeduceAnimationInfo(targetAnimation.animation); - if (animationInfo) { - const babylonMorphTarget = target instanceof MorphTarget ? (target as MorphTarget) : (target[0] as MorphTarget); - if (babylonMorphTarget) { - const babylonMorphTargetManager = babylonScene.morphTargetManagers.find((morphTargetManager) => { - for (let j = 0; j < morphTargetManager.numTargets; ++j) { - if (morphTargetManager.getTarget(j) === babylonMorphTarget) { - return true; - } - } - return false; - }); - if (babylonMorphTargetManager) { - const babylonMesh = babylonScene.meshes.find((mesh) => { - return (mesh as Mesh).morphTargetManager === babylonMorphTargetManager; - }) as Mesh; - if (babylonMesh) { - if (!morphAnimations.has(babylonMesh)) { - morphAnimations.set(babylonMesh, new Map()); - } - morphAnimations.get(babylonMesh)?.set(babylonMorphTarget, animation); - morphAnimationMeshes.add(babylonMesh); - sampleAnimations.set(babylonMesh, animation); - } - } - } - } - } else { - // this is the place for the KHR_animation_pointer. - } - } - morphAnimationMeshes.forEach((mesh) => { - const morphTargetManager = mesh.morphTargetManager!; - let combinedAnimationGroup: Nullable = null; - const animationKeys: IAnimationKey[] = []; - const sampleAnimation = sampleAnimations.get(mesh)!; - const sampleAnimationKeys = sampleAnimation.getKeys(); - const numAnimationKeys = sampleAnimationKeys.length; - /* - Due to how glTF expects morph target animation data to be formatted, we need to rearrange the individual morph target animation tracks, - such that we have a single animation, where a given keyframe input value has successive output values for each morph target belonging to the manager. - See: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations +// /** +// * @internal +// * Create node and morph animations from the animation groups +// * @param babylonScene +// * @param glTFAnimations +// * @param nodeMap +// * @param nodes +// * @param binaryWriter +// * @param bufferViews +// * @param accessors +// * @param animationSampleRate +// */ +// public static _CreateNodeAndMorphAnimationFromAnimationGroups( +// babylonScene: Scene, +// glTFAnimations: IAnimation[], +// nodeMap: { [key: number]: number }, +// binaryWriter: _BinaryWriter, +// bufferViews: IBufferView[], +// accessors: IAccessor[], +// animationSampleRate: number, +// shouldExportAnimation?: (animation: Animation) => boolean +// ) { +// let glTFAnimation: IAnimation; +// if (babylonScene.animationGroups) { +// const animationGroups = babylonScene.animationGroups; +// for (const animationGroup of animationGroups) { +// const morphAnimations: Map> = new Map(); +// const sampleAnimations: Map = new Map(); +// const morphAnimationMeshes: Set = new Set(); +// const animationGroupFrameDiff = animationGroup.to - animationGroup.from; +// glTFAnimation = { +// name: animationGroup.name, +// channels: [], +// samplers: [], +// }; +// for (let i = 0; i < animationGroup.targetedAnimations.length; ++i) { +// const targetAnimation = animationGroup.targetedAnimations[i]; +// const target = targetAnimation.target; +// const animation = targetAnimation.animation; +// if (shouldExportAnimation && !shouldExportAnimation(animation)) { +// continue; +// } +// if (this._IsTransformable(target) || (target.length === 1 && this._IsTransformable(target[0]))) { +// const animationInfo = _GLTFAnimation._DeduceAnimationInfo(targetAnimation.animation); +// if (animationInfo) { +// const babylonTransformNode = this._IsTransformable(target) ? target : this._IsTransformable(target[0]) ? target[0] : null; +// if (babylonTransformNode) { +// _GLTFAnimation._AddAnimation( +// `${animation.name}`, +// glTFAnimation, +// babylonTransformNode, +// animation, +// animationInfo.dataAccessorType, +// animationInfo.animationChannelTargetPath, +// nodeMap, +// binaryWriter, +// bufferViews, +// accessors, +// animationInfo.useQuaternion, +// animationSampleRate +// ); +// } +// } +// } else if (target instanceof MorphTarget || (target.length === 1 && target[0] instanceof MorphTarget)) { +// const animationInfo = _GLTFAnimation._DeduceAnimationInfo(targetAnimation.animation); +// if (animationInfo) { +// const babylonMorphTarget = target instanceof MorphTarget ? (target as MorphTarget) : (target[0] as MorphTarget); +// if (babylonMorphTarget) { +// const babylonMorphTargetManager = babylonScene.morphTargetManagers.find((morphTargetManager) => { +// for (let j = 0; j < morphTargetManager.numTargets; ++j) { +// if (morphTargetManager.getTarget(j) === babylonMorphTarget) { +// return true; +// } +// } +// return false; +// }); +// if (babylonMorphTargetManager) { +// const babylonMesh = babylonScene.meshes.find((mesh) => { +// return (mesh as Mesh).morphTargetManager === babylonMorphTargetManager; +// }) as Mesh; +// if (babylonMesh) { +// if (!morphAnimations.has(babylonMesh)) { +// morphAnimations.set(babylonMesh, new Map()); +// } +// morphAnimations.get(babylonMesh)?.set(babylonMorphTarget, animation); +// morphAnimationMeshes.add(babylonMesh); +// sampleAnimations.set(babylonMesh, animation); +// } +// } +// } +// } +// } else { +// // this is the place for the KHR_animation_pointer. +// } +// } +// morphAnimationMeshes.forEach((mesh) => { +// const morphTargetManager = mesh.morphTargetManager!; +// let combinedAnimationGroup: Nullable = null; +// const animationKeys: IAnimationKey[] = []; +// const sampleAnimation = sampleAnimations.get(mesh)!; +// const sampleAnimationKeys = sampleAnimation.getKeys(); +// const numAnimationKeys = sampleAnimationKeys.length; +// /* +// Due to how glTF expects morph target animation data to be formatted, we need to rearrange the individual morph target animation tracks, +// such that we have a single animation, where a given keyframe input value has successive output values for each morph target belonging to the manager. +// See: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations - We do this via constructing a new Animation track, and interleaving the frames of each morph target animation track in the current Animation Group - We reuse the Babylon Animation data structure for ease of handling export of cubic spline animation keys, and to reuse the - existing _GLTFAnimation.AddAnimation codepath with minimal modification, however the constructed Babylon Animation is NOT intended for use in-engine. - */ - for (let i = 0; i < numAnimationKeys; ++i) { - for (let j = 0; j < morphTargetManager.numTargets; ++j) { - const morphTarget = morphTargetManager.getTarget(j); - const animationsByMorphTarget = morphAnimations.get(mesh); - if (animationsByMorphTarget) { - const morphTargetAnimation = animationsByMorphTarget.get(morphTarget); - if (morphTargetAnimation) { - if (!combinedAnimationGroup) { - combinedAnimationGroup = new Animation( - `${animationGroup.name}_${mesh.name}_MorphWeightAnimation`, - "influence", - morphTargetAnimation.framePerSecond, - Animation.ANIMATIONTYPE_FLOAT, - morphTargetAnimation.loopMode, - morphTargetAnimation.enableBlending - ); - } - animationKeys.push(morphTargetAnimation.getKeys()[i]); - } else { - animationKeys.push({ - frame: animationGroup.from + (animationGroupFrameDiff / numAnimationKeys) * i, - value: morphTarget.influence, - inTangent: sampleAnimationKeys[0].inTangent ? 0 : undefined, - outTangent: sampleAnimationKeys[0].outTangent ? 0 : undefined, - }); - } - } - } - } - combinedAnimationGroup!.setKeys(animationKeys); - const animationInfo = _GLTFAnimation._DeduceAnimationInfo(combinedAnimationGroup!); - if (animationInfo) { - _GLTFAnimation._AddAnimation( - `${animationGroup.name}_${mesh.name}_MorphWeightAnimation`, - glTFAnimation, - mesh, - combinedAnimationGroup!, - animationInfo.dataAccessorType, - animationInfo.animationChannelTargetPath, - nodeMap, - binaryWriter, - bufferViews, - accessors, - animationInfo.useQuaternion, - animationSampleRate, - morphTargetManager?.numTargets - ); - } - }); - if (glTFAnimation.channels.length && glTFAnimation.samplers.length) { - glTFAnimations.push(glTFAnimation); - } - } - } - } +// We do this via constructing a new Animation track, and interleaving the frames of each morph target animation track in the current Animation Group +// We reuse the Babylon Animation data structure for ease of handling export of cubic spline animation keys, and to reuse the +// existing _GLTFAnimation.AddAnimation codepath with minimal modification, however the constructed Babylon Animation is NOT intended for use in-engine. +// */ +// for (let i = 0; i < numAnimationKeys; ++i) { +// for (let j = 0; j < morphTargetManager.numTargets; ++j) { +// const morphTarget = morphTargetManager.getTarget(j); +// const animationsByMorphTarget = morphAnimations.get(mesh); +// if (animationsByMorphTarget) { +// const morphTargetAnimation = animationsByMorphTarget.get(morphTarget); +// if (morphTargetAnimation) { +// if (!combinedAnimationGroup) { +// combinedAnimationGroup = new Animation( +// `${animationGroup.name}_${mesh.name}_MorphWeightAnimation`, +// "influence", +// morphTargetAnimation.framePerSecond, +// Animation.ANIMATIONTYPE_FLOAT, +// morphTargetAnimation.loopMode, +// morphTargetAnimation.enableBlending +// ); +// } +// animationKeys.push(morphTargetAnimation.getKeys()[i]); +// } else { +// animationKeys.push({ +// frame: animationGroup.from + (animationGroupFrameDiff / numAnimationKeys) * i, +// value: morphTarget.influence, +// inTangent: sampleAnimationKeys[0].inTangent ? 0 : undefined, +// outTangent: sampleAnimationKeys[0].outTangent ? 0 : undefined, +// }); +// } +// } +// } +// } +// combinedAnimationGroup!.setKeys(animationKeys); +// const animationInfo = _GLTFAnimation._DeduceAnimationInfo(combinedAnimationGroup!); +// if (animationInfo) { +// _GLTFAnimation._AddAnimation( +// `${animationGroup.name}_${mesh.name}_MorphWeightAnimation`, +// glTFAnimation, +// mesh, +// combinedAnimationGroup!, +// animationInfo.dataAccessorType, +// animationInfo.animationChannelTargetPath, +// nodeMap, +// binaryWriter, +// bufferViews, +// accessors, +// animationInfo.useQuaternion, +// animationSampleRate, +// morphTargetManager?.numTargets +// ); +// } +// }); +// if (glTFAnimation.channels.length && glTFAnimation.samplers.length) { +// glTFAnimations.push(glTFAnimation); +// } +// } +// } +// } - private static _AddAnimation( - name: string, - glTFAnimation: IAnimation, - babylonTransformNode: Node, - animation: Animation, - dataAccessorType: AccessorType, - animationChannelTargetPath: AnimationChannelTargetPath, - nodeMap: { [key: number]: number }, - binaryWriter: _BinaryWriter, - bufferViews: IBufferView[], - accessors: IAccessor[], - useQuaternion: boolean, - animationSampleRate: number, - morphAnimationChannels?: number - ) { - const animationData = _GLTFAnimation._CreateNodeAnimation(babylonTransformNode, animation, animationChannelTargetPath, useQuaternion, animationSampleRate); - let bufferView: IBufferView; - let accessor: IAccessor; - let keyframeAccessorIndex: number; - let dataAccessorIndex: number; - let outputLength: number; - let animationSampler: IAnimationSampler; - let animationChannel: IAnimationChannel; +// private static _AddAnimation( +// name: string, +// glTFAnimation: IAnimation, +// babylonTransformNode: Node, +// animation: Animation, +// dataAccessorType: AccessorType, +// animationChannelTargetPath: AnimationChannelTargetPath, +// nodeMap: { [key: number]: number }, +// binaryWriter: _BinaryWriter, +// bufferViews: IBufferView[], +// accessors: IAccessor[], +// useQuaternion: boolean, +// animationSampleRate: number, +// morphAnimationChannels?: number +// ) { +// const animationData = _GLTFAnimation._CreateNodeAnimation(babylonTransformNode, animation, animationChannelTargetPath, useQuaternion, animationSampleRate); +// let bufferView: IBufferView; +// let accessor: IAccessor; +// let keyframeAccessorIndex: number; +// let dataAccessorIndex: number; +// let outputLength: number; +// let animationSampler: IAnimationSampler; +// let animationChannel: IAnimationChannel; - if (animationData) { - /* - * Now that we have the glTF converted morph target animation data, - * we can remove redundant input data so that we have n input frames, - * and morphAnimationChannels * n output frames - */ - if (morphAnimationChannels) { - let index = 0; - let currentInput: number = 0; - const newInputs: number[] = []; - while (animationData.inputs.length > 0) { - currentInput = animationData.inputs.shift()!; - if (index % morphAnimationChannels == 0) { - newInputs.push(currentInput); - } - index++; - } - animationData.inputs = newInputs; - } +// if (animationData) { +// /* +// * Now that we have the glTF converted morph target animation data, +// * we can remove redundant input data so that we have n input frames, +// * and morphAnimationChannels * n output frames +// */ +// if (morphAnimationChannels) { +// let index = 0; +// let currentInput: number = 0; +// const newInputs: number[] = []; +// while (animationData.inputs.length > 0) { +// currentInput = animationData.inputs.shift()!; +// if (index % morphAnimationChannels == 0) { +// newInputs.push(currentInput); +// } +// index++; +// } +// animationData.inputs = newInputs; +// } - const nodeIndex = nodeMap[babylonTransformNode.uniqueId]; +// const nodeIndex = nodeMap[babylonTransformNode.uniqueId]; - // Creates buffer view and accessor for key frames. - let byteLength = animationData.inputs.length * 4; - bufferView = _GLTFUtilities._CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, `${name} keyframe data view`); - bufferViews.push(bufferView); - animationData.inputs.forEach(function (input) { - binaryWriter.setFloat32(input); - }); +// // Creates buffer view and accessor for key frames. +// let byteLength = animationData.inputs.length * 4; +// bufferView = _GLTFUtilities._CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, `${name} keyframe data view`); +// bufferViews.push(bufferView); +// animationData.inputs.forEach(function (input) { +// binaryWriter.setFloat32(input); +// }); - accessor = _GLTFUtilities._CreateAccessor( - bufferViews.length - 1, - `${name} keyframes`, - AccessorType.SCALAR, - AccessorComponentType.FLOAT, - animationData.inputs.length, - null, - [animationData.inputsMin], - [animationData.inputsMax] - ); - accessors.push(accessor); - keyframeAccessorIndex = accessors.length - 1; +// accessor = _GLTFUtilities._CreateAccessor( +// bufferViews.length - 1, +// `${name} keyframes`, +// AccessorType.SCALAR, +// AccessorComponentType.FLOAT, +// animationData.inputs.length, +// null, +// [animationData.inputsMin], +// [animationData.inputsMax] +// ); +// accessors.push(accessor); +// keyframeAccessorIndex = accessors.length - 1; - // create bufferview and accessor for keyed values. - outputLength = animationData.outputs.length; - byteLength = _GLTFUtilities._GetDataAccessorElementCount(dataAccessorType) * 4 * animationData.outputs.length; +// // create bufferview and accessor for keyed values. +// outputLength = animationData.outputs.length; +// byteLength = _GLTFUtilities._GetDataAccessorElementCount(dataAccessorType) * 4 * animationData.outputs.length; - // check for in and out tangents - bufferView = _GLTFUtilities._CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, `${name} data view`); - bufferViews.push(bufferView); +// // check for in and out tangents +// bufferView = _GLTFUtilities._CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, `${name} data view`); +// bufferViews.push(bufferView); - animationData.outputs.forEach(function (output) { - output.forEach(function (entry) { - binaryWriter.setFloat32(entry); - }); - }); +// animationData.outputs.forEach(function (output) { +// output.forEach(function (entry) { +// binaryWriter.setFloat32(entry); +// }); +// }); - accessor = _GLTFUtilities._CreateAccessor(bufferViews.length - 1, `${name} data`, dataAccessorType, AccessorComponentType.FLOAT, outputLength, null, null, null); - accessors.push(accessor); - dataAccessorIndex = accessors.length - 1; +// accessor = _GLTFUtilities._CreateAccessor(bufferViews.length - 1, `${name} data`, dataAccessorType, AccessorComponentType.FLOAT, outputLength, null, null, null); +// accessors.push(accessor); +// dataAccessorIndex = accessors.length - 1; - // create sampler - animationSampler = { - interpolation: animationData.samplerInterpolation, - input: keyframeAccessorIndex, - output: dataAccessorIndex, - }; - glTFAnimation.samplers.push(animationSampler); +// // create sampler +// animationSampler = { +// interpolation: animationData.samplerInterpolation, +// input: keyframeAccessorIndex, +// output: dataAccessorIndex, +// }; +// glTFAnimation.samplers.push(animationSampler); - // create channel - animationChannel = { - sampler: glTFAnimation.samplers.length - 1, - target: { - node: nodeIndex, - path: animationChannelTargetPath, - }, - }; - glTFAnimation.channels.push(animationChannel); - } - } +// // create channel +// animationChannel = { +// sampler: glTFAnimation.samplers.length - 1, +// target: { +// node: nodeIndex, +// path: animationChannelTargetPath, +// }, +// }; +// glTFAnimation.channels.push(animationChannel); +// } +// } - /** - * Create a baked animation - * @param babylonTransformNode BabylonJS mesh - * @param animation BabylonJS animation corresponding to the BabylonJS mesh - * @param animationChannelTargetPath animation target channel - * @param minFrame minimum animation frame - * @param maxFrame maximum animation frame - * @param fps frames per second of the animation - * @param sampleRate - * @param inputs input key frames of the animation - * @param outputs output key frame data of the animation - * @param minMaxFrames - * @param minMaxFrames.min - * @param minMaxFrames.max - * @param useQuaternion specifies if quaternions should be used - */ - private static _CreateBakedAnimation( - babylonTransformNode: Node, - animation: Animation, - animationChannelTargetPath: AnimationChannelTargetPath, - minFrame: number, - maxFrame: number, - fps: number, - sampleRate: number, - inputs: number[], - outputs: number[][], - minMaxFrames: { min: number; max: number }, - useQuaternion: boolean - ) { - let value: number | Vector3 | Quaternion; - const quaternionCache: Quaternion = Quaternion.Identity(); - let previousTime: Nullable = null; - let time: number; - let maxUsedFrame: Nullable = null; - let currKeyFrame: Nullable = null; - let nextKeyFrame: Nullable = null; - let prevKeyFrame: Nullable = null; - let endFrame: Nullable = null; - minMaxFrames.min = Tools.FloatRound(minFrame / fps); +// /** +// * Create a baked animation +// * @param babylonTransformNode BabylonJS mesh +// * @param animation BabylonJS animation corresponding to the BabylonJS mesh +// * @param animationChannelTargetPath animation target channel +// * @param minFrame minimum animation frame +// * @param maxFrame maximum animation frame +// * @param fps frames per second of the animation +// * @param sampleRate +// * @param inputs input key frames of the animation +// * @param outputs output key frame data of the animation +// * @param minMaxFrames +// * @param minMaxFrames.min +// * @param minMaxFrames.max +// * @param useQuaternion specifies if quaternions should be used +// */ +// private static _CreateBakedAnimation( +// babylonTransformNode: Node, +// animation: Animation, +// animationChannelTargetPath: AnimationChannelTargetPath, +// minFrame: number, +// maxFrame: number, +// fps: number, +// sampleRate: number, +// inputs: number[], +// outputs: number[][], +// minMaxFrames: { min: number; max: number }, +// useQuaternion: boolean +// ) { +// let value: number | Vector3 | Quaternion; +// const quaternionCache: Quaternion = Quaternion.Identity(); +// let previousTime: Nullable = null; +// let time: number; +// let maxUsedFrame: Nullable = null; +// let currKeyFrame: Nullable = null; +// let nextKeyFrame: Nullable = null; +// let prevKeyFrame: Nullable = null; +// let endFrame: Nullable = null; +// minMaxFrames.min = Tools.FloatRound(minFrame / fps); - const keyFrames = animation.getKeys(); +// const keyFrames = animation.getKeys(); - for (let i = 0, length = keyFrames.length; i < length; ++i) { - endFrame = null; - currKeyFrame = keyFrames[i]; +// for (let i = 0, length = keyFrames.length; i < length; ++i) { +// endFrame = null; +// currKeyFrame = keyFrames[i]; - if (i + 1 < length) { - nextKeyFrame = keyFrames[i + 1]; - if ((currKeyFrame.value.equals && currKeyFrame.value.equals(nextKeyFrame.value)) || currKeyFrame.value === nextKeyFrame.value) { - if (i === 0) { - // set the first frame to itself - endFrame = currKeyFrame.frame; - } else { - continue; - } - } else { - endFrame = nextKeyFrame.frame; - } - } else { - // at the last key frame - prevKeyFrame = keyFrames[i - 1]; - if ((currKeyFrame.value.equals && currKeyFrame.value.equals(prevKeyFrame.value)) || currKeyFrame.value === prevKeyFrame.value) { - continue; - } else { - endFrame = maxFrame; - } - } - if (endFrame) { - for (let f = currKeyFrame.frame; f <= endFrame; f += sampleRate) { - time = Tools.FloatRound(f / fps); - if (time === previousTime) { - continue; - } - previousTime = time; - maxUsedFrame = time; - const state = { - key: 0, - repeatCount: 0, - loopMode: animation.loopMode, - }; - value = animation._interpolate(f, state); +// if (i + 1 < length) { +// nextKeyFrame = keyFrames[i + 1]; +// if ((currKeyFrame.value.equals && currKeyFrame.value.equals(nextKeyFrame.value)) || currKeyFrame.value === nextKeyFrame.value) { +// if (i === 0) { +// // set the first frame to itself +// endFrame = currKeyFrame.frame; +// } else { +// continue; +// } +// } else { +// endFrame = nextKeyFrame.frame; +// } +// } else { +// // at the last key frame +// prevKeyFrame = keyFrames[i - 1]; +// if ((currKeyFrame.value.equals && currKeyFrame.value.equals(prevKeyFrame.value)) || currKeyFrame.value === prevKeyFrame.value) { +// continue; +// } else { +// endFrame = maxFrame; +// } +// } +// if (endFrame) { +// for (let f = currKeyFrame.frame; f <= endFrame; f += sampleRate) { +// time = Tools.FloatRound(f / fps); +// if (time === previousTime) { +// continue; +// } +// previousTime = time; +// maxUsedFrame = time; +// const state = { +// key: 0, +// repeatCount: 0, +// loopMode: animation.loopMode, +// }; +// value = animation._interpolate(f, state); - _GLTFAnimation._SetInterpolatedValue(babylonTransformNode, value, time, animation, animationChannelTargetPath, quaternionCache, inputs, outputs, useQuaternion); - } - } - } - if (maxUsedFrame) { - minMaxFrames.max = maxUsedFrame; - } - } +// _GLTFAnimation._SetInterpolatedValue(babylonTransformNode, value, time, animation, animationChannelTargetPath, quaternionCache, inputs, outputs, useQuaternion); +// } +// } +// } +// if (maxUsedFrame) { +// minMaxFrames.max = maxUsedFrame; +// } +// } - private static _ConvertFactorToVector3OrQuaternion( - factor: number, - babylonTransformNode: Node, - animation: Animation, - animationChannelTargetPath: AnimationChannelTargetPath, - useQuaternion: boolean - ): Vector3 | Quaternion { - const basePositionRotationOrScale = _GLTFAnimation._GetBasePositionRotationOrScale(babylonTransformNode, animationChannelTargetPath, useQuaternion); - // handles single component x, y, z or w component animation by using a base property and animating over a component. - const property = animation.targetProperty.split("."); - const componentName = property ? property[1] : ""; // x, y, z, or w component - const value = useQuaternion ? Quaternion.FromArray(basePositionRotationOrScale).normalize() : Vector3.FromArray(basePositionRotationOrScale); +// private static _ConvertFactorToVector3OrQuaternion( +// factor: number, +// babylonTransformNode: Node, +// animation: Animation, +// animationChannelTargetPath: AnimationChannelTargetPath, +// useQuaternion: boolean +// ): Vector3 | Quaternion { +// const basePositionRotationOrScale = _GLTFAnimation._GetBasePositionRotationOrScale(babylonTransformNode, animationChannelTargetPath, useQuaternion); +// // handles single component x, y, z or w component animation by using a base property and animating over a component. +// const property = animation.targetProperty.split("."); +// const componentName = property ? property[1] : ""; // x, y, z, or w component +// const value = useQuaternion ? Quaternion.FromArray(basePositionRotationOrScale).normalize() : Vector3.FromArray(basePositionRotationOrScale); - switch (componentName) { - case "x": - case "y": - case "z": { - value[componentName] = factor; - break; - } - case "w": { - (value as Quaternion).w = factor; - break; - } - default: { - Tools.Error(`glTFAnimation: Unsupported component name "${componentName}"!`); - } - } +// switch (componentName) { +// case "x": +// case "y": +// case "z": { +// value[componentName] = factor; +// break; +// } +// case "w": { +// (value as Quaternion).w = factor; +// break; +// } +// default: { +// Tools.Error(`glTFAnimation: Unsupported component name "${componentName}"!`); +// } +// } - return value; - } +// return value; +// } - private static _SetInterpolatedValue( - babylonTransformNode: Node, - value: number | Vector3 | Quaternion, - time: number, - animation: Animation, - animationChannelTargetPath: AnimationChannelTargetPath, - quaternionCache: Quaternion, - inputs: number[], - outputs: number[][], - useQuaternion: boolean - ) { - let cacheValue: Vector3 | Quaternion | number; - inputs.push(time); +// private static _SetInterpolatedValue( +// babylonTransformNode: Node, +// value: number | Vector3 | Quaternion, +// time: number, +// animation: Animation, +// animationChannelTargetPath: AnimationChannelTargetPath, +// quaternionCache: Quaternion, +// inputs: number[], +// outputs: number[][], +// useQuaternion: boolean +// ) { +// let cacheValue: Vector3 | Quaternion | number; +// inputs.push(time); - if (animationChannelTargetPath === AnimationChannelTargetPath.WEIGHTS) { - outputs.push([value as number]); - return; - } +// if (animationChannelTargetPath === AnimationChannelTargetPath.WEIGHTS) { +// outputs.push([value as number]); +// return; +// } - if (animation.dataType === Animation.ANIMATIONTYPE_FLOAT) { - value = this._ConvertFactorToVector3OrQuaternion(value as number, babylonTransformNode, animation, animationChannelTargetPath, useQuaternion); - } +// if (animation.dataType === Animation.ANIMATIONTYPE_FLOAT) { +// value = this._ConvertFactorToVector3OrQuaternion(value as number, babylonTransformNode, animation, animationChannelTargetPath, useQuaternion); +// } - if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { - if (useQuaternion) { - quaternionCache = value as Quaternion; - } else { - cacheValue = value as Vector3; - Quaternion.RotationYawPitchRollToRef(cacheValue.y, cacheValue.x, cacheValue.z, quaternionCache); - } - outputs.push(quaternionCache.asArray()); - } else { - // scaling and position animation - cacheValue = value as Vector3; - outputs.push(cacheValue.asArray()); - } - } +// if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { +// if (useQuaternion) { +// quaternionCache = value as Quaternion; +// } else { +// cacheValue = value as Vector3; +// Quaternion.RotationYawPitchRollToRef(cacheValue.y, cacheValue.x, cacheValue.z, quaternionCache); +// } +// outputs.push(quaternionCache.asArray()); +// } else { +// // scaling and position animation +// cacheValue = value as Vector3; +// outputs.push(cacheValue.asArray()); +// } +// } - /** - * Creates linear animation from the animation key frames - * @param babylonTransformNode BabylonJS mesh - * @param animation BabylonJS animation - * @param animationChannelTargetPath The target animation channel - * @param inputs Array to store the key frame times - * @param outputs Array to store the key frame data - * @param useQuaternion Specifies if quaternions are used in the animation - */ - private static _CreateLinearOrStepAnimation( - babylonTransformNode: Node, - animation: Animation, - animationChannelTargetPath: AnimationChannelTargetPath, - inputs: number[], - outputs: number[][], - useQuaternion: boolean - ) { - for (const keyFrame of animation.getKeys()) { - inputs.push(keyFrame.frame / animation.framePerSecond); // keyframes in seconds. - _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonTransformNode, useQuaternion); - } - } +// /** +// * Creates linear animation from the animation key frames +// * @param babylonTransformNode BabylonJS mesh +// * @param animation BabylonJS animation +// * @param animationChannelTargetPath The target animation channel +// * @param inputs Array to store the key frame times +// * @param outputs Array to store the key frame data +// * @param useQuaternion Specifies if quaternions are used in the animation +// */ +// private static _CreateLinearOrStepAnimation( +// babylonTransformNode: Node, +// animation: Animation, +// animationChannelTargetPath: AnimationChannelTargetPath, +// inputs: number[], +// outputs: number[][], +// useQuaternion: boolean +// ) { +// for (const keyFrame of animation.getKeys()) { +// inputs.push(keyFrame.frame / animation.framePerSecond); // keyframes in seconds. +// _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonTransformNode, useQuaternion); +// } +// } - /** - * Creates cubic spline animation from the animation key frames - * @param babylonTransformNode BabylonJS mesh - * @param animation BabylonJS animation - * @param animationChannelTargetPath The target animation channel - * @param inputs Array to store the key frame times - * @param outputs Array to store the key frame data - * @param useQuaternion Specifies if quaternions are used in the animation - */ - private static _CreateCubicSplineAnimation( - babylonTransformNode: Node, - animation: Animation, - animationChannelTargetPath: AnimationChannelTargetPath, - inputs: number[], - outputs: number[][], - useQuaternion: boolean - ) { - animation.getKeys().forEach(function (keyFrame) { - inputs.push(keyFrame.frame / animation.framePerSecond); // keyframes in seconds. - _GLTFAnimation._AddSplineTangent(_TangentType.INTANGENT, outputs, animationChannelTargetPath, AnimationSamplerInterpolation.CUBICSPLINE, keyFrame, useQuaternion); - _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonTransformNode, useQuaternion); +// /** +// * Creates cubic spline animation from the animation key frames +// * @param babylonTransformNode BabylonJS mesh +// * @param animation BabylonJS animation +// * @param animationChannelTargetPath The target animation channel +// * @param inputs Array to store the key frame times +// * @param outputs Array to store the key frame data +// * @param useQuaternion Specifies if quaternions are used in the animation +// */ +// private static _CreateCubicSplineAnimation( +// babylonTransformNode: Node, +// animation: Animation, +// animationChannelTargetPath: AnimationChannelTargetPath, +// inputs: number[], +// outputs: number[][], +// useQuaternion: boolean +// ) { +// animation.getKeys().forEach(function (keyFrame) { +// inputs.push(keyFrame.frame / animation.framePerSecond); // keyframes in seconds. +// _GLTFAnimation._AddSplineTangent(_TangentType.INTANGENT, outputs, animationChannelTargetPath, AnimationSamplerInterpolation.CUBICSPLINE, keyFrame, useQuaternion); +// _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonTransformNode, useQuaternion); - _GLTFAnimation._AddSplineTangent(_TangentType.OUTTANGENT, outputs, animationChannelTargetPath, AnimationSamplerInterpolation.CUBICSPLINE, keyFrame, useQuaternion); - }); - } +// _GLTFAnimation._AddSplineTangent(_TangentType.OUTTANGENT, outputs, animationChannelTargetPath, AnimationSamplerInterpolation.CUBICSPLINE, keyFrame, useQuaternion); +// }); +// } - private static _GetBasePositionRotationOrScale(babylonTransformNode: Node, animationChannelTargetPath: AnimationChannelTargetPath, useQuaternion: boolean) { - let basePositionRotationOrScale: number[]; - if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { - if (useQuaternion) { - const q = (babylonTransformNode as TransformNode).rotationQuaternion; - basePositionRotationOrScale = (q ?? Quaternion.Identity()).asArray(); - } else { - const r: Vector3 = (babylonTransformNode as TransformNode).rotation; - basePositionRotationOrScale = (r ?? Vector3.Zero()).asArray(); - } - } else if (animationChannelTargetPath === AnimationChannelTargetPath.TRANSLATION) { - const p: Vector3 = (babylonTransformNode as TransformNode).position; - basePositionRotationOrScale = (p ?? Vector3.Zero()).asArray(); - } else { - // scale - const s: Vector3 = (babylonTransformNode as TransformNode).scaling; - basePositionRotationOrScale = (s ?? Vector3.One()).asArray(); - } - return basePositionRotationOrScale; - } +// private static _GetBasePositionRotationOrScale(babylonTransformNode: Node, animationChannelTargetPath: AnimationChannelTargetPath, useQuaternion: boolean) { +// let basePositionRotationOrScale: number[]; +// if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { +// if (useQuaternion) { +// const q = (babylonTransformNode as TransformNode).rotationQuaternion; +// basePositionRotationOrScale = (q ?? Quaternion.Identity()).asArray(); +// } else { +// const r: Vector3 = (babylonTransformNode as TransformNode).rotation; +// basePositionRotationOrScale = (r ?? Vector3.Zero()).asArray(); +// } +// } else if (animationChannelTargetPath === AnimationChannelTargetPath.TRANSLATION) { +// const p: Vector3 = (babylonTransformNode as TransformNode).position; +// basePositionRotationOrScale = (p ?? Vector3.Zero()).asArray(); +// } else { +// // scale +// const s: Vector3 = (babylonTransformNode as TransformNode).scaling; +// basePositionRotationOrScale = (s ?? Vector3.One()).asArray(); +// } +// return basePositionRotationOrScale; +// } - /** - * Adds a key frame value - * @param keyFrame - * @param animation - * @param outputs - * @param animationChannelTargetPath - * @param babylonTransformNode - * @param useQuaternion - */ - private static _AddKeyframeValue( - keyFrame: IAnimationKey, - animation: Animation, - outputs: number[][], - animationChannelTargetPath: AnimationChannelTargetPath, - babylonTransformNode: Node, - useQuaternion: boolean - ) { - let newPositionRotationOrScale: Nullable; - const animationType = animation.dataType; - if (animationType === Animation.ANIMATIONTYPE_VECTOR3) { - let value = keyFrame.value.asArray(); - if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { - const array = Vector3.FromArray(value); - const rotationQuaternion = Quaternion.RotationYawPitchRoll(array.y, array.x, array.z); - value = rotationQuaternion.asArray(); - } - outputs.push(value); // scale vector. - } else if (animationType === Animation.ANIMATIONTYPE_FLOAT) { - if (animationChannelTargetPath === AnimationChannelTargetPath.WEIGHTS) { - outputs.push([keyFrame.value]); - } else { - // handles single component x, y, z or w component animation by using a base property and animating over a component. - newPositionRotationOrScale = this._ConvertFactorToVector3OrQuaternion( - keyFrame.value as number, - babylonTransformNode, - animation, - animationChannelTargetPath, - useQuaternion - ); - if (newPositionRotationOrScale) { - if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { - const posRotScale = useQuaternion - ? (newPositionRotationOrScale as Quaternion) - : Quaternion.RotationYawPitchRoll(newPositionRotationOrScale.y, newPositionRotationOrScale.x, newPositionRotationOrScale.z).normalize(); - outputs.push(posRotScale.asArray()); - } - outputs.push(newPositionRotationOrScale.asArray()); - } - } - } else if (animationType === Animation.ANIMATIONTYPE_QUATERNION) { - outputs.push((keyFrame.value as Quaternion).normalize().asArray()); - } else { - Tools.Error("glTFAnimation: Unsupported key frame values for animation!"); - } - } +// /** +// * Adds a key frame value +// * @param keyFrame +// * @param animation +// * @param outputs +// * @param animationChannelTargetPath +// * @param babylonTransformNode +// * @param useQuaternion +// */ +// private static _AddKeyframeValue( +// keyFrame: IAnimationKey, +// animation: Animation, +// outputs: number[][], +// animationChannelTargetPath: AnimationChannelTargetPath, +// babylonTransformNode: Node, +// useQuaternion: boolean +// ) { +// let newPositionRotationOrScale: Nullable; +// const animationType = animation.dataType; +// if (animationType === Animation.ANIMATIONTYPE_VECTOR3) { +// let value = keyFrame.value.asArray(); +// if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { +// const array = Vector3.FromArray(value); +// const rotationQuaternion = Quaternion.RotationYawPitchRoll(array.y, array.x, array.z); +// value = rotationQuaternion.asArray(); +// } +// outputs.push(value); // scale vector. +// } else if (animationType === Animation.ANIMATIONTYPE_FLOAT) { +// if (animationChannelTargetPath === AnimationChannelTargetPath.WEIGHTS) { +// outputs.push([keyFrame.value]); +// } else { +// // handles single component x, y, z or w component animation by using a base property and animating over a component. +// newPositionRotationOrScale = this._ConvertFactorToVector3OrQuaternion( +// keyFrame.value as number, +// babylonTransformNode, +// animation, +// animationChannelTargetPath, +// useQuaternion +// ); +// if (newPositionRotationOrScale) { +// if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { +// const posRotScale = useQuaternion +// ? (newPositionRotationOrScale as Quaternion) +// : Quaternion.RotationYawPitchRoll(newPositionRotationOrScale.y, newPositionRotationOrScale.x, newPositionRotationOrScale.z).normalize(); +// outputs.push(posRotScale.asArray()); +// } +// outputs.push(newPositionRotationOrScale.asArray()); +// } +// } +// } else if (animationType === Animation.ANIMATIONTYPE_QUATERNION) { +// outputs.push((keyFrame.value as Quaternion).normalize().asArray()); +// } else { +// Tools.Error("glTFAnimation: Unsupported key frame values for animation!"); +// } +// } - /** - * @internal - * Determine the interpolation based on the key frames - * @param keyFrames - * @param animationChannelTargetPath - * @param useQuaternion - */ - private static _DeduceInterpolation( - keyFrames: IAnimationKey[], - animationChannelTargetPath: AnimationChannelTargetPath, - useQuaternion: boolean - ): { interpolationType: AnimationSamplerInterpolation; shouldBakeAnimation: boolean } { - let interpolationType: AnimationSamplerInterpolation | undefined; - let shouldBakeAnimation = false; - let key: IAnimationKey; +// /** +// * @internal +// * Determine the interpolation based on the key frames +// * @param keyFrames +// * @param animationChannelTargetPath +// * @param useQuaternion +// */ +// private static _DeduceInterpolation( +// keyFrames: IAnimationKey[], +// animationChannelTargetPath: AnimationChannelTargetPath, +// useQuaternion: boolean +// ): { interpolationType: AnimationSamplerInterpolation; shouldBakeAnimation: boolean } { +// let interpolationType: AnimationSamplerInterpolation | undefined; +// let shouldBakeAnimation = false; +// let key: IAnimationKey; - if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION && !useQuaternion) { - return { interpolationType: AnimationSamplerInterpolation.LINEAR, shouldBakeAnimation: true }; - } +// if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION && !useQuaternion) { +// return { interpolationType: AnimationSamplerInterpolation.LINEAR, shouldBakeAnimation: true }; +// } - for (let i = 0, length = keyFrames.length; i < length; ++i) { - key = keyFrames[i]; - if (key.inTangent || key.outTangent) { - if (interpolationType) { - if (interpolationType !== AnimationSamplerInterpolation.CUBICSPLINE) { - interpolationType = AnimationSamplerInterpolation.LINEAR; - shouldBakeAnimation = true; - break; - } - } else { - interpolationType = AnimationSamplerInterpolation.CUBICSPLINE; - } - } else { - if (interpolationType) { - if ( - interpolationType === AnimationSamplerInterpolation.CUBICSPLINE || - (key.interpolation && key.interpolation === AnimationKeyInterpolation.STEP && interpolationType !== AnimationSamplerInterpolation.STEP) - ) { - interpolationType = AnimationSamplerInterpolation.LINEAR; - shouldBakeAnimation = true; - break; - } - } else { - if (key.interpolation && key.interpolation === AnimationKeyInterpolation.STEP) { - interpolationType = AnimationSamplerInterpolation.STEP; - } else { - interpolationType = AnimationSamplerInterpolation.LINEAR; - } - } - } - } - if (!interpolationType) { - interpolationType = AnimationSamplerInterpolation.LINEAR; - } +// for (let i = 0, length = keyFrames.length; i < length; ++i) { +// key = keyFrames[i]; +// if (key.inTangent || key.outTangent) { +// if (interpolationType) { +// if (interpolationType !== AnimationSamplerInterpolation.CUBICSPLINE) { +// interpolationType = AnimationSamplerInterpolation.LINEAR; +// shouldBakeAnimation = true; +// break; +// } +// } else { +// interpolationType = AnimationSamplerInterpolation.CUBICSPLINE; +// } +// } else { +// if (interpolationType) { +// if ( +// interpolationType === AnimationSamplerInterpolation.CUBICSPLINE || +// (key.interpolation && key.interpolation === AnimationKeyInterpolation.STEP && interpolationType !== AnimationSamplerInterpolation.STEP) +// ) { +// interpolationType = AnimationSamplerInterpolation.LINEAR; +// shouldBakeAnimation = true; +// break; +// } +// } else { +// if (key.interpolation && key.interpolation === AnimationKeyInterpolation.STEP) { +// interpolationType = AnimationSamplerInterpolation.STEP; +// } else { +// interpolationType = AnimationSamplerInterpolation.LINEAR; +// } +// } +// } +// } +// if (!interpolationType) { +// interpolationType = AnimationSamplerInterpolation.LINEAR; +// } - return { interpolationType: interpolationType, shouldBakeAnimation: shouldBakeAnimation }; - } +// return { interpolationType: interpolationType, shouldBakeAnimation: shouldBakeAnimation }; +// } - /** - * Adds an input tangent or output tangent to the output data - * If an input tangent or output tangent is missing, it uses the zero vector or zero quaternion - * @param tangentType Specifies which type of tangent to handle (inTangent or outTangent) - * @param outputs The animation data by keyframe - * @param animationChannelTargetPath The target animation channel - * @param interpolation The interpolation type - * @param keyFrame The key frame with the animation data - * @param useQuaternion Specifies if quaternions are used - */ - private static _AddSplineTangent( - tangentType: _TangentType, - outputs: number[][], - animationChannelTargetPath: AnimationChannelTargetPath, - interpolation: AnimationSamplerInterpolation, - keyFrame: IAnimationKey, - useQuaternion: boolean - ) { - let tangent: number[]; - const tangentValue: Vector3 | Quaternion | number = tangentType === _TangentType.INTANGENT ? keyFrame.inTangent : keyFrame.outTangent; - if (interpolation === AnimationSamplerInterpolation.CUBICSPLINE) { - if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { - if (tangentValue) { - if (useQuaternion) { - tangent = (tangentValue as Quaternion).asArray(); - } else { - const array = tangentValue as Vector3; - tangent = Quaternion.RotationYawPitchRoll(array.y, array.x, array.z).asArray(); - } - } else { - tangent = [0, 0, 0, 0]; - } - } else if (animationChannelTargetPath === AnimationChannelTargetPath.WEIGHTS) { - if (tangentValue) { - tangent = [tangentValue as number]; - } else { - tangent = [0]; - } - } else { - if (tangentValue) { - tangent = (tangentValue as Vector3).asArray(); - } else { - tangent = [0, 0, 0]; - } - } +// /** +// * Adds an input tangent or output tangent to the output data +// * If an input tangent or output tangent is missing, it uses the zero vector or zero quaternion +// * @param tangentType Specifies which type of tangent to handle (inTangent or outTangent) +// * @param outputs The animation data by keyframe +// * @param animationChannelTargetPath The target animation channel +// * @param interpolation The interpolation type +// * @param keyFrame The key frame with the animation data +// * @param useQuaternion Specifies if quaternions are used +// */ +// private static _AddSplineTangent( +// tangentType: _TangentType, +// outputs: number[][], +// animationChannelTargetPath: AnimationChannelTargetPath, +// interpolation: AnimationSamplerInterpolation, +// keyFrame: IAnimationKey, +// useQuaternion: boolean +// ) { +// let tangent: number[]; +// const tangentValue: Vector3 | Quaternion | number = tangentType === _TangentType.INTANGENT ? keyFrame.inTangent : keyFrame.outTangent; +// if (interpolation === AnimationSamplerInterpolation.CUBICSPLINE) { +// if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { +// if (tangentValue) { +// if (useQuaternion) { +// tangent = (tangentValue as Quaternion).asArray(); +// } else { +// const array = tangentValue as Vector3; +// tangent = Quaternion.RotationYawPitchRoll(array.y, array.x, array.z).asArray(); +// } +// } else { +// tangent = [0, 0, 0, 0]; +// } +// } else if (animationChannelTargetPath === AnimationChannelTargetPath.WEIGHTS) { +// if (tangentValue) { +// tangent = [tangentValue as number]; +// } else { +// tangent = [0]; +// } +// } else { +// if (tangentValue) { +// tangent = (tangentValue as Vector3).asArray(); +// } else { +// tangent = [0, 0, 0]; +// } +// } - outputs.push(tangent); - } - } +// outputs.push(tangent); +// } +// } - /** - * Get the minimum and maximum key frames' frame values - * @param keyFrames animation key frames - * @returns the minimum and maximum key frame value - */ - private static _CalculateMinMaxKeyFrames(keyFrames: IAnimationKey[]): { min: number; max: number } { - let min: number = Infinity; - let max: number = -Infinity; - keyFrames.forEach(function (keyFrame) { - min = Math.min(min, keyFrame.frame); - max = Math.max(max, keyFrame.frame); - }); +// /** +// * Get the minimum and maximum key frames' frame values +// * @param keyFrames animation key frames +// * @returns the minimum and maximum key frame value +// */ +// private static _CalculateMinMaxKeyFrames(keyFrames: IAnimationKey[]): { min: number; max: number } { +// let min: number = Infinity; +// let max: number = -Infinity; +// keyFrames.forEach(function (keyFrame) { +// min = Math.min(min, keyFrame.frame); +// max = Math.max(max, keyFrame.frame); +// }); - return { min: min, max: max }; - } -} +// return { min: min, max: max }; +// } +// } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFData.ts b/packages/dev/serializers/src/glTF/2.0/glTFData.ts index 6befaf8b627..0b0cf6a5d0e 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFData.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFData.ts @@ -1,4 +1,23 @@ import { ImageMimeType } from "babylonjs-gltf2interface"; +import { Tools } from "core/Misc/tools"; + +function getMimeType(fileName: string): string | undefined { + if (fileName.endsWith(".glb")) { + return "model/gltf-binary"; + } else if (fileName.endsWith(".bin")) { + return "application/octet-stream"; + } else if (fileName.endsWith(".gltf")) { + return "model/gltf+json"; + } else if (fileName.endsWith(".jpeg") || fileName.endsWith(".jpg")) { + return ImageMimeType.JPEG; + } else if (fileName.endsWith(".png")) { + return ImageMimeType.PNG; + } else if (fileName.endsWith(".webp")) { + return ImageMimeType.WEBP; + } + + return undefined; +} /** * Class for holding and downloading glTF file data @@ -7,51 +26,23 @@ export class GLTFData { /** * Object which contains the file name as the key and its data as the value */ - glTFFiles: { [fileName: string]: string | Blob }; + public readonly files: { [fileName: string]: string | Blob } = {}; /** - * Initializes the glTF file object + * @deprecated Use files instead */ - public constructor() { - this.glTFFiles = {}; + public get glTFFiles() { + return this.files; } /** * Downloads the glTF data as files based on their names and data */ public downloadFiles(): void { - /** - * Checks for a matching suffix at the end of a string (for ES5 and lower) - * @param str Source string - * @param suffix Suffix to search for in the source string - * @returns Boolean indicating whether the suffix was found (true) or not (false) - */ - function endsWith(str: string, suffix: string): boolean { - return str.indexOf(suffix, str.length - suffix.length) !== -1; - } - - for (const key in this.glTFFiles) { - const link = document.createElement("a"); - document.body.appendChild(link); - link.setAttribute("type", "hidden"); - link.download = key; - const blob = this.glTFFiles[key]; - let mimeType; - - if (endsWith(key, ".glb")) { - mimeType = { type: "model/gltf-binary" }; - } else if (endsWith(key, ".bin")) { - mimeType = { type: "application/octet-stream" }; - } else if (endsWith(key, ".gltf")) { - mimeType = { type: "model/gltf+json" }; - } else if (endsWith(key, ".jpeg") || endsWith(key, ".jpg")) { - mimeType = { type: ImageMimeType.JPEG }; - } else if (endsWith(key, ".png")) { - mimeType = { type: ImageMimeType.PNG }; - } - - link.href = window.URL.createObjectURL(new Blob([blob], mimeType)); - link.click(); + for (const key in this.files) { + const value = this.files[key]; + const blob = new Blob([value], { type: getMimeType(key) }); + Tools.Download(blob, key); } } } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index df74d41f952..6b5213f785d 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1,3 +1,6 @@ +/* eslint-disable jsdoc/require-jsdoc */ +/* eslint-disable babylonjs/available */ + import type { IBufferView, IAccessor, @@ -16,254 +19,193 @@ import type { ISkin, ICamera, } from "babylonjs-gltf2interface"; -import { AccessorType, ImageMimeType, MeshPrimitiveMode, AccessorComponentType, CameraType } from "babylonjs-gltf2interface"; +import { AccessorComponentType, AccessorType, ImageMimeType } from "babylonjs-gltf2interface"; -import type { FloatArray, IndicesArray, Nullable } from "core/types"; -import { Matrix, TmpVectors, Vector2, Vector3, Vector4, Quaternion } from "core/Maths/math.vector"; -import { Color3, Color4 } from "core/Maths/math.color"; +import type { IndicesArray, Nullable } from "core/types"; +import { TmpVectors, Quaternion } from "core/Maths/math.vector"; import { Tools } from "core/Misc/tools"; +import type { Buffer } from "core/Buffers/buffer"; import { VertexBuffer } from "core/Buffers/buffer"; import type { Node } from "core/node"; import { TransformNode } from "core/Meshes/transformNode"; -import type { AbstractMesh } from "core/Meshes/abstractMesh"; import type { SubMesh } from "core/Meshes/subMesh"; import { Mesh } from "core/Meshes/mesh"; -import type { MorphTarget } from "core/Morph/morphTarget"; -import { LinesMesh } from "core/Meshes/linesMesh"; import { InstancedMesh } from "core/Meshes/instancedMesh"; -import type { Bone } from "core/Bones/bone"; import type { BaseTexture } from "core/Materials/Textures/baseTexture"; import type { Texture } from "core/Materials/Textures/texture"; import { Material } from "core/Materials/material"; import { Engine } from "core/Engines/engine"; import type { Scene } from "core/scene"; +import { EngineStore } from "core/Engines/engineStore"; import type { IGLTFExporterExtensionV2 } from "./glTFExporterExtension"; -import { _GLTFMaterialExporter } from "./glTFMaterialExporter"; +import { GLTFMaterialExporter } from "./glTFMaterialExporter"; import type { IExportOptions } from "./glTFSerializer"; -import { _GLTFUtilities } from "./glTFUtilities"; import { GLTFData } from "./glTFData"; -import { _GLTFAnimation } from "./glTFAnimation"; +import { + areIndices32Bits, + convertToRightHandedPosition, + convertToRightHandedRotation, + createAccessor, + createBufferView, + dataArrayToUint8Array, + getAccessorType, + getAttributeType, + getMinMax, + getPrimitiveMode, + indicesArrayToUint8Array, + isNoopNode, + isTriangleFillMode, +} from "./glTFUtilities"; +import { DataWriter } from "./dataWriter"; import { Camera } from "core/Cameras/camera"; -import { EngineStore } from "core/Engines/engineStore"; -import { MultiMaterial } from "core/Materials/multiMaterial"; - -// Matrix that converts handedness on the X-axis. -const convertHandednessMatrix = Matrix.Compose(new Vector3(-1, 1, 1), Quaternion.Identity(), Vector3.Zero()); +import { MultiMaterial, PBRMaterial, StandardMaterial } from "core/Materials"; +import { Logger } from "core/Misc/logger"; +import { enumerateFloatValues } from "core/Buffers/bufferUtils"; // 180 degrees rotation in Y. -const rotation180Y = new Quaternion(0, 1, 0, 0); - -function isNoopNode(node: Node, useRightHandedSystem: boolean): boolean { - if (!(node instanceof TransformNode)) { - return false; - } - - // Transform - if (useRightHandedSystem) { - const matrix = node.getWorldMatrix(); - if (!matrix.isIdentity()) { - return false; - } - } else { - const matrix = node.getWorldMatrix().multiplyToRef(convertHandednessMatrix, TmpVectors.Matrix[0]); - if (!matrix.isIdentity()) { - return false; - } - } +// const rotation180Y = new Quaternion(0, 1, 0, 0); - // Geometry - if ((node instanceof Mesh && node.geometry) || (node instanceof InstancedMesh && node.sourceMesh.geometry)) { - return false; - } +class ExporterState { + // Babylon indices array, start, count, offset, flip -> glTF accessor index + private _indicesAccessorMap = new Map, Map>>>>(); - return true; -} + // Babylon buffer -> glTF buffer view index + private _vertexBufferViewMap = new Map(); -function convertNodeHandedness(node: INode): void { - const translation = Vector3.FromArrayToRef(node.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); - const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); - const scale = Vector3.FromArrayToRef(node.scale || [1, 1, 1], 0, TmpVectors.Vector3[1]); - const matrix = Matrix.ComposeToRef(scale, rotation, translation, TmpVectors.Matrix[0]).multiplyToRef(convertHandednessMatrix, TmpVectors.Matrix[0]); + // Babylon vertex buffer, start, count -> glTF accessor index + private _vertexAccessorMap = new Map>>(); - matrix.decompose(scale, rotation, translation); + // Babylon mesh -> glTF mesh index + private _meshMap = new Map(); - if (translation.equalsToFloats(0, 0, 0)) { - delete node.translation; - } else { - node.translation = translation.asArray(); + public constructor(convertToRightHanded: boolean) { + this.convertToRightHanded = convertToRightHanded; } - if (Quaternion.IsIdentity(rotation)) { - delete node.rotation; - } else { - node.rotation = rotation.asArray(); - } + public readonly convertToRightHanded: boolean; + + // Only used when convertToRightHanded is true. + public readonly convertedToRightHandedBuffers = new Map(); - if (scale.equalsToFloats(1, 1, 1)) { - delete node.scale; - } else { - node.scale = scale.asArray(); + public getIndicesAccessor(indices: Nullable, start: number, count: number, offset: number, flip: boolean): number | undefined { + return this._indicesAccessorMap.get(indices)?.get(start)?.get(count)?.get(offset)?.get(flip); } -} -function getBinaryWriterFunc(binaryWriter: _BinaryWriter, attributeComponentKind: AccessorComponentType): Nullable<(entry: number, byteOffset?: number) => void> { - switch (attributeComponentKind) { - case AccessorComponentType.UNSIGNED_BYTE: { - return binaryWriter.setUInt8.bind(binaryWriter); - } - case AccessorComponentType.UNSIGNED_SHORT: { - return binaryWriter.setUInt16.bind(binaryWriter); + public setIndicesAccessor(indices: Nullable, start: number, count: number, offset: number, flip: boolean, accessorIndex: number): void { + let map1 = this._indicesAccessorMap.get(indices); + if (!map1) { + map1 = new Map>>>(); + this._indicesAccessorMap.set(indices, map1); } - case AccessorComponentType.UNSIGNED_INT: { - return binaryWriter.setUInt32.bind(binaryWriter); + + let map2 = map1.get(start); + if (!map2) { + map2 = new Map>>(); + map1.set(start, map2); } - case AccessorComponentType.FLOAT: { - return binaryWriter.setFloat32.bind(binaryWriter); + + let map3 = map2.get(count); + if (!map3) { + map3 = new Map>(); + map2.set(count, map3); } - default: { - Tools.Warn("Unsupported Attribute Component kind: " + attributeComponentKind); - return null; + + let map4 = map3.get(offset); + if (!map4) { + map4 = new Map(); + map3.set(offset, map4); } + + map4.set(flip, accessorIndex); } -} -/** - * Utility interface for storing vertex attribute data - * @internal - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -interface _IVertexAttributeData { - /** - * Specifies the Babylon Vertex Buffer Type (Position, Normal, Color, etc.) - */ - kind: string; + public getVertexBufferView(buffer: Buffer): number | undefined { + return this._vertexBufferViewMap.get(buffer); + } - /** - * Specifies the glTF Accessor Type (VEC2, VEC3, etc.) - */ - accessorType: AccessorType; + public setVertexBufferView(buffer: Buffer, bufferViewIndex: number): void { + this._vertexBufferViewMap.set(buffer, bufferViewIndex); + } - /** - * Specifies the glTF Accessor Component Type (BYTE, UNSIGNED_BYTE, FLOAT, SHORT, INT, etc..) - */ - accessorComponentType: AccessorComponentType; + public getVertexAccessor(vertexBuffer: VertexBuffer, start: number, count: number): number | undefined { + return this._vertexAccessorMap.get(vertexBuffer)?.get(start)?.get(count); + } - /** - * Specifies the BufferView index for the vertex attribute data - */ - bufferViewIndex?: number; + public setVertexAccessor(vertexBuffer: VertexBuffer, start: number, count: number, accessorIndex: number): void { + let map1 = this._vertexAccessorMap.get(vertexBuffer); + if (!map1) { + map1 = new Map>(); + this._vertexAccessorMap.set(vertexBuffer, map1); + } - /** - * Specifies the number of bytes per attribute element (e.g. position would be 3 floats (x/y/z) where each float is 4 bytes, so a 12 byte stride) - */ - byteStride?: number; + let map2 = map1.get(start); + if (!map2) { + map2 = new Map(); + map1.set(start, map2); + } - /** - * Specifies information about each morph target associated with this attribute - */ - morphTargetInfo?: Readonly<{ bufferViewIndex: number; vertexCount: number; accessorType: AccessorType; minMax?: { min: Vector3; max: Vector3 } }>[]; + map2.set(count, accessorIndex); + } + + public getMesh(mesh: Mesh): number | undefined { + return this._meshMap.get(mesh); + } + + public setMesh(mesh: Mesh, meshIndex: number): void { + this._meshMap.set(mesh, meshIndex); + } } -/** - * Converts Babylon Scene into glTF 2.0. - * @internal - */ -export class _Exporter { - /** - * Stores the glTF to export - */ - public _glTF: IGLTF; - /** - * Stores all generated buffer views, which represents views into the main glTF buffer data - */ - public _bufferViews: IBufferView[]; - /** - * Stores all the generated accessors, which is used for accessing the data within the buffer views in glTF - */ - public _accessors: IAccessor[]; - /** - * Stores all the generated nodes, which contains transform and/or mesh information per node - */ - public _nodes: INode[]; - /** - * Stores all the generated glTF scenes, which stores multiple node hierarchies - */ - private _scenes: IScene[]; - /** - * Stores all the generated glTF cameras - */ - private _cameras: ICamera[]; - /** - * Stores all the generated mesh information, each containing a set of primitives to render in glTF - */ - private _meshes: IMesh[]; - /** - * Stores all the generated material information, which represents the appearance of each primitive - */ - public _materials: IMaterial[]; +/** @internal */ +export class GLTFExporter { + public readonly _glTF: IGLTF = { + asset: { generator: `Babylon.js v${Engine.Version}`, version: "2.0" }, + }; - public _materialMap: { [materialID: number]: number }; - /** - * Stores all the generated texture information, which is referenced by glTF materials - */ - public _textures: ITexture[]; - /** - * Stores all the generated image information, which is referenced by glTF textures - */ - public _images: IImage[]; + public readonly _animations: IAnimation[] = []; + public readonly _accessors: IAccessor[] = []; + public readonly _bufferViews: IBufferView[] = []; + public readonly _cameras: ICamera[] = []; + public readonly _images: IImage[] = []; + public readonly _materials: IMaterial[] = []; + public readonly _meshes: IMesh[] = []; + public readonly _nodes: INode[] = []; + public readonly _samplers: ISampler[] = []; + public readonly _scenes: IScene[] = []; + public readonly _skins: ISkin[] = []; + public readonly _textures: ITexture[] = []; - /** - * Stores all the texture samplers - */ - public _samplers: ISampler[]; - /** - * Stores all the generated glTF skins - */ - public _skins: ISkin[]; - /** - * Stores all the generated animation samplers, which is referenced by glTF animations - */ - /** - * Stores the animations for glTF models - */ - private _animations: IAnimation[]; - /** - * Stores the total amount of bytes stored in the glTF buffer - */ - private _totalByteLength: number; - /** - * Stores a reference to the Babylon scene containing the source geometry and material information - */ - public _babylonScene: Scene; - /** - * Stores a map of the image data, where the key is the file name and the value - * is the image data - */ - public _imageData: { [fileName: string]: { data: ArrayBuffer; mimeType: ImageMimeType } }; + public readonly _babylonScene: Scene; + public readonly _imageData: { [fileName: string]: { data: ArrayBuffer; mimeType: ImageMimeType } } = {}; + private readonly _orderedImageData: Array<{ data: ArrayBuffer; mimeType: ImageMimeType }> = []; - private _orderedImageData: Array<{ data: ArrayBuffer; mimeType: ImageMimeType }>; + // /** + // * Baked animation sample rate + // */ + // private _animationSampleRate: number; - /** - * Stores a map of the unique id of a node to its index in the node array - */ - private _nodeMap: { [key: number]: number }; + private readonly _options: Required; - /** - * Baked animation sample rate - */ - private _animationSampleRate: number; + private readonly _materialExporter = new GLTFMaterialExporter(this); + + private readonly _extensions: { [name: string]: IGLTFExporterExtensionV2 } = {}; - private _options: IExportOptions; + private readonly _dataWriter = new DataWriter(4); - private _localEngine: Engine; + private readonly _shouldExportNodeMap = new Map(); - public _glTFMaterialExporter: _GLTFMaterialExporter; + // Babylon node -> glTF node index + private readonly _nodeMap = new Map(); - private _extensions: { [name: string]: IGLTFExporterExtensionV2 } = {}; + // Babylon material -> glTF material index + public readonly _materialMap = new Map(); - private static _ExtensionNames = new Array(); - private static _ExtensionFactories: { [name: string]: (exporter: _Exporter) => IGLTFExporterExtensionV2 } = {}; + // A material in this set requires UVs + public readonly _materialNeedsUVsSet = new Set(); + + private static readonly _ExtensionNames = new Array(); + private static readonly _ExtensionFactories: { [name: string]: (exporter: GLTFExporter) => IGLTFExporterExtensionV2 } = {}; private _applyExtension( node: Nullable, @@ -289,7 +231,7 @@ export class _Exporter { actionAsync: (extension: IGLTFExporterExtensionV2, node: Nullable) => Promise> | undefined ): Promise> { const extensions: IGLTFExporterExtensionV2[] = []; - for (const name of _Exporter._ExtensionNames) { + for (const name of GLTFExporter._ExtensionNames) { extensions.push(this._extensions[name]); } @@ -300,26 +242,15 @@ export class _Exporter { return this._applyExtensions(babylonTexture, (extension, node) => extension.preExportTextureAsync && extension.preExportTextureAsync(context, node, mimeType)); } - public _extensionsPostExportMeshPrimitiveAsync( - context: string, - meshPrimitive: IMeshPrimitive, - babylonSubMesh: SubMesh, - binaryWriter: _BinaryWriter - ): Promise> { + public _extensionsPostExportMeshPrimitiveAsync(context: string, meshPrimitive: IMeshPrimitive, babylonSubMesh: SubMesh): Promise> { return this._applyExtensions( meshPrimitive, - (extension, node) => extension.postExportMeshPrimitiveAsync && extension.postExportMeshPrimitiveAsync(context, node, babylonSubMesh, binaryWriter) + (extension, node) => extension.postExportMeshPrimitiveAsync && extension.postExportMeshPrimitiveAsync(context, node, babylonSubMesh) ); } - public _extensionsPostExportNodeAsync( - context: string, - node: Nullable, - babylonNode: Node, - nodeMap: { [key: number]: number }, - binaryWriter: _BinaryWriter - ): Promise> { - return this._applyExtensions(node, (extension, node) => extension.postExportNodeAsync && extension.postExportNodeAsync(context, node, babylonNode, nodeMap, binaryWriter)); + public _extensionsPostExportNodeAsync(context: string, node: Nullable, babylonNode: Node, nodeMap: { [key: number]: number }): Promise> { + return this._applyExtensions(node, (extension, node) => extension.postExportNodeAsync && extension.postExportNodeAsync(context, node, babylonNode, nodeMap)); } public _extensionsPostExportMaterialAsync(context: string, material: Nullable, babylonMaterial: Material): Promise> { @@ -329,7 +260,7 @@ export class _Exporter { public _extensionsPostExportMaterialAdditionalTextures(context: string, material: IMaterial, babylonMaterial: Material): BaseTexture[] { const output: BaseTexture[] = []; - for (const name of _Exporter._ExtensionNames) { + for (const name of GLTFExporter._ExtensionNames) { const extension = this._extensions[name]; if (extension.postExportMaterialAdditionalTextures) { @@ -341,7 +272,7 @@ export class _Exporter { } public _extensionsPostExportTextures(context: string, textureInfo: ITextureInfo, babylonTexture: BaseTexture): void { - for (const name of _Exporter._ExtensionNames) { + for (const name of GLTFExporter._ExtensionNames) { const extension = this._extensions[name]; if (extension.postExportTexture) { @@ -351,7 +282,7 @@ export class _Exporter { } private _forEachExtensions(action: (extension: IGLTFExporterExtensionV2) => void): void { - for (const name of _Exporter._ExtensionNames) { + for (const name of GLTFExporter._ExtensionNames) { const extension = this._extensions[name]; if (extension.enabled) { action(extension); @@ -362,27 +293,18 @@ export class _Exporter { private _extensionsOnExporting(): void { this._forEachExtensions((extension) => { if (extension.wasUsed) { - if (this._glTF.extensionsUsed == null) { - this._glTF.extensionsUsed = []; - } - + this._glTF.extensionsUsed ||= []; if (this._glTF.extensionsUsed.indexOf(extension.name) === -1) { this._glTF.extensionsUsed.push(extension.name); } if (extension.required) { - if (this._glTF.extensionsRequired == null) { - this._glTF.extensionsRequired = []; - } + this._glTF.extensionsRequired ||= []; if (this._glTF.extensionsRequired.indexOf(extension.name) === -1) { this._glTF.extensionsRequired.push(extension.name); } } - if (this._glTF.extensions == null) { - this._glTF.extensions = {}; - } - if (extension.onExporting) { extension.onExporting(); } @@ -390,56 +312,38 @@ export class _Exporter { }); } - /** - * Load glTF serializer extensions - */ private _loadExtensions(): void { - for (const name of _Exporter._ExtensionNames) { - const extension = _Exporter._ExtensionFactories[name](this); + for (const name of GLTFExporter._ExtensionNames) { + const extension = GLTFExporter._ExtensionFactories[name](this); this._extensions[name] = extension; } } - /** - * Creates a glTF Exporter instance, which can accept optional exporter options - * @param babylonScene Babylon scene object - * @param options Options to modify the behavior of the exporter - */ - public constructor(babylonScene?: Nullable, options?: IExportOptions) { - this._glTF = { - asset: { generator: `Babylon.js v${Engine.Version}`, version: "2.0" }, - }; - babylonScene = babylonScene || EngineStore.LastCreatedScene; + public constructor(babylonScene: Nullable = EngineStore.LastCreatedScene, options?: IExportOptions) { if (!babylonScene) { - return; + throw new Error("No scene available to export"); } + this._babylonScene = babylonScene; - this._bufferViews = []; - this._accessors = []; - this._meshes = []; - this._scenes = []; - this._cameras = []; - this._nodes = []; - this._images = []; - this._materials = []; - this._materialMap = []; - this._textures = []; - this._samplers = []; - this._skins = []; - this._animations = []; - this._imageData = {}; - this._orderedImageData = []; - this._options = options || {}; - this._animationSampleRate = this._options.animationSampleRate || 1 / 60; - - this._glTFMaterialExporter = new _GLTFMaterialExporter(this); + + this._options = { + shouldExportNode: () => true, + shouldExportAnimation: () => true, + metadataSelector: (metadata) => metadata, + animationSampleRate: 1 / 60, + exportWithoutWaitingForScene: false, + exportUnusedUVs: false, + removeNoopRootNodes: true, + includeCoordinateSystemConversionNodes: false, + ...options, + }; + this._loadExtensions(); } public dispose() { - for (const extensionKey in this._extensions) { - const extension = this._extensions[extensionKey]; - + for (const key in this._extensions) { + const extension = this._extensions[key]; extension.dispose(); } } @@ -448,555 +352,137 @@ export class _Exporter { return this._options; } - /** - * Registers a glTF exporter extension - * @param name Name of the extension to export - * @param factory The factory function that creates the exporter extension - */ - public static RegisterExtension(name: string, factory: (exporter: _Exporter) => IGLTFExporterExtensionV2): void { - if (_Exporter.UnregisterExtension(name)) { + public static RegisterExtension(name: string, factory: (exporter: GLTFExporter) => IGLTFExporterExtensionV2): void { + if (GLTFExporter.UnregisterExtension(name)) { Tools.Warn(`Extension with the name ${name} already exists`); } - _Exporter._ExtensionFactories[name] = factory; - _Exporter._ExtensionNames.push(name); + GLTFExporter._ExtensionFactories[name] = factory; + GLTFExporter._ExtensionNames.push(name); } - /** - * Un-registers an exporter extension - * @param name The name fo the exporter extension - * @returns A boolean indicating whether the extension has been un-registered - */ public static UnregisterExtension(name: string): boolean { - if (!_Exporter._ExtensionFactories[name]) { + if (!GLTFExporter._ExtensionFactories[name]) { return false; } - delete _Exporter._ExtensionFactories[name]; + delete GLTFExporter._ExtensionFactories[name]; - const index = _Exporter._ExtensionNames.indexOf(name); + const index = GLTFExporter._ExtensionNames.indexOf(name); if (index !== -1) { - _Exporter._ExtensionNames.splice(index, 1); + GLTFExporter._ExtensionNames.splice(index, 1); } return true; } - private _reorderIndicesBasedOnPrimitiveMode(submesh: SubMesh, primitiveMode: number, babylonIndices: IndicesArray, byteOffset: number, binaryWriter: _BinaryWriter) { - switch (primitiveMode) { - case Material.TriangleFillMode: { - if (!byteOffset) { - byteOffset = 0; - } - for (let i = submesh.indexStart, length = submesh.indexStart + submesh.indexCount; i < length; i = i + 3) { - const index = byteOffset + i * 4; - // swap the second and third indices - const secondIndex = binaryWriter.getUInt32(index + 4); - const thirdIndex = binaryWriter.getUInt32(index + 8); - binaryWriter.setUInt32(thirdIndex, index + 4); - binaryWriter.setUInt32(secondIndex, index + 8); - } - break; - } - case Material.TriangleFanDrawMode: { - for (let i = submesh.indexStart + submesh.indexCount - 1, start = submesh.indexStart; i >= start; --i) { - binaryWriter.setUInt32(babylonIndices[i], byteOffset); - byteOffset += 4; - } - break; - } - case Material.TriangleStripDrawMode: { - if (submesh.indexCount >= 3) { - binaryWriter.setUInt32(babylonIndices[submesh.indexStart + 2], byteOffset + 4); - binaryWriter.setUInt32(babylonIndices[submesh.indexStart + 1], byteOffset + 8); - } - break; - } - } - } - - /** - * Reorders the vertex attribute data based on the primitive mode. This is necessary when indices are not available and the winding order is - * clock-wise during export to glTF - * @param submesh BabylonJS submesh - * @param primitiveMode Primitive mode of the mesh - * @param vertexBufferKind The type of vertex attribute - * @param meshAttributeArray The vertex attribute data - * @param byteOffset The offset to the binary data - * @param binaryWriter The binary data for the glTF file - */ - private _reorderVertexAttributeDataBasedOnPrimitiveMode( - submesh: SubMesh, - primitiveMode: number, - vertexBufferKind: string, - meshAttributeArray: FloatArray, - byteOffset: number, - binaryWriter: _BinaryWriter - ): void { - switch (primitiveMode) { - case Material.TriangleFillMode: { - this._reorderTriangleFillMode(submesh, vertexBufferKind, meshAttributeArray, byteOffset, binaryWriter); - break; - } - case Material.TriangleStripDrawMode: { - this._reorderTriangleStripDrawMode(submesh, vertexBufferKind, meshAttributeArray, byteOffset, binaryWriter); - break; - } - case Material.TriangleFanDrawMode: { - this._reorderTriangleFanMode(submesh, vertexBufferKind, meshAttributeArray, byteOffset, binaryWriter); - break; - } - } - } - - /** - * Reorders the vertex attributes in the correct triangle mode order . This is necessary when indices are not available and the winding order is - * clock-wise during export to glTF - * @param submesh BabylonJS submesh - * @param vertexBufferKind The type of vertex attribute - * @param meshAttributeArray The vertex attribute data - * @param byteOffset The offset to the binary data - * @param binaryWriter The binary data for the glTF file - */ - private _reorderTriangleFillMode(submesh: SubMesh, vertexBufferKind: string, meshAttributeArray: FloatArray, byteOffset: number, binaryWriter: _BinaryWriter) { - const vertexBuffer = this._getVertexBufferFromMesh(vertexBufferKind, submesh.getMesh() as Mesh); - if (vertexBuffer) { - const stride = vertexBuffer.byteStride / VertexBuffer.GetTypeByteLength(vertexBuffer.type); - if (submesh.verticesCount % 3 !== 0) { - Tools.Error("The submesh vertices for the triangle fill mode is not divisible by 3!"); - } else { - const vertexData: Vector2[] | Vector3[] | Vector4[] = []; - let index = 0; - switch (vertexBufferKind) { - case VertexBuffer.PositionKind: - case VertexBuffer.NormalKind: { - for (let x = submesh.verticesStart; x < submesh.verticesStart + submesh.verticesCount; x = x + 3) { - index = x * stride; - (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index)); - (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index + 2 * stride)); - (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index + stride)); - } - break; - } - case VertexBuffer.TangentKind: { - for (let x = submesh.verticesStart; x < submesh.verticesStart + submesh.verticesCount; x = x + 3) { - index = x * stride; - (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index)); - (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index + 2 * stride)); - (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index + stride)); - } - break; - } - case VertexBuffer.ColorKind: { - const size = vertexBuffer.getSize(); - for (let x = submesh.verticesStart; x < submesh.verticesStart + submesh.verticesCount; x = x + size) { - index = x * stride; - if (size === 4) { - (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index)); - (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index + 2 * stride)); - (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index + stride)); - } else { - (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index)); - (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index + 2 * stride)); - (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index + stride)); - } - } - break; - } - case VertexBuffer.UVKind: - case VertexBuffer.UV2Kind: { - for (let x = submesh.verticesStart; x < submesh.verticesStart + submesh.verticesCount; x = x + 3) { - index = x * stride; - (vertexData as Vector2[]).push(Vector2.FromArray(meshAttributeArray, index)); - (vertexData as Vector2[]).push(Vector2.FromArray(meshAttributeArray, index + 2 * stride)); - (vertexData as Vector2[]).push(Vector2.FromArray(meshAttributeArray, index + stride)); - } - break; - } - default: { - Tools.Error(`Unsupported Vertex Buffer type: ${vertexBufferKind}`); - } - } - this._writeVertexAttributeData(vertexData, byteOffset, vertexBufferKind, binaryWriter); - } - } else { - Tools.Warn(`reorderTriangleFillMode: Vertex Buffer Kind ${vertexBufferKind} not present!`); - } - } - - /** - * Reorders the vertex attributes in the correct triangle strip order. This is necessary when indices are not available and the winding order is - * clock-wise during export to glTF - * @param submesh BabylonJS submesh - * @param vertexBufferKind The type of vertex attribute - * @param meshAttributeArray The vertex attribute data - * @param byteOffset The offset to the binary data - * @param binaryWriter The binary data for the glTF file - */ - private _reorderTriangleStripDrawMode(submesh: SubMesh, vertexBufferKind: string, meshAttributeArray: FloatArray, byteOffset: number, binaryWriter: _BinaryWriter) { - const vertexBuffer = this._getVertexBufferFromMesh(vertexBufferKind, submesh.getMesh() as Mesh); - if (vertexBuffer) { - const stride = vertexBuffer.byteStride / VertexBuffer.GetTypeByteLength(vertexBuffer.type); - - const vertexData: Vector2[] | Vector3[] | Vector4[] = []; - let index = 0; - switch (vertexBufferKind) { - case VertexBuffer.PositionKind: - case VertexBuffer.NormalKind: { - index = submesh.verticesStart; - (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index + 2 * stride)); - (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index + stride)); - break; - } - case VertexBuffer.TangentKind: { - for (let x = submesh.verticesStart + submesh.verticesCount - 1; x >= submesh.verticesStart; --x) { - index = x * stride; - (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index)); - } - break; - } - case VertexBuffer.ColorKind: { - for (let x = submesh.verticesStart + submesh.verticesCount - 1; x >= submesh.verticesStart; --x) { - index = x * stride; - vertexBuffer.getSize() === 4 - ? (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index)) - : (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index)); - } - break; - } - case VertexBuffer.UVKind: - case VertexBuffer.UV2Kind: { - for (let x = submesh.verticesStart + submesh.verticesCount - 1; x >= submesh.verticesStart; --x) { - index = x * stride; - (vertexData as Vector2[]).push(Vector2.FromArray(meshAttributeArray, index)); - } - break; - } - default: { - Tools.Error(`Unsupported Vertex Buffer type: ${vertexBufferKind}`); - } - } - this._writeVertexAttributeData(vertexData, byteOffset + 12, vertexBufferKind, binaryWriter); - } else { - Tools.Warn(`reorderTriangleStripDrawMode: Vertex buffer kind ${vertexBufferKind} not present!`); - } - } - - /** - * Reorders the vertex attributes in the correct triangle fan order. This is necessary when indices are not available and the winding order is - * clock-wise during export to glTF - * @param submesh BabylonJS submesh - * @param vertexBufferKind The type of vertex attribute - * @param meshAttributeArray The vertex attribute data - * @param byteOffset The offset to the binary data - * @param binaryWriter The binary data for the glTF file - */ - private _reorderTriangleFanMode(submesh: SubMesh, vertexBufferKind: string, meshAttributeArray: FloatArray, byteOffset: number, binaryWriter: _BinaryWriter) { - const vertexBuffer = this._getVertexBufferFromMesh(vertexBufferKind, submesh.getMesh() as Mesh); - if (vertexBuffer) { - const stride = vertexBuffer.byteStride / VertexBuffer.GetTypeByteLength(vertexBuffer.type); - - const vertexData: Vector2[] | Vector3[] | Vector4[] = []; - let index = 0; - switch (vertexBufferKind) { - case VertexBuffer.PositionKind: - case VertexBuffer.NormalKind: { - for (let x = submesh.verticesStart + submesh.verticesCount - 1; x >= submesh.verticesStart; --x) { - index = x * stride; - (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index)); - } - break; - } - case VertexBuffer.TangentKind: { - for (let x = submesh.verticesStart + submesh.verticesCount - 1; x >= submesh.verticesStart; --x) { - index = x * stride; - (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index)); - } - break; - } - case VertexBuffer.ColorKind: { - for (let x = submesh.verticesStart + submesh.verticesCount - 1; x >= submesh.verticesStart; --x) { - index = x * stride; - (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index)); - vertexBuffer.getSize() === 4 - ? (vertexData as Vector4[]).push(Vector4.FromArray(meshAttributeArray, index)) - : (vertexData as Vector3[]).push(Vector3.FromArray(meshAttributeArray, index)); - } - break; - } - case VertexBuffer.UVKind: - case VertexBuffer.UV2Kind: { - for (let x = submesh.verticesStart + submesh.verticesCount - 1; x >= submesh.verticesStart; --x) { - index = x * stride; - (vertexData as Vector2[]).push(Vector2.FromArray(meshAttributeArray, index)); - } - break; - } - default: { - Tools.Error(`Unsupported Vertex Buffer type: ${vertexBufferKind}`); - } - } - this._writeVertexAttributeData(vertexData, byteOffset, vertexBufferKind, binaryWriter); - } else { - Tools.Warn(`reorderTriangleFanMode: Vertex buffer kind ${vertexBufferKind} not present!`); - } - } - - /** - * Writes the vertex attribute data to binary - * @param vertices The vertices to write to the binary writer - * @param byteOffset The offset into the binary writer to overwrite binary data - * @param vertexAttributeKind The vertex attribute type - * @param binaryWriter The writer containing the binary data - */ - private _writeVertexAttributeData(vertices: Vector2[] | Vector3[] | Vector4[], byteOffset: number, vertexAttributeKind: string, binaryWriter: _BinaryWriter) { - for (const vertex of vertices) { - if (vertexAttributeKind === VertexBuffer.NormalKind) { - vertex.normalize(); - } else if (vertexAttributeKind === VertexBuffer.TangentKind && vertex instanceof Vector4) { - _GLTFUtilities._NormalizeTangentFromRef(vertex); - } - - for (const component of vertex.asArray()) { - binaryWriter.setFloat32(component, byteOffset); - byteOffset += 4; - } - } - } - - /** - * Writes mesh attribute data to a data buffer - * Returns the bytelength of the data - * @param vertexBufferKind Indicates what kind of vertex data is being passed in - * @param attributeComponentKind - * @param meshAttributeArray Array containing the attribute data - * @param stride Specifies the space between data - * @param binaryWriter The buffer to write the binary data to - * @param babylonTransformNode - */ - public _writeAttributeData( - vertexBufferKind: string, - attributeComponentKind: AccessorComponentType, - meshAttributeArray: FloatArray, - stride: number, - binaryWriter: _BinaryWriter, - babylonTransformNode: TransformNode - ) { - let vertexAttributes: number[][] = []; - let index: number; - - switch (vertexBufferKind) { - case VertexBuffer.PositionKind: { - for (let k = 0, length = meshAttributeArray.length / stride; k < length; ++k) { - index = k * stride; - const vertexData = Vector3.FromArray(meshAttributeArray, index); - vertexAttributes.push(vertexData.asArray()); - } - break; - } - case VertexBuffer.NormalKind: { - for (let k = 0, length = meshAttributeArray.length / stride; k < length; ++k) { - index = k * stride; - const vertexData = Vector3.FromArray(meshAttributeArray, index); - vertexAttributes.push(vertexData.normalize().asArray()); - } - break; - } - case VertexBuffer.TangentKind: { - for (let k = 0, length = meshAttributeArray.length / stride; k < length; ++k) { - index = k * stride; - const vertexData = Vector4.FromArray(meshAttributeArray, index); - _GLTFUtilities._NormalizeTangentFromRef(vertexData); - vertexAttributes.push(vertexData.asArray()); - } - break; - } - case VertexBuffer.ColorKind: { - const meshMaterial = (babylonTransformNode as Mesh).material; - const convertToLinear = meshMaterial ? meshMaterial.getClassName() === "StandardMaterial" : true; - const vertexData: Color3 | Color4 = stride === 3 ? new Color3() : new Color4(); - const useExactSrgbConversions = this._babylonScene.getEngine().useExactSrgbConversions; - for (let k = 0, length = meshAttributeArray.length / stride; k < length; ++k) { - index = k * stride; - if (stride === 3) { - Color3.FromArrayToRef(meshAttributeArray, index, vertexData as Color3); - if (convertToLinear) { - (vertexData as Color3).toLinearSpaceToRef(vertexData as Color3, useExactSrgbConversions); - } - } else { - Color4.FromArrayToRef(meshAttributeArray, index, vertexData as Color4); - if (convertToLinear) { - (vertexData as Color4).toLinearSpaceToRef(vertexData as Color4, useExactSrgbConversions); - } - } - vertexAttributes.push(vertexData.asArray()); - } - break; - } - case VertexBuffer.UVKind: - case VertexBuffer.UV2Kind: { - for (let k = 0, length = meshAttributeArray.length / stride; k < length; ++k) { - index = k * stride; - const vertexData = Vector2.FromArray(meshAttributeArray, index); - vertexAttributes.push(vertexData.asArray()); - } - break; - } - case VertexBuffer.MatricesIndicesKind: - case VertexBuffer.MatricesIndicesExtraKind: { - for (let k = 0, length = meshAttributeArray.length / stride; k < length; ++k) { - index = k * stride; - const vertexData = Vector4.FromArray(meshAttributeArray, index); - vertexAttributes.push(vertexData.asArray()); - } - break; - } - case VertexBuffer.MatricesWeightsKind: - case VertexBuffer.MatricesWeightsExtraKind: { - for (let k = 0, length = meshAttributeArray.length / stride; k < length; ++k) { - index = k * stride; - const vertexData = Vector4.FromArray(meshAttributeArray, index); - vertexAttributes.push(vertexData.asArray()); - } - break; - } - default: { - Tools.Warn("Unsupported Vertex Buffer Type: " + vertexBufferKind); - vertexAttributes = []; - } - } - - const writeBinaryFunc = getBinaryWriterFunc(binaryWriter, attributeComponentKind); - - if (writeBinaryFunc) { - for (const vertexAttribute of vertexAttributes) { - for (const component of vertexAttribute) { - writeBinaryFunc(component); - } - } - } - } - - private _createMorphTargetBufferViewKind( - vertexBufferKind: string, - accessorType: AccessorType, - attributeComponentKind: AccessorComponentType, - mesh: Mesh, - morphTarget: MorphTarget, - binaryWriter: _BinaryWriter, - byteStride: number - ): Nullable<{ bufferViewIndex: number; vertexCount: number; accessorType: AccessorType; minMax?: { min: Vector3; max: Vector3 } }> { - let vertexCount: number; - let minMax: { min: Vector3; max: Vector3 } | undefined; - const morphData: number[] = []; - const difference: Vector3 = TmpVectors.Vector3[0]; - - switch (vertexBufferKind) { - case VertexBuffer.PositionKind: { - const morphPositions = morphTarget.getPositions(); - if (!morphPositions) { - return null; - } - - const originalPositions = mesh.getVerticesData(VertexBuffer.PositionKind, undefined, undefined, true)!; - const vertexStart = 0; - const min = new Vector3(Infinity, Infinity, Infinity); - const max = new Vector3(-Infinity, -Infinity, -Infinity); - vertexCount = originalPositions.length / 3; - for (let i = vertexStart; i < vertexCount; ++i) { - const originalPosition = Vector3.FromArray(originalPositions, i * 3); - const morphPosition = Vector3.FromArray(morphPositions, i * 3); - morphPosition.subtractToRef(originalPosition, difference); - min.copyFromFloats(Math.min(difference.x, min.x), Math.min(difference.y, min.y), Math.min(difference.z, min.z)); - max.copyFromFloats(Math.max(difference.x, max.x), Math.max(difference.y, max.y), Math.max(difference.z, max.z)); - morphData.push(difference.x, difference.y, difference.z); - } - - minMax = { min, max }; - - break; - } - case VertexBuffer.NormalKind: { - const morphNormals = morphTarget.getNormals(); - if (!morphNormals) { - return null; - } - - const originalNormals = mesh.getVerticesData(VertexBuffer.NormalKind, undefined, undefined, true)!; - const vertexStart = 0; - vertexCount = originalNormals.length / 3; - for (let i = vertexStart; i < vertexCount; ++i) { - const originalNormal = Vector3.FromArray(originalNormals, i * 3).normalize(); - const morphNormal = Vector3.FromArray(morphNormals, i * 3).normalize(); - morphNormal.subtractToRef(originalNormal, difference); - morphData.push(difference.x, difference.y, difference.z); - } - - break; - } - case VertexBuffer.TangentKind: { - const morphTangents = morphTarget.getTangents(); - if (!morphTangents) { - return null; - } - - // Handedness cannot be displaced, so morph target tangents omit the w component - accessorType = AccessorType.VEC3; - byteStride = 12; // 3 components (x/y/z) * 4 bytes (float32) - - const originalTangents = mesh.getVerticesData(VertexBuffer.TangentKind, undefined, undefined, true)!; - const vertexStart = 0; - vertexCount = originalTangents.length / 4; - for (let i = vertexStart; i < vertexCount; ++i) { - // Only read the x, y, z components and ignore w - const originalTangent = Vector3.FromArray(originalTangents, i * 4); - _GLTFUtilities._NormalizeTangentFromRef(originalTangent); - - // Morph target tangents omit the w component so it won't be present in the data - const morphTangent = Vector3.FromArray(morphTangents, i * 3); - _GLTFUtilities._NormalizeTangentFromRef(morphTangent); - - morphTangent.subtractToRef(originalTangent, difference); - morphData.push(difference.x, difference.y, difference.z); - } - - break; - } - default: { - return null; - } - } - - const binaryWriterFunc = getBinaryWriterFunc(binaryWriter, attributeComponentKind); - if (!binaryWriterFunc) { - return null; - } - - const typeByteLength = VertexBuffer.GetTypeByteLength(attributeComponentKind); - const byteLength = morphData.length * typeByteLength; - const bufferView = _GLTFUtilities._CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, byteStride, `${vertexBufferKind} - ${morphTarget.name} (Morph Target)`); - this._bufferViews.push(bufferView); - const bufferViewIndex = this._bufferViews.length - 1; - - for (const value of morphData) { - binaryWriterFunc(value); - } - - return { bufferViewIndex, vertexCount, accessorType, minMax }; - } - - /** - * Generates glTF json data - * @param shouldUseGlb Indicates whether the json should be written for a glb file - * @param glTFPrefix Text to use when prefixing a glTF file - * @param prettyPrint Indicates whether the json file should be pretty printed (true) or not (false) - * @returns json data as string - */ - private _generateJSON(shouldUseGlb: boolean, glTFPrefix?: string, prettyPrint?: boolean): string { - const buffer: IBuffer = { byteLength: this._totalByteLength }; + // /** + // * Writes mesh attribute data to a data buffer + // * Returns the bytelength of the data + // * @param vertexBufferKind Indicates what kind of vertex data is being passed in + // * @param attributeComponentKind + // * @param meshPrimitive + // * @param morphTarget + // * @param meshAttributeArray Array containing the attribute data + // * @param morphTargetAttributeArray + // * @param stride Specifies the space between data + // * @param dataWriter The buffer to write the binary data to + // * @param minMax + // */ + // public writeMorphTargetAttributeData( + // vertexBufferKind: string, + // attributeComponentKind: AccessorComponentType, + // meshPrimitive: SubMesh, + // meshAttributeArray: FloatArray, + // morphTargetAttributeArray: FloatArray, + // stride: number, + // dataWriter: DataWriter, + // minMax?: any + // ) { + // let vertexAttributes: number[][] = []; + // let index: number; + // let difference: Vector3 = new Vector3(); + // let difference4: Vector4 = new Vector4(0, 0, 0, 0); + + // switch (vertexBufferKind) { + // case VertexBuffer.PositionKind: { + // for (let k = meshPrimitive.verticesStart; k < meshPrimitive.verticesCount; ++k) { + // index = meshPrimitive.indexStart + k * stride; + // const vertexData = Vector3.FromArray(meshAttributeArray, index); + // const morphData = Vector3.FromArray(morphTargetAttributeArray, index); + // difference = morphData.subtractToRef(vertexData, difference); + // if (minMax) { + // minMax.min.copyFromFloats(Math.min(difference.x, minMax.min.x), Math.min(difference.y, minMax.min.y), Math.min(difference.z, minMax.min.z)); + // minMax.max.copyFromFloats(Math.max(difference.x, minMax.max.x), Math.max(difference.y, minMax.max.y), Math.max(difference.z, minMax.max.z)); + // } + // vertexAttributes.push(difference.asArray()); + // } + // break; + // } + // case VertexBuffer.NormalKind: { + // for (let k = meshPrimitive.verticesStart; k < meshPrimitive.verticesCount; ++k) { + // index = meshPrimitive.indexStart + k * stride; + // const vertexData = Vector3.FromArray(meshAttributeArray, index).normalize(); + // const morphData = Vector3.FromArray(morphTargetAttributeArray, index).normalize(); + // difference = morphData.subtractToRef(vertexData, difference); + // vertexAttributes.push(difference.asArray()); + // } + // break; + // } + // case VertexBuffer.TangentKind: { + // for (let k = meshPrimitive.verticesStart; k < meshPrimitive.verticesCount; ++k) { + // index = meshPrimitive.indexStart + k * (stride + 1); + // const vertexData = Vector4.FromArray(meshAttributeArray, index); + // normalizeTangent(vertexData); + // const morphData = Vector4.FromArray(morphTargetAttributeArray, index); + // normalizeTangent(morphData); + // difference4 = morphData.subtractToRef(vertexData, difference4); + // vertexAttributes.push([difference4.x, difference4.y, difference4.z]); + // } + // break; + // } + // default: { + // Tools.Warn("Unsupported Vertex Buffer Type: " + vertexBufferKind); + // vertexAttributes = []; + // } + // } + + // let writeBinaryFunc; + // switch (attributeComponentKind) { + // case AccessorComponentType.UNSIGNED_BYTE: { + // writeBinaryFunc = dataWriter.setUInt8.bind(dataWriter); + // break; + // } + // case AccessorComponentType.UNSIGNED_SHORT: { + // writeBinaryFunc = dataWriter.setUInt16.bind(dataWriter); + // break; + // } + // case AccessorComponentType.UNSIGNED_INT: { + // writeBinaryFunc = dataWriter.setUInt32.bind(dataWriter); + // break; + // } + // case AccessorComponentType.FLOAT: { + // writeBinaryFunc = dataWriter.setFloat32.bind(dataWriter); + // break; + // } + // default: { + // Tools.Warn("Unsupported Attribute Component kind: " + attributeComponentKind); + // return; + // } + // } + + // for (const vertexAttribute of vertexAttributes) { + // for (const component of vertexAttribute) { + // writeBinaryFunc(component); + // } + // } + // } + + private _generateJSON(shouldUseGlb: boolean, bufferByteLength: number, fileName?: string, prettyPrint?: boolean): string { + const buffer: IBuffer = { byteLength: bufferByteLength }; let imageName: string; let imageData: { data: ArrayBuffer; mimeType: ImageMimeType }; let bufferView: IBufferView; - let byteOffset: number = this._totalByteLength; + let byteOffset: number = bufferByteLength; if (buffer.byteLength) { this._glTF.buffers = [buffer]; @@ -1045,80 +531,56 @@ export class _Exporter { if (image.uri) { imageData = this._imageData[image.uri]; this._orderedImageData.push(imageData); - imageName = image.uri.split(".")[0] + " image"; - bufferView = _GLTFUtilities._CreateBufferView(0, byteOffset, imageData.data.byteLength, undefined, imageName); + bufferView = createBufferView(0, byteOffset, imageData.data.byteLength, undefined); byteOffset += imageData.data.byteLength; this._bufferViews.push(bufferView); image.bufferView = this._bufferViews.length - 1; image.name = imageName; image.mimeType = imageData.mimeType; image.uri = undefined; - if (!this._glTF.images) { - this._glTF.images = []; - } - this._glTF.images.push(image); + this._glTF.images!.push(image); } }); + // Replace uri with bufferview and mime type for glb buffer.byteLength = byteOffset; } } if (!shouldUseGlb) { - buffer.uri = glTFPrefix + ".bin"; + buffer.uri = fileName + ".bin"; } - const jsonText = prettyPrint ? JSON.stringify(this._glTF, null, 2) : JSON.stringify(this._glTF); - - return jsonText; + return prettyPrint ? JSON.stringify(this._glTF, null, 2) : JSON.stringify(this._glTF); } - /** - * Generates data for .gltf and .bin files based on the glTF prefix string - * @param glTFPrefix Text to use when prefixing a glTF file - * @param dispose Dispose the exporter - * @returns GLTFData with glTF file data - */ - public _generateGLTFAsync(glTFPrefix: string, dispose = true): Promise { - return this._generateBinaryAsync().then((binaryBuffer) => { - this._extensionsOnExporting(); - const jsonText = this._generateJSON(false, glTFPrefix, true); - const bin = new Blob([binaryBuffer], { type: "application/octet-stream" }); + public async generateGLTFAsync(glTFPrefix: string): Promise { + const binaryBuffer = await this._generateBinaryAsync(); - const glTFFileName = glTFPrefix + ".gltf"; - const glTFBinFile = glTFPrefix + ".bin"; + this._extensionsOnExporting(); + const jsonText = this._generateJSON(false, binaryBuffer.byteLength, glTFPrefix, true); + const bin = new Blob([binaryBuffer], { type: "application/octet-stream" }); - const container = new GLTFData(); + const glTFFileName = glTFPrefix + ".gltf"; + const glTFBinFile = glTFPrefix + ".bin"; - container.glTFFiles[glTFFileName] = jsonText; - container.glTFFiles[glTFBinFile] = bin; + const container = new GLTFData(); - if (this._imageData) { - for (const image in this._imageData) { - container.glTFFiles[image] = new Blob([this._imageData[image].data], { type: this._imageData[image].mimeType }); - } - } + container.files[glTFFileName] = jsonText; + container.files[glTFBinFile] = bin; - if (dispose) { - this.dispose(); + if (this._imageData) { + for (const image in this._imageData) { + container.files[image] = new Blob([this._imageData[image].data], { type: this._imageData[image].mimeType }); } + } - return container; - }); + return container; } - /** - * Creates a binary buffer for glTF - * @returns array buffer for binary data - */ - private _generateBinaryAsync(): Promise { - const binaryWriter = new _BinaryWriter(4); - return this._createSceneAsync(binaryWriter).then(() => { - if (this._localEngine) { - this._localEngine.dispose(); - } - return binaryWriter.getArrayBuffer(); - }); + private async _generateBinaryAsync(): Promise { + await this._exportSceneAsync(); + return this._dataWriter.getOutputData(); } /** @@ -1133,129 +595,119 @@ export class _Exporter { return padding; } - /** - * @internal - */ - public _generateGLBAsync(glTFPrefix: string, dispose = true): Promise { - return this._generateBinaryAsync().then((binaryBuffer) => { - this._extensionsOnExporting(); - const jsonText = this._generateJSON(true); - const glbFileName = glTFPrefix + ".glb"; - const headerLength = 12; - const chunkLengthPrefix = 8; - let jsonLength = jsonText.length; - let encodedJsonText; - let imageByteLength = 0; - // make use of TextEncoder when available - if (typeof TextEncoder !== "undefined") { - const encoder = new TextEncoder(); - encodedJsonText = encoder.encode(jsonText); - jsonLength = encodedJsonText.length; - } - for (let i = 0; i < this._orderedImageData.length; ++i) { - imageByteLength += this._orderedImageData[i].data.byteLength; - } - const jsonPadding = this._getPadding(jsonLength); - const binPadding = this._getPadding(binaryBuffer.byteLength); - const imagePadding = this._getPadding(imageByteLength); - - const byteLength = headerLength + 2 * chunkLengthPrefix + jsonLength + jsonPadding + binaryBuffer.byteLength + binPadding + imageByteLength + imagePadding; - - //header - const headerBuffer = new ArrayBuffer(headerLength); - const headerBufferView = new DataView(headerBuffer); - headerBufferView.setUint32(0, 0x46546c67, true); //glTF - headerBufferView.setUint32(4, 2, true); // version - headerBufferView.setUint32(8, byteLength, true); // total bytes in file - - //json chunk - const jsonChunkBuffer = new ArrayBuffer(chunkLengthPrefix + jsonLength + jsonPadding); - const jsonChunkBufferView = new DataView(jsonChunkBuffer); - jsonChunkBufferView.setUint32(0, jsonLength + jsonPadding, true); - jsonChunkBufferView.setUint32(4, 0x4e4f534a, true); - - //json chunk bytes - const jsonData = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix); - // if TextEncoder was available, we can simply copy the encoded array - if (encodedJsonText) { - jsonData.set(encodedJsonText); - } else { - const blankCharCode = "_".charCodeAt(0); - for (let i = 0; i < jsonLength; ++i) { - const charCode = jsonText.charCodeAt(i); - // if the character doesn't fit into a single UTF-16 code unit, just put a blank character - if (charCode != jsonText.codePointAt(i)) { - jsonData[i] = blankCharCode; - } else { - jsonData[i] = charCode; - } + public async generateGLBAsync(glTFPrefix: string): Promise { + const binaryBuffer = await this._generateBinaryAsync(); + + this._extensionsOnExporting(); + const jsonText = this._generateJSON(true, binaryBuffer.byteLength); + const glbFileName = glTFPrefix + ".glb"; + const headerLength = 12; + const chunkLengthPrefix = 8; + let jsonLength = jsonText.length; + let encodedJsonText; + let imageByteLength = 0; + // make use of TextEncoder when available + if (typeof TextEncoder !== "undefined") { + const encoder = new TextEncoder(); + encodedJsonText = encoder.encode(jsonText); + jsonLength = encodedJsonText.length; + } + for (let i = 0; i < this._orderedImageData.length; ++i) { + imageByteLength += this._orderedImageData[i].data.byteLength; + } + const jsonPadding = this._getPadding(jsonLength); + const binPadding = this._getPadding(binaryBuffer.byteLength); + const imagePadding = this._getPadding(imageByteLength); + + const byteLength = headerLength + 2 * chunkLengthPrefix + jsonLength + jsonPadding + binaryBuffer.byteLength + binPadding + imageByteLength + imagePadding; + + // header + const headerBuffer = new ArrayBuffer(headerLength); + const headerBufferView = new DataView(headerBuffer); + headerBufferView.setUint32(0, 0x46546c67, true); //glTF + headerBufferView.setUint32(4, 2, true); // version + headerBufferView.setUint32(8, byteLength, true); // total bytes in file + + // json chunk + const jsonChunkBuffer = new ArrayBuffer(chunkLengthPrefix + jsonLength + jsonPadding); + const jsonChunkBufferView = new DataView(jsonChunkBuffer); + jsonChunkBufferView.setUint32(0, jsonLength + jsonPadding, true); + jsonChunkBufferView.setUint32(4, 0x4e4f534a, true); + + // json chunk bytes + const jsonData = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix); + // if TextEncoder was available, we can simply copy the encoded array + if (encodedJsonText) { + jsonData.set(encodedJsonText); + } else { + const blankCharCode = "_".charCodeAt(0); + for (let i = 0; i < jsonLength; ++i) { + const charCode = jsonText.charCodeAt(i); + // if the character doesn't fit into a single UTF-16 code unit, just put a blank character + if (charCode != jsonText.codePointAt(i)) { + jsonData[i] = blankCharCode; + } else { + jsonData[i] = charCode; } } + } - //json padding - const jsonPaddingView = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix + jsonLength); - for (let i = 0; i < jsonPadding; ++i) { - jsonPaddingView[i] = 0x20; - } - - //binary chunk - const binaryChunkBuffer = new ArrayBuffer(chunkLengthPrefix); - const binaryChunkBufferView = new DataView(binaryChunkBuffer); - binaryChunkBufferView.setUint32(0, binaryBuffer.byteLength + imageByteLength + imagePadding, true); - binaryChunkBufferView.setUint32(4, 0x004e4942, true); - - // binary padding - const binPaddingBuffer = new ArrayBuffer(binPadding); - const binPaddingView = new Uint8Array(binPaddingBuffer); - for (let i = 0; i < binPadding; ++i) { - binPaddingView[i] = 0; - } + // json padding + const jsonPaddingView = new Uint8Array(jsonChunkBuffer, chunkLengthPrefix + jsonLength); + for (let i = 0; i < jsonPadding; ++i) { + jsonPaddingView[i] = 0x20; + } - const imagePaddingBuffer = new ArrayBuffer(imagePadding); - const imagePaddingView = new Uint8Array(imagePaddingBuffer); - for (let i = 0; i < imagePadding; ++i) { - imagePaddingView[i] = 0; - } + // binary chunk + const binaryChunkBuffer = new ArrayBuffer(chunkLengthPrefix); + const binaryChunkBufferView = new DataView(binaryChunkBuffer); + binaryChunkBufferView.setUint32(0, binaryBuffer.byteLength + binPadding + imageByteLength + imagePadding, true); + binaryChunkBufferView.setUint32(4, 0x004e4942, true); - const glbData = [headerBuffer, jsonChunkBuffer, binaryChunkBuffer, binaryBuffer]; + // binary padding + const binPaddingBuffer = new ArrayBuffer(binPadding); + const binPaddingView = new Uint8Array(binPaddingBuffer); + for (let i = 0; i < binPadding; ++i) { + binPaddingView[i] = 0; + } - // binary data - for (let i = 0; i < this._orderedImageData.length; ++i) { - glbData.push(this._orderedImageData[i].data); - } + const imagePaddingBuffer = new ArrayBuffer(imagePadding); + const imagePaddingView = new Uint8Array(imagePaddingBuffer); + for (let i = 0; i < imagePadding; ++i) { + imagePaddingView[i] = 0; + } - glbData.push(binPaddingBuffer); + const glbData = [headerBuffer, jsonChunkBuffer, binaryChunkBuffer, binaryBuffer]; - glbData.push(imagePaddingBuffer); + // binary data + for (let i = 0; i < this._orderedImageData.length; ++i) { + glbData.push(this._orderedImageData[i].data); + } - const glbFile = new Blob(glbData, { type: "application/octet-stream" }); + glbData.push(binPaddingBuffer); - const container = new GLTFData(); - container.glTFFiles[glbFileName] = glbFile; + glbData.push(imagePaddingBuffer); - if (this._localEngine != null) { - this._localEngine.dispose(); - } + const glbFile = new Blob(glbData, { type: "application/octet-stream" }); - if (dispose) { - this.dispose(); - } + const container = new GLTFData(); + container.files[glbFileName] = glbFile; - return container; - }); + return container; } - /** - * Sets the TRS for each node - * @param node glTF Node for storing the transformation data - * @param babylonTransformNode Babylon mesh used as the source for the transformation data - */ - private _setNodeTransformation(node: INode, babylonTransformNode: TransformNode): void { + private _setNodeTransformation(node: INode, babylonTransformNode: TransformNode, convertToRightHanded: boolean): void { if (!babylonTransformNode.getPivotPoint().equalsToFloats(0, 0, 0)) { Tools.Warn("Pivot points are not supported in the glTF serializer"); } + if (!babylonTransformNode.position.equalsToFloats(0, 0, 0)) { - node.translation = babylonTransformNode.position.asArray(); + const translation = TmpVectors.Vector3[0].copyFrom(babylonTransformNode.position); + if (convertToRightHanded) { + convertToRightHandedPosition(translation); + } + + node.translation = translation.asArray(); } if (!babylonTransformNode.scaling.equalsToFloats(1, 1, 1)) { @@ -1267,1083 +719,959 @@ export class _Exporter { rotationQuaternion.multiplyInPlace(babylonTransformNode.rotationQuaternion); } if (!Quaternion.IsIdentity(rotationQuaternion)) { + if (convertToRightHanded) { + convertToRightHandedRotation(rotationQuaternion); + } + node.rotation = rotationQuaternion.normalize().asArray(); } } - private _setCameraTransformation(node: INode, babylonCamera: Camera): void { - const translation = TmpVectors.Vector3[0]; - const rotation = TmpVectors.Quaternion[0]; - babylonCamera.getWorldMatrix().decompose(undefined, rotation, translation); - - if (!translation.equalsToFloats(0, 0, 0)) { - node.translation = translation.asArray(); - } - - // // Rotation by 180 as glTF has a different convention than Babylon. - rotation.multiplyInPlace(rotation180Y); - - if (!Quaternion.IsIdentity(rotation)) { - node.rotation = rotation.asArray(); - } - } + // private _setCameraTransformation(node: INode, babylonCamera: Camera, convertToRightHanded: boolean): void { + // const translation = TmpVectors.Vector3[0]; + // const rotation = TmpVectors.Quaternion[0]; + // babylonCamera.getWorldMatrix().decompose(undefined, rotation, translation); + + // if (!translation.equalsToFloats(0, 0, 0)) { + // if (convertToRightHanded) { + // convertToRightHandedPosition(translation); + // } + + // node.translation = translation.asArray(); + // } + + // // Rotation by 180 as glTF has a different convention than Babylon. + // rotation.multiplyInPlace(rotation180Y); + + // if (!Quaternion.IsIdentity(rotation)) { + // if (convertToRightHanded) { + // convertToRightHandedRotation(rotation); + // } + + // node.rotation = rotation.asArray(); + // } + // } + + // /** + // * Creates a bufferview based on the vertices type for the Babylon mesh + // * @param babylonSubMesh The Babylon submesh that the morph target is applied to + // * @param meshPrimitive + // * @param babylonMorphTarget the morph target to be exported + // * @param dataWriter The buffer to write the bufferview data to + // */ + // private _setMorphTargetAttributes(babylonSubMesh: SubMesh, meshPrimitive: IMeshPrimitive, babylonMorphTarget: MorphTarget, dataWriter: DataWriter) { + // if (babylonMorphTarget) { + // if (!meshPrimitive.targets) { + // meshPrimitive.targets = []; + // } + // const target: { [attribute: string]: number } = {}; + // const mesh = babylonSubMesh.getMesh() as Mesh; + // if (babylonMorphTarget.hasNormals) { + // const vertexNormals = mesh.getVerticesData(VertexBuffer.NormalKind, undefined, undefined, true)!; + // const morphNormals = babylonMorphTarget.getNormals()!; + // const count = babylonSubMesh.verticesCount; + // const byteStride = 12; // 3 x 4 byte floats + // const byteLength = count * byteStride; + // const bufferView = createBufferView(0, dataWriter.getByteOffset(), byteLength, byteStride, babylonMorphTarget.name + "_NORMAL"); + // this._bufferViews.push(bufferView); + + // const bufferViewIndex = this._bufferViews.length - 1; + // const accessor = createAccessor(bufferViewIndex, babylonMorphTarget.name + " - " + "NORMAL", AccessorType.VEC3, AccessorComponentType.FLOAT, count, 0, null, null); + // this._accessors.push(accessor); + // target.NORMAL = this._accessors.length - 1; + + // this.writeMorphTargetAttributeData(VertexBuffer.NormalKind, AccessorComponentType.FLOAT, babylonSubMesh, vertexNormals, morphNormals, byteStride / 4, dataWriter); + // } + // if (babylonMorphTarget.hasPositions) { + // const vertexPositions = mesh.getVerticesData(VertexBuffer.PositionKind, undefined, undefined, true)!; + // const morphPositions = babylonMorphTarget.getPositions()!; + // const count = babylonSubMesh.verticesCount; + // const byteStride = 12; // 3 x 4 byte floats + // const byteLength = count * byteStride; + // const bufferView = createBufferView(0, dataWriter.getByteOffset(), byteLength, byteStride, babylonMorphTarget.name + "_POSITION"); + // this._bufferViews.push(bufferView); + + // const bufferViewIndex = this._bufferViews.length - 1; + // const minMax = { min: new Vector3(Infinity, Infinity, Infinity), max: new Vector3(-Infinity, -Infinity, -Infinity) }; + // const accessor = createAccessor( + // bufferViewIndex, + // babylonMorphTarget.name + " - " + "POSITION", + // AccessorType.VEC3, + // AccessorComponentType.FLOAT, + // count, + // 0, + // null, + // null + // ); + // this._accessors.push(accessor); + // target.POSITION = this._accessors.length - 1; + + // this.writeMorphTargetAttributeData( + // VertexBuffer.PositionKind, + // AccessorComponentType.FLOAT, + // babylonSubMesh, + // vertexPositions, + // morphPositions, + // byteStride / 4, + // dataWriter, + // minMax + // ); + // accessor.min = minMax.min!.asArray(); + // accessor.max = minMax.max!.asArray(); + // } + // if (babylonMorphTarget.hasTangents) { + // const vertexTangents = mesh.getVerticesData(VertexBuffer.TangentKind, undefined, undefined, true)!; + // const morphTangents = babylonMorphTarget.getTangents()!; + // const count = babylonSubMesh.verticesCount; + // const byteStride = 12; // 3 x 4 byte floats + // const byteLength = count * byteStride; + // const bufferView = createBufferView(0, dataWriter.getByteOffset(), byteLength, byteStride, babylonMorphTarget.name + "_NORMAL"); + // this._bufferViews.push(bufferView); + + // const bufferViewIndex = this._bufferViews.length - 1; + // const accessor = createAccessor(bufferViewIndex, babylonMorphTarget.name + " - " + "TANGENT", AccessorType.VEC3, AccessorComponentType.FLOAT, count, 0, null, null); + // this._accessors.push(accessor); + // target.TANGENT = this._accessors.length - 1; + + // this.writeMorphTargetAttributeData( + // VertexBuffer.TangentKind, + // AccessorComponentType.FLOAT, + // babylonSubMesh, + // vertexTangents, + // morphTangents, + // byteStride / 4, + // dataWriter + // ); + // } + // meshPrimitive.targets.push(target); + // } + // } + + // /** + // * Sets data for the primitive attributes of each submesh + // * @param mesh glTF Mesh object to store the primitive attribute information + // * @param babylonTransformNode Babylon mesh to get the primitive attribute data from + // * @param convertToRightHanded Whether to convert from left-handed to right-handed + // * @param dataWriter Buffer to write the attribute data to + // */ + // private _setPrimitiveAttributesAsync(mesh: IMesh, babylonTransformNode: TransformNode, convertToRightHanded: boolean, dataWriter: DataWriter): Promise { + // const promises: Promise[] = []; + // let bufferMesh: Nullable = null; + // let bufferView: IBufferView; + // let minMax: { min: Nullable; max: Nullable }; + + // if (babylonTransformNode instanceof Mesh) { + // bufferMesh = babylonTransformNode as Mesh; + // } else if (babylonTransformNode instanceof InstancedMesh) { + // bufferMesh = (babylonTransformNode as InstancedMesh).sourceMesh; + // } + // const attributeData: _IVertexAttributeData[] = [ + // { kind: VertexBuffer.PositionKind, accessorType: AccessorType.VEC3, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 12 }, + // { kind: VertexBuffer.NormalKind, accessorType: AccessorType.VEC3, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 12 }, + // { kind: VertexBuffer.ColorKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 16 }, + // { kind: VertexBuffer.TangentKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 16 }, + // { kind: VertexBuffer.UVKind, accessorType: AccessorType.VEC2, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 8 }, + // { kind: VertexBuffer.UV2Kind, accessorType: AccessorType.VEC2, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 8 }, + // { kind: VertexBuffer.MatricesIndicesKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.UNSIGNED_SHORT, byteStride: 8 }, + // { kind: VertexBuffer.MatricesIndicesExtraKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.UNSIGNED_SHORT, byteStride: 8 }, + // { kind: VertexBuffer.MatricesWeightsKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 16 }, + // { kind: VertexBuffer.MatricesWeightsExtraKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 16 }, + // ]; + + // if (bufferMesh) { + // let indexBufferViewIndex: Nullable = null; + // const primitiveMode = this._getMeshPrimitiveMode(bufferMesh); + // const vertexAttributeBufferViews: { [attributeKind: string]: number } = {}; + // const morphTargetManager = bufferMesh.morphTargetManager; + + // // For each BabylonMesh, create bufferviews for each 'kind' + // for (const attribute of attributeData) { + // const attributeKind = attribute.kind; + // const attributeComponentKind = attribute.accessorComponentType; + // if (bufferMesh.isVerticesDataPresent(attributeKind, true)) { + // const vertexBuffer = this._getVertexBuffer(attributeKind, bufferMesh); + // attribute.byteStride = vertexBuffer + // ? vertexBuffer.getSize() * VertexBuffer.GetTypeByteLength(attribute.accessorComponentType) + // : VertexBuffer.DeduceStride(attributeKind) * 4; + // if (attribute.byteStride === 12) { + // attribute.accessorType = AccessorType.VEC3; + // } + + // this._createBufferViewKind(attributeKind, attributeComponentKind, babylonTransformNode, dataWriter, attribute.byteStride); + // attribute.bufferViewIndex = this._bufferViews.length - 1; + // vertexAttributeBufferViews[attributeKind] = attribute.bufferViewIndex; + // } + // } + + // if (bufferMesh.getTotalIndices()) { + // const indices = bufferMesh.getIndices(); + // if (indices) { + // const byteLength = indices.length * 4; + // bufferView = createBufferView(0, dataWriter.getByteOffset(), byteLength, undefined, "Indices - " + bufferMesh.name); + // this._bufferViews.push(bufferView); + // indexBufferViewIndex = this._bufferViews.length - 1; + + // for (let k = 0, length = indices.length; k < length; ++k) { + // dataWriter.setUInt32(indices[k]); + // } + // } + // } + + // if (bufferMesh.subMeshes) { + // // go through all mesh primitives (submeshes) + // for (const submesh of bufferMesh.subMeshes) { + // let babylonMaterial = submesh.getMaterial() || bufferMesh.getScene().defaultMaterial; + + // let materialIndex: Nullable = null; + // if (babylonMaterial) { + // if (bufferMesh instanceof LinesMesh) { + // // get the color from the lines mesh and set it in the material + // const material: IMaterial = { + // name: bufferMesh.name + " material", + // }; + // if (!bufferMesh.color.equals(Color3.White()) || bufferMesh.alpha < 1) { + // material.pbrMetallicRoughness = { + // baseColorFactor: bufferMesh.color.asArray().concat([bufferMesh.alpha]), + // }; + // } + // this._materials.push(material); + // materialIndex = this._materials.length - 1; + // } else if (babylonMaterial instanceof MultiMaterial) { + // const subMaterial = babylonMaterial.subMaterials[submesh.materialIndex]; + // if (subMaterial) { + // babylonMaterial = subMaterial; + // materialIndex = this._materialMap[babylonMaterial.uniqueId]; + // } + // } else { + // materialIndex = this._materialMap[babylonMaterial.uniqueId]; + // } + // } + + // const glTFMaterial: Nullable = materialIndex != null ? this._materials[materialIndex] : null; + + // const meshPrimitive: IMeshPrimitive = { attributes: {} }; + // this._setPrimitiveMode(meshPrimitive, primitiveMode); + + // for (const attribute of attributeData) { + // const attributeKind = attribute.kind; + // if ((attributeKind === VertexBuffer.UVKind || attributeKind === VertexBuffer.UV2Kind) && !this._options.exportUnusedUVs) { + // if (!glTFMaterial || !this._glTFMaterialExporter._hasTexturesPresent(glTFMaterial)) { + // continue; + // } + // } + // const vertexData = bufferMesh.getVerticesData(attributeKind, undefined, undefined, true); + // if (vertexData) { + // const vertexBuffer = this._getVertexBuffer(attributeKind, bufferMesh); + // if (vertexBuffer) { + // const stride = vertexBuffer.getSize(); + // const bufferViewIndex = attribute.bufferViewIndex; + // if (bufferViewIndex != undefined) { + // // check to see if bufferviewindex has a numeric value assigned. + // minMax = { min: null, max: null }; + // if (attributeKind == VertexBuffer.PositionKind) { + // minMax = calculateMinMaxPositions(vertexData, 0, vertexData.length / stride); + // } + // const accessor = createAccessor( + // bufferViewIndex, + // attributeKind + " - " + babylonTransformNode.name, + // attribute.accessorType, + // attribute.accessorComponentType, + // vertexData.length / stride, + // 0, + // minMax.min, + // minMax.max + // ); + // this._accessors.push(accessor); + // this._setAttributeKind(meshPrimitive, attributeKind); + // } + // } + // } + // } + + // if (indexBufferViewIndex) { + // // Create accessor + // const accessor = createAccessor( + // indexBufferViewIndex, + // "indices - " + babylonTransformNode.name, + // AccessorType.SCALAR, + // AccessorComponentType.UNSIGNED_INT, + // submesh.indexCount, + // submesh.indexStart * 4, + // null, + // null + // ); + // this._accessors.push(accessor); + // meshPrimitive.indices = this._accessors.length - 1; + // } + + // if (Object.keys(meshPrimitive.attributes).length > 0) { + // const sideOrientation = bufferMesh.overrideMaterialSideOrientation !== null ? bufferMesh.overrideMaterialSideOrientation : babylonMaterial.sideOrientation; + + // if (sideOrientation === (this._babylonScene.useRightHandedSystem ? Material.ClockWiseSideOrientation : Material.CounterClockWiseSideOrientation)) { + // let byteOffset = indexBufferViewIndex != null ? this._bufferViews[indexBufferViewIndex].byteOffset : null; + // if (byteOffset == null) { + // byteOffset = 0; + // } + // let babylonIndices: Nullable = null; + // if (indexBufferViewIndex != null) { + // babylonIndices = bufferMesh.getIndices(); + // } + // if (babylonIndices) { + // this._reorderIndicesBasedOnPrimitiveMode(submesh, primitiveMode, babylonIndices, byteOffset, dataWriter); + // } else { + // for (const attribute of attributeData) { + // const vertexData = bufferMesh.getVerticesData(attribute.kind, undefined, undefined, true); + // if (vertexData) { + // const byteOffset = this._bufferViews[vertexAttributeBufferViews[attribute.kind]].byteOffset || 0; + // this._reorderVertexAttributeDataBasedOnPrimitiveMode(submesh, primitiveMode, attribute.kind, vertexData, byteOffset, dataWriter); + // } + // } + // } + // } + + // if (materialIndex != null) { + // meshPrimitive.material = materialIndex; + // } + // } + // if (morphTargetManager) { + // // By convention, morph target names are stored in the mesh extras. + // if (!mesh.extras) { + // mesh.extras = {}; + // } + // mesh.extras.targetNames = []; + + // for (let i = 0; i < morphTargetManager.numTargets; ++i) { + // const target = morphTargetManager.getTarget(i); + // this._setMorphTargetAttributes(submesh, meshPrimitive, target, dataWriter); + // mesh.extras.targetNames.push(target.name); + // } + // } + + // mesh.primitives.push(meshPrimitive); + + // this._extensionsPostExportMeshPrimitiveAsync("postExport", meshPrimitive, submesh, dataWriter); + // promises.push(); + // } + // } + // } + // return Promise.all(promises).then(() => { + // /* do nothing */ + // }); + // } + + private async _exportSceneAsync(): Promise { + const scene: IScene = { nodes: [] }; - private _getVertexBufferFromMesh(attributeKind: string, bufferMesh: Mesh): Nullable { - if (bufferMesh.isVerticesDataPresent(attributeKind, true)) { - const vertexBuffer = bufferMesh.getVertexBuffer(attributeKind, true); - if (vertexBuffer) { - return vertexBuffer; + // Scene metadata + if (this._babylonScene.metadata) { + if (this._options.metadataSelector) { + scene.extras = this._options.metadataSelector(this._babylonScene.metadata); + } else if (this._babylonScene.metadata.gltf) { + scene.extras = this._babylonScene.metadata.gltf.extras; } } - return null; - } - /** - * Creates a bufferview based on the vertices type for the Babylon mesh - * @param kind Indicates the type of vertices data - * @param attributeComponentKind Indicates the numerical type used to store the data - * @param babylonTransformNode The Babylon mesh to get the vertices data from - * @param binaryWriter The buffer to write the bufferview data to - * @param byteStride - */ - private _createBufferViewKind( - kind: string, - attributeComponentKind: AccessorComponentType, - babylonTransformNode: TransformNode, - binaryWriter: _BinaryWriter, - byteStride: number - ) { - const bufferMesh = - babylonTransformNode instanceof Mesh - ? (babylonTransformNode as Mesh) - : babylonTransformNode instanceof InstancedMesh - ? (babylonTransformNode as InstancedMesh).sourceMesh - : null; - - if (bufferMesh) { - const vertexBuffer = bufferMesh.getVertexBuffer(kind, true); - const vertexData = bufferMesh.getVerticesData(kind, undefined, undefined, true); - - if (vertexBuffer && vertexData) { - const typeByteLength = VertexBuffer.GetTypeByteLength(attributeComponentKind); - const byteLength = vertexData.length * typeByteLength; - const bufferView = _GLTFUtilities._CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, byteStride, kind + " - " + bufferMesh.name); - this._bufferViews.push(bufferView); - - this._writeAttributeData(kind, attributeComponentKind, vertexData, byteStride / typeByteLength, binaryWriter, babylonTransformNode); - } - } - } + // TODO: + // deal with this from the loader: + // babylonMaterial.invertNormalMapX = !this._babylonScene.useRightHandedSystem; + // babylonMaterial.invertNormalMapY = this._babylonScene.useRightHandedSystem; - /** - * The primitive mode of the Babylon mesh - * @param babylonMesh The BabylonJS mesh - * @returns Unsigned integer of the primitive mode or null - */ - private _getMeshPrimitiveMode(babylonMesh: AbstractMesh): number { - if (babylonMesh instanceof LinesMesh) { - return Material.LineListDrawMode; - } - if (babylonMesh instanceof InstancedMesh || babylonMesh instanceof Mesh) { - const baseMesh = babylonMesh instanceof Mesh ? babylonMesh : babylonMesh.sourceMesh; - if (typeof baseMesh.overrideRenderingFillMode === "number") { - return baseMesh.overrideRenderingFillMode; - } - } - return babylonMesh.material ? babylonMesh.material.fillMode : Material.TriangleFillMode; - } + const rootNodesRH = new Array(); + const rootNodesLH = new Array(); - /** - * Sets the primitive mode of the glTF mesh primitive - * @param meshPrimitive glTF mesh primitive - * @param primitiveMode The primitive mode - */ - private _setPrimitiveMode(meshPrimitive: IMeshPrimitive, primitiveMode: number) { - switch (primitiveMode) { - case Material.TriangleFillMode: { - // glTF defaults to using Triangle Mode - break; - } - case Material.TriangleStripDrawMode: { - meshPrimitive.mode = MeshPrimitiveMode.TRIANGLE_STRIP; - break; - } - case Material.TriangleFanDrawMode: { - meshPrimitive.mode = MeshPrimitiveMode.TRIANGLE_FAN; - break; - } - case Material.PointListDrawMode: { - meshPrimitive.mode = MeshPrimitiveMode.POINTS; - break; - } - case Material.PointFillMode: { - meshPrimitive.mode = MeshPrimitiveMode.POINTS; - break; - } - case Material.LineLoopDrawMode: { - meshPrimitive.mode = MeshPrimitiveMode.LINE_LOOP; - break; - } - case Material.LineListDrawMode: { - meshPrimitive.mode = MeshPrimitiveMode.LINES; - break; - } - case Material.LineStripDrawMode: { - meshPrimitive.mode = MeshPrimitiveMode.LINE_STRIP; - break; - } + for (const rootNode of this._babylonScene.rootNodes) { + if (this._options.removeNoopRootNodes && !this._options.includeCoordinateSystemConversionNodes && isNoopNode(rootNode, this._babylonScene.useRightHandedSystem)) { + rootNodesRH.push(...rootNode.getChildren()); + } else if (this._babylonScene.useRightHandedSystem) { + rootNodesRH.push(rootNode); + } else { + rootNodesLH.push(rootNode); + } + } + + // // Export babylon cameras to glTF cameras + // const cameraMap = new Map(); + // for (const camera of cameras) { + // const glTFCamera: ICamera = { + // type: camera.mode === Camera.PERSPECTIVE_CAMERA ? CameraType.PERSPECTIVE : CameraType.ORTHOGRAPHIC, + // }; + + // if (camera.name) { + // glTFCamera.name = camera.name; + // } + + // if (glTFCamera.type === CameraType.PERSPECTIVE) { + // glTFCamera.perspective = { + // aspectRatio: camera.getEngine().getAspectRatio(camera), + // yfov: camera.fovMode === Camera.FOVMODE_VERTICAL_FIXED ? camera.fov : camera.fov * camera.getEngine().getAspectRatio(camera), + // znear: camera.minZ, + // zfar: camera.maxZ, + // }; + // } else if (glTFCamera.type === CameraType.ORTHOGRAPHIC) { + // const halfWidth = camera.orthoLeft && camera.orthoRight ? 0.5 * (camera.orthoRight - camera.orthoLeft) : camera.getEngine().getRenderWidth() * 0.5; + // const halfHeight = camera.orthoBottom && camera.orthoTop ? 0.5 * (camera.orthoTop - camera.orthoBottom) : camera.getEngine().getRenderHeight() * 0.5; + // glTFCamera.orthographic = { + // xmag: halfWidth, + // ymag: halfHeight, + // znear: camera.minZ, + // zfar: camera.maxZ, + // }; + // } + + // cameraMap.set(camera, this._cameras.length); + // this._cameras.push(glTFCamera); + // } + + // await this._materialExporter.convertMaterialsToGLTFAsync(this._getMaterials(nodes)); + + scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, true))); + scene.nodes.push(...(await this._exportNodesAsync(rootNodesRH, false))); + this._scenes.push(scene); + + // return this._exportNodesAndAnimationsAsync(nodes, convertToRightHandedMap, dataWriter).then((nodeMap) => { + // return this._createSkinsAsync(nodeMap, dataWriter).then((skinMap) => { + // for (const babylonNode of nodes) { + // const glTFNodeIndex = nodeMap[babylonNode.uniqueId]; + // if (glTFNodeIndex !== undefined) { + // const glTFNode = this._nodes[glTFNodeIndex]; + + // if (babylonNode.metadata) { + // if (this._options.metadataSelector) { + // glTFNode.extras = this._options.metadataSelector(babylonNode.metadata); + // } else if (babylonNode.metadata.gltf) { + // glTFNode.extras = babylonNode.metadata.gltf.extras; + // } + // } + + // if (babylonNode instanceof Camera) { + // glTFNode.camera = cameraMap.get(babylonNode); + // } + + // if (!babylonNode.parent || removedRootNodes.has(babylonNode.parent)) { + // scene.nodes.push(glTFNodeIndex); + // } + + // if (babylonNode instanceof Mesh) { + // if (babylonNode.skeleton) { + // glTFNode.skin = skinMap[babylonNode.skeleton.uniqueId]; + // } + // } + + // const directDescendents = babylonNode.getDescendants(true); + // if (!glTFNode.children && directDescendents && directDescendents.length) { + // const children: number[] = []; + // for (const descendent of directDescendents) { + // if (nodeMap[descendent.uniqueId] != null) { + // children.push(nodeMap[descendent.uniqueId]); + // } + // } + // if (children.length) { + // glTFNode.children = children; + // } + // } + // } + // } + + // if (scene.nodes.length) { + // this._scenes.push(scene); + // } + // }); + // }); + // }); + } + + private _shouldExportNode(babylonNode: Node): boolean { + let result = this._shouldExportNodeMap.get(babylonNode); + + if (result === undefined) { + result = this._options.shouldExportNode(babylonNode); + this._shouldExportNodeMap.set(babylonNode, result); } - } - /** - * Sets the vertex attribute accessor based of the glTF mesh primitive - * @param meshPrimitive glTF mesh primitive - * @param attributeKind vertex attribute - */ - private _setAttributeKind(attributes: { [name: string]: number }, attributeKind: string): void { - switch (attributeKind) { - case VertexBuffer.PositionKind: { - attributes.POSITION = this._accessors.length - 1; - break; - } - case VertexBuffer.NormalKind: { - attributes.NORMAL = this._accessors.length - 1; - break; - } - case VertexBuffer.ColorKind: { - attributes.COLOR_0 = this._accessors.length - 1; - break; - } - case VertexBuffer.TangentKind: { - attributes.TANGENT = this._accessors.length - 1; - break; - } - case VertexBuffer.UVKind: { - attributes.TEXCOORD_0 = this._accessors.length - 1; - break; - } - case VertexBuffer.UV2Kind: { - attributes.TEXCOORD_1 = this._accessors.length - 1; - break; - } - case VertexBuffer.MatricesIndicesKind: { - attributes.JOINTS_0 = this._accessors.length - 1; - break; - } - case VertexBuffer.MatricesIndicesExtraKind: { - attributes.JOINTS_1 = this._accessors.length - 1; - break; - } - case VertexBuffer.MatricesWeightsKind: { - attributes.WEIGHTS_0 = this._accessors.length - 1; - break; - } - case VertexBuffer.MatricesWeightsExtraKind: { - attributes.WEIGHTS_1 = this._accessors.length - 1; - break; - } - default: { - Tools.Warn("Unsupported Vertex Buffer Type: " + attributeKind); - } + return result; + } + + private async _exportNodesAsync(babylonRootNodes: Node[], convertToRightHanded: boolean): Promise { + const nodes = new Array(); + + const state = new ExporterState(convertToRightHanded); + + this._exportBuffers(babylonRootNodes, convertToRightHanded, state); + + for (const babylonNode of babylonRootNodes) { + if (this._shouldExportNode(babylonNode)) { + nodes.push(await this._exportNodeAsync(babylonNode, state)); + } + } + + return nodes; + } + + private _collectBuffers(babylonNode: Node, bufferToVertexBuffersMap: Map, vertexBufferToMeshesMap: Map): void { + if (!this._shouldExportNode(babylonNode)) { + return; } - } - /** - * Sets data for the primitive attributes of each submesh - * @param mesh glTF Mesh object to store the primitive attribute information - * @param babylonTransformNode Babylon mesh to get the primitive attribute data from - * @param binaryWriter Buffer to write the attribute data to - * @returns promise that resolves when done setting the primitive attributes - */ - private _setPrimitiveAttributesAsync(mesh: IMesh, babylonTransformNode: TransformNode, binaryWriter: _BinaryWriter): Promise { - const promises: Promise[] = []; - let bufferMesh: Nullable = null; - let bufferView: IBufferView; - let minMax: { min: Nullable; max: Nullable }; - - if (babylonTransformNode instanceof Mesh) { - bufferMesh = babylonTransformNode as Mesh; - } else if (babylonTransformNode instanceof InstancedMesh) { - bufferMesh = (babylonTransformNode as InstancedMesh).sourceMesh; - } - const attributeData: _IVertexAttributeData[] = [ - { kind: VertexBuffer.PositionKind, accessorType: AccessorType.VEC3, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 12 }, - { kind: VertexBuffer.NormalKind, accessorType: AccessorType.VEC3, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 12 }, - { kind: VertexBuffer.ColorKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 16 }, - { kind: VertexBuffer.TangentKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 16 }, - { kind: VertexBuffer.UVKind, accessorType: AccessorType.VEC2, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 8 }, - { kind: VertexBuffer.UV2Kind, accessorType: AccessorType.VEC2, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 8 }, - { kind: VertexBuffer.MatricesIndicesKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.UNSIGNED_SHORT, byteStride: 8 }, - { kind: VertexBuffer.MatricesIndicesExtraKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.UNSIGNED_SHORT, byteStride: 8 }, - { kind: VertexBuffer.MatricesWeightsKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 16 }, - { kind: VertexBuffer.MatricesWeightsExtraKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 16 }, - ]; - - if (bufferMesh) { - let indexBufferViewIndex: Nullable = null; - const primitiveMode = this._getMeshPrimitiveMode(bufferMesh); - const vertexAttributeBufferViews: { [attributeKind: string]: number } = {}; - const morphTargetManager = bufferMesh.morphTargetManager; - - // For each BabylonMesh, create bufferviews for each 'kind' - for (const attribute of attributeData) { - const attributeKind = attribute.kind; - const attributeComponentKind = attribute.accessorComponentType; - if (bufferMesh.isVerticesDataPresent(attributeKind, true)) { - const vertexBuffer = this._getVertexBufferFromMesh(attributeKind, bufferMesh); - attribute.byteStride = vertexBuffer - ? vertexBuffer.getSize() * VertexBuffer.GetTypeByteLength(attribute.accessorComponentType) - : VertexBuffer.DeduceStride(attributeKind) * 4; - if (attribute.byteStride === 12) { - attribute.accessorType = AccessorType.VEC3; - } + if (babylonNode instanceof Mesh && babylonNode.geometry) { + const vertexBuffers = babylonNode.geometry.getVertexBuffers(); + if (vertexBuffers) { + for (const kind in vertexBuffers) { + const vertexBuffer = vertexBuffers[kind]; - this._createBufferViewKind(attributeKind, attributeComponentKind, babylonTransformNode, binaryWriter, attribute.byteStride); - attribute.bufferViewIndex = this._bufferViews.length - 1; - vertexAttributeBufferViews[attributeKind] = attribute.bufferViewIndex; - - // Write any morph target data to the buffer and create an associated buffer view - if (morphTargetManager) { - for (let i = 0; i < morphTargetManager.numTargets; ++i) { - const morphTarget = morphTargetManager.getTarget(i); - const morphTargetInfo = this._createMorphTargetBufferViewKind( - attributeKind, - attribute.accessorType, - attributeComponentKind, - bufferMesh, - morphTarget, - binaryWriter, - attribute.byteStride - ); - - // Store info about the morph target that will be needed later when creating per-submesh accessors - if (morphTargetInfo) { - if (!attribute.morphTargetInfo) { - attribute.morphTargetInfo = []; - } - attribute.morphTargetInfo[i] = morphTargetInfo; - } - } + const buffer = vertexBuffer._buffer; + const vertexBufferArray = bufferToVertexBuffersMap.get(buffer) || []; + bufferToVertexBuffersMap.set(buffer, vertexBufferArray); + if (vertexBufferArray.indexOf(vertexBuffer) === -1) { + vertexBufferArray.push(vertexBuffer); } - } - } - - if (bufferMesh.getTotalIndices()) { - const indices = bufferMesh.getIndices(); - if (indices) { - const byteLength = indices.length * 4; - bufferView = _GLTFUtilities._CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, "Indices - " + bufferMesh.name); - this._bufferViews.push(bufferView); - indexBufferViewIndex = this._bufferViews.length - 1; - for (let k = 0, length = indices.length; k < length; ++k) { - binaryWriter.setUInt32(indices[k]); + const meshes = vertexBufferToMeshesMap.get(vertexBuffer) || []; + vertexBufferToMeshesMap.set(vertexBuffer, meshes); + if (meshes.indexOf(babylonNode) === -1) { + meshes.push(babylonNode); } } } + } - if (bufferMesh.subMeshes) { - // go through all mesh primitives (submeshes) - for (const submesh of bufferMesh.subMeshes) { - let babylonMaterial = submesh.getMaterial() || bufferMesh.getScene().defaultMaterial; - - let materialIndex: Nullable = null; - if (babylonMaterial) { - if (bufferMesh instanceof LinesMesh) { - // get the color from the lines mesh and set it in the material - const material: IMaterial = { - name: bufferMesh.name + " material", - }; - if (!bufferMesh.color.equals(Color3.White()) || bufferMesh.alpha < 1) { - material.pbrMetallicRoughness = { - baseColorFactor: bufferMesh.color.asArray().concat([bufferMesh.alpha]), - }; - } - this._materials.push(material); - materialIndex = this._materials.length - 1; - } else if (babylonMaterial instanceof MultiMaterial) { - const subMaterial = babylonMaterial.subMaterials[submesh.materialIndex]; - if (subMaterial) { - babylonMaterial = subMaterial; - materialIndex = this._materialMap[babylonMaterial.uniqueId]; - } - } else { - materialIndex = this._materialMap[babylonMaterial.uniqueId]; - } - } - - const glTFMaterial: Nullable = materialIndex != null ? this._materials[materialIndex] : null; + for (const babylonChildNode of babylonNode.getChildren()) { + this._collectBuffers(babylonChildNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap); + } + } - const meshPrimitive: IMeshPrimitive = { attributes: {} }; - this._setPrimitiveMode(meshPrimitive, primitiveMode); + private _exportBuffers(babylonRootNodes: Node[], convertToRightHanded: boolean, state: ExporterState): void { + const bufferToVertexBuffersMap = new Map(); + const vertexBufferToMeshesMap = new Map(); - for (const attribute of attributeData) { - const attributeKind = attribute.kind; - if ((attributeKind === VertexBuffer.UVKind || attributeKind === VertexBuffer.UV2Kind) && !this._options.exportUnusedUVs) { - if (!glTFMaterial || !this._glTFMaterialExporter._hasTexturesPresent(glTFMaterial)) { - continue; - } - } - const vertexData = bufferMesh.getVerticesData(attributeKind, undefined, undefined, true); - if (vertexData) { - const vertexBuffer = this._getVertexBufferFromMesh(attributeKind, bufferMesh); - if (vertexBuffer) { - const stride = vertexBuffer.getSize(); - const bufferViewIndex = attribute.bufferViewIndex; - if (bufferViewIndex != undefined) { - // check to see if bufferviewindex has a numeric value assigned. - minMax = { min: null, max: null }; - if (attributeKind == VertexBuffer.PositionKind) { - minMax = _GLTFUtilities._CalculateMinMaxPositions(vertexData, 0, vertexData.length / stride); - } - const accessor = _GLTFUtilities._CreateAccessor( - bufferViewIndex, - attributeKind + " - " + babylonTransformNode.name, - attribute.accessorType, - attribute.accessorComponentType, - vertexData.length / stride, - 0, - minMax.min, - minMax.max - ); - this._accessors.push(accessor); - this._setAttributeKind(meshPrimitive.attributes, attributeKind); - } - } - } - } + for (const babylonNode of babylonRootNodes) { + this._collectBuffers(babylonNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap); + } - if (indexBufferViewIndex) { - // Create accessor - const accessor = _GLTFUtilities._CreateAccessor( - indexBufferViewIndex, - "indices - " + babylonTransformNode.name, - AccessorType.SCALAR, - AccessorComponentType.UNSIGNED_INT, - submesh.indexCount, - submesh.indexStart * 4, - null, - null - ); - this._accessors.push(accessor); - meshPrimitive.indices = this._accessors.length - 1; - } + for (const [buffer, vertexBuffers] of bufferToVertexBuffersMap) { + const data = buffer.getData(); + if (!data) { + throw new Error("Buffer data is not available"); + } - if (Object.keys(meshPrimitive.attributes).length > 0) { - const sideOrientation = babylonMaterial._getEffectiveOrientation(bufferMesh); + const byteStride = vertexBuffers[0].byteStride; + if (vertexBuffers.some((vertexBuffer) => vertexBuffer.byteStride !== byteStride)) { + throw new Error("Vertex buffers pointing to the same buffer must have the same byte stride"); + } - if (sideOrientation === (this._babylonScene.useRightHandedSystem ? Material.ClockWiseSideOrientation : Material.CounterClockWiseSideOrientation)) { - let byteOffset = indexBufferViewIndex != null ? this._bufferViews[indexBufferViewIndex].byteOffset : null; - if (byteOffset == null) { - byteOffset = 0; - } - let babylonIndices: Nullable = null; - if (indexBufferViewIndex != null) { - babylonIndices = bufferMesh.getIndices(); - } - if (babylonIndices) { - this._reorderIndicesBasedOnPrimitiveMode(submesh, primitiveMode, babylonIndices, byteOffset, binaryWriter); - } else { - for (const attribute of attributeData) { - const vertexData = bufferMesh.getVerticesData(attribute.kind, undefined, undefined, true); - if (vertexData) { - const byteOffset = this._bufferViews[vertexAttributeBufferViews[attribute.kind]].byteOffset || 0; - this._reorderVertexAttributeDataBasedOnPrimitiveMode(submesh, primitiveMode, attribute.kind, vertexData, byteOffset, binaryWriter); - } - } - } - } + const bytes = dataArrayToUint8Array(data).slice(); - if (materialIndex != null) { - meshPrimitive.material = materialIndex; + // Normalize normals and tangents. + for (const vertexBuffer of vertexBuffers) { + switch (vertexBuffer.getKind()) { + case VertexBuffer.NormalKind: + case VertexBuffer.TangentKind: { + for (const mesh of vertexBufferToMeshesMap.get(vertexBuffer)!) { + const { byteOffset, byteStride, type, normalized } = vertexBuffer; + const size = vertexBuffer.getSize(); + enumerateFloatValues(bytes, byteOffset, byteStride, size, type, mesh.getTotalVertices() * size, normalized, (values) => { + const invLength = 1 / Math.sqrt(values[0] * values[0] + values[1] * values[1] + values[2] * values[2]); + values[0] *= invLength; + values[1] *= invLength; + values[2] *= invLength; + }); } } + } + } - // If there are morph targets, write out targets and associated accessors - if (morphTargetManager) { - // By convention, morph target names are stored in the mesh extras. - if (!mesh.extras) { - mesh.extras = {}; - } - mesh.extras.targetNames = []; - - for (let i = 0; i < morphTargetManager.numTargets; ++i) { - const morphTarget = morphTargetManager.getTarget(i); - mesh.extras.targetNames.push(morphTarget.name); - - for (const attribute of attributeData) { - const morphTargetInfo = attribute.morphTargetInfo?.[i]; - if (morphTargetInfo) { - // Write the accessor - const byteOffset = 0; - const accessor = _GLTFUtilities._CreateAccessor( - morphTargetInfo.bufferViewIndex, - `${attribute.kind} - ${morphTarget.name} (Morph Target)`, - morphTargetInfo.accessorType, - attribute.accessorComponentType, - morphTargetInfo.vertexCount, - byteOffset, - morphTargetInfo.minMax?.min?.asArray() ?? null, - morphTargetInfo.minMax?.max?.asArray() ?? null - ); - this._accessors.push(accessor); - - // Create a target that references the new accessor - if (!meshPrimitive.targets) { - meshPrimitive.targets = []; - } - - if (!meshPrimitive.targets[i]) { - meshPrimitive.targets[i] = {}; - } - - this._setAttributeKind(meshPrimitive.targets[i], attribute.kind); - } + if (convertToRightHanded) { + for (const vertexBuffer of vertexBuffers) { + switch (vertexBuffer.getKind()) { + case VertexBuffer.PositionKind: + case VertexBuffer.NormalKind: + case VertexBuffer.TangentKind: { + for (const mesh of vertexBufferToMeshesMap.get(vertexBuffer)!) { + const { byteOffset, byteStride, type, normalized } = vertexBuffer; + const size = vertexBuffer.getSize(); + enumerateFloatValues(bytes, byteOffset, byteStride, size, type, mesh.getTotalVertices() * size, normalized, (values) => { + values[0] = -values[0]; + }); } } } - - mesh.primitives.push(meshPrimitive); - - this._extensionsPostExportMeshPrimitiveAsync("postExport", meshPrimitive, submesh, binaryWriter); - promises.push(); } + + // Save converted bytes for min/max computation. + state.convertedToRightHandedBuffers.set(buffer, bytes); } + + const byteOffset = this._dataWriter.byteOffset; + this._dataWriter.writeUint8Array(bytes); + this._bufferViews.push(createBufferView(0, byteOffset, bytes.length, byteStride)); + state.setVertexBufferView(buffer, this._bufferViews.length - 1); } - return Promise.all(promises).then(() => { - /* do nothing */ - }); } - /** - * Creates a glTF scene based on the array of meshes - * Returns the total byte offset - * @param binaryWriter Buffer to write binary data to - * @returns a promise that resolves when done - */ - private _createSceneAsync(binaryWriter: _BinaryWriter): Promise { - const scene: IScene = { nodes: [] }; - let glTFNodeIndex: number; - let glTFNode: INode; - let directDescendents: Node[]; - const nodes: Node[] = [...this._babylonScene.transformNodes, ...this._babylonScene.meshes, ...this._babylonScene.lights, ...this._babylonScene.cameras]; - const removedRootNodes = new Set(); + private async _exportNodeAsync(babylonNode: Node, state: ExporterState): Promise { + let nodeIndex = this._nodeMap.get(babylonNode); + if (nodeIndex !== undefined) { + return nodeIndex; + } - // Scene metadata - if (this._babylonScene.metadata) { - if (this._options.metadataSelector) { - scene.extras = this._options.metadataSelector(this._babylonScene.metadata); - } else if (this._babylonScene.metadata.gltf) { - scene.extras = this._babylonScene.metadata.gltf.extras; - } + const node: INode = {}; + nodeIndex = this._nodes.length; + this._nodes.push(node); + this._nodeMap.set(babylonNode, nodeIndex); + + if (babylonNode.name) { + node.name = babylonNode.name; } - // Remove no-op root nodes - if ((this._options.removeNoopRootNodes ?? true) && !this._options.includeCoordinateSystemConversionNodes) { - for (const rootNode of this._babylonScene.rootNodes) { - if (isNoopNode(rootNode, this._babylonScene.useRightHandedSystem)) { - removedRootNodes.add(rootNode); + if (babylonNode instanceof TransformNode) { + this._setNodeTransformation(node, babylonNode, state.convertToRightHanded); - // Exclude the node from list of nodes to export - nodes.splice(nodes.indexOf(rootNode), 1); + if (babylonNode instanceof Mesh || babylonNode instanceof InstancedMesh) { + const babylonMesh = babylonNode instanceof Mesh ? babylonNode : babylonNode.sourceMesh; + if (babylonMesh.subMeshes && babylonMesh.subMeshes.length > 0) { + node.mesh = await this._exportMeshAsync(babylonMesh, state); } + } else if (babylonNode instanceof Camera) { + // TODO: handle camera + } else { + // TODO: handle other Babylon node types } } - // Export babylon cameras to glTFCamera - const cameraMap = new Map(); - this._babylonScene.cameras.forEach((camera) => { - if (this._options.shouldExportNode && !this._options.shouldExportNode(camera)) { - return; + for (const babylonChildNode of babylonNode.getChildren()) { + if (this._shouldExportNode(babylonChildNode)) { + node.children ||= []; + node.children.push(await this._exportNodeAsync(babylonChildNode, state)); } + } - const glTFCamera: ICamera = { - type: camera.mode === Camera.PERSPECTIVE_CAMERA ? CameraType.PERSPECTIVE : CameraType.ORTHOGRAPHIC, - }; + return nodeIndex; + } - if (camera.name) { - glTFCamera.name = camera.name; - } + private _exportIndices( + indices: Nullable, + start: number, + count: number, + offset: number, + fillMode: number, + sideOrientation: number, + state: ExporterState, + primitive: IMeshPrimitive + ): void { + const is32Bits = areIndices32Bits(indices, count); + let indicesToExport = indices; - if (glTFCamera.type === CameraType.PERSPECTIVE) { - glTFCamera.perspective = { - aspectRatio: camera.getEngine().getAspectRatio(camera), - yfov: camera.fovMode === Camera.FOVMODE_VERTICAL_FIXED ? camera.fov : camera.fov * camera.getEngine().getAspectRatio(camera), - znear: camera.minZ, - zfar: camera.maxZ, - }; - } else if (glTFCamera.type === CameraType.ORTHOGRAPHIC) { - const halfWidth = camera.orthoLeft && camera.orthoRight ? 0.5 * (camera.orthoRight - camera.orthoLeft) : camera.getEngine().getRenderWidth() * 0.5; - const halfHeight = camera.orthoBottom && camera.orthoTop ? 0.5 * (camera.orthoTop - camera.orthoBottom) : camera.getEngine().getRenderHeight() * 0.5; - glTFCamera.orthographic = { - xmag: halfWidth, - ymag: halfHeight, - znear: camera.minZ, - zfar: camera.maxZ, - }; - } + primitive.mode = getPrimitiveMode(fillMode); - cameraMap.set(camera, this._cameras.length); - this._cameras.push(glTFCamera); - }); + // Flip if triangle winding order is not CCW as glTF is always CCW. + const flip = isTriangleFillMode(fillMode) && sideOrientation !== Material.CounterClockWiseSideOrientation; + if (flip) { + if (fillMode === Material.TriangleStripDrawMode || fillMode === Material.TriangleFanDrawMode) { + throw new Error("Triangle strip/fan fill mode is not implemented"); + } - const [exportNodes, exportMaterials] = this._getExportNodes(nodes); - return this._glTFMaterialExporter._convertMaterialsToGLTFAsync(exportMaterials, ImageMimeType.PNG, true).then(() => { - return this._createNodeMapAndAnimationsAsync(exportNodes, binaryWriter).then((nodeMap) => { - return this._createSkinsAsync(nodeMap, binaryWriter).then((skinMap) => { - this._nodeMap = nodeMap; + primitive.mode = getPrimitiveMode(fillMode); - this._totalByteLength = binaryWriter.getByteOffset(); - if (this._totalByteLength == undefined) { - throw new Error("undefined byte length!"); - } + const newIndices = is32Bits ? new Uint32Array(count) : new Uint16Array(count); - // Build Hierarchy with the node map. - for (const babylonNode of nodes) { - glTFNodeIndex = this._nodeMap[babylonNode.uniqueId]; - if (glTFNodeIndex !== undefined) { - glTFNode = this._nodes[glTFNodeIndex]; - - if (babylonNode.metadata) { - if (this._options.metadataSelector) { - glTFNode.extras = this._options.metadataSelector(babylonNode.metadata); - } else if (babylonNode.metadata.gltf) { - glTFNode.extras = babylonNode.metadata.gltf.extras; - } - } + if (indices) { + for (let i = 0; i + 2 < count; i += 3) { + newIndices[i] = indices[start + i] + offset; + newIndices[i + 1] = indices[start + i + 2] + offset; + newIndices[i + 2] = indices[start + i + 1] + offset; + } + } else { + for (let i = 0; i + 2 < count; i += 3) { + newIndices[i] = i; + newIndices[i + 1] = i + 2; + newIndices[i + 2] = i + 1; + } + } - if (babylonNode instanceof Camera) { - glTFNode.camera = cameraMap.get(babylonNode); - } + indicesToExport = newIndices; + } else if (indices && offset !== 0) { + const newIndices = is32Bits ? new Uint32Array(count) : new Uint16Array(count); + for (let i = 0; i < count; i++) { + newIndices[i] = indices[start + i] + offset; + } - if (this._options.shouldExportNode && !this._options.shouldExportNode(babylonNode)) { - Tools.Log("Omitting " + babylonNode.name + " from scene."); - } else { - if (!babylonNode.parent && !this._babylonScene.useRightHandedSystem) { - convertNodeHandedness(glTFNode); - } + indicesToExport = newIndices; + } - if (!babylonNode.parent || removedRootNodes.has(babylonNode.parent)) { - scene.nodes.push(glTFNodeIndex); - } - } + if (indicesToExport) { + let accessorIndex = state.getIndicesAccessor(indices, start, count, offset, flip); + if (accessorIndex === undefined) { + const bufferViewByteOffset = this._dataWriter.byteOffset; + const bytes = indicesArrayToUint8Array(indicesToExport, start, count, is32Bits); + this._dataWriter.writeUint8Array(bytes); + this._bufferViews.push(createBufferView(0, bufferViewByteOffset, bytes.length)); + const bufferViewIndex = this._bufferViews.length - 1; - if (babylonNode instanceof Mesh) { - if (babylonNode.skeleton) { - glTFNode.skin = skinMap[babylonNode.skeleton.uniqueId]; - } - } + const componentType = is32Bits ? AccessorComponentType.UNSIGNED_INT : AccessorComponentType.UNSIGNED_SHORT; + this._accessors.push(createAccessor(bufferViewIndex, AccessorType.SCALAR, componentType, count, 0)); + accessorIndex = this._accessors.length - 1; + state.setIndicesAccessor(indices, start, count, offset, flip, accessorIndex); + } - directDescendents = babylonNode.getDescendants(true); - if (!glTFNode.children && directDescendents && directDescendents.length) { - const children: number[] = []; - for (const descendent of directDescendents) { - if (this._nodeMap[descendent.uniqueId] != null) { - children.push(this._nodeMap[descendent.uniqueId]); - } - } - if (children.length) { - glTFNode.children = children; - } - } - } - } - if (scene.nodes.length) { - this._scenes.push(scene); - } - }); - }); - }); + primitive.indices = accessorIndex; + } } - /** - * Getting the nodes and materials that would be exported. - * @param nodes Babylon transform nodes - * @returns Set of materials which would be exported. - */ - private _getExportNodes(nodes: Node[]): [Node[], Set] { - const exportNodes: Node[] = []; - const exportMaterials: Set = new Set(); - - for (const babylonNode of nodes) { - if (!this._options.shouldExportNode || this._options.shouldExportNode(babylonNode)) { - exportNodes.push(babylonNode); - - const babylonMesh = babylonNode as AbstractMesh; - if (babylonMesh.subMeshes && babylonMesh.subMeshes.length > 0) { - const material = babylonMesh.material || babylonMesh.getScene().defaultMaterial; - if (material instanceof MultiMaterial) { - for (const subMaterial of material.subMaterials) { - if (subMaterial) { - exportMaterials.add(subMaterial); - } - } - } else { - exportMaterials.add(material); - } - } - } else { - `Excluding node ${babylonNode.name}`; + private _exportVertexBuffer(vertexBuffer: VertexBuffer, babylonMaterial: Material, start: number, count: number, state: ExporterState, primitive: IMeshPrimitive): void { + const kind = vertexBuffer.getKind(); + if (kind.startsWith("uv") && !this._options.exportUnusedUVs) { + if (!babylonMaterial || !this._materialNeedsUVsSet.has(babylonMaterial)) { + return; } } - return [exportNodes, exportMaterials]; - } + // TODO: StandardMaterial color spaces + // probably have to create new buffer view to store new colors during collectBuffers and figure out if only standardMaterial is using it + // separate map by color space - /** - * Creates a mapping of Node unique id to node index and handles animations - * @param nodes Babylon transform nodes - * @param binaryWriter Buffer to write binary data to - * @returns Node mapping of unique id to index - */ - private _createNodeMapAndAnimationsAsync(nodes: Node[], binaryWriter: _BinaryWriter): Promise<{ [key: number]: number }> { - let promiseChain = Promise.resolve(); - const nodeMap: { [key: number]: number } = {}; - let nodeIndex: number; - const runtimeGLTFAnimation: IAnimation = { - name: "runtime animations", - channels: [], - samplers: [], - }; - const idleGLTFAnimations: IAnimation[] = []; - - for (const babylonNode of nodes) { - promiseChain = promiseChain.then(() => { - return this._createNodeAsync(babylonNode, binaryWriter).then((node) => { - const promise = this._extensionsPostExportNodeAsync("createNodeAsync", node, babylonNode, nodeMap, binaryWriter); - if (promise == null) { - Tools.Warn(`Not exporting node ${babylonNode.name}`); - return Promise.resolve(); - } else { - return promise.then((node) => { - if (!node) { - return; - } - this._nodes.push(node); - nodeIndex = this._nodes.length - 1; - nodeMap[babylonNode.uniqueId] = nodeIndex; - - if (!this._babylonScene.animationGroups.length) { - _GLTFAnimation._CreateMorphTargetAnimationFromMorphTargetAnimations( - babylonNode, - runtimeGLTFAnimation, - idleGLTFAnimations, - nodeMap, - this._nodes, - binaryWriter, - this._bufferViews, - this._accessors, - this._animationSampleRate, - this._options.shouldExportAnimation - ); - if (babylonNode.animations.length) { - _GLTFAnimation._CreateNodeAnimationFromNodeAnimations( - babylonNode, - runtimeGLTFAnimation, - idleGLTFAnimations, - nodeMap, - this._nodes, - binaryWriter, - this._bufferViews, - this._accessors, - this._animationSampleRate, - this._options.shouldExportAnimation - ); - } - } - }); - } - }); - }); + let accessorIndex = state.getVertexAccessor(vertexBuffer, start, count); + if (accessorIndex === undefined) { + // Get min/max from converted or original data. + const data = state.convertedToRightHandedBuffers.get(vertexBuffer._buffer) || vertexBuffer._buffer.getData()!; + const minMax = kind === VertexBuffer.PositionKind ? getMinMax(data, vertexBuffer, start, count) : null; + + const bufferViewIndex = state.getVertexBufferView(vertexBuffer._buffer)!; + const byteOffset = vertexBuffer.byteOffset + start * vertexBuffer.byteStride; + this._accessors.push(createAccessor(bufferViewIndex, getAccessorType(kind), vertexBuffer.type, count, byteOffset, minMax)); + accessorIndex = this._accessors.length - 1; } - return promiseChain.then(() => { - if (runtimeGLTFAnimation.channels.length && runtimeGLTFAnimation.samplers.length) { - this._animations.push(runtimeGLTFAnimation); - } - idleGLTFAnimations.forEach((idleGLTFAnimation) => { - if (idleGLTFAnimation.channels.length && idleGLTFAnimation.samplers.length) { - this._animations.push(idleGLTFAnimation); - } - }); - - if (this._babylonScene.animationGroups.length) { - _GLTFAnimation._CreateNodeAndMorphAnimationFromAnimationGroups( - this._babylonScene, - this._animations, - nodeMap, - binaryWriter, - this._bufferViews, - this._accessors, - this._animationSampleRate, - this._options.shouldExportAnimation - ); - } + state.setVertexAccessor(vertexBuffer, start, count, accessorIndex); - return nodeMap; - }); + primitive.attributes[getAttributeType(kind)] = accessorIndex; } - /** - * Creates a glTF node from a Babylon mesh - * @param babylonNode Source Babylon mesh - * @param binaryWriter Buffer for storing geometry data - * @returns glTF node - */ - private _createNodeAsync(babylonNode: Node, binaryWriter: _BinaryWriter): Promise { - return Promise.resolve().then(() => { - // create node to hold translation/rotation/scale and the mesh - const node: INode = {}; - // create mesh - const mesh: IMesh = { primitives: [] }; - - if (babylonNode.name) { - node.name = babylonNode.name; - } + private async _exportMaterialAsync(babylonMaterial: Material, vertexBuffers: { [kind: string]: VertexBuffer }, subMesh: SubMesh, primitive: IMeshPrimitive): Promise { + let materialIndex = this._materialMap.get(babylonMaterial); + if (materialIndex === undefined) { + // TODO: Handle LinesMesh + // if (babylonMesh instanceof LinesMesh) { + // const material: IMaterial = { + // name: babylonMaterial.name, + // }; - if (babylonNode instanceof TransformNode) { - // Set transformation - this._setNodeTransformation(node, babylonNode); - if (babylonNode instanceof Mesh) { - const morphTargetManager = babylonNode.morphTargetManager; - if (morphTargetManager && morphTargetManager.numTargets > 0) { - mesh.weights = []; - for (let i = 0; i < morphTargetManager.numTargets; ++i) { - mesh.weights.push(morphTargetManager.getTarget(i).influence); - } - } - } - return this._setPrimitiveAttributesAsync(mesh, babylonNode, binaryWriter).then(() => { - if (mesh.primitives.length) { - this._meshes.push(mesh); - node.mesh = this._meshes.length - 1; - } - return node; - }); - } else if (babylonNode instanceof Camera) { - this._setCameraTransformation(node, babylonNode); - return node; - } else { - return node; - } - }); - } + // if (!babylonMesh.color.equals(Color3.White()) || babylonMesh.alpha < 1) { + // material.pbrMetallicRoughness = { + // baseColorFactor: [...babylonMesh.color.asArray(), babylonMesh.alpha], + // }; + // } - /** - * Creates a glTF skin from a Babylon skeleton - * @param nodeMap Babylon transform nodes - * @param binaryWriter Buffer to write binary data to - * @returns Node mapping of unique id to index - */ - private _createSkinsAsync(nodeMap: { [key: number]: number }, binaryWriter: _BinaryWriter): Promise<{ [key: number]: number }> { - const promiseChain = Promise.resolve(); - const skinMap: { [key: number]: number } = {}; - for (const skeleton of this._babylonScene.skeletons) { - if (skeleton.bones.length <= 0) { - continue; - } - // create skin - const skin: ISkin = { joints: [] }; - const inverseBindMatrices: Matrix[] = []; - - const boneIndexMap: { [index: number]: Bone } = {}; - let maxBoneIndex = -1; - for (let i = 0; i < skeleton.bones.length; ++i) { - const bone = skeleton.bones[i]; - const boneIndex = bone.getIndex() ?? i; - if (boneIndex !== -1) { - boneIndexMap[boneIndex] = bone; - if (boneIndex > maxBoneIndex) { - maxBoneIndex = boneIndex; - } - } - } - - for (let boneIndex = 0; boneIndex <= maxBoneIndex; ++boneIndex) { - const bone = boneIndexMap[boneIndex]; - inverseBindMatrices.push(bone.getInvertedAbsoluteTransform()); + // this._materials.push(material); + // materialIndex = this._materials.length - 1; + // } - const transformNode = bone.getTransformNode(); - if (transformNode && nodeMap[transformNode.uniqueId] !== null && nodeMap[transformNode.uniqueId] !== undefined) { - skin.joints.push(nodeMap[transformNode.uniqueId]); - } else { - Tools.Warn("Exporting a bone without a linked transform node is currently unsupported"); - } + const hasUVs = vertexBuffers && Object.keys(vertexBuffers).some((kind) => kind.startsWith("uv")); + babylonMaterial = babylonMaterial instanceof MultiMaterial ? babylonMaterial.subMaterials[subMesh.materialIndex]! : babylonMaterial; + if (babylonMaterial instanceof PBRMaterial) { + materialIndex = await this._materialExporter.exportPBRMaterialAsync(babylonMaterial, ImageMimeType.PNG, hasUVs); + } else if (babylonMaterial instanceof StandardMaterial) { + materialIndex = await this._materialExporter.exportStandardMaterialAsync(babylonMaterial, ImageMimeType.PNG, hasUVs); + } else { + Logger.Warn(`Unsupported material '${babylonMaterial.name}' with type ${babylonMaterial.getClassName()}`); + return; } - if (skin.joints.length > 0) { - // create buffer view for inverse bind matrices - const byteStride = 64; // 4 x 4 matrix of 32 bit float - const byteLength = inverseBindMatrices.length * byteStride; - const bufferViewOffset = binaryWriter.getByteOffset(); - const bufferView = _GLTFUtilities._CreateBufferView(0, bufferViewOffset, byteLength, undefined, "InverseBindMatrices" + " - " + skeleton.name); - this._bufferViews.push(bufferView); - const bufferViewIndex = this._bufferViews.length - 1; - const bindMatrixAccessor = _GLTFUtilities._CreateAccessor( - bufferViewIndex, - "InverseBindMatrices" + " - " + skeleton.name, - AccessorType.MAT4, - AccessorComponentType.FLOAT, - inverseBindMatrices.length, - null, - null, - null - ); - const inverseBindAccessorIndex = this._accessors.push(bindMatrixAccessor) - 1; - skin.inverseBindMatrices = inverseBindAccessorIndex; - this._skins.push(skin); - skinMap[skeleton.uniqueId] = this._skins.length - 1; - - inverseBindMatrices.forEach((mat) => { - mat.m.forEach((cell: number) => { - binaryWriter.setFloat32(cell); - }); - }); - } + this._materialMap.set(babylonMaterial, materialIndex); } - return promiseChain.then(() => { - return skinMap; - }); - } -} -/** - * @internal - * - * Stores glTF binary data. If the array buffer byte length is exceeded, it doubles in size dynamically - */ -export class _BinaryWriter { - /** - * Array buffer which stores all binary data - */ - private _arrayBuffer: ArrayBuffer; - /** - * View of the array buffer - */ - private _dataView: DataView; - /** - * byte offset of data in array buffer - */ - private _byteOffset: number; - /** - * Initialize binary writer with an initial byte length - * @param byteLength Initial byte length of the array buffer - */ - constructor(byteLength: number) { - this._arrayBuffer = new ArrayBuffer(byteLength); - this._dataView = new DataView(this._arrayBuffer); - this._byteOffset = 0; - } - /** - * Resize the array buffer to the specified byte length - * @param byteLength The new byte length - * @returns The resized array buffer - */ - private _resizeBuffer(byteLength: number): ArrayBuffer { - const newBuffer = new ArrayBuffer(byteLength); - const copyOldBufferSize = Math.min(this._arrayBuffer.byteLength, byteLength); - const oldUint8Array = new Uint8Array(this._arrayBuffer, 0, copyOldBufferSize); - const newUint8Array = new Uint8Array(newBuffer); - newUint8Array.set(oldUint8Array, 0); - this._arrayBuffer = newBuffer; - this._dataView = new DataView(this._arrayBuffer); - - return newBuffer; - } - /** - * Get an array buffer with the length of the byte offset - * @returns ArrayBuffer resized to the byte offset - */ - public getArrayBuffer(): ArrayBuffer { - return this._resizeBuffer(this.getByteOffset()); - } - /** - * Get the byte offset of the array buffer - * @returns byte offset - */ - public getByteOffset(): number { - if (this._byteOffset == undefined) { - throw new Error("Byte offset is undefined!"); - } - return this._byteOffset; - } - /** - * Stores an UInt8 in the array buffer - * @param entry - * @param byteOffset If defined, specifies where to set the value as an offset. - */ - public setUInt8(entry: number, byteOffset?: number) { - if (byteOffset != null) { - if (byteOffset < this._byteOffset) { - this._dataView.setUint8(byteOffset, entry); - } else { - Tools.Error("BinaryWriter: byteoffset is greater than the current binary buffer length!"); - } - } else { - if (this._byteOffset + 1 > this._arrayBuffer.byteLength) { - this._resizeBuffer(this._arrayBuffer.byteLength * 2); - } - this._dataView.setUint8(this._byteOffset, entry); - this._byteOffset += 1; - } + primitive.material = materialIndex; } - /** - * Stores an UInt16 in the array buffer - * @param entry - * @param byteOffset If defined, specifies where to set the value as an offset. - */ - public setUInt16(entry: number, byteOffset?: number) { - if (byteOffset != null) { - if (byteOffset < this._byteOffset) { - this._dataView.setUint16(byteOffset, entry, true); - } else { - Tools.Error("BinaryWriter: byteoffset is greater than the current binary buffer length!"); - } - } else { - if (this._byteOffset + 2 > this._arrayBuffer.byteLength) { - this._resizeBuffer(this._arrayBuffer.byteLength * 2); - } - this._dataView.setUint16(this._byteOffset, entry, true); - this._byteOffset += 2; + private async _exportMeshAsync(babylonMesh: Mesh, state: ExporterState): Promise { + let meshIndex = state.getMesh(babylonMesh); + if (meshIndex !== undefined) { + return meshIndex; } - } - /** - * Gets an UInt32 in the array buffer - * @param byteOffset If defined, specifies where to set the value as an offset. - * @returns entry - */ - public getUInt32(byteOffset: number): number { - if (byteOffset < this._byteOffset) { - return this._dataView.getUint32(byteOffset, true); - } else { - Tools.Error("BinaryWriter: byteoffset is greater than the current binary buffer length!"); - throw new Error("BinaryWriter: byteoffset is greater than the current binary buffer length!"); - } - } + const mesh: IMesh = { primitives: [] }; + meshIndex = this._meshes.length; + this._meshes.push(mesh); + state.setMesh(babylonMesh, meshIndex); - public getVector3Float32FromRef(vector3: Vector3, byteOffset: number): void { - if (byteOffset + 8 > this._byteOffset) { - Tools.Error(`BinaryWriter: byteoffset is greater than the current binary buffer length!`); - } else { - vector3.x = this._dataView.getFloat32(byteOffset, true); - vector3.y = this._dataView.getFloat32(byteOffset + 4, true); - vector3.z = this._dataView.getFloat32(byteOffset + 8, true); - } - } + const indices = babylonMesh.isUnIndexed ? null : babylonMesh.getIndices(); + const vertexBuffers = babylonMesh.geometry?.getVertexBuffers(); - public setVector3Float32FromRef(vector3: Vector3, byteOffset: number): void { - if (byteOffset + 8 > this._byteOffset) { - Tools.Error(`BinaryWriter: byteoffset is greater than the current binary buffer length!`); - } else { - this._dataView.setFloat32(byteOffset, vector3.x, true); - this._dataView.setFloat32(byteOffset + 4, vector3.y, true); - this._dataView.setFloat32(byteOffset + 8, vector3.z, true); - } - } + const subMeshes = babylonMesh.subMeshes; + if (vertexBuffers && subMeshes && subMeshes.length > 0) { + for (const subMesh of subMeshes) { + const primitive: IMeshPrimitive = { attributes: {} }; - public getVector4Float32FromRef(vector4: Vector4, byteOffset: number): void { - if (byteOffset + 12 > this._byteOffset) { - Tools.Error(`BinaryWriter: byteoffset is greater than the current binary buffer length!`); - } else { - vector4.x = this._dataView.getFloat32(byteOffset, true); - vector4.y = this._dataView.getFloat32(byteOffset + 4, true); - vector4.z = this._dataView.getFloat32(byteOffset + 8, true); - vector4.w = this._dataView.getFloat32(byteOffset + 12, true); - } - } + // Material + const babylonMaterial = subMesh.getMaterial() || this._babylonScene.defaultMaterial; + await this._exportMaterialAsync(babylonMaterial, vertexBuffers, subMesh, primitive); - public setVector4Float32FromRef(vector4: Vector4, byteOffset: number): void { - if (byteOffset + 12 > this._byteOffset) { - Tools.Error(`BinaryWriter: byteoffset is greater than the current binary buffer length!`); - } else { - this._dataView.setFloat32(byteOffset, vector4.x, true); - this._dataView.setFloat32(byteOffset + 4, vector4.y, true); - this._dataView.setFloat32(byteOffset + 8, vector4.z, true); - this._dataView.setFloat32(byteOffset + 12, vector4.w, true); - } - } - /** - * Stores a Float32 in the array buffer - * @param entry - * @param byteOffset - */ - public setFloat32(entry: number, byteOffset?: number) { - if (isNaN(entry)) { - Tools.Error("Invalid data being written!"); - } - if (byteOffset != null) { - if (byteOffset < this._byteOffset) { - this._dataView.setFloat32(byteOffset, entry, true); - } else { - Tools.Error("BinaryWriter: byteoffset is greater than the current binary length!"); - } - } - if (this._byteOffset + 4 > this._arrayBuffer.byteLength) { - this._resizeBuffer(this._arrayBuffer.byteLength * 2); - } - this._dataView.setFloat32(this._byteOffset, entry, true); - this._byteOffset += 4; - } - /** - * Stores an UInt32 in the array buffer - * @param entry - * @param byteOffset If defined, specifies where to set the value as an offset. - */ - public setUInt32(entry: number, byteOffset?: number) { - if (byteOffset != null) { - if (byteOffset < this._byteOffset) { - this._dataView.setUint32(byteOffset, entry, true); - } else { - Tools.Error("BinaryWriter: byteoffset is greater than the current binary buffer length!"); - } - } else { - if (this._byteOffset + 4 > this._arrayBuffer.byteLength) { - this._resizeBuffer(this._arrayBuffer.byteLength * 2); - } - this._dataView.setUint32(this._byteOffset, entry, true); - this._byteOffset += 4; - } - } - /** - * Stores an Int16 in the array buffer - * @param entry - * @param byteOffset If defined, specifies where to set the value as an offset. - */ - public setInt16(entry: number, byteOffset?: number) { - if (byteOffset != null) { - if (byteOffset < this._byteOffset) { - this._dataView.setInt16(byteOffset, entry, true); - } else { - Tools.Error("BinaryWriter: byteoffset is greater than the current binary buffer length!"); - } - } else { - if (this._byteOffset + 2 > this._arrayBuffer.byteLength) { - this._resizeBuffer(this._arrayBuffer.byteLength * 2); - } - this._dataView.setInt16(this._byteOffset, entry, true); - this._byteOffset += 2; - } - } - /** - * Stores a byte in the array buffer - * @param entry - * @param byteOffset If defined, specifies where to set the value as an offset. - */ - public setByte(entry: number, byteOffset?: number) { - if (byteOffset != null) { - if (byteOffset < this._byteOffset) { - this._dataView.setInt8(byteOffset, entry); - } else { - Tools.Error("BinaryWriter: byteoffset is greater than the current binary buffer length!"); - } - } else { - if (this._byteOffset + 1 > this._arrayBuffer.byteLength) { - this._resizeBuffer(this._arrayBuffer.byteLength * 2); - } - this._dataView.setInt8(this._byteOffset, entry); - this._byteOffset++; - } - } + // Index buffer + const fillMode = babylonMesh.overrideRenderingFillMode ?? babylonMaterial.fillMode; + const sideOrientation = babylonMaterial._getEffectiveOrientation(babylonMesh); + this._exportIndices(indices, subMesh.indexStart, subMesh.indexCount, -subMesh.verticesStart, fillMode, sideOrientation, state, primitive); + + // Vertex buffers + for (const vertexBuffer of Object.values(vertexBuffers)) { + this._exportVertexBuffer(vertexBuffer, babylonMaterial, subMesh.verticesStart, subMesh.verticesCount, state, primitive); + } + + mesh.primitives.push(primitive); + } + } + + // TODO: handle morph targets + // TODO: handle skeleton + + return meshIndex; + } + + // const promise = this._extensionsPostExportNodeAsync("createNodeAsync", node, babylonNode, nodeMap); + // if (promise == null) { + // Tools.Warn(`Not exporting node ${babylonNode.name}`); + // return Promise.resolve(); + // } else { + // return promise.then((node) => { + // if (!node) { + // return; + // } + + // if (!this._babylonScene.animationGroups.length) { + // _GLTFAnimation._CreateMorphTargetAnimationFromMorphTargetAnimations( + // babylonNode, + // runtimeGLTFAnimation, + // idleGLTFAnimations, + // nodeMap, + // this._nodes, + // dataWriter, + // this._bufferViews, + // this._accessors, + // this._animationSampleRate, + // this._options.shouldExportAnimation + // ); + // if (babylonNode.animations.length) { + // _GLTFAnimation._CreateNodeAnimationFromNodeAnimations( + // babylonNode, + // runtimeGLTFAnimation, + // idleGLTFAnimations, + // nodeMap, + // this._nodes, + // dataWriter, + // this._bufferViews, + // this._accessors, + // this._animationSampleRate, + // this._options.shouldExportAnimation + // ); + // } + // } + // }); + // } + // }); + // }); + // } + + // return promise.then(() => { + // if (runtimeGLTFAnimation.channels.length && runtimeGLTFAnimation.samplers.length) { + // this._animations.push(runtimeGLTFAnimation); + // } + // idleGLTFAnimations.forEach((idleGLTFAnimation) => { + // if (idleGLTFAnimation.channels.length && idleGLTFAnimation.samplers.length) { + // this._animations.push(idleGLTFAnimation); + // } + // }); + + // if (this._babylonScene.animationGroups.length) { + // _GLTFAnimation._CreateNodeAndMorphAnimationFromAnimationGroups( + // this._babylonScene, + // this._animations, + // nodeMap, + // dataWriter, + // this._bufferViews, + // this._accessors, + // this._animationSampleRate, + // this._options.shouldExportAnimation + // ); + // } + + // return nodeMap; + // }); + + // return nodeMap; + // } + + // /** + // * Creates a glTF skin from a Babylon skeleton + // * @param babylonScene Babylon Scene + // * @param nodeMap Babylon transform nodes + // * @param dataWriter Buffer to write binary data to + // * @returns Node mapping of unique id to index + // */ + // private _createSkinsAsync(nodeMap: { [key: number]: number }, dataWriter: DataWriter): Promise<{ [key: number]: number }> { + // const promiseChain = Promise.resolve(); + // const skinMap: { [key: number]: number } = {}; + // for (const skeleton of this._babylonScene.skeletons) { + // if (skeleton.bones.length <= 0) { + // continue; + // } + // // create skin + // const skin: ISkin = { joints: [] }; + // const inverseBindMatrices: Matrix[] = []; + + // const boneIndexMap: { [index: number]: Bone } = {}; + // let maxBoneIndex = -1; + // for (let i = 0; i < skeleton.bones.length; ++i) { + // const bone = skeleton.bones[i]; + // const boneIndex = bone.getIndex() ?? i; + // if (boneIndex !== -1) { + // boneIndexMap[boneIndex] = bone; + // if (boneIndex > maxBoneIndex) { + // maxBoneIndex = boneIndex; + // } + // } + // } + + // for (let boneIndex = 0; boneIndex <= maxBoneIndex; ++boneIndex) { + // const bone = boneIndexMap[boneIndex]; + // inverseBindMatrices.push(bone.getAbsoluteInverseBindMatrix()); + + // const transformNode = bone.getTransformNode(); + // if (transformNode && nodeMap[transformNode.uniqueId] !== null && nodeMap[transformNode.uniqueId] !== undefined) { + // skin.joints.push(nodeMap[transformNode.uniqueId]); + // } else { + // Tools.Warn("Exporting a bone without a linked transform node is currently unsupported"); + // } + // } + + // if (skin.joints.length > 0) { + // // create buffer view for inverse bind matrices + // const byteStride = 64; // 4 x 4 matrix of 32 bit float + // const byteLength = inverseBindMatrices.length * byteStride; + // const bufferViewOffset = dataWriter.getByteOffset(); + // const bufferView = createBufferView(0, bufferViewOffset, byteLength, undefined, "InverseBindMatrices" + " - " + skeleton.name); + // this._bufferViews.push(bufferView); + // const bufferViewIndex = this._bufferViews.length - 1; + // const bindMatrixAccessor = createAccessor( + // bufferViewIndex, + // "InverseBindMatrices" + " - " + skeleton.name, + // AccessorType.MAT4, + // AccessorComponentType.FLOAT, + // inverseBindMatrices.length, + // null, + // null, + // null + // ); + // const inverseBindAccessorIndex = this._accessors.push(bindMatrixAccessor) - 1; + // skin.inverseBindMatrices = inverseBindAccessorIndex; + // this._skins.push(skin); + // skinMap[skeleton.uniqueId] = this._skins.length - 1; + + // inverseBindMatrices.forEach((mat) => { + // mat.m.forEach((cell: number) => { + // dataWriter.setFloat32(cell); + // }); + // }); + // } + // } + // return promiseChain.then(() => { + // return skinMap; + // }); + // } } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts index 4a52c97fa5e..5c88f397cf2 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts @@ -6,7 +6,6 @@ import type { Texture } from "core/Materials/Textures/texture"; import type { SubMesh } from "core/Meshes/subMesh"; import type { IDisposable } from "core/scene"; -import type { _BinaryWriter } from "./glTFExporter"; import type { IGLTFExporterExtension } from "../glTFFileExporter"; import type { Material } from "core/Materials/material"; import type { BaseTexture } from "core/Materials/Textures/baseTexture"; @@ -38,14 +37,13 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo postExportTexture?(context: string, textureInfo: ITextureInfo, babylonTexture: BaseTexture): void; /** - * Define this method to modify the default behavior when exporting texture info + * Define this method to modify the default behavior when exporting a mesh primitive * @param context The context when loading the asset * @param meshPrimitive glTF mesh primitive * @param babylonSubMesh Babylon submesh - * @param binaryWriter glTF serializer binary writer instance * @returns nullable IMeshPrimitive promise */ - postExportMeshPrimitiveAsync?(context: string, meshPrimitive: Nullable, babylonSubMesh: SubMesh, binaryWriter: _BinaryWriter): Promise; + postExportMeshPrimitiveAsync?(context: string, meshPrimitive: Nullable, babylonSubMesh: SubMesh): Promise; /** * Define this method to modify the default behavior when exporting a node @@ -54,7 +52,7 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo * @param babylonNode BabylonJS node * @returns nullable INode promise */ - postExportNodeAsync?(context: string, node: Nullable, babylonNode: Node, nodeMap: { [key: number]: number }, binaryWriter: _BinaryWriter): Promise>; + postExportNodeAsync?(context: string, node: Nullable, babylonNode: Node, nodeMap: { [key: number]: number }): Promise>; /** * Define this method to modify the default behavior when exporting a material diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index a2588ea4aa6..ef475ecc215 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -1,4 +1,6 @@ -import type { ITextureInfo, IMaterial, IMaterialPbrMetallicRoughness, IMaterialOcclusionTextureInfo, ISampler, IMaterialExtension } from "babylonjs-gltf2interface"; +/* eslint-disable babylonjs/available */ + +import type { ITextureInfo, IMaterial, IMaterialPbrMetallicRoughness, IMaterialOcclusionTextureInfo, ISampler } from "babylonjs-gltf2interface"; import { ImageMimeType, MaterialAlphaMode, TextureMagFilter, TextureMinFilter, TextureWrapMode } from "babylonjs-gltf2interface"; import type { Nullable } from "core/types"; @@ -13,62 +15,31 @@ import { RawTexture } from "core/Materials/Textures/rawTexture"; import type { Scene } from "core/scene"; -import type { _Exporter } from "./glTFExporter"; +import type { GLTFExporter } from "./glTFExporter"; import { Constants } from "core/Engines/constants"; import { DumpTools } from "core/Misc/dumpTools"; import type { Material } from "core/Materials/material"; import type { StandardMaterial } from "core/Materials/standardMaterial"; import type { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; -import type { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -import "core/Engines/Extensions/engine.readTexture"; +const epsilon = 1e-6; +const dielectricSpecular = new Color3(0.04, 0.04, 0.04); +const maxSpecularPower = 1024; +const white = Color3.White(); +const black = Color3.Black(); -/** - * Interface for storing specular glossiness factors - * @internal - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -interface _IPBRSpecularGlossiness { - /** - * Represents the linear diffuse factors of the material - */ +interface IPBRSpecularGlossiness { diffuseColor: Color3; - /** - * Represents the linear specular factors of the material - */ specularColor: Color3; - /** - * Represents the smoothness of the material - */ glossiness: number; } -/** - * Interface for storing metallic roughness factors - * @internal - */ -// eslint-disable-next-line @typescript-eslint/naming-convention -interface _IPBRMetallicRoughness { - /** - * Represents the albedo color of the material - */ +interface IPBRMetallicRoughness { baseColor: Color3; - /** - * Represents the metalness of the material - */ metallic: Nullable; - /** - * Represents the roughness of the material - */ roughness: Nullable; - /** - * The metallic roughness texture data - */ metallicRoughnessTextureData?: Nullable; - /** - * The base color texture data - */ baseColorTextureData?: Nullable; } @@ -86,240 +57,88 @@ function getFileExtensionFromMimeType(mimeType: ImageMimeType): string { } /** - * Utility methods for working with glTF material conversion properties. This class should only be used internally - * @internal + * Computes the metallic factor + * @param diffuse diffused value + * @param specular specular value + * @param oneMinusSpecularStrength one minus the specular strength + * @returns metallic value */ -export class _GLTFMaterialExporter { - /** - * Represents the dielectric specular values for R, G and B - */ - private static readonly _DielectricSpecular: Color3 = new Color3(0.04, 0.04, 0.04); - - /** - * Allows the maximum specular power to be defined for material calculations - */ - private static readonly _MaxSpecularPower = 1024; - - /** - * Mapping to store textures - */ - private _textureMap: { [textureId: string]: ITextureInfo } = {}; - - // Mapping of internal textures to images to avoid exporting duplicate images. - private _internalTextureToImage: { [uniqueId: number]: { [mimeType: string]: Promise } } = {}; - - /** - * Numeric tolerance value - */ - private static readonly _Epsilon = 1e-6; - - /** - * Reference to the glTF Exporter - */ - private _exporter: _Exporter; - - constructor(exporter: _Exporter) { - this._textureMap = {}; - this._exporter = exporter; - } - - /** - * Specifies if two colors are approximately equal in value - * @param color1 first color to compare to - * @param color2 second color to compare to - * @param epsilon threshold value - * @returns boolean specifying if the colors are approximately equal in value - */ - private static _FuzzyEquals(color1: Color3, color2: Color3, epsilon: number): boolean { - return Scalar.WithinEpsilon(color1.r, color2.r, epsilon) && Scalar.WithinEpsilon(color1.g, color2.g, epsilon) && Scalar.WithinEpsilon(color1.b, color2.b, epsilon); +function solveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number { + if (specular < dielectricSpecular.r) { + dielectricSpecular; + return 0; } - /** - * Gets the materials from a Babylon scene and converts them to glTF materials - * @param exportMaterials - * @param mimeType texture mime type - * @param hasTextureCoords specifies if texture coordinates are present on the material - * @returns promise that resolves after all materials have been converted - */ - public _convertMaterialsToGLTFAsync(exportMaterials: Set, mimeType: ImageMimeType, hasTextureCoords: boolean) { - const promises: Promise[] = []; - exportMaterials.forEach((material) => { - if (material.getClassName() === "StandardMaterial") { - promises.push(this._convertStandardMaterialAsync(material as StandardMaterial, mimeType, hasTextureCoords)); - } else if (material.getClassName().indexOf("PBR") !== -1) { - promises.push(this._convertPBRMaterialAsync(material as PBRMaterial, mimeType, hasTextureCoords)); - } else { - Tools.Warn(`Unsupported material type: ${material.name}`); - } - }); - - return Promise.all(promises).then(() => { - /* do nothing */ - }); - } + const a = dielectricSpecular.r; + const b = (diffuse * oneMinusSpecularStrength) / (1.0 - dielectricSpecular.r) + specular - 2.0 * dielectricSpecular.r; + const c = dielectricSpecular.r - specular; + const D = b * b - 4.0 * a * c; + return Scalar.Clamp((-b + Math.sqrt(D)) / (2.0 * a), 0, 1); +} - /** - * Makes a copy of the glTF material without the texture parameters - * @param originalMaterial original glTF material - * @returns glTF material without texture parameters - */ - public _stripTexturesFromMaterial(originalMaterial: IMaterial): IMaterial { - const newMaterial: IMaterial = {}; - if (originalMaterial) { - newMaterial.name = originalMaterial.name; - newMaterial.doubleSided = originalMaterial.doubleSided; - newMaterial.alphaMode = originalMaterial.alphaMode; - newMaterial.alphaCutoff = originalMaterial.alphaCutoff; - newMaterial.emissiveFactor = originalMaterial.emissiveFactor; - const originalPBRMetallicRoughness = originalMaterial.pbrMetallicRoughness; - if (originalPBRMetallicRoughness) { - newMaterial.pbrMetallicRoughness = {}; - newMaterial.pbrMetallicRoughness.baseColorFactor = originalPBRMetallicRoughness.baseColorFactor; - newMaterial.pbrMetallicRoughness.metallicFactor = originalPBRMetallicRoughness.metallicFactor; - newMaterial.pbrMetallicRoughness.roughnessFactor = originalPBRMetallicRoughness.roughnessFactor; - } - } - return newMaterial; +/** + * Sets the glTF alpha mode to a glTF material from the Babylon Material + * @param glTFMaterial glTF material + * @param babylonMaterial Babylon material + */ +function setAlphaMode(glTFMaterial: IMaterial, babylonMaterial: Material & { alphaCutOff?: number }): void { + if (babylonMaterial.needAlphaBlending()) { + glTFMaterial.alphaMode = MaterialAlphaMode.BLEND; + } else if (babylonMaterial.needAlphaTesting()) { + glTFMaterial.alphaMode = MaterialAlphaMode.MASK; + glTFMaterial.alphaCutoff = babylonMaterial.alphaCutOff; } +} - /** - * Specifies if the material has any texture parameters present - * @param material glTF Material - * @returns boolean specifying if texture parameters are present - */ - public _hasTexturesPresent(material: IMaterial): boolean { - if (material.emissiveTexture || material.normalTexture || material.occlusionTexture) { - return true; - } - const pbrMat = material.pbrMetallicRoughness; - if (pbrMat) { - if (pbrMat.baseColorTexture || pbrMat.metallicRoughnessTexture) { - return true; - } - } - - if (material.extensions) { - for (const extension in material.extensions) { - const extensionObject = material.extensions[extension]; - if (extensionObject as IMaterialExtension) { - return extensionObject.hasTextures?.(); - } - } - } - - return false; - } +function createWhiteTexture(width: number, height: number, scene: Scene): Texture { + const data = new Uint8Array(width * height * 4); - public _getTextureInfo(babylonTexture: Nullable): Nullable { - if (babylonTexture) { - const textureUid = babylonTexture.uid; - if (textureUid in this._textureMap) { - return this._textureMap[textureUid]; - } - } - return null; + for (let i = 0; i < data.length; i = i + 4) { + data[i] = data[i + 1] = data[i + 2] = data[i + 3] = 0xff; } - /** - * Converts a Babylon StandardMaterial to a glTF Metallic Roughness Material - * @param babylonStandardMaterial - * @returns glTF Metallic Roughness Material representation - */ - public _convertToGLTFPBRMetallicRoughness(babylonStandardMaterial: StandardMaterial): IMaterialPbrMetallicRoughness { - // Defines a cubic bezier curve where x is specular power and y is roughness - const P0 = new Vector2(0, 1); - const P1 = new Vector2(0, 0.1); - const P2 = new Vector2(0, 0.1); - const P3 = new Vector2(1300, 0.1); + const rawTexture = RawTexture.CreateRGBATexture(data, width, height, scene); - /** - * Given the control points, solve for x based on a given t for a cubic bezier curve - * @param t a value between 0 and 1 - * @param p0 first control point - * @param p1 second control point - * @param p2 third control point - * @param p3 fourth control point - * @returns number result of cubic bezier curve at the specified t - */ - function cubicBezierCurve(t: number, p0: number, p1: number, p2: number, p3: number): number { - return (1 - t) * (1 - t) * (1 - t) * p0 + 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t * p3; - } + return rawTexture; +} - /** - * Evaluates a specified specular power value to determine the appropriate roughness value, - * based on a pre-defined cubic bezier curve with specular on the abscissa axis (x-axis) - * and roughness on the ordinant axis (y-axis) - * @param specularPower specular power of standard material - * @returns Number representing the roughness value - */ - function solveForRoughness(specularPower: number): number { - // Given P0.x = 0, P1.x = 0, P2.x = 0 - // x = t * t * t * P3.x - // t = (x / P3.x)^(1/3) - const t = Math.pow(specularPower / P3.x, 0.333333); - return cubicBezierCurve(t, P0.y, P1.y, P2.y, P3.y); +function convertPixelArrayToFloat32(pixels: ArrayBufferView): Float32Array { + if (pixels instanceof Uint8Array) { + const length = pixels.length; + const buffer = new Float32Array(pixels.length); + for (let i = 0; i < length; ++i) { + buffer[i] = pixels[i] / 255; } + return buffer; + } else if (pixels instanceof Float32Array) { + return pixels; + } else { + throw new Error("Unsupported pixel format!"); + } +} - const diffuse = babylonStandardMaterial.diffuseColor.toLinearSpace(babylonStandardMaterial.getScene().getEngine().useExactSrgbConversions).scale(0.5); - const opacity = babylonStandardMaterial.alpha; - const specularPower = Scalar.Clamp(babylonStandardMaterial.specularPower, 0, _GLTFMaterialExporter._MaxSpecularPower); - - const roughness = solveForRoughness(specularPower); - - const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = { - baseColorFactor: [diffuse.r, diffuse.g, diffuse.b, opacity], - metallicFactor: 0, - roughnessFactor: roughness, - }; +/** + * Utility methods for working with glTF material conversion properties. + * @internal + */ +export class GLTFMaterialExporter { + private _exporter: GLTFExporter; - return glTFPbrMetallicRoughness; - } + // Mapping to store textures + private _textureMap = new Map(); - /** - * Computes the metallic factor - * @param diffuse diffused value - * @param specular specular value - * @param oneMinusSpecularStrength one minus the specular strength - * @returns metallic value - */ - public static _SolveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number { - if (specular < this._DielectricSpecular.r) { - this._DielectricSpecular; - return 0; - } + // Mapping of internal textures to images to avoid exporting duplicate images + private _internalTextureToImage: { [uniqueId: number]: { [mimeType: string]: Promise } } = {}; - const a = this._DielectricSpecular.r; - const b = (diffuse * oneMinusSpecularStrength) / (1.0 - this._DielectricSpecular.r) + specular - 2.0 * this._DielectricSpecular.r; - const c = this._DielectricSpecular.r - specular; - const D = b * b - 4.0 * a * c; - return Scalar.Clamp((-b + Math.sqrt(D)) / (2.0 * a), 0, 1); + constructor(exporter: GLTFExporter) { + this._exporter = exporter; } - /** - * Sets the glTF alpha mode to a glTF material from the Babylon Material - * @param glTFMaterial glTF material - * @param babylonMaterial Babylon material - */ - private static _SetAlphaMode(glTFMaterial: IMaterial, babylonMaterial: Material & { alphaCutOff: number }): void { - if (babylonMaterial.needAlphaBlending()) { - glTFMaterial.alphaMode = MaterialAlphaMode.BLEND; - } else if (babylonMaterial.needAlphaTesting()) { - glTFMaterial.alphaMode = MaterialAlphaMode.MASK; - glTFMaterial.alphaCutoff = babylonMaterial.alphaCutOff; - } + public getTextureInfo(babylonTexture: Nullable): Nullable { + return babylonTexture ? this._textureMap.get(babylonTexture) || null : null; } - /** - * Converts a Babylon Standard Material to a glTF Material - * @param babylonStandardMaterial BJS Standard Material - * @param mimeType mime type to use for the textures - * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied - * @returns promise, resolved with the material - */ - public _convertStandardMaterialAsync(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, hasTextureCoords: boolean): Promise { - const materialMap = this._exporter._materialMap; - const materials = this._exporter._materials; - const promises = []; + public async exportStandardMaterialAsync(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, hasUVs: boolean): Promise { const pbrMetallicRoughness = this._convertToGLTFPBRMetallicRoughness(babylonStandardMaterial); const material: IMaterial = { name: babylonStandardMaterial.name }; @@ -329,16 +148,21 @@ export class _GLTFMaterialExporter { } material.doubleSided = true; } - if (hasTextureCoords) { - if (babylonStandardMaterial.diffuseTexture) { + + if (hasUVs) { + const promises: Promise[] = []; + + const diffuseTexture = babylonStandardMaterial.diffuseTexture; + if (diffuseTexture) { promises.push( - this._exportTextureAsync(babylonStandardMaterial.diffuseTexture, mimeType).then((textureInfo) => { + this._exportTextureAsync(diffuseTexture, mimeType).then((textureInfo) => { if (textureInfo) { pbrMetallicRoughness.baseColorTexture = textureInfo; } }) ); } + const bumpTexture = babylonStandardMaterial.bumpTexture; if (bumpTexture) { promises.push( @@ -352,20 +176,24 @@ export class _GLTFMaterialExporter { }) ); } - if (babylonStandardMaterial.emissiveTexture) { + + const emissiveTexture = babylonStandardMaterial.emissiveTexture; + if (emissiveTexture) { material.emissiveFactor = [1.0, 1.0, 1.0]; promises.push( - this._exportTextureAsync(babylonStandardMaterial.emissiveTexture, mimeType).then((textureInfo) => { + this._exportTextureAsync(emissiveTexture, mimeType).then((textureInfo) => { if (textureInfo) { material.emissiveTexture = textureInfo; } }) ); } - if (babylonStandardMaterial.ambientTexture) { + + const ambientTexture = babylonStandardMaterial.ambientTexture; + if (ambientTexture) { promises.push( - this._exportTextureAsync(babylonStandardMaterial.ambientTexture, mimeType).then((textureInfo) => { + this._exportTextureAsync(ambientTexture, mimeType).then((textureInfo) => { if (textureInfo) { const occlusionTexture: IMaterialOcclusionTextureInfo = { index: textureInfo.index, @@ -375,6 +203,11 @@ export class _GLTFMaterialExporter { }) ); } + + if (promises.length > 0) { + this._exporter._materialNeedsUVsSet.add(babylonStandardMaterial); + await Promise.all(promises); + } } if (babylonStandardMaterial.alpha < 1.0 || babylonStandardMaterial.opacityTexture) { @@ -384,53 +217,85 @@ export class _GLTFMaterialExporter { Tools.Warn(babylonStandardMaterial.name + ": glTF 2.0 does not support alpha mode: " + babylonStandardMaterial.alphaMode.toString()); } } - if (babylonStandardMaterial.emissiveColor && !_GLTFMaterialExporter._FuzzyEquals(babylonStandardMaterial.emissiveColor, Color3.Black(), _GLTFMaterialExporter._Epsilon)) { + + if (babylonStandardMaterial.emissiveColor && !babylonStandardMaterial.emissiveColor.equalsWithEpsilon(black, epsilon)) { material.emissiveFactor = babylonStandardMaterial.emissiveColor.asArray(); } material.pbrMetallicRoughness = pbrMetallicRoughness; - _GLTFMaterialExporter._SetAlphaMode(material, babylonStandardMaterial); + setAlphaMode(material, babylonStandardMaterial); + + await this._finishMaterialAsync(material, babylonStandardMaterial, mimeType); + const materials = this._exporter._materials; materials.push(material); - materialMap[babylonStandardMaterial.uniqueId] = materials.length - 1; + return materials.length - 1; + } + + private _convertToGLTFPBRMetallicRoughness(babylonStandardMaterial: StandardMaterial): IMaterialPbrMetallicRoughness { + // Defines a cubic bezier curve where x is specular power and y is roughness + const P0 = new Vector2(0, 1); + const P1 = new Vector2(0, 0.1); + const P2 = new Vector2(0, 0.1); + const P3 = new Vector2(1300, 0.1); - return this._finishMaterial(promises, material, babylonStandardMaterial, mimeType); + /** + * Given the control points, solve for x based on a given t for a cubic bezier curve + * @param t a value between 0 and 1 + * @param p0 first control point + * @param p1 second control point + * @param p2 third control point + * @param p3 fourth control point + * @returns number result of cubic bezier curve at the specified t + */ + function cubicBezierCurve(t: number, p0: number, p1: number, p2: number, p3: number): number { + return (1 - t) * (1 - t) * (1 - t) * p0 + 3 * (1 - t) * (1 - t) * t * p1 + 3 * (1 - t) * t * t * p2 + t * t * t * p3; + } + + /** + * Evaluates a specified specular power value to determine the appropriate roughness value, + * based on a pre-defined cubic bezier curve with specular on the abscissa axis (x-axis) + * and roughness on the ordinant axis (y-axis) + * @param specularPower specular power of standard material + * @returns Number representing the roughness value + */ + function solveForRoughness(specularPower: number): number { + // Given P0.x = 0, P1.x = 0, P2.x = 0 + // x = t * t * t * P3.x + // t = (x / P3.x)^(1/3) + const t = Math.pow(specularPower / P3.x, 0.333333); + return cubicBezierCurve(t, P0.y, P1.y, P2.y, P3.y); + } + + const diffuse = babylonStandardMaterial.diffuseColor.toLinearSpace(babylonStandardMaterial.getScene().getEngine().useExactSrgbConversions).scale(0.5); + const opacity = babylonStandardMaterial.alpha; + const specularPower = Scalar.Clamp(babylonStandardMaterial.specularPower, 0, maxSpecularPower); + + const roughness = solveForRoughness(specularPower); + + const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = { + baseColorFactor: [diffuse.r, diffuse.g, diffuse.b, opacity], + metallicFactor: 0, + roughnessFactor: roughness, + }; + + return glTFPbrMetallicRoughness; } - private _finishMaterial(promises: Promise[], glTFMaterial: IMaterial, babylonMaterial: Material, mimeType: ImageMimeType) { - return Promise.all(promises).then(() => { - const textures = this._exporter._extensionsPostExportMaterialAdditionalTextures("exportMaterial", glTFMaterial, babylonMaterial); - let tasks: Nullable>[]> = null; + private async _finishMaterialAsync(glTFMaterial: IMaterial, babylonMaterial: Material, mimeType: ImageMimeType): Promise { + const textures = this._exporter._extensionsPostExportMaterialAdditionalTextures("exportMaterial", glTFMaterial, babylonMaterial); - for (const texture of textures) { - if (!tasks) { - tasks = []; - } - tasks.push(this._exportTextureAsync(texture, mimeType)); - } + const promises: Array>> = []; - if (!tasks) { - tasks = [Promise.resolve(null)]; - } + for (const texture of textures) { + promises.push(this._exportTextureAsync(texture, mimeType)); + } - return Promise.all(tasks).then(() => { - const extensionWork = this._exporter._extensionsPostExportMaterialAsync("exportMaterial", glTFMaterial, babylonMaterial); - if (!extensionWork) { - return glTFMaterial; - } - return extensionWork.then(() => glTFMaterial); - }); - }); + await Promise.all(promises); + + await this._exporter._extensionsPostExportMaterialAsync("exportMaterial", glTFMaterial, babylonMaterial); } - /** - * Converts an image typed array buffer to a base64 image - * @param buffer typed array buffer - * @param width width of the image - * @param height height of the image - * @param mimeType mimetype of the image - * @returns base64 image string - */ private async _getImageDataAsync(buffer: Uint8Array | Float32Array, width: number, height: number, mimeType: ImageMimeType): Promise { const textureType = Constants.TEXTURETYPE_UNSIGNED_INT; @@ -447,25 +312,6 @@ export class _GLTFMaterialExporter { return (await DumpTools.DumpDataAsync(width, height, data, mimeType, undefined, true, true)) as ArrayBuffer; } - /** - * Generates a white texture based on the specified width and height - * @param width width of the texture in pixels - * @param height height of the texture in pixels - * @param scene babylonjs scene - * @returns white texture - */ - private _createWhiteTexture(width: number, height: number, scene: Scene): Texture { - const data = new Uint8Array(width * height * 4); - - for (let i = 0; i < data.length; i = i + 4) { - data[i] = data[i + 1] = data[i + 2] = data[i + 3] = 0xff; - } - - const rawTexture = RawTexture.CreateRGBATexture(data, width, height, scene); - - return rawTexture; - } - /** * Resizes the two source textures to the same dimensions. If a texture is null, a default white texture is generated. If both textures are null, returns null * @param texture1 first texture to resize @@ -483,14 +329,14 @@ export class _GLTFMaterialExporter { if (texture1 && texture1 instanceof Texture) { resizedTexture1 = TextureTools.CreateResizedCopy(texture1, texture2Size.width, texture2Size.height, true); } else { - resizedTexture1 = this._createWhiteTexture(texture2Size.width, texture2Size.height, scene); + resizedTexture1 = createWhiteTexture(texture2Size.width, texture2Size.height, scene); } resizedTexture2 = texture2!; } else if (texture1Size.width > texture2Size.width) { if (texture2 && texture2 instanceof Texture) { resizedTexture2 = TextureTools.CreateResizedCopy(texture2, texture1Size.width, texture1Size.height, true); } else { - resizedTexture2 = this._createWhiteTexture(texture1Size.width, texture1Size.height, scene); + resizedTexture2 = createWhiteTexture(texture1Size.width, texture1Size.height, scene); } resizedTexture1 = texture1!; } else { @@ -504,31 +350,10 @@ export class _GLTFMaterialExporter { }; } - /** - * Converts an array of pixels to a Float32Array - * Throws an error if the pixel format is not supported - * @param pixels - array buffer containing pixel values - * @returns Float32 of pixels - */ - private _convertPixelArrayToFloat32(pixels: ArrayBufferView): Float32Array { - if (pixels instanceof Uint8Array) { - const length = pixels.length; - const buffer = new Float32Array(pixels.length); - for (let i = 0; i < length; ++i) { - buffer[i] = pixels[i] / 255; - } - return buffer; - } else if (pixels instanceof Float32Array) { - return pixels; - } else { - throw new Error("Unsupported pixel format!"); - } - } - /** * Convert Specular Glossiness Textures to Metallic Roughness * See link below for info on the material conversions from PBR Metallic/Roughness and Specular/Glossiness - * @link https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows-bjs/js/babylon.pbrUtilities.js + * @see https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Archived/KHR_materials_pbrSpecularGlossiness/examples/convert-between-workflows-bjs/js/babylon.pbrUtilities.js * @param diffuseTexture texture used to store diffuse information * @param specularGlossinessTexture texture used to store specular and glossiness information * @param factors specular glossiness material factors @@ -538,12 +363,12 @@ export class _GLTFMaterialExporter { private async _convertSpecularGlossinessTexturesToMetallicRoughnessAsync( diffuseTexture: Nullable, specularGlossinessTexture: Nullable, - factors: _IPBRSpecularGlossiness, + factors: IPBRSpecularGlossiness, mimeType: ImageMimeType - ): Promise<_IPBRMetallicRoughness> { + ): Promise { const promises = new Array>(); if (!(diffuseTexture || specularGlossinessTexture)) { - return Promise.reject("_ConvertSpecularGlosinessTexturesToMetallicRoughness: diffuse and specular glossiness textures are not defined!"); + return Promise.reject("diffuse and specular glossiness textures are not defined!"); } const scene: Nullable = diffuseTexture ? diffuseTexture.getScene() : specularGlossinessTexture ? specularGlossinessTexture.getScene() : null; @@ -562,12 +387,12 @@ export class _GLTFMaterialExporter { const specularPixels = await resizedTextures.texture2.readPixels(); if (diffusePixels) { - diffuseBuffer = this._convertPixelArrayToFloat32(diffusePixels); + diffuseBuffer = convertPixelArrayToFloat32(diffusePixels); } else { return Promise.reject("Failed to retrieve pixels from diffuse texture!"); } if (specularPixels) { - specularGlossinessBuffer = this._convertPixelArrayToFloat32(specularPixels); + specularGlossinessBuffer = convertPixelArrayToFloat32(specularPixels); } else { return Promise.reject("Failed to retrieve pixels from specular glossiness texture!"); } @@ -578,7 +403,7 @@ export class _GLTFMaterialExporter { const baseColorBuffer = new Uint8Array(byteLength); const strideSize = 4; - const maxBaseColor = Color3.Black(); + const maxBaseColor = black; let maxMetallic = 0; let maxRoughness = 0; @@ -594,7 +419,7 @@ export class _GLTFMaterialExporter { .multiply(factors.specularColor); const glossiness = specularGlossinessBuffer[offset + 3] * factors.glossiness; - const specularGlossiness: _IPBRSpecularGlossiness = { + const specularGlossiness: IPBRSpecularGlossiness = { diffuseColor: diffuseColor, specularColor: specularColor, glossiness: glossiness, @@ -620,7 +445,7 @@ export class _GLTFMaterialExporter { } // Retrieves the metallic roughness factors from the maximum texture values. - const metallicRoughnessFactors: _IPBRMetallicRoughness = { + const metallicRoughnessFactors: IPBRMetallicRoughness = { baseColor: maxBaseColor, metallic: maxMetallic, roughness: maxRoughness, @@ -633,9 +458,9 @@ export class _GLTFMaterialExporter { for (let w = 0; w < width; ++w) { const destinationOffset = (width * h + w) * strideSize; - baseColorBuffer[destinationOffset] /= metallicRoughnessFactors.baseColor.r > _GLTFMaterialExporter._Epsilon ? metallicRoughnessFactors.baseColor.r : 1; - baseColorBuffer[destinationOffset + 1] /= metallicRoughnessFactors.baseColor.g > _GLTFMaterialExporter._Epsilon ? metallicRoughnessFactors.baseColor.g : 1; - baseColorBuffer[destinationOffset + 2] /= metallicRoughnessFactors.baseColor.b > _GLTFMaterialExporter._Epsilon ? metallicRoughnessFactors.baseColor.b : 1; + baseColorBuffer[destinationOffset] /= metallicRoughnessFactors.baseColor.r > epsilon ? metallicRoughnessFactors.baseColor.r : 1; + baseColorBuffer[destinationOffset + 1] /= metallicRoughnessFactors.baseColor.g > epsilon ? metallicRoughnessFactors.baseColor.g : 1; + baseColorBuffer[destinationOffset + 2] /= metallicRoughnessFactors.baseColor.b > epsilon ? metallicRoughnessFactors.baseColor.b : 1; const linearBaseColorPixel = Color3.FromInts( baseColorBuffer[destinationOffset], @@ -647,17 +472,16 @@ export class _GLTFMaterialExporter { baseColorBuffer[destinationOffset + 1] = sRGBBaseColorPixel.g * 255; baseColorBuffer[destinationOffset + 2] = sRGBBaseColorPixel.b * 255; - if (!_GLTFMaterialExporter._FuzzyEquals(sRGBBaseColorPixel, Color3.White(), _GLTFMaterialExporter._Epsilon)) { + if (!sRGBBaseColorPixel.equalsWithEpsilon(white, epsilon)) { writeOutBaseColorTexture = true; } - metallicRoughnessBuffer[destinationOffset + 1] /= - metallicRoughnessFactors.roughness! > _GLTFMaterialExporter._Epsilon ? metallicRoughnessFactors.roughness! : 1; - metallicRoughnessBuffer[destinationOffset + 2] /= metallicRoughnessFactors.metallic! > _GLTFMaterialExporter._Epsilon ? metallicRoughnessFactors.metallic! : 1; + metallicRoughnessBuffer[destinationOffset + 1] /= metallicRoughnessFactors.roughness! > epsilon ? metallicRoughnessFactors.roughness! : 1; + metallicRoughnessBuffer[destinationOffset + 2] /= metallicRoughnessFactors.metallic! > epsilon ? metallicRoughnessFactors.metallic! : 1; const metallicRoughnessPixel = Color3.FromInts(255, metallicRoughnessBuffer[destinationOffset + 1], metallicRoughnessBuffer[destinationOffset + 2]); - if (!_GLTFMaterialExporter._FuzzyEquals(metallicRoughnessPixel, Color3.White(), _GLTFMaterialExporter._Epsilon)) { + if (!metallicRoughnessPixel.equalsWithEpsilon(white, epsilon)) { writeOutMetallicRoughnessTexture = true; } } @@ -691,21 +515,17 @@ export class _GLTFMaterialExporter { * @param specularGlossiness interface with specular glossiness material properties * @returns interface with metallic roughness material properties */ - private _convertSpecularGlossinessToMetallicRoughness(specularGlossiness: _IPBRSpecularGlossiness): _IPBRMetallicRoughness { + private _convertSpecularGlossinessToMetallicRoughness(specularGlossiness: IPBRSpecularGlossiness): IPBRMetallicRoughness { const diffusePerceivedBrightness = this._getPerceivedBrightness(specularGlossiness.diffuseColor); const specularPerceivedBrightness = this._getPerceivedBrightness(specularGlossiness.specularColor); const oneMinusSpecularStrength = 1 - this._getMaxComponent(specularGlossiness.specularColor); - const metallic = _GLTFMaterialExporter._SolveMetallic(diffusePerceivedBrightness, specularPerceivedBrightness, oneMinusSpecularStrength); - const baseColorFromDiffuse = specularGlossiness.diffuseColor.scale( - oneMinusSpecularStrength / (1.0 - _GLTFMaterialExporter._DielectricSpecular.r) / Math.max(1 - metallic, _GLTFMaterialExporter._Epsilon) - ); - const baseColorFromSpecular = specularGlossiness.specularColor - .subtract(_GLTFMaterialExporter._DielectricSpecular.scale(1 - metallic)) - .scale(1 / Math.max(metallic, _GLTFMaterialExporter._Epsilon)); + const metallic = solveMetallic(diffusePerceivedBrightness, specularPerceivedBrightness, oneMinusSpecularStrength); + const baseColorFromDiffuse = specularGlossiness.diffuseColor.scale(oneMinusSpecularStrength / (1.0 - dielectricSpecular.r) / Math.max(1 - metallic)); + const baseColorFromSpecular = specularGlossiness.specularColor.subtract(dielectricSpecular.scale(1 - metallic)).scale(1 / Math.max(metallic)); let baseColor = Color3.Lerp(baseColorFromDiffuse, baseColorFromSpecular, metallic * metallic); baseColor = baseColor.clampToRef(0, 1, baseColor); - const metallicRoughness: _IPBRMetallicRoughness = { + const metallicRoughness: IPBRMetallicRoughness = { baseColor: baseColor, metallic: metallic, roughness: 1 - specularGlossiness.glossiness, @@ -743,26 +563,24 @@ export class _GLTFMaterialExporter { * @param babylonPBRMaterial BJS PBR Metallic Roughness Material * @param mimeType mime type to use for the textures * @param glTFPbrMetallicRoughness glTF PBR Metallic Roughness interface - * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied + * @param hasUVs specifies if texture coordinates are present on the submesh to determine if textures should be applied * @returns glTF PBR Metallic Roughness factors */ - private _convertMetalRoughFactorsToMetallicRoughnessAsync( + private async _convertMetalRoughFactorsToMetallicRoughnessAsync( babylonPBRMaterial: PBRBaseMaterial, mimeType: ImageMimeType, glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness, - hasTextureCoords: boolean - ): Promise<_IPBRMetallicRoughness> { - const promises = []; - const baseColor = babylonPBRMaterial._albedoColor; - const metallic = babylonPBRMaterial._metallic; - const roughness = babylonPBRMaterial._roughness; - const metallicRoughness: _IPBRMetallicRoughness = { - baseColor: baseColor, - metallic: metallic, - roughness: roughness, + hasUVs: boolean + ): Promise { + const promises: Promise[] = []; + + const metallicRoughness: IPBRMetallicRoughness = { + baseColor: babylonPBRMaterial._albedoColor, + metallic: babylonPBRMaterial._metallic, + roughness: babylonPBRMaterial._roughness, }; - if (hasTextureCoords) { + if (hasUVs) { const albedoTexture = babylonPBRMaterial._albedoTexture; if (albedoTexture) { promises.push( @@ -784,9 +602,13 @@ export class _GLTFMaterialExporter { ); } } - return Promise.all(promises).then(() => { - return metallicRoughness; - }); + + if (promises.length > 0) { + this._exporter._materialNeedsUVsSet.add(babylonPBRMaterial); + await Promise.all(promises); + } + + return metallicRoughness; } private _getTextureSampler(texture: Nullable): ISampler { @@ -894,60 +716,59 @@ export class _GLTFMaterialExporter { * @param babylonPBRMaterial BJS PBR Metallic Roughness Material * @param mimeType mime type to use for the textures * @param pbrMetallicRoughness glTF PBR Metallic Roughness interface - * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied + * @param hasUVs specifies if texture coordinates are present on the submesh to determine if textures should be applied * @returns glTF PBR Metallic Roughness factors */ - private _convertSpecGlossFactorsToMetallicRoughnessAsync( + private async _convertSpecGlossFactorsToMetallicRoughnessAsync( babylonPBRMaterial: PBRBaseMaterial, mimeType: ImageMimeType, pbrMetallicRoughness: IMaterialPbrMetallicRoughness, - hasTextureCoords: boolean - ): Promise<_IPBRMetallicRoughness> { - return Promise.resolve().then(() => { - const specGloss: _IPBRSpecularGlossiness = { - diffuseColor: babylonPBRMaterial._albedoColor, - specularColor: babylonPBRMaterial._reflectivityColor, - glossiness: babylonPBRMaterial._microSurface, - }; - const albedoTexture = babylonPBRMaterial._albedoTexture; - const reflectivityTexture = babylonPBRMaterial._reflectivityTexture; - const useMicrosurfaceFromReflectivityMapAlpha = babylonPBRMaterial._useMicroSurfaceFromReflectivityMapAlpha; - if (reflectivityTexture && !useMicrosurfaceFromReflectivityMapAlpha) { - return Promise.reject("_ConvertPBRMaterial: Glossiness values not included in the reflectivity texture are currently not supported"); + hasUVs: boolean + ): Promise { + const specGloss: IPBRSpecularGlossiness = { + diffuseColor: babylonPBRMaterial._albedoColor, + specularColor: babylonPBRMaterial._reflectivityColor, + glossiness: babylonPBRMaterial._microSurface, + }; + + const albedoTexture = babylonPBRMaterial._albedoTexture; + const reflectivityTexture = babylonPBRMaterial._reflectivityTexture; + const useMicrosurfaceFromReflectivityMapAlpha = babylonPBRMaterial._useMicroSurfaceFromReflectivityMapAlpha; + if (reflectivityTexture && !useMicrosurfaceFromReflectivityMapAlpha) { + return Promise.reject("_ConvertPBRMaterial: Glossiness values not included in the reflectivity texture are currently not supported"); + } + + if ((albedoTexture || reflectivityTexture) && hasUVs) { + this._exporter._materialNeedsUVsSet.add(babylonPBRMaterial); + + const samplerIndex = this._exportTextureSampler(albedoTexture || reflectivityTexture); + const metallicRoughnessFactors = await this._convertSpecularGlossinessTexturesToMetallicRoughnessAsync(albedoTexture, reflectivityTexture, specGloss, mimeType); + + const textures = this._exporter._textures; + + if (metallicRoughnessFactors.baseColorTextureData) { + const imageIndex = this._exportImage(`baseColor${textures.length}`, mimeType, metallicRoughnessFactors.baseColorTextureData); + pbrMetallicRoughness.baseColorTexture = this._exportTextureInfo(imageIndex, samplerIndex, albedoTexture?.coordinatesIndex); } - if ((albedoTexture || reflectivityTexture) && hasTextureCoords) { - const samplerIndex = this._exportTextureSampler(albedoTexture || reflectivityTexture); - return this._convertSpecularGlossinessTexturesToMetallicRoughnessAsync(albedoTexture, reflectivityTexture, specGloss, mimeType).then((metallicRoughnessFactors) => { - const textures = this._exporter._textures; - if (metallicRoughnessFactors.baseColorTextureData) { - const imageIndex = this._exportImage(`baseColor${textures.length}`, mimeType, metallicRoughnessFactors.baseColorTextureData); - pbrMetallicRoughness.baseColorTexture = this._exportTextureInfo(imageIndex, samplerIndex, albedoTexture?.coordinatesIndex); - } - if (metallicRoughnessFactors.metallicRoughnessTextureData) { - const imageIndex = this._exportImage(`metallicRoughness${textures.length}`, mimeType, metallicRoughnessFactors.metallicRoughnessTextureData); - pbrMetallicRoughness.metallicRoughnessTexture = this._exportTextureInfo(imageIndex, samplerIndex, reflectivityTexture?.coordinatesIndex); - } - return metallicRoughnessFactors; - }); - } else { - return this._convertSpecularGlossinessToMetallicRoughness(specGloss); + if (metallicRoughnessFactors.metallicRoughnessTextureData) { + const imageIndex = this._exportImage(`metallicRoughness${textures.length}`, mimeType, metallicRoughnessFactors.metallicRoughnessTextureData); + pbrMetallicRoughness.metallicRoughnessTexture = this._exportTextureInfo(imageIndex, samplerIndex, reflectivityTexture?.coordinatesIndex); } - }); + + return metallicRoughnessFactors; + } else { + return this._convertSpecularGlossinessToMetallicRoughness(specGloss); + } } - /** - * Converts a Babylon PBR Base Material to a glTF Material - * @param babylonPBRMaterial BJS PBR Base Material - * @param mimeType mime type to use for the textures - * @param hasTextureCoords specifies if texture coordinates are present on the submesh to determine if textures should be applied - * @returns async glTF Material representation - */ - public _convertPBRMaterialAsync(babylonPBRMaterial: PBRBaseMaterial, mimeType: ImageMimeType, hasTextureCoords: boolean): Promise { + public async exportPBRMaterialAsync(babylonPBRMaterial: PBRBaseMaterial, mimeType: ImageMimeType, hasUVs: boolean): Promise { const glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness = {}; + const glTFMaterial: IMaterial = { name: babylonPBRMaterial.name, }; + const useMetallicRoughness = babylonPBRMaterial.isMetallicWorkflow(); if (useMetallicRoughness) { @@ -956,68 +777,69 @@ export class _GLTFMaterialExporter { if (albedoColor) { glTFPbrMetallicRoughness.baseColorFactor = [albedoColor.r, albedoColor.g, albedoColor.b, alpha]; } - return this._convertMetalRoughFactorsToMetallicRoughnessAsync(babylonPBRMaterial, mimeType, glTFPbrMetallicRoughness, hasTextureCoords).then((metallicRoughness) => { - return this._setMetallicRoughnessPbrMaterial(metallicRoughness, babylonPBRMaterial, glTFMaterial, glTFPbrMetallicRoughness, mimeType, hasTextureCoords); - }); - } else { - return this._convertSpecGlossFactorsToMetallicRoughnessAsync(babylonPBRMaterial, mimeType, glTFPbrMetallicRoughness, hasTextureCoords).then((metallicRoughness) => { - return this._setMetallicRoughnessPbrMaterial(metallicRoughness, babylonPBRMaterial, glTFMaterial, glTFPbrMetallicRoughness, mimeType, hasTextureCoords); - }); } + + const metallicRoughness = useMetallicRoughness + ? await this._convertMetalRoughFactorsToMetallicRoughnessAsync(babylonPBRMaterial, mimeType, glTFPbrMetallicRoughness, hasUVs) + : await this._convertSpecGlossFactorsToMetallicRoughnessAsync(babylonPBRMaterial, mimeType, glTFPbrMetallicRoughness, hasUVs); + + await this._setMetallicRoughnessPbrMaterialAsync(metallicRoughness, babylonPBRMaterial, glTFMaterial, glTFPbrMetallicRoughness, mimeType, hasUVs); + await this._finishMaterialAsync(glTFMaterial, babylonPBRMaterial, mimeType); + + const materials = this._exporter._materials; + materials.push(glTFMaterial); + return materials.length - 1; } - private _setMetallicRoughnessPbrMaterial( - metallicRoughness: Nullable<_IPBRMetallicRoughness>, + private async _setMetallicRoughnessPbrMaterialAsync( + metallicRoughness: IPBRMetallicRoughness, babylonPBRMaterial: PBRBaseMaterial, glTFMaterial: IMaterial, glTFPbrMetallicRoughness: IMaterialPbrMetallicRoughness, mimeType: ImageMimeType, - hasTextureCoords: boolean - ): Promise { - const materialMap = this._exporter._materialMap; - const materials = this._exporter._materials; - const promises = []; - if (metallicRoughness) { - _GLTFMaterialExporter._SetAlphaMode(glTFMaterial, babylonPBRMaterial as PBRMaterial); - if ( - !( - _GLTFMaterialExporter._FuzzyEquals(metallicRoughness.baseColor, Color3.White(), _GLTFMaterialExporter._Epsilon) && - babylonPBRMaterial.alpha >= _GLTFMaterialExporter._Epsilon - ) - ) { - glTFPbrMetallicRoughness.baseColorFactor = [metallicRoughness.baseColor.r, metallicRoughness.baseColor.g, metallicRoughness.baseColor.b, babylonPBRMaterial.alpha]; - } + hasUVs: boolean + ): Promise { + setAlphaMode(glTFMaterial, babylonPBRMaterial); - if (metallicRoughness.metallic != null && metallicRoughness.metallic !== 1) { - glTFPbrMetallicRoughness.metallicFactor = metallicRoughness.metallic; - } - if (metallicRoughness.roughness != null && metallicRoughness.roughness !== 1) { - glTFPbrMetallicRoughness.roughnessFactor = metallicRoughness.roughness; - } + if (!metallicRoughness.baseColor.equalsWithEpsilon(white, epsilon) || !Scalar.WithinEpsilon(babylonPBRMaterial.alpha, 1, epsilon)) { + glTFPbrMetallicRoughness.baseColorFactor = [metallicRoughness.baseColor.r, metallicRoughness.baseColor.g, metallicRoughness.baseColor.b, babylonPBRMaterial.alpha]; + } - if (babylonPBRMaterial.backFaceCulling != null && !babylonPBRMaterial.backFaceCulling) { - if (!babylonPBRMaterial._twoSidedLighting) { - Tools.Warn(babylonPBRMaterial.name + ": Back-face culling disabled and two-sided lighting disabled is not supported in glTF."); - } - glTFMaterial.doubleSided = true; + if (metallicRoughness.metallic != null && metallicRoughness.metallic !== 1) { + glTFPbrMetallicRoughness.metallicFactor = metallicRoughness.metallic; + } + if (metallicRoughness.roughness != null && metallicRoughness.roughness !== 1) { + glTFPbrMetallicRoughness.roughnessFactor = metallicRoughness.roughness; + } + + if (babylonPBRMaterial.backFaceCulling != null && !babylonPBRMaterial.backFaceCulling) { + if (!babylonPBRMaterial._twoSidedLighting) { + Tools.Warn(babylonPBRMaterial.name + ": Back-face culling disabled and two-sided lighting disabled is not supported in glTF."); } + glTFMaterial.doubleSided = true; + } + + if (hasUVs) { + const promises: Promise[] = []; - if (hasTextureCoords) { - const bumpTexture = babylonPBRMaterial._bumpTexture; - if (bumpTexture) { - const promise = this._exportTextureAsync(bumpTexture, mimeType).then((glTFTexture) => { + const bumpTexture = babylonPBRMaterial._bumpTexture; + if (bumpTexture) { + promises.push( + this._exportTextureAsync(bumpTexture, mimeType).then((glTFTexture) => { if (glTFTexture) { glTFMaterial.normalTexture = glTFTexture; if (bumpTexture.level !== 1) { glTFMaterial.normalTexture.scale = bumpTexture.level; } } - }); - promises.push(promise); - } - const ambientTexture = babylonPBRMaterial._ambientTexture; - if (ambientTexture) { - const promise = this._exportTextureAsync(ambientTexture, mimeType).then((glTFTexture) => { + }) + ); + } + + const ambientTexture = babylonPBRMaterial._ambientTexture; + if (ambientTexture) { + promises.push( + this._exportTextureAsync(ambientTexture, mimeType).then((glTFTexture) => { if (glTFTexture) { const occlusionTexture: IMaterialOcclusionTextureInfo = { index: glTFTexture.index, @@ -1031,30 +853,33 @@ export class _GLTFMaterialExporter { occlusionTexture.strength = ambientTextureStrength; } } - }); - promises.push(promise); - } - const emissiveTexture = babylonPBRMaterial._emissiveTexture; - if (emissiveTexture) { - const promise = this._exportTextureAsync(emissiveTexture, mimeType).then((glTFTexture) => { + }) + ); + } + + const emissiveTexture = babylonPBRMaterial._emissiveTexture; + if (emissiveTexture) { + promises.push( + this._exportTextureAsync(emissiveTexture, mimeType).then((glTFTexture) => { if (glTFTexture) { glTFMaterial.emissiveTexture = glTFTexture; } - }); - promises.push(promise); - } + }) + ); } - const emissiveColor = babylonPBRMaterial._emissiveColor; - if (!_GLTFMaterialExporter._FuzzyEquals(emissiveColor, Color3.Black(), _GLTFMaterialExporter._Epsilon)) { - glTFMaterial.emissiveFactor = emissiveColor.asArray(); + + if (promises.length > 0) { + this._exporter._materialNeedsUVsSet.add(babylonPBRMaterial); + await Promise.all(promises); } + } - glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness; - materials.push(glTFMaterial); - materialMap[babylonPBRMaterial.uniqueId] = materials.length - 1; + const emissiveColor = babylonPBRMaterial._emissiveColor; + if (!emissiveColor.equalsWithEpsilon(black, epsilon)) { + glTFMaterial.emissiveFactor = emissiveColor.asArray(); } - return this._finishMaterial(promises, glTFMaterial, babylonPBRMaterial, mimeType); + glTFMaterial.pbrMetallicRoughness = glTFPbrMetallicRoughness; } private _getPixelsFromTexture(babylonTexture: BaseTexture): Promise> { @@ -1065,13 +890,7 @@ export class _GLTFMaterialExporter { return pixels; } - /** - * Extracts a texture from a Babylon texture into file data and glTF data - * @param babylonTexture Babylon texture to extract - * @param mimeType Mime Type of the babylonTexture - * @returns glTF texture info, or null if the texture format is not supported - */ - public _exportTextureAsync(babylonTexture: BaseTexture, mimeType: ImageMimeType): Promise> { + private async _exportTextureAsync(babylonTexture: BaseTexture, mimeType: ImageMimeType): Promise> { const extensionPromise = this._exporter._extensionsPreExportTextureAsync("exporter", babylonTexture as Texture, mimeType); if (!extensionPromise) { return this._exportTextureInfoAsync(babylonTexture, mimeType); @@ -1085,9 +904,9 @@ export class _GLTFMaterialExporter { }); } - public async _exportTextureInfoAsync(babylonTexture: BaseTexture, mimeType: ImageMimeType): Promise> { - const textureUid = babylonTexture.uid; - if (!(textureUid in this._textureMap)) { + private async _exportTextureInfoAsync(babylonTexture: BaseTexture, mimeType: ImageMimeType): Promise> { + let textureInfo = this._textureMap.get(babylonTexture); + if (!textureInfo) { const pixels = await this._getPixelsFromTexture(babylonTexture); if (!pixels) { return null; @@ -1123,12 +942,12 @@ export class _GLTFMaterialExporter { internalTextureToImage[internalTextureUniqueId][mimeType] = imageIndexPromise; } - const textureInfo = this._exportTextureInfo(await imageIndexPromise, samplerIndex, babylonTexture.coordinatesIndex); - this._textureMap[textureUid] = textureInfo; - this._exporter._extensionsPostExportTextures("exporter", this._textureMap[textureUid], babylonTexture); + textureInfo = this._exportTextureInfo(await imageIndexPromise, samplerIndex, babylonTexture.coordinatesIndex); + this._textureMap.set(babylonTexture, textureInfo); + this._exporter._extensionsPostExportTextures("exporter", textureInfo, babylonTexture); } - return this._textureMap[textureUid]; + return textureInfo; } private _exportImage(name: string, mimeType: ImageMimeType, data: ArrayBuffer): number { diff --git a/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts b/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts index c346d8b61d9..1787b779fa3 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts @@ -2,7 +2,7 @@ import type { Node } from "core/node"; import type { Scene } from "core/scene"; import type { Animation } from "core/Animations/animation"; import type { GLTFData } from "./glTFData"; -import { _Exporter } from "./glTFExporter"; +import { GLTFExporter } from "./glTFExporter"; /** * Holds a collection of exporter options and parameters @@ -61,55 +61,40 @@ export interface IExportOptions { */ export class GLTF2Export { /** - * Exports the geometry of the scene to .gltf file format asynchronously - * @param scene Babylon scene with scene hierarchy information - * @param filePrefix File prefix to use when generating the glTF file + * Exports the scene to .gltf file format + * @param scene Babylon scene + * @param fileName Name to use for the .gltf file * @param options Exporter options - * @returns Returns an object with a .gltf file and associates texture names - * as keys and their data and paths as values + * @returns Returns the exported data */ - public static GLTFAsync(scene: Scene, filePrefix: string, options?: IExportOptions): Promise { - return scene.whenReadyAsync().then(() => { - const glTFPrefix = filePrefix.replace(/\.[^/.]+$/, ""); - const gltfGenerator = new _Exporter(scene, options); - return gltfGenerator._generateGLTFAsync(glTFPrefix); - }); - } + public static async GLTFAsync(scene: Scene, fileName: string, options?: IExportOptions): Promise { + if (!options || !options.exportWithoutWaitingForScene) { + await scene.whenReadyAsync(); + } - private static _PreExportAsync(scene: Scene, options?: IExportOptions): Promise { - return Promise.resolve().then(() => { - if (options && options.exportWithoutWaitingForScene) { - return Promise.resolve(); - } else { - return scene.whenReadyAsync(); - } - }); - } + const exporter = new GLTFExporter(scene, options); + const data = await exporter.generateGLTFAsync(fileName.replace(/\.[^/.]+$/, "")); + exporter.dispose(); - private static _PostExportAsync(scene: Scene, glTFData: GLTFData, options?: IExportOptions): Promise { - return Promise.resolve().then(() => { - if (options && options.exportWithoutWaitingForScene) { - return glTFData; - } else { - return glTFData; - } - }); + return data; } /** - * Exports the geometry of the scene to .glb file format asychronously - * @param scene Babylon scene with scene hierarchy information - * @param filePrefix File prefix to use when generating glb file + * Exports the scene to .glb file format + * @param scene Babylon scene + * @param fileName Name to use for the .glb file * @param options Exporter options - * @returns Returns an object with a .glb filename as key and data as value + * @returns Returns the exported data */ - public static GLBAsync(scene: Scene, filePrefix: string, options?: IExportOptions): Promise { - return this._PreExportAsync(scene, options).then(() => { - const glTFPrefix = filePrefix.replace(/\.[^/.]+$/, ""); - const gltfGenerator = new _Exporter(scene, options); - return gltfGenerator._generateGLBAsync(glTFPrefix).then((glTFData) => { - return this._PostExportAsync(scene, glTFData, options); - }); - }); + public static async GLBAsync(scene: Scene, fileName: string, options?: IExportOptions): Promise { + if (!options || !options.exportWithoutWaitingForScene) { + await scene.whenReadyAsync(); + } + + const exporter = new GLTFExporter(scene, options); + const data = await exporter.generateGLBAsync(fileName.replace(/\.[^/.]+$/, "")); + exporter.dispose(); + + return data; } } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index a89f788042c..b3b049b4d65 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -1,137 +1,336 @@ +/* eslint-disable jsdoc/require-jsdoc */ + import type { IBufferView, AccessorComponentType, IAccessor } from "babylonjs-gltf2interface"; -import { AccessorType } from "babylonjs-gltf2interface"; +import { AccessorType, MeshPrimitiveMode } from "babylonjs-gltf2interface"; -import type { FloatArray, Nullable } from "core/types"; +import type { DataArray, IndicesArray, Nullable } from "core/types"; import type { Vector4 } from "core/Maths/math.vector"; -import { Vector3 } from "core/Maths/math.vector"; +import { Quaternion, TmpVectors, Matrix, Vector3 } from "core/Maths/math.vector"; +import { VertexBuffer } from "core/Buffers/buffer"; +import { Material } from "core/Materials/material"; +import { TransformNode } from "core/Meshes/transformNode"; +import { Mesh } from "core/Meshes/mesh"; +import { InstancedMesh } from "core/Meshes/instancedMesh"; +import { enumerateFloatValues } from "core/Buffers/bufferUtils"; +import type { Node } from "core/node"; + +// Matrix that converts handedness on the X-axis. +const convertHandednessMatrix = Matrix.Compose(new Vector3(-1, 1, 1), Quaternion.Identity(), Vector3.Zero()); /** - * @internal + * Creates a buffer view based on the supplied arguments + * @param bufferIndex index value of the specified buffer + * @param byteOffset byte offset value + * @param byteLength byte length of the bufferView + * @param byteStride byte distance between conequential elements + * @returns bufferView for glTF */ -export class _GLTFUtilities { - /** - * Creates a buffer view based on the supplied arguments - * @param bufferIndex index value of the specified buffer - * @param byteOffset byte offset value - * @param byteLength byte length of the bufferView - * @param byteStride byte distance between conequential elements - * @param name name of the buffer view - * @returns bufferView for glTF - */ - public static _CreateBufferView(bufferIndex: number, byteOffset: number, byteLength: number, byteStride?: number, name?: string): IBufferView { - const bufferview: IBufferView = { buffer: bufferIndex, byteLength: byteLength }; - if (byteOffset) { - bufferview.byteOffset = byteOffset; - } - if (name) { - bufferview.name = name; - } - if (byteStride) { - bufferview.byteStride = byteStride; - } +export function createBufferView(bufferIndex: number, byteOffset: number, byteLength: number, byteStride?: number): IBufferView { + const bufferview: IBufferView = { buffer: bufferIndex, byteLength: byteLength }; - return bufferview; - } - - /** - * Creates an accessor based on the supplied arguments - * @param bufferviewIndex The index of the bufferview referenced by this accessor - * @param name The name of the accessor - * @param type The type of the accessor - * @param componentType The datatype of components in the attribute - * @param count The number of attributes referenced by this accessor - * @param byteOffset The offset relative to the start of the bufferView in bytes - * @param min Minimum value of each component in this attribute - * @param max Maximum value of each component in this attribute - * @returns accessor for glTF - */ - public static _CreateAccessor( - bufferviewIndex: number, - name: string, - type: AccessorType, - componentType: AccessorComponentType, - count: number, - byteOffset: Nullable, - min: Nullable, - max: Nullable - ): IAccessor { - const accessor: IAccessor = { name: name, bufferView: bufferviewIndex, componentType: componentType, count: count, type: type }; - - if (min != null) { - accessor.min = min; - } - if (max != null) { - accessor.max = max; + if (byteOffset) { + bufferview.byteOffset = byteOffset; + } + + if (byteStride) { + bufferview.byteStride = byteStride; + } + + return bufferview; +} + +/** + * Creates an accessor based on the supplied arguments + * @param bufferViewIndex The index of the bufferview referenced by this accessor + * @param type The type of the accessor + * @param componentType The datatype of components in the attribute + * @param count The number of attributes referenced by this accessor + * @param byteOffset The offset relative to the start of the bufferView in bytes + * @param minMax Minimum and maximum value of each component in this attribute + * @returns accessor for glTF + */ +export function createAccessor( + bufferViewIndex: number, + type: AccessorType, + componentType: AccessorComponentType, + count: number, + byteOffset: Nullable, + minMax: Nullable<{ min: number[]; max: number[] }> = null +): IAccessor { + const accessor: IAccessor = { bufferView: bufferViewIndex, componentType: componentType, count: count, type: type }; + + if (minMax != null) { + accessor.min = minMax.min; + accessor.max = minMax.max; + } + + if (byteOffset != null) { + accessor.byteOffset = byteOffset; + } + + return accessor; +} + +export function getAccessorElementCount(accessorType: AccessorType): number { + switch (accessorType) { + case AccessorType.MAT2: + return 4; + case AccessorType.MAT3: + return 9; + case AccessorType.MAT4: + return 16; + case AccessorType.SCALAR: + return 1; + case AccessorType.VEC2: + return 2; + case AccessorType.VEC3: + return 3; + case AccessorType.VEC4: + return 4; + } +} + +export function getAccessorType(kind: string): AccessorType { + switch (kind) { + case VertexBuffer.PositionKind: + case VertexBuffer.NormalKind: + return AccessorType.VEC3; + case VertexBuffer.ColorKind: + case VertexBuffer.TangentKind: + case VertexBuffer.MatricesIndicesKind: + case VertexBuffer.MatricesIndicesExtraKind: + case VertexBuffer.MatricesWeightsKind: + case VertexBuffer.MatricesWeightsExtraKind: + return AccessorType.VEC4; + case VertexBuffer.UVKind: + case VertexBuffer.UV2Kind: + case VertexBuffer.UV3Kind: + case VertexBuffer.UV4Kind: + case VertexBuffer.UV5Kind: + case VertexBuffer.UV6Kind: + return AccessorType.VEC2; + } + + throw new Error(`Unknown kind ${kind}`); +} + +export function getAttributeType(kind: string): string { + switch (kind) { + case VertexBuffer.PositionKind: + return "POSITION"; + case VertexBuffer.NormalKind: + return "NORMAL"; + case VertexBuffer.TangentKind: + return "TANGENT"; + case VertexBuffer.ColorKind: + return "COLOR_0"; + case VertexBuffer.UVKind: + return "TEXCOORD_0"; + case VertexBuffer.UV2Kind: + return "TEXCOORD_1"; + case VertexBuffer.UV3Kind: + return "TEXCOORD_2"; + case VertexBuffer.UV4Kind: + return "TEXCOORD_3"; + case VertexBuffer.UV5Kind: + return "TEXCOORD_4"; + case VertexBuffer.UV6Kind: + return "TEXCOORD_5"; + case VertexBuffer.MatricesIndicesKind: + return "JOINTS_0"; + case VertexBuffer.MatricesIndicesExtraKind: + return "JOINTS_1"; + case VertexBuffer.MatricesWeightsKind: + return "WEIGHTS_0"; + case VertexBuffer.MatricesWeightsExtraKind: + return "WEIGHTS_1"; + } + + throw new Error(`Unknown kind: ${kind}`); +} + +export function getPrimitiveMode(fillMode: number): MeshPrimitiveMode { + switch (fillMode) { + case Material.TriangleFillMode: + return MeshPrimitiveMode.TRIANGLES; + case Material.TriangleStripDrawMode: + return MeshPrimitiveMode.TRIANGLE_STRIP; + case Material.TriangleFanDrawMode: + return MeshPrimitiveMode.TRIANGLE_FAN; + case Material.PointListDrawMode: + case Material.PointFillMode: + return MeshPrimitiveMode.POINTS; + case Material.LineLoopDrawMode: + return MeshPrimitiveMode.LINE_LOOP; + case Material.LineListDrawMode: + return MeshPrimitiveMode.LINES; + case Material.LineStripDrawMode: + return MeshPrimitiveMode.LINE_STRIP; + } + + throw new Error(`Unknown fill mode: ${fillMode}`); +} + +export function isTriangleFillMode(fillMode: number): boolean { + switch (fillMode) { + case Material.TriangleFillMode: + case Material.TriangleStripDrawMode: + case Material.TriangleFanDrawMode: + return true; + } + + return false; +} + +export function normalizeTangent(tangent: Vector4) { + const length = Math.sqrt(tangent.x * tangent.x + tangent.y * tangent.y + tangent.z * tangent.z); + if (length > 0) { + tangent.x /= length; + tangent.y /= length; + tangent.z /= length; + } +} + +export function convertToRightHandedPosition(value: Vector3): Vector3 { + value.x *= -1; + return value; +} + +export function convertToRightHandedRotation(value: Quaternion): Quaternion { + value.x *= -1; + value.y *= -1; + return value; +} + +// /** +// * Converts a new right-handed Vector3 +// * @param vector vector3 array +// * @returns right-handed Vector3 +// */ +// public static _GetRightHandedNormalVector3(vector: Vector3): Vector3 { +// return new Vector3(vector.x, vector.y, -vector.z); +// } + +// /** +// * Converts a Vector3 to right-handed +// * @param vector Vector3 to convert to right-handed +// */ +// public static _GetRightHandedNormalVector3FromRef(vector: Vector3) { +// vector.z *= -1; +// } + +// /** +// * Converts a three element number array to right-handed +// * @param vector number array to convert to right-handed +// */ +// public static _GetRightHandedNormalArray3FromRef(vector: number[]) { +// vector[2] *= -1; +// } + +// /** +// * Converts a Vector4 to right-handed +// * @param vector Vector4 to convert to right-handed +// */ +// public static _GetRightHandedVector4FromRef(vector: Vector4) { +// vector.z *= -1; +// vector.w *= -1; +// } + +// /** +// * Converts a Vector4 to right-handed +// * @param vector Vector4 to convert to right-handed +// */ +// public static _GetRightHandedArray4FromRef(vector: number[]) { +// vector[2] *= -1; +// vector[3] *= -1; +// } + +// /** +// * Converts a Quaternion to right-handed +// * @param quaternion Source quaternion to convert to right-handed +// */ +// public static _GetRightHandedQuaternionFromRef(quaternion: Quaternion) { +// quaternion.x *= -1; +// quaternion.y *= -1; +// } + +// /** +// * Converts a Quaternion to right-handed +// * @param quaternion Source quaternion to convert to right-handed +// */ +// public static _GetRightHandedQuaternionArrayFromRef(quaternion: number[]) { +// quaternion[0] *= -1; +// quaternion[1] *= -1; +// } + +export function isNoopNode(node: Node, useRightHandedSystem: boolean): boolean { + if (!(node instanceof TransformNode)) { + return false; + } + + // Transform + if (useRightHandedSystem) { + const matrix = node.getWorldMatrix(); + if (!matrix.isIdentity()) { + return false; } - if (byteOffset != null) { - accessor.byteOffset = byteOffset; + } else { + const matrix = node.getWorldMatrix().multiplyToRef(convertHandednessMatrix, TmpVectors.Matrix[0]); + if (!matrix.isIdentity()) { + return false; } + } - return accessor; - } - - /** - * Calculates the minimum and maximum values of an array of position floats - * @param positions Positions array of a mesh - * @param vertexStart Starting vertex offset to calculate min and max values - * @param vertexCount Number of vertices to check for min and max values - * @returns min number array and max number array - */ - public static _CalculateMinMaxPositions(positions: FloatArray, vertexStart: number, vertexCount: number): { min: number[]; max: number[] } { - const min = [Infinity, Infinity, Infinity]; - const max = [-Infinity, -Infinity, -Infinity]; - const positionStrideSize = 3; - let indexOffset: number; - let position: Vector3; - let vector: number[]; - - if (vertexCount) { - for (let i = vertexStart, length = vertexStart + vertexCount; i < length; ++i) { - indexOffset = positionStrideSize * i; - - position = Vector3.FromArray(positions, indexOffset); - vector = position.asArray(); - - for (let j = 0; j < positionStrideSize; ++j) { - const num = vector[j]; - if (num < min[j]) { - min[j] = num; - } - if (num > max[j]) { - max[j] = num; - } - ++indexOffset; - } - } - } - return { min, max }; + // Geometry + if ((node instanceof Mesh && node.geometry) || (node instanceof InstancedMesh && node.sourceMesh.geometry)) { + return false; } - public static _NormalizeTangentFromRef(tangent: Vector4 | Vector3) { - const length = Math.sqrt(tangent.x * tangent.x + tangent.y * tangent.y + tangent.z * tangent.z); - if (length > 0) { - tangent.x /= length; - tangent.y /= length; - tangent.z /= length; + return true; +} + +export function areIndices32Bits(indices: Nullable, count: number): boolean { + if (indices) { + if (indices instanceof Array) { + return indices.some((value) => value >= 65536); } + + return indices.BYTES_PER_ELEMENT === 4; } - public static _GetDataAccessorElementCount(accessorType: AccessorType) { - switch (accessorType) { - case AccessorType.MAT2: - return 4; - case AccessorType.MAT3: - return 9; - case AccessorType.MAT4: - return 16; - case AccessorType.SCALAR: - return 1; - case AccessorType.VEC2: - return 2; - case AccessorType.VEC3: - return 3; - case AccessorType.VEC4: - return 4; - } + return count >= 65536; +} + +export function indicesArrayToUint8Array(indices: IndicesArray, start: number, count: number, is32Bits: boolean): Uint8Array { + if (indices instanceof Array) { + const subarray = indices.slice(start, start + count); + indices = is32Bits ? new Uint32Array(subarray) : new Uint16Array(subarray); + return new Uint8Array(indices.buffer, indices.byteOffset, indices.byteLength); } + + return ArrayBuffer.isView(indices) ? new Uint8Array(indices.buffer, indices.byteOffset, indices.byteLength) : new Uint8Array(indices); +} + +export function dataArrayToUint8Array(data: DataArray): Uint8Array { + if (data instanceof Array) { + const floatData = new Float32Array(data); + return new Uint8Array(floatData.buffer, floatData.byteOffset, floatData.byteLength); + } + + return ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data); +} + +export function getMinMax(data: DataArray, vertexBuffer: VertexBuffer, start: number, count: number): { min: number[]; max: number[] } { + const { byteOffset, byteStride, type, normalized } = vertexBuffer; + const size = vertexBuffer.getSize(); + const min = new Array(size).fill(Infinity); + const max = new Array(size).fill(-Infinity); + enumerateFloatValues(data, byteOffset + start * byteStride, byteStride, size, type, count * size, normalized, (values) => { + for (let i = 0; i < size; i++) { + min[i] = Math.min(min[i], values[i]); + max[i] = Math.max(max[i], values[i]); + } + }); + + return { min, max }; } diff --git a/packages/dev/serializers/src/glTF/2.0/index.ts b/packages/dev/serializers/src/glTF/2.0/index.ts index 6ce87eb2bab..b5b53fa542b 100644 --- a/packages/dev/serializers/src/glTF/2.0/index.ts +++ b/packages/dev/serializers/src/glTF/2.0/index.ts @@ -1,9 +1,2 @@ -/* eslint-disable import/no-internal-modules */ -export * from "./glTFAnimation"; export * from "./glTFData"; -export * from "./glTFExporter"; -export * from "./glTFExporterExtension"; -export * from "./glTFMaterialExporter"; export * from "./glTFSerializer"; -export * from "./glTFUtilities"; -export * from "./Extensions/index"; diff --git a/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts b/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts index 4193da355f3..15d4fffb12d 100644 --- a/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts +++ b/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts @@ -1040,11 +1040,6 @@ declare module BABYLON.GLTF2 { lights: IKHRLightsPunctual_Light[]; } - /** @internal */ - interface IMaterialExtension { - hasTextures?(): boolean; - } - /** @internal */ interface IKHRMaterialsClearcoat extends IMaterialExtension { clearcoatFactor?: number; diff --git a/packages/tools/tests/test/visualization/config.json b/packages/tools/tests/test/visualization/config.json index ace0e535fd6..e7e89de70ee 100644 --- a/packages/tools/tests/test/visualization/config.json +++ b/packages/tools/tests/test/visualization/config.json @@ -793,29 +793,29 @@ }, { "title": "GLTF Serializer with Negative World Matrix (left handed, once)", - "playgroundId": "#KX53VK#48", + "playgroundId": "#KX53VK#77", "referenceImage": "glTFSerializerNegativeWorldMatrix.png", "errorRatio": 1.1 }, { "title": "GLTF Serializer with Negative World Matrix (left handed, twice)", - "playgroundId": "#KX53VK#48", + "playgroundId": "#KX53VK#77", "referenceImage": "glTFSerializerNegativeWorldMatrix.png", - "replace": "iterations = 1, iterations = 2", + "replace": "//options//, iterations = 2;", "errorRatio": 1.1 }, { "title": "GLTF Serializer with Negative World Matrix (right handed, once)", - "playgroundId": "#KX53VK#48", + "playgroundId": "#KX53VK#76", "referenceImage": "glTFSerializerNegativeWorldMatrix.png", - "replace": "useRightHandedSystem = false, useRightHandedSystem = true", + "replace": "//options//, useRightHandedSystem = true;", "errorRatio": 1.1 }, { "title": "GLTF Serializer with Negative World Matrix (right handed, twice)", - "playgroundId": "#KX53VK#48", + "playgroundId": "#KX53VK#76", "referenceImage": "glTFSerializerNegativeWorldMatrix.png", - "replace": "useRightHandedSystem = false, useRightHandedSystem = true, iterations = 1, iterations = 2", + "replace": "//options//, useRightHandedSystem = true; iterations = 2;", "errorRatio": 1.1 }, { From ebac165907fb75ee6520d44a795924dac6942bb3 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 7 Oct 2024 16:17:19 -0300 Subject: [PATCH 002/133] Added triangle re-ordering logic --- .../serializers/src/glTF/2.0/glTFExporter.ts | 35 +++++++++++++++++-- 1 file changed, 32 insertions(+), 3 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 6b5213f785d..49157703f93 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -62,6 +62,7 @@ import { Camera } from "core/Cameras/camera"; import { MultiMaterial, PBRMaterial, StandardMaterial } from "core/Materials"; import { Logger } from "core/Misc/logger"; import { enumerateFloatValues } from "core/Buffers/bufferUtils"; +import { Constants } from "core/Engines/constants"; // 180 degrees rotation in Y. // const rotation180Y = new Quaternion(0, 1, 0, 0); @@ -1207,13 +1208,19 @@ export class GLTFExporter { return nodes; } - private _collectBuffers(babylonNode: Node, bufferToVertexBuffersMap: Map, vertexBufferToMeshesMap: Map): void { + private _collectBuffers( + babylonNode: Node, + bufferToVertexBuffersMap: Map, + vertexBufferToMeshesMap: Map, + indexBuffersToMeshesMap: Map + ): void { if (!this._shouldExportNode(babylonNode)) { return; } if (babylonNode instanceof Mesh && babylonNode.geometry) { const vertexBuffers = babylonNode.geometry.getVertexBuffers(); + const indexBuffers = babylonNode.geometry.getIndices(); if (vertexBuffers) { for (const kind in vertexBuffers) { const vertexBuffer = vertexBuffers[kind]; @@ -1232,19 +1239,40 @@ export class GLTFExporter { } } } + + if (indexBuffers) { + const meshes = indexBuffersToMeshesMap.get(indexBuffers) || []; + indexBuffersToMeshesMap.set(indexBuffers, meshes); + } } for (const babylonChildNode of babylonNode.getChildren()) { - this._collectBuffers(babylonChildNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap); + this._collectBuffers(babylonChildNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap, indexBuffersToMeshesMap); } } private _exportBuffers(babylonRootNodes: Node[], convertToRightHanded: boolean, state: ExporterState): void { const bufferToVertexBuffersMap = new Map(); const vertexBufferToMeshesMap = new Map(); + const indexBufferToMeshesMap = new Map(); for (const babylonNode of babylonRootNodes) { - this._collectBuffers(babylonNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap); + this._collectBuffers(babylonNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap, indexBufferToMeshesMap); + } + + for (const [buffer] of indexBufferToMeshesMap) { + if (!convertToRightHanded) { + const type = buffer instanceof Uint16Array ? Constants.UNSIGNED_SHORT : buffer instanceof Uint32Array ? Constants.UNSIGNED_INT : Constants.INT; + const bytesPerIndex = type == Constants.UNSIGNED_SHORT ? 2 : 4; + enumerateFloatValues(buffer, 0, 3 * bytesPerIndex, 3, type, buffer.length, false, (values) => { + const a = values[0]; + const b = values[1]; + const c = values[2]; + values[0] = c; + values[1] = b; + values[2] = a; + }); + } } for (const [buffer, vertexBuffers] of bufferToVertexBuffersMap) { @@ -1279,6 +1307,7 @@ export class GLTFExporter { } } + // Performs coordinate conversion if needed (only for position, normal and tanget). if (convertToRightHanded) { for (const vertexBuffer of vertexBuffers) { switch (vertexBuffer.getKind()) { From 7bc831e837e2febd7edeea168472a534e355c330 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 7 Oct 2024 17:30:31 -0300 Subject: [PATCH 003/133] Updated index logic to flip based on coordinate system --- .../serializers/src/glTF/2.0/glTFExporter.ts | 51 +++++-------------- 1 file changed, 12 insertions(+), 39 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 49157703f93..5396310fd82 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -62,7 +62,6 @@ import { Camera } from "core/Cameras/camera"; import { MultiMaterial, PBRMaterial, StandardMaterial } from "core/Materials"; import { Logger } from "core/Misc/logger"; import { enumerateFloatValues } from "core/Buffers/bufferUtils"; -import { Constants } from "core/Engines/constants"; // 180 degrees rotation in Y. // const rotation180Y = new Quaternion(0, 1, 0, 0); @@ -1201,26 +1200,20 @@ export class GLTFExporter { for (const babylonNode of babylonRootNodes) { if (this._shouldExportNode(babylonNode)) { - nodes.push(await this._exportNodeAsync(babylonNode, state)); + nodes.push(await this._exportNodeAsync(babylonNode, state, convertToRightHanded)); } } return nodes; } - private _collectBuffers( - babylonNode: Node, - bufferToVertexBuffersMap: Map, - vertexBufferToMeshesMap: Map, - indexBuffersToMeshesMap: Map - ): void { + private _collectBuffers(babylonNode: Node, bufferToVertexBuffersMap: Map, vertexBufferToMeshesMap: Map): void { if (!this._shouldExportNode(babylonNode)) { return; } if (babylonNode instanceof Mesh && babylonNode.geometry) { const vertexBuffers = babylonNode.geometry.getVertexBuffers(); - const indexBuffers = babylonNode.geometry.getIndices(); if (vertexBuffers) { for (const kind in vertexBuffers) { const vertexBuffer = vertexBuffers[kind]; @@ -1239,40 +1232,19 @@ export class GLTFExporter { } } } - - if (indexBuffers) { - const meshes = indexBuffersToMeshesMap.get(indexBuffers) || []; - indexBuffersToMeshesMap.set(indexBuffers, meshes); - } } for (const babylonChildNode of babylonNode.getChildren()) { - this._collectBuffers(babylonChildNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap, indexBuffersToMeshesMap); + this._collectBuffers(babylonChildNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap); } } private _exportBuffers(babylonRootNodes: Node[], convertToRightHanded: boolean, state: ExporterState): void { const bufferToVertexBuffersMap = new Map(); const vertexBufferToMeshesMap = new Map(); - const indexBufferToMeshesMap = new Map(); for (const babylonNode of babylonRootNodes) { - this._collectBuffers(babylonNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap, indexBufferToMeshesMap); - } - - for (const [buffer] of indexBufferToMeshesMap) { - if (!convertToRightHanded) { - const type = buffer instanceof Uint16Array ? Constants.UNSIGNED_SHORT : buffer instanceof Uint32Array ? Constants.UNSIGNED_INT : Constants.INT; - const bytesPerIndex = type == Constants.UNSIGNED_SHORT ? 2 : 4; - enumerateFloatValues(buffer, 0, 3 * bytesPerIndex, 3, type, buffer.length, false, (values) => { - const a = values[0]; - const b = values[1]; - const c = values[2]; - values[0] = c; - values[1] = b; - values[2] = a; - }); - } + this._collectBuffers(babylonNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap); } for (const [buffer, vertexBuffers] of bufferToVertexBuffersMap) { @@ -1336,7 +1308,7 @@ export class GLTFExporter { } } - private async _exportNodeAsync(babylonNode: Node, state: ExporterState): Promise { + private async _exportNodeAsync(babylonNode: Node, state: ExporterState, convertToRightHanded: boolean): Promise { let nodeIndex = this._nodeMap.get(babylonNode); if (nodeIndex !== undefined) { return nodeIndex; @@ -1357,7 +1329,7 @@ export class GLTFExporter { if (babylonNode instanceof Mesh || babylonNode instanceof InstancedMesh) { const babylonMesh = babylonNode instanceof Mesh ? babylonNode : babylonNode.sourceMesh; if (babylonMesh.subMeshes && babylonMesh.subMeshes.length > 0) { - node.mesh = await this._exportMeshAsync(babylonMesh, state); + node.mesh = await this._exportMeshAsync(babylonMesh, state, convertToRightHanded); } } else if (babylonNode instanceof Camera) { // TODO: handle camera @@ -1369,7 +1341,7 @@ export class GLTFExporter { for (const babylonChildNode of babylonNode.getChildren()) { if (this._shouldExportNode(babylonChildNode)) { node.children ||= []; - node.children.push(await this._exportNodeAsync(babylonChildNode, state)); + node.children.push(await this._exportNodeAsync(babylonChildNode, state, convertToRightHanded)); } } @@ -1384,7 +1356,8 @@ export class GLTFExporter { fillMode: number, sideOrientation: number, state: ExporterState, - primitive: IMeshPrimitive + primitive: IMeshPrimitive, + convertToRightHanded: boolean ): void { const is32Bits = areIndices32Bits(indices, count); let indicesToExport = indices; @@ -1393,7 +1366,7 @@ export class GLTFExporter { // Flip if triangle winding order is not CCW as glTF is always CCW. const flip = isTriangleFillMode(fillMode) && sideOrientation !== Material.CounterClockWiseSideOrientation; - if (flip) { + if (flip && convertToRightHanded) { if (fillMode === Material.TriangleStripDrawMode || fillMode === Material.TriangleFanDrawMode) { throw new Error("Triangle strip/fan fill mode is not implemented"); } @@ -1510,7 +1483,7 @@ export class GLTFExporter { primitive.material = materialIndex; } - private async _exportMeshAsync(babylonMesh: Mesh, state: ExporterState): Promise { + private async _exportMeshAsync(babylonMesh: Mesh, state: ExporterState, convertToRightHanded: boolean): Promise { let meshIndex = state.getMesh(babylonMesh); if (meshIndex !== undefined) { return meshIndex; @@ -1536,7 +1509,7 @@ export class GLTFExporter { // Index buffer const fillMode = babylonMesh.overrideRenderingFillMode ?? babylonMaterial.fillMode; const sideOrientation = babylonMaterial._getEffectiveOrientation(babylonMesh); - this._exportIndices(indices, subMesh.indexStart, subMesh.indexCount, -subMesh.verticesStart, fillMode, sideOrientation, state, primitive); + this._exportIndices(indices, subMesh.indexStart, subMesh.indexCount, -subMesh.verticesStart, fillMode, sideOrientation, state, primitive, convertToRightHanded); // Vertex buffers for (const vertexBuffer of Object.values(vertexBuffers)) { From c87e0ff150de3046b7cfc8796b7f300335cde663 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Tue, 8 Oct 2024 15:37:52 -0300 Subject: [PATCH 004/133] Added camera support --- .../serializers/src/glTF/2.0/glTFExporter.ts | 138 ++++++++++-------- 1 file changed, 77 insertions(+), 61 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 5396310fd82..ae7ff25c3e2 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -19,7 +19,7 @@ import type { ISkin, ICamera, } from "babylonjs-gltf2interface"; -import { AccessorComponentType, AccessorType, ImageMimeType } from "babylonjs-gltf2interface"; +import { AccessorComponentType, AccessorType, CameraType, ImageMimeType } from "babylonjs-gltf2interface"; import type { IndicesArray, Nullable } from "core/types"; import { TmpVectors, Quaternion } from "core/Maths/math.vector"; @@ -64,7 +64,7 @@ import { Logger } from "core/Misc/logger"; import { enumerateFloatValues } from "core/Buffers/bufferUtils"; // 180 degrees rotation in Y. -// const rotation180Y = new Quaternion(0, 1, 0, 0); +const rotation180Y = new Quaternion(0, 1, 0, 0); class ExporterState { // Babylon indices array, start, count, offset, flip -> glTF accessor index @@ -79,6 +79,8 @@ class ExporterState { // Babylon mesh -> glTF mesh index private _meshMap = new Map(); + private _camerasMap = new Map(); + public constructor(convertToRightHanded: boolean) { this.convertToRightHanded = convertToRightHanded; } @@ -155,6 +157,14 @@ class ExporterState { public setMesh(mesh: Mesh, meshIndex: number): void { this._meshMap.set(mesh, meshIndex); } + + public getCameraIndex(camera: Camera): number | undefined { + return this._camerasMap.get(camera); + } + + public setCameraIndex(camera: Camera, cameraIndex: number): void { + this._camerasMap.set(camera, cameraIndex); + } } /** @internal */ @@ -727,30 +737,30 @@ export class GLTFExporter { } } - // private _setCameraTransformation(node: INode, babylonCamera: Camera, convertToRightHanded: boolean): void { - // const translation = TmpVectors.Vector3[0]; - // const rotation = TmpVectors.Quaternion[0]; - // babylonCamera.getWorldMatrix().decompose(undefined, rotation, translation); + private _setCameraTransformation(node: INode, babylonCamera: Camera, convertToRightHanded: boolean): void { + const translation = TmpVectors.Vector3[0]; + const rotation = TmpVectors.Quaternion[0]; + babylonCamera.getWorldMatrix().decompose(undefined, rotation, translation); - // if (!translation.equalsToFloats(0, 0, 0)) { - // if (convertToRightHanded) { - // convertToRightHandedPosition(translation); - // } + if (!translation.equalsToFloats(0, 0, 0)) { + if (convertToRightHanded) { + convertToRightHandedPosition(translation); + } - // node.translation = translation.asArray(); - // } + node.translation = translation.asArray(); + } - // // Rotation by 180 as glTF has a different convention than Babylon. - // rotation.multiplyInPlace(rotation180Y); + // Rotation by 180 as glTF has a different convention than Babylon. + rotation.multiplyInPlace(rotation180Y); - // if (!Quaternion.IsIdentity(rotation)) { - // if (convertToRightHanded) { - // convertToRightHandedRotation(rotation); - // } + if (!Quaternion.IsIdentity(rotation)) { + if (convertToRightHanded) { + convertToRightHandedRotation(rotation); + } - // node.rotation = rotation.asArray(); - // } - // } + node.rotation = rotation.asArray(); + } + } // /** // * Creates a bufferview based on the vertices type for the Babylon mesh @@ -1089,43 +1099,48 @@ export class GLTFExporter { } } - // // Export babylon cameras to glTF cameras - // const cameraMap = new Map(); - // for (const camera of cameras) { - // const glTFCamera: ICamera = { - // type: camera.mode === Camera.PERSPECTIVE_CAMERA ? CameraType.PERSPECTIVE : CameraType.ORTHOGRAPHIC, - // }; - - // if (camera.name) { - // glTFCamera.name = camera.name; - // } - - // if (glTFCamera.type === CameraType.PERSPECTIVE) { - // glTFCamera.perspective = { - // aspectRatio: camera.getEngine().getAspectRatio(camera), - // yfov: camera.fovMode === Camera.FOVMODE_VERTICAL_FIXED ? camera.fov : camera.fov * camera.getEngine().getAspectRatio(camera), - // znear: camera.minZ, - // zfar: camera.maxZ, - // }; - // } else if (glTFCamera.type === CameraType.ORTHOGRAPHIC) { - // const halfWidth = camera.orthoLeft && camera.orthoRight ? 0.5 * (camera.orthoRight - camera.orthoLeft) : camera.getEngine().getRenderWidth() * 0.5; - // const halfHeight = camera.orthoBottom && camera.orthoTop ? 0.5 * (camera.orthoTop - camera.orthoBottom) : camera.getEngine().getRenderHeight() * 0.5; - // glTFCamera.orthographic = { - // xmag: halfWidth, - // ymag: halfHeight, - // znear: camera.minZ, - // zfar: camera.maxZ, - // }; - // } - - // cameraMap.set(camera, this._cameras.length); - // this._cameras.push(glTFCamera); - // } + const stateLH = new ExporterState(true); + const stateRH = new ExporterState(false); - // await this._materialExporter.convertMaterialsToGLTFAsync(this._getMaterials(nodes)); + // Export babylon cameras to glTF cameras + for (const camera of this._babylonScene.cameras) { + stateLH.setCameraIndex(camera, 0); + stateRH.setCameraIndex(camera, 0); - scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, true))); - scene.nodes.push(...(await this._exportNodesAsync(rootNodesRH, false))); + const glTFCamera: ICamera = { + type: camera.mode === Camera.PERSPECTIVE_CAMERA ? CameraType.PERSPECTIVE : CameraType.ORTHOGRAPHIC, + }; + + if (camera.name) { + glTFCamera.name = camera.name; + } + + if (glTFCamera.type === CameraType.PERSPECTIVE) { + glTFCamera.perspective = { + aspectRatio: camera.getEngine().getAspectRatio(camera), + yfov: camera.fovMode === Camera.FOVMODE_VERTICAL_FIXED ? camera.fov : camera.fov * camera.getEngine().getAspectRatio(camera), + znear: camera.minZ, + zfar: camera.maxZ, + }; + } else if (glTFCamera.type === CameraType.ORTHOGRAPHIC) { + const halfWidth = camera.orthoLeft && camera.orthoRight ? 0.5 * (camera.orthoRight - camera.orthoLeft) : camera.getEngine().getRenderWidth() * 0.5; + const halfHeight = camera.orthoBottom && camera.orthoTop ? 0.5 * (camera.orthoTop - camera.orthoBottom) : camera.getEngine().getRenderHeight() * 0.5; + glTFCamera.orthographic = { + xmag: halfWidth, + ymag: halfHeight, + znear: camera.minZ, + zfar: camera.maxZ, + }; + } + + stateLH.setCameraIndex(camera, this._cameras.length); + stateRH.setCameraIndex(camera, this._cameras.length); + this._cameras.push(glTFCamera); + } + + // await this._materialExporter.convertMaterialsToGLTFAsync(this._getMaterials(nodes)); + scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, true, stateLH))); + scene.nodes.push(...(await this._exportNodesAsync(rootNodesRH, false, stateRH))); this._scenes.push(scene); // return this._exportNodesAndAnimationsAsync(nodes, convertToRightHandedMap, dataWriter).then((nodeMap) => { @@ -1191,11 +1206,9 @@ export class GLTFExporter { return result; } - private async _exportNodesAsync(babylonRootNodes: Node[], convertToRightHanded: boolean): Promise { + private async _exportNodesAsync(babylonRootNodes: Node[], convertToRightHanded: boolean, state: ExporterState): Promise { const nodes = new Array(); - const state = new ExporterState(convertToRightHanded); - this._exportBuffers(babylonRootNodes, convertToRightHanded, state); for (const babylonNode of babylonRootNodes) { @@ -1331,13 +1344,16 @@ export class GLTFExporter { if (babylonMesh.subMeshes && babylonMesh.subMeshes.length > 0) { node.mesh = await this._exportMeshAsync(babylonMesh, state, convertToRightHanded); } - } else if (babylonNode instanceof Camera) { - // TODO: handle camera } else { // TODO: handle other Babylon node types } } + if (babylonNode instanceof Camera) { + node.camera = state.getCameraIndex(babylonNode); + this._setCameraTransformation(node, babylonNode, convertToRightHanded); + } + for (const babylonChildNode of babylonNode.getChildren()) { if (this._shouldExportNode(babylonChildNode)) { node.children ||= []; From 4acc5b6259e36ca44f116472605f7bdb68bf711f Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Tue, 8 Oct 2024 15:52:43 -0300 Subject: [PATCH 005/133] Added camera export --- .../serializers/src/glTF/2.0/glTFExporter.ts | 81 +++++++++++-------- 1 file changed, 46 insertions(+), 35 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index ae7ff25c3e2..993c32f9da4 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -162,6 +162,10 @@ class ExporterState { return this._camerasMap.get(camera); } + public getSkeletonIndex(skeletonID: number): number | undefined { + return 0; + } + public setCameraIndex(camera: Camera, cameraIndex: number): void { this._camerasMap.set(camera, cameraIndex); } @@ -762,6 +766,43 @@ export class GLTFExporter { } } + // Export babylon cameras to glTF cameras + private _exportCameras(states: ExporterState[]): void { + for (const camera of this._babylonScene.cameras) { + const glTFCamera: ICamera = { + type: camera.mode === Camera.PERSPECTIVE_CAMERA ? CameraType.PERSPECTIVE : CameraType.ORTHOGRAPHIC, + }; + + if (camera.name) { + glTFCamera.name = camera.name; + } + + if (glTFCamera.type === CameraType.PERSPECTIVE) { + glTFCamera.perspective = { + aspectRatio: camera.getEngine().getAspectRatio(camera), + yfov: camera.fovMode === Camera.FOVMODE_VERTICAL_FIXED ? camera.fov : camera.fov * camera.getEngine().getAspectRatio(camera), + znear: camera.minZ, + zfar: camera.maxZ, + }; + } else if (glTFCamera.type === CameraType.ORTHOGRAPHIC) { + const halfWidth = camera.orthoLeft && camera.orthoRight ? 0.5 * (camera.orthoRight - camera.orthoLeft) : camera.getEngine().getRenderWidth() * 0.5; + const halfHeight = camera.orthoBottom && camera.orthoTop ? 0.5 * (camera.orthoTop - camera.orthoBottom) : camera.getEngine().getRenderHeight() * 0.5; + glTFCamera.orthographic = { + xmag: halfWidth, + ymag: halfHeight, + znear: camera.minZ, + zfar: camera.maxZ, + }; + } + + for (const state of states) { + state.setCameraIndex(camera, this._cameras.length); + } + + this._cameras.push(glTFCamera); + } + } + // /** // * Creates a bufferview based on the vertices type for the Babylon mesh // * @param babylonSubMesh The Babylon submesh that the morph target is applied to @@ -1102,41 +1143,7 @@ export class GLTFExporter { const stateLH = new ExporterState(true); const stateRH = new ExporterState(false); - // Export babylon cameras to glTF cameras - for (const camera of this._babylonScene.cameras) { - stateLH.setCameraIndex(camera, 0); - stateRH.setCameraIndex(camera, 0); - - const glTFCamera: ICamera = { - type: camera.mode === Camera.PERSPECTIVE_CAMERA ? CameraType.PERSPECTIVE : CameraType.ORTHOGRAPHIC, - }; - - if (camera.name) { - glTFCamera.name = camera.name; - } - - if (glTFCamera.type === CameraType.PERSPECTIVE) { - glTFCamera.perspective = { - aspectRatio: camera.getEngine().getAspectRatio(camera), - yfov: camera.fovMode === Camera.FOVMODE_VERTICAL_FIXED ? camera.fov : camera.fov * camera.getEngine().getAspectRatio(camera), - znear: camera.minZ, - zfar: camera.maxZ, - }; - } else if (glTFCamera.type === CameraType.ORTHOGRAPHIC) { - const halfWidth = camera.orthoLeft && camera.orthoRight ? 0.5 * (camera.orthoRight - camera.orthoLeft) : camera.getEngine().getRenderWidth() * 0.5; - const halfHeight = camera.orthoBottom && camera.orthoTop ? 0.5 * (camera.orthoTop - camera.orthoBottom) : camera.getEngine().getRenderHeight() * 0.5; - glTFCamera.orthographic = { - xmag: halfWidth, - ymag: halfHeight, - znear: camera.minZ, - zfar: camera.maxZ, - }; - } - - stateLH.setCameraIndex(camera, this._cameras.length); - stateRH.setCameraIndex(camera, this._cameras.length); - this._cameras.push(glTFCamera); - } + this._exportCameras([stateLH, stateRH]); // await this._materialExporter.convertMaterialsToGLTFAsync(this._getMaterials(nodes)); scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, true, stateLH))); @@ -1344,6 +1351,10 @@ export class GLTFExporter { if (babylonMesh.subMeshes && babylonMesh.subMeshes.length > 0) { node.mesh = await this._exportMeshAsync(babylonMesh, state, convertToRightHanded); } + + if (babylonNode.skeleton) { + node.skin = state.getSkeletonIndex(babylonNode.skeleton.uniqueId); + } } else { // TODO: handle other Babylon node types } From c43aedb015dbcbc8358179bb1eadbd4b0409ade5 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Tue, 8 Oct 2024 17:10:48 -0300 Subject: [PATCH 006/133] Started adding support for skeleton --- .../serializers/src/glTF/2.0/glTFExporter.ts | 255 ++++++++++++------ 1 file changed, 174 insertions(+), 81 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 993c32f9da4..7c606f385fb 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -23,6 +23,7 @@ import { AccessorComponentType, AccessorType, CameraType, ImageMimeType } from " import type { IndicesArray, Nullable } from "core/types"; import { TmpVectors, Quaternion } from "core/Maths/math.vector"; +import type { Matrix } from "core/Maths/math.vector"; import { Tools } from "core/Misc/tools"; import type { Buffer } from "core/Buffers/buffer"; import { VertexBuffer } from "core/Buffers/buffer"; @@ -62,6 +63,7 @@ import { Camera } from "core/Cameras/camera"; import { MultiMaterial, PBRMaterial, StandardMaterial } from "core/Materials"; import { Logger } from "core/Misc/logger"; import { enumerateFloatValues } from "core/Buffers/bufferUtils"; +import type { Bone } from "core/Bones"; // 180 degrees rotation in Y. const rotation180Y = new Quaternion(0, 1, 0, 0); @@ -81,6 +83,8 @@ class ExporterState { private _camerasMap = new Map(); + private _nodeSkeletonMap = new Map(); + public constructor(convertToRightHanded: boolean) { this.convertToRightHanded = convertToRightHanded; } @@ -162,12 +166,16 @@ class ExporterState { return this._camerasMap.get(camera); } + public setCameraIndex(camera: Camera, cameraIndex: number): void { + this._camerasMap.set(camera, cameraIndex); + } + public getSkeletonIndex(skeletonID: number): number | undefined { - return 0; + return this._nodeSkeletonMap.get(skeletonID); } - public setCameraIndex(camera: Camera, cameraIndex: number): void { - this._camerasMap.set(camera, cameraIndex); + public setSkeletonIndex(nodeID: number, skeletonID: number): void { + this._nodeSkeletonMap.set(nodeID, skeletonID); } } @@ -803,6 +811,166 @@ export class GLTFExporter { } } + // Builds all skins in the skins array so nodes can reference it during node parsing. + private _exportEmptySkeletons(states: ExporterState[]): void { + for (const skeleton of this._babylonScene.skeletons) { + if (skeleton.bones.length <= 0) { + continue; + } + + const skin: ISkin = { joints: [] }; + this._skins.push(skin); + + for (const state of states) { + state.setSkeletonIndex(skeleton.uniqueId, this._skins.length - 1); + } + } + } + + private _bindNodesToBones(state: ExporterState) { + for (const skeleton of this._babylonScene.skeletons) { + if (skeleton.bones.length <= 0) { + continue; + } + + const skinIndex = state.getSkeletonIndex(skeleton.uniqueId); + + if (skinIndex == undefined) { + continue; + } + + const skin = this._skins[skinIndex]; + const boneIndexMap: { [index: number]: Bone } = {}; + const inverseBindMatrices: Matrix[] = []; + + let maxBoneIndex = -1; + for (let i = 0; i < skeleton.bones.length; ++i) { + const bone = skeleton.bones[i]; + const boneIndex = bone.getIndex() ?? i; + if (boneIndex !== -1) { + boneIndexMap[boneIndex] = bone; + if (boneIndex > maxBoneIndex) { + maxBoneIndex = boneIndex; + } + } + } + + // Set joints index to scene node. + for (let boneIndex = 0; boneIndex <= maxBoneIndex; ++boneIndex) { + const bone = boneIndexMap[boneIndex]; + inverseBindMatrices.push(bone.getAbsoluteInverseBindMatrix()); + const transformNode = bone.getTransformNode(); + + if (transformNode !== null) { + const nodeID = this._nodeMap.get(transformNode); + if (transformNode && nodeID !== null && nodeID !== undefined) { + skin.joints.push(nodeID); + } else { + Tools.Warn("Exporting a bone without a linked transform node is currently unsupported"); + } + } else { + Tools.Warn("Exporting a bone without a linked transform node is currently unsupported"); + } + } + + if (skin.joints.length > 0) { + // create buffer view for inverse bind matrices + const byteStride = 64; // 4 x 4 matrix of 32 bit float + const byteLength = inverseBindMatrices.length * byteStride; + const bufferViewOffset = this._dataWriter.byteOffset; + const bufferView = createBufferView(0, bufferViewOffset, byteLength, undefined); + this._bufferViews.push(bufferView); + const bufferViewIndex = this._bufferViews.length - 1; + const bindMatrixAccessor = createAccessor(bufferViewIndex, AccessorType.MAT4, AccessorComponentType.FLOAT, inverseBindMatrices.length, null, null); + const inverseBindAccessorIndex = this._accessors.push(bindMatrixAccessor) - 1; + skin.inverseBindMatrices = inverseBindAccessorIndex; + inverseBindMatrices.forEach((mat) => { + mat.m.forEach((cell: number) => { + this._dataWriter.writeFloat32(cell); + }); + }); + } + } + } + + // // /** + // // * Creates a glTF skin from a Babylon skeleton + // // * @param babylonScene Babylon Scene + // // * @param nodeMap Babylon transform nodes + // // * @param dataWriter Buffer to write binary data to + // // * @returns Node mapping of unique id to index + // // */ + // private _exportSkeletonsAsync(states: ExporterState[]): void { + // for (const skeleton of this._babylonScene.skeletons) { + // if (skeleton.bones.length <= 0) { + // continue; + // } + // // create skin + // const skin: ISkin = { joints: [] }; + // const inverseBindMatrices: Matrix[] = []; + + // const boneIndexMap: { [index: number]: Bone } = {}; + // let maxBoneIndex = -1; + // for (let i = 0; i < skeleton.bones.length; ++i) { + // const bone = skeleton.bones[i]; + // const boneIndex = bone.getIndex() ?? i; + // if (boneIndex !== -1) { + // boneIndexMap[boneIndex] = bone; + // if (boneIndex > maxBoneIndex) { + // maxBoneIndex = boneIndex; + // } + // } + // } + + // for (let boneIndex = 0; boneIndex <= maxBoneIndex; ++boneIndex) { + // const bone = boneIndexMap[boneIndex]; + // inverseBindMatrices.push(bone.getAbsoluteInverseBindMatrix()); + + // const transformNode = bone.getTransformNode(); + + // if (transformNode && nodeMap[transformNode.uniqueId] !== null && nodeMap[transformNode.uniqueId] !== undefined) { + // skin.joints.push(nodeMap[transformNode.uniqueId]); + // } else { + // Tools.Warn("Exporting a bone without a linked transform node is currently unsupported"); + // } + // } + + // if (skin.joints.length > 0) { + // // create buffer view for inverse bind matrices + // const byteStride = 64; // 4 x 4 matrix of 32 bit float + // const byteLength = inverseBindMatrices.length * byteStride; + // const bufferViewOffset = dataWriter.getByteOffset(); + // const bufferView = createBufferView(0, bufferViewOffset, byteLength, undefined, "InverseBindMatrices" + " - " + skeleton.name); + // this._bufferViews.push(bufferView); + // const bufferViewIndex = this._bufferViews.length - 1; + // const bindMatrixAccessor = createAccessor( + // bufferViewIndex, + // "InverseBindMatrices" + " - " + skeleton.name, + // AccessorType.MAT4, + // AccessorComponentType.FLOAT, + // inverseBindMatrices.length, + // null, + // null, + // null + // ); + + // const inverseBindAccessorIndex = this._accessors.push(bindMatrixAccessor) - 1; + // skin.inverseBindMatrices = inverseBindAccessorIndex; + // this._skins.push(skin); + + // for (const state of states) { + // state.setSkeletonIndex(skeleton.uniqueId, this._skins.length - 1); + // } + + // inverseBindMatrices.forEach((mat) => { + // mat.m.forEach((cell: number) => { + // dataWriter.setFloat32(cell); + // }); + // }); + // } + // } + // } + // /** // * Creates a bufferview based on the vertices type for the Babylon mesh // * @param babylonSubMesh The Babylon submesh that the morph target is applied to @@ -1144,12 +1312,15 @@ export class GLTFExporter { const stateRH = new ExporterState(false); this._exportCameras([stateLH, stateRH]); + this._exportEmptySkeletons([stateLH, stateRH]); // await this._materialExporter.convertMaterialsToGLTFAsync(this._getMaterials(nodes)); scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, true, stateLH))); scene.nodes.push(...(await this._exportNodesAsync(rootNodesRH, false, stateRH))); this._scenes.push(scene); + this._bindNodesToBones(stateLH); + // return this._exportNodesAndAnimationsAsync(nodes, convertToRightHandedMap, dataWriter).then((nodeMap) => { // return this._createSkinsAsync(nodeMap, dataWriter).then((skinMap) => { // for (const babylonNode of nodes) { @@ -1625,82 +1796,4 @@ export class GLTFExporter { // return nodeMap; // } - - // /** - // * Creates a glTF skin from a Babylon skeleton - // * @param babylonScene Babylon Scene - // * @param nodeMap Babylon transform nodes - // * @param dataWriter Buffer to write binary data to - // * @returns Node mapping of unique id to index - // */ - // private _createSkinsAsync(nodeMap: { [key: number]: number }, dataWriter: DataWriter): Promise<{ [key: number]: number }> { - // const promiseChain = Promise.resolve(); - // const skinMap: { [key: number]: number } = {}; - // for (const skeleton of this._babylonScene.skeletons) { - // if (skeleton.bones.length <= 0) { - // continue; - // } - // // create skin - // const skin: ISkin = { joints: [] }; - // const inverseBindMatrices: Matrix[] = []; - - // const boneIndexMap: { [index: number]: Bone } = {}; - // let maxBoneIndex = -1; - // for (let i = 0; i < skeleton.bones.length; ++i) { - // const bone = skeleton.bones[i]; - // const boneIndex = bone.getIndex() ?? i; - // if (boneIndex !== -1) { - // boneIndexMap[boneIndex] = bone; - // if (boneIndex > maxBoneIndex) { - // maxBoneIndex = boneIndex; - // } - // } - // } - - // for (let boneIndex = 0; boneIndex <= maxBoneIndex; ++boneIndex) { - // const bone = boneIndexMap[boneIndex]; - // inverseBindMatrices.push(bone.getAbsoluteInverseBindMatrix()); - - // const transformNode = bone.getTransformNode(); - // if (transformNode && nodeMap[transformNode.uniqueId] !== null && nodeMap[transformNode.uniqueId] !== undefined) { - // skin.joints.push(nodeMap[transformNode.uniqueId]); - // } else { - // Tools.Warn("Exporting a bone without a linked transform node is currently unsupported"); - // } - // } - - // if (skin.joints.length > 0) { - // // create buffer view for inverse bind matrices - // const byteStride = 64; // 4 x 4 matrix of 32 bit float - // const byteLength = inverseBindMatrices.length * byteStride; - // const bufferViewOffset = dataWriter.getByteOffset(); - // const bufferView = createBufferView(0, bufferViewOffset, byteLength, undefined, "InverseBindMatrices" + " - " + skeleton.name); - // this._bufferViews.push(bufferView); - // const bufferViewIndex = this._bufferViews.length - 1; - // const bindMatrixAccessor = createAccessor( - // bufferViewIndex, - // "InverseBindMatrices" + " - " + skeleton.name, - // AccessorType.MAT4, - // AccessorComponentType.FLOAT, - // inverseBindMatrices.length, - // null, - // null, - // null - // ); - // const inverseBindAccessorIndex = this._accessors.push(bindMatrixAccessor) - 1; - // skin.inverseBindMatrices = inverseBindAccessorIndex; - // this._skins.push(skin); - // skinMap[skeleton.uniqueId] = this._skins.length - 1; - - // inverseBindMatrices.forEach((mat) => { - // mat.m.forEach((cell: number) => { - // dataWriter.setFloat32(cell); - // }); - // }); - // } - // } - // return promiseChain.then(() => { - // return skinMap; - // }); - // } } From 1bdb0baddee26f43e63ab8120d8c54a96f624bd0 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 9 Oct 2024 18:18:41 -0300 Subject: [PATCH 007/133] Remove cameras that are not assigned to nodes --- .../serializers/src/glTF/2.0/glTFExporter.ts | 206 ++++++------------ 1 file changed, 65 insertions(+), 141 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 7c606f385fb..8d2bbd5954d 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -81,10 +81,6 @@ class ExporterState { // Babylon mesh -> glTF mesh index private _meshMap = new Map(); - private _camerasMap = new Map(); - - private _nodeSkeletonMap = new Map(); - public constructor(convertToRightHanded: boolean) { this.convertToRightHanded = convertToRightHanded; } @@ -161,22 +157,6 @@ class ExporterState { public setMesh(mesh: Mesh, meshIndex: number): void { this._meshMap.set(mesh, meshIndex); } - - public getCameraIndex(camera: Camera): number | undefined { - return this._camerasMap.get(camera); - } - - public setCameraIndex(camera: Camera, cameraIndex: number): void { - this._camerasMap.set(camera, cameraIndex); - } - - public getSkeletonIndex(skeletonID: number): number | undefined { - return this._nodeSkeletonMap.get(skeletonID); - } - - public setSkeletonIndex(nodeID: number, skeletonID: number): void { - this._nodeSkeletonMap.set(nodeID, skeletonID); - } } /** @internal */ @@ -222,6 +202,9 @@ export class GLTFExporter { // Babylon material -> glTF material index public readonly _materialMap = new Map(); + private readonly _camerasMap = new Map(); + private readonly _nodesCameraMap = new Map(); + private readonly _skinMap = new Map(); // A material in this set requires UVs public readonly _materialNeedsUVsSet = new Set(); @@ -471,19 +454,19 @@ export class GLTFExporter { // let writeBinaryFunc; // switch (attributeComponentKind) { // case AccessorComponentType.UNSIGNED_BYTE: { - // writeBinaryFunc = dataWriter.setUInt8.bind(dataWriter); + // writeBinaryFunc = dataWriter.writeUInt8.bind(dataWriter); // break; // } // case AccessorComponentType.UNSIGNED_SHORT: { - // writeBinaryFunc = dataWriter.setUInt16.bind(dataWriter); + // writeBinaryFunc = dataWriter.writeUInt16.bind(dataWriter); // break; // } // case AccessorComponentType.UNSIGNED_INT: { - // writeBinaryFunc = dataWriter.setUInt32.bind(dataWriter); + // writeBinaryFunc = dataWriter.writeUInt32.bind(dataWriter); // break; // } // case AccessorComponentType.FLOAT: { - // writeBinaryFunc = dataWriter.setFloat32.bind(dataWriter); + // writeBinaryFunc = dataWriter.writeFloat32.bind(dataWriter); // break; // } // default: { @@ -775,7 +758,7 @@ export class GLTFExporter { } // Export babylon cameras to glTF cameras - private _exportCameras(states: ExporterState[]): void { + private _listAvailableCameras(): void { for (const camera of this._babylonScene.cameras) { const glTFCamera: ICamera = { type: camera.mode === Camera.PERSPECTIVE_CAMERA ? CameraType.PERSPECTIVE : CameraType.ORTHOGRAPHIC, @@ -802,17 +785,25 @@ export class GLTFExporter { zfar: camera.maxZ, }; } + this._camerasMap.set(camera, glTFCamera); + } + } - for (const state of states) { - state.setCameraIndex(camera, this._cameras.length); + // Cleanup unused cameras and assign index to nodes. + private _exportAndAssignCameras(): void { + for (const [, gltfCamera] of this._camerasMap) { + const usedNodes = this._nodesCameraMap.get(gltfCamera); + if (usedNodes !== undefined) { + this._cameras.push(gltfCamera); + for (const node of usedNodes) { + node.camera = this._cameras.length - 1; + } } - - this._cameras.push(glTFCamera); } } // Builds all skins in the skins array so nodes can reference it during node parsing. - private _exportEmptySkeletons(states: ExporterState[]): void { + private _exportEmptySkeletons(): void { for (const skeleton of this._babylonScene.skeletons) { if (skeleton.bones.length <= 0) { continue; @@ -820,20 +811,17 @@ export class GLTFExporter { const skin: ISkin = { joints: [] }; this._skins.push(skin); - - for (const state of states) { - state.setSkeletonIndex(skeleton.uniqueId, this._skins.length - 1); - } + this._skinMap.set(skeleton.uniqueId, this._skins.length - 1); } } - private _bindNodesToBones(state: ExporterState) { + private _bindNodesToBones() { for (const skeleton of this._babylonScene.skeletons) { if (skeleton.bones.length <= 0) { continue; } - const skinIndex = state.getSkeletonIndex(skeleton.uniqueId); + const skinIndex = this._skinMap.get(skeleton.uniqueId); if (skinIndex == undefined) { continue; @@ -893,84 +881,6 @@ export class GLTFExporter { } } - // // /** - // // * Creates a glTF skin from a Babylon skeleton - // // * @param babylonScene Babylon Scene - // // * @param nodeMap Babylon transform nodes - // // * @param dataWriter Buffer to write binary data to - // // * @returns Node mapping of unique id to index - // // */ - // private _exportSkeletonsAsync(states: ExporterState[]): void { - // for (const skeleton of this._babylonScene.skeletons) { - // if (skeleton.bones.length <= 0) { - // continue; - // } - // // create skin - // const skin: ISkin = { joints: [] }; - // const inverseBindMatrices: Matrix[] = []; - - // const boneIndexMap: { [index: number]: Bone } = {}; - // let maxBoneIndex = -1; - // for (let i = 0; i < skeleton.bones.length; ++i) { - // const bone = skeleton.bones[i]; - // const boneIndex = bone.getIndex() ?? i; - // if (boneIndex !== -1) { - // boneIndexMap[boneIndex] = bone; - // if (boneIndex > maxBoneIndex) { - // maxBoneIndex = boneIndex; - // } - // } - // } - - // for (let boneIndex = 0; boneIndex <= maxBoneIndex; ++boneIndex) { - // const bone = boneIndexMap[boneIndex]; - // inverseBindMatrices.push(bone.getAbsoluteInverseBindMatrix()); - - // const transformNode = bone.getTransformNode(); - - // if (transformNode && nodeMap[transformNode.uniqueId] !== null && nodeMap[transformNode.uniqueId] !== undefined) { - // skin.joints.push(nodeMap[transformNode.uniqueId]); - // } else { - // Tools.Warn("Exporting a bone without a linked transform node is currently unsupported"); - // } - // } - - // if (skin.joints.length > 0) { - // // create buffer view for inverse bind matrices - // const byteStride = 64; // 4 x 4 matrix of 32 bit float - // const byteLength = inverseBindMatrices.length * byteStride; - // const bufferViewOffset = dataWriter.getByteOffset(); - // const bufferView = createBufferView(0, bufferViewOffset, byteLength, undefined, "InverseBindMatrices" + " - " + skeleton.name); - // this._bufferViews.push(bufferView); - // const bufferViewIndex = this._bufferViews.length - 1; - // const bindMatrixAccessor = createAccessor( - // bufferViewIndex, - // "InverseBindMatrices" + " - " + skeleton.name, - // AccessorType.MAT4, - // AccessorComponentType.FLOAT, - // inverseBindMatrices.length, - // null, - // null, - // null - // ); - - // const inverseBindAccessorIndex = this._accessors.push(bindMatrixAccessor) - 1; - // skin.inverseBindMatrices = inverseBindAccessorIndex; - // this._skins.push(skin); - - // for (const state of states) { - // state.setSkeletonIndex(skeleton.uniqueId, this._skins.length - 1); - // } - - // inverseBindMatrices.forEach((mat) => { - // mat.m.forEach((cell: number) => { - // dataWriter.setFloat32(cell); - // }); - // }); - // } - // } - // } - // /** // * Creates a bufferview based on the vertices type for the Babylon mesh // * @param babylonSubMesh The Babylon submesh that the morph target is applied to @@ -991,11 +901,11 @@ export class GLTFExporter { // const count = babylonSubMesh.verticesCount; // const byteStride = 12; // 3 x 4 byte floats // const byteLength = count * byteStride; - // const bufferView = createBufferView(0, dataWriter.getByteOffset(), byteLength, byteStride, babylonMorphTarget.name + "_NORMAL"); + // const bufferView = createBufferView(0, dataWriter.byteOffset, byteLength, byteStride); // this._bufferViews.push(bufferView); // const bufferViewIndex = this._bufferViews.length - 1; - // const accessor = createAccessor(bufferViewIndex, babylonMorphTarget.name + " - " + "NORMAL", AccessorType.VEC3, AccessorComponentType.FLOAT, count, 0, null, null); + // const accessor = createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, count, 0, null); // this._accessors.push(accessor); // target.NORMAL = this._accessors.length - 1; @@ -1007,21 +917,12 @@ export class GLTFExporter { // const count = babylonSubMesh.verticesCount; // const byteStride = 12; // 3 x 4 byte floats // const byteLength = count * byteStride; - // const bufferView = createBufferView(0, dataWriter.getByteOffset(), byteLength, byteStride, babylonMorphTarget.name + "_POSITION"); + // const bufferView = createBufferView(0, dataWriter.byteOffset, byteLength, byteStride); // this._bufferViews.push(bufferView); // const bufferViewIndex = this._bufferViews.length - 1; // const minMax = { min: new Vector3(Infinity, Infinity, Infinity), max: new Vector3(-Infinity, -Infinity, -Infinity) }; - // const accessor = createAccessor( - // bufferViewIndex, - // babylonMorphTarget.name + " - " + "POSITION", - // AccessorType.VEC3, - // AccessorComponentType.FLOAT, - // count, - // 0, - // null, - // null - // ); + // const accessor = createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, count, 0, null); // this._accessors.push(accessor); // target.POSITION = this._accessors.length - 1; @@ -1044,11 +945,11 @@ export class GLTFExporter { // const count = babylonSubMesh.verticesCount; // const byteStride = 12; // 3 x 4 byte floats // const byteLength = count * byteStride; - // const bufferView = createBufferView(0, dataWriter.getByteOffset(), byteLength, byteStride, babylonMorphTarget.name + "_NORMAL"); + // const bufferView = createBufferView(0, dataWriter.byteOffset, byteLength, byteStride); // this._bufferViews.push(bufferView); // const bufferViewIndex = this._bufferViews.length - 1; - // const accessor = createAccessor(bufferViewIndex, babylonMorphTarget.name + " - " + "TANGENT", AccessorType.VEC3, AccessorComponentType.FLOAT, count, 0, null, null); + // const accessor = createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, count, 0, null); // this._accessors.push(accessor); // target.TANGENT = this._accessors.length - 1; @@ -1308,18 +1209,16 @@ export class GLTFExporter { } } - const stateLH = new ExporterState(true); - const stateRH = new ExporterState(false); - - this._exportCameras([stateLH, stateRH]); - this._exportEmptySkeletons([stateLH, stateRH]); + this._listAvailableCameras(); + this._exportEmptySkeletons(); // await this._materialExporter.convertMaterialsToGLTFAsync(this._getMaterials(nodes)); - scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, true, stateLH))); - scene.nodes.push(...(await this._exportNodesAsync(rootNodesRH, false, stateRH))); + scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, true))); + scene.nodes.push(...(await this._exportNodesAsync(rootNodesRH, false))); this._scenes.push(scene); - this._bindNodesToBones(stateLH); + this._bindNodesToBones(); + this._exportAndAssignCameras(); // return this._exportNodesAndAnimationsAsync(nodes, convertToRightHandedMap, dataWriter).then((nodeMap) => { // return this._createSkinsAsync(nodeMap, dataWriter).then((skinMap) => { @@ -1384,8 +1283,9 @@ export class GLTFExporter { return result; } - private async _exportNodesAsync(babylonRootNodes: Node[], convertToRightHanded: boolean, state: ExporterState): Promise { + private async _exportNodesAsync(babylonRootNodes: Node[], convertToRightHanded: boolean): Promise { const nodes = new Array(); + const state = new ExporterState(convertToRightHanded); this._exportBuffers(babylonRootNodes, convertToRightHanded, state); @@ -1524,7 +1424,7 @@ export class GLTFExporter { } if (babylonNode.skeleton) { - node.skin = state.getSkeletonIndex(babylonNode.skeleton.uniqueId); + node.skin = this._skinMap.get(babylonNode.skeleton.uniqueId); } } else { // TODO: handle other Babylon node types @@ -1532,8 +1432,16 @@ export class GLTFExporter { } if (babylonNode instanceof Camera) { - node.camera = state.getCameraIndex(babylonNode); - this._setCameraTransformation(node, babylonNode, convertToRightHanded); + const gltfCamera = this._camerasMap.get(babylonNode); + + if (gltfCamera) { + if (this._nodesCameraMap.get(gltfCamera) === undefined) { + this._nodesCameraMap.set(gltfCamera, []); + } + + this._nodesCameraMap.get(gltfCamera)?.push(node); + this._setCameraTransformation(node, babylonNode, convertToRightHanded); + } } for (const babylonChildNode of babylonNode.getChildren()) { @@ -1718,6 +1626,22 @@ export class GLTFExporter { } } + // TO DO: Add support for morph targets. + // const morphTargetManager = babylonMesh.morphTargetManager; + // if (morphTargetManager) { + // // By convention, morph target names are stored in the mesh extras. + // if (!mesh.extras) { + // mesh.extras = {}; + // } + // mesh.extras.targetNames = []; + + // for (let i = 0; i < morphTargetManager.numTargets; ++i) { + // const target = morphTargetManager.getTarget(i); + // this._setMorphTargetAttributes(submesh, meshPrimitive, target, dataWriter); + // mesh.extras.targetNames.push(target.name); + // } + // } + // TODO: handle morph targets // TODO: handle skeleton From 04de5554cfc4586665e103ed1614a85727890489 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 9 Oct 2024 18:57:39 -0300 Subject: [PATCH 008/133] Added logic to not include empty or unused skins --- .../serializers/src/glTF/2.0/glTFExporter.ts | 43 +++++++++++++------ 1 file changed, 30 insertions(+), 13 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 8d2bbd5954d..1eda8e3f44a 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -63,7 +63,7 @@ import { Camera } from "core/Cameras/camera"; import { MultiMaterial, PBRMaterial, StandardMaterial } from "core/Materials"; import { Logger } from "core/Misc/logger"; import { enumerateFloatValues } from "core/Buffers/bufferUtils"; -import type { Bone } from "core/Bones"; +import type { Bone, Skeleton } from "core/Bones"; // 180 degrees rotation in Y. const rotation180Y = new Quaternion(0, 1, 0, 0); @@ -204,7 +204,8 @@ export class GLTFExporter { public readonly _materialMap = new Map(); private readonly _camerasMap = new Map(); private readonly _nodesCameraMap = new Map(); - private readonly _skinMap = new Map(); + private readonly _skinMap = new Map(); + private readonly _nodesSkinMap = new Map(); // A material in this set requires UVs public readonly _materialNeedsUVsSet = new Set(); @@ -803,31 +804,30 @@ export class GLTFExporter { } // Builds all skins in the skins array so nodes can reference it during node parsing. - private _exportEmptySkeletons(): void { + private _listAvailableSkeletons(): void { for (const skeleton of this._babylonScene.skeletons) { if (skeleton.bones.length <= 0) { continue; } const skin: ISkin = { joints: [] }; - this._skins.push(skin); - this._skinMap.set(skeleton.uniqueId, this._skins.length - 1); + //this._skins.push(skin); + this._skinMap.set(skeleton, skin); } } - private _bindNodesToBones() { + private _exportAndAssignSkeletons() { for (const skeleton of this._babylonScene.skeletons) { if (skeleton.bones.length <= 0) { continue; } - const skinIndex = this._skinMap.get(skeleton.uniqueId); + const skin = this._skinMap.get(skeleton); - if (skinIndex == undefined) { + if (skin == undefined) { continue; } - const skin = this._skins[skinIndex]; const boneIndexMap: { [index: number]: Bone } = {}; const inverseBindMatrices: Matrix[] = []; @@ -861,7 +861,11 @@ export class GLTFExporter { } } - if (skin.joints.length > 0) { + // Nodes that use this skin. + const skinedNodes = this._nodesSkinMap.get(skin); + + // Only create skeleton if it has at least one joint and is used by a mesh. + if (skin.joints.length > 0 && skinedNodes !== undefined) { // create buffer view for inverse bind matrices const byteStride = 64; // 4 x 4 matrix of 32 bit float const byteLength = inverseBindMatrices.length * byteStride; @@ -877,6 +881,11 @@ export class GLTFExporter { this._dataWriter.writeFloat32(cell); }); }); + + this._skins.push(skin); + for (const skinedNode of skinedNodes) { + skinedNode.skin = this._skins.length - 1; + } } } } @@ -1210,15 +1219,15 @@ export class GLTFExporter { } this._listAvailableCameras(); - this._exportEmptySkeletons(); + this._listAvailableSkeletons(); // await this._materialExporter.convertMaterialsToGLTFAsync(this._getMaterials(nodes)); scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, true))); scene.nodes.push(...(await this._exportNodesAsync(rootNodesRH, false))); this._scenes.push(scene); - this._bindNodesToBones(); this._exportAndAssignCameras(); + this._exportAndAssignSkeletons(); // return this._exportNodesAndAnimationsAsync(nodes, convertToRightHandedMap, dataWriter).then((nodeMap) => { // return this._createSkinsAsync(nodeMap, dataWriter).then((skinMap) => { @@ -1424,7 +1433,15 @@ export class GLTFExporter { } if (babylonNode.skeleton) { - node.skin = this._skinMap.get(babylonNode.skeleton.uniqueId); + const skin = this._skinMap.get(babylonNode.skeleton); + + if (skin !== undefined) { + if (this._nodesSkinMap.get(skin) === undefined) { + this._nodesSkinMap.set(skin, []); + } + + this._nodesSkinMap.get(skin)?.push(node); + } } } else { // TODO: handle other Babylon node types From b0091a3505c642945fb5cb8efe6c9b8d2b328587 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Tue, 15 Oct 2024 13:51:53 -0300 Subject: [PATCH 009/133] Got skeleton working --- .../serializers/src/glTF/2.0/dataWriter.ts | 6 + .../serializers/src/glTF/2.0/glTFExporter.ts | 105 +++++++++++++++--- .../src/glTF/2.0/glTFSerializer.ts | 5 + 3 files changed, 99 insertions(+), 17 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/dataWriter.ts b/packages/dev/serializers/src/glTF/2.0/dataWriter.ts index 2e9d8945a4b..fa19241b894 100644 --- a/packages/dev/serializers/src/glTF/2.0/dataWriter.ts +++ b/packages/dev/serializers/src/glTF/2.0/dataWriter.ts @@ -56,6 +56,12 @@ export class DataWriter { this._byteOffset += value.byteLength; } + public writeUint16Array(value: Uint16Array): void { + this._checkGrowBuffer(value.byteLength); + this._data.set(value, this._byteOffset); + this._byteOffset += value.byteLength; + } + private _checkGrowBuffer(byteLength: number): void { const newByteLength = this.byteOffset + byteLength; if (newByteLength > this._data.byteLength) { diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 1eda8e3f44a..3cf20eae4d7 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -21,7 +21,7 @@ import type { } from "babylonjs-gltf2interface"; import { AccessorComponentType, AccessorType, CameraType, ImageMimeType } from "babylonjs-gltf2interface"; -import type { IndicesArray, Nullable } from "core/types"; +import type { FloatArray, IndicesArray, Nullable } from "core/types"; import { TmpVectors, Quaternion } from "core/Maths/math.vector"; import type { Matrix } from "core/Maths/math.vector"; import { Tools } from "core/Misc/tools"; @@ -78,15 +78,20 @@ class ExporterState { // Babylon vertex buffer, start, count -> glTF accessor index private _vertexAccessorMap = new Map>>(); + private _remappedBufferView = new Map>(); + // Babylon mesh -> glTF mesh index private _meshMap = new Map(); - public constructor(convertToRightHanded: boolean) { + public constructor(convertToRightHanded: boolean, userUint16SkinIndex: boolean) { this.convertToRightHanded = convertToRightHanded; + this.userUint16SkinIndex = userUint16SkinIndex; } public readonly convertToRightHanded: boolean; + public readonly userUint16SkinIndex: boolean; + // Only used when convertToRightHanded is true. public readonly convertedToRightHandedBuffers = new Map(); @@ -130,6 +135,15 @@ class ExporterState { this._vertexBufferViewMap.set(buffer, bufferViewIndex); } + public setRemappedBufferView(buffer: Buffer, vertexBuffer: VertexBuffer, bufferViewIndex: number) { + this._remappedBufferView.set(buffer, new Map()); + this._remappedBufferView.get(buffer)?.set(vertexBuffer, bufferViewIndex); + } + + public getRemappedBufferView(buffer: Buffer, vertexBuffer: VertexBuffer): number | undefined { + return this._remappedBufferView.get(buffer)?.get(vertexBuffer); + } + public getVertexAccessor(vertexBuffer: VertexBuffer, start: number, count: number): number | undefined { return this._vertexAccessorMap.get(vertexBuffer)?.get(start)?.get(count); } @@ -341,6 +355,7 @@ export class GLTFExporter { exportUnusedUVs: false, removeNoopRootNodes: true, includeCoordinateSystemConversionNodes: false, + userUint16SkinIndex: false, ...options, }; @@ -1222,8 +1237,8 @@ export class GLTFExporter { this._listAvailableSkeletons(); // await this._materialExporter.convertMaterialsToGLTFAsync(this._getMaterials(nodes)); - scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, true))); - scene.nodes.push(...(await this._exportNodesAsync(rootNodesRH, false))); + scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, true, this._options.userUint16SkinIndex))); + scene.nodes.push(...(await this._exportNodesAsync(rootNodesRH, false, this._options.userUint16SkinIndex))); this._scenes.push(scene); this._exportAndAssignCameras(); @@ -1292,9 +1307,9 @@ export class GLTFExporter { return result; } - private async _exportNodesAsync(babylonRootNodes: Node[], convertToRightHanded: boolean): Promise { + private async _exportNodesAsync(babylonRootNodes: Node[], convertToRightHanded: boolean, useUint16SkinIndex: boolean): Promise { const nodes = new Array(); - const state = new ExporterState(convertToRightHanded); + const state = new ExporterState(convertToRightHanded, useUint16SkinIndex); this._exportBuffers(babylonRootNodes, convertToRightHanded, state); @@ -1405,6 +1420,52 @@ export class GLTFExporter { this._dataWriter.writeUint8Array(bytes); this._bufferViews.push(createBufferView(0, byteOffset, bytes.length, byteStride)); state.setVertexBufferView(buffer, this._bufferViews.length - 1); + + const floatMatricesIndices = new Map(); + + // If buffers are of type MatricesWeightsKind and have float values, we need to create a new buffer instead. + for (const vertexBuffer of vertexBuffers) { + switch (vertexBuffer.getKind()) { + case VertexBuffer.MatricesIndicesKind: + case VertexBuffer.MatricesIndicesExtraKind: { + if (vertexBuffer.type == VertexBuffer.FLOAT) { + for (const mesh of vertexBufferToMeshesMap.get(vertexBuffer)!) { + const floatData = vertexBuffer.getFloatData(mesh.getTotalVertices()); + if (floatData !== null) { + floatMatricesIndices.set(vertexBuffer, floatData); + } + } + } + } + } + } + + if (floatMatricesIndices.size !== 0) { + Logger.Warn( + `Joints conversion needed: some joints are stored as floats in Babylon but GLTF requires UNSIGNED BYTES. We will perform the conversion but this might lead to unused data in the buffer.` + ); + } + + for (const [vertexBuffer, array] of floatMatricesIndices) { + const byteOffset = this._dataWriter.byteOffset; + if (state.userUint16SkinIndex) { + const newArray = new Uint16Array(array.length); + for (let index = 0; index < array.length; index++) { + newArray[index] = array[index]; + } + this._dataWriter.writeUint16Array(newArray); + this._bufferViews.push(createBufferView(0, byteOffset, newArray.byteLength, 4 * 2)); + } else { + const newArray = new Uint8Array(array.length); + for (let index = 0; index < array.length; index++) { + newArray[index] = array[index]; + } + this._dataWriter.writeUint8Array(newArray); + this._bufferViews.push(createBufferView(0, byteOffset, newArray.byteLength, 4)); + } + + state.setRemappedBufferView(buffer, vertexBuffer, this._bufferViews.length - 1); + } } } @@ -1549,25 +1610,35 @@ export class GLTFExporter { } } - // TODO: StandardMaterial color spaces - // probably have to create new buffer view to store new colors during collectBuffers and figure out if only standardMaterial is using it - // separate map by color space - let accessorIndex = state.getVertexAccessor(vertexBuffer, start, count); + if (accessorIndex === undefined) { // Get min/max from converted or original data. const data = state.convertedToRightHandedBuffers.get(vertexBuffer._buffer) || vertexBuffer._buffer.getData()!; const minMax = kind === VertexBuffer.PositionKind ? getMinMax(data, vertexBuffer, start, count) : null; - const bufferViewIndex = state.getVertexBufferView(vertexBuffer._buffer)!; - const byteOffset = vertexBuffer.byteOffset + start * vertexBuffer.byteStride; - this._accessors.push(createAccessor(bufferViewIndex, getAccessorType(kind), vertexBuffer.type, count, byteOffset, minMax)); - accessorIndex = this._accessors.length - 1; + if ((kind === VertexBuffer.MatricesIndicesKind || kind === VertexBuffer.MatricesIndicesExtraKind) && vertexBuffer.type === VertexBuffer.FLOAT) { + const bufferViewIndex = state.getRemappedBufferView(vertexBuffer._buffer, vertexBuffer); + if (bufferViewIndex !== undefined) { + const byteOffset = vertexBuffer.byteOffset + start * vertexBuffer.byteStride; + this._accessors.push(createAccessor(bufferViewIndex, getAccessorType(kind), VertexBuffer.UNSIGNED_BYTE, count, byteOffset, minMax)); + accessorIndex = this._accessors.length - 1; + state.setVertexAccessor(vertexBuffer, start, count, accessorIndex); + primitive.attributes[getAttributeType(kind)] = accessorIndex; + } + } else { + const bufferViewIndex = state.getVertexBufferView(vertexBuffer._buffer)!; + const byteOffset = vertexBuffer.byteOffset + start * vertexBuffer.byteStride; + this._accessors.push(createAccessor(bufferViewIndex, getAccessorType(kind), vertexBuffer.type, count, byteOffset, minMax)); + accessorIndex = this._accessors.length - 1; + state.setVertexAccessor(vertexBuffer, start, count, accessorIndex); + primitive.attributes[getAttributeType(kind)] = accessorIndex; + } } - state.setVertexAccessor(vertexBuffer, start, count, accessorIndex); - - primitive.attributes[getAttributeType(kind)] = accessorIndex; + // TODO: StandardMaterial color spaces + // probably have to create new buffer view to store new colors during collectBuffers and figure out if only standardMaterial is using it + // separate map by color space } private async _exportMaterialAsync(babylonMaterial: Material, vertexBuffers: { [kind: string]: VertexBuffer }, subMesh: SubMesh, primitive: IMeshPrimitive): Promise { diff --git a/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts b/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts index 1787b779fa3..c5662352928 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts @@ -54,6 +54,11 @@ export interface IExportOptions { * @deprecated Please use removeNoopRootNodes instead */ includeCoordinateSystemConversionNodes?: boolean; + + /** + * If set to true it will export skin matrix index as Uint16. + */ + userUint16SkinIndex?: boolean; } /** From b201c4d4b35874fab08726dfc2471049d6b66529 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Tue, 15 Oct 2024 16:37:35 -0300 Subject: [PATCH 010/133] Reverted changes to glTFLoader --- packages/dev/loaders/src/glTF/2.0/glTFLoader.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/dev/loaders/src/glTF/2.0/glTFLoader.ts b/packages/dev/loaders/src/glTF/2.0/glTFLoader.ts index 92c1d810a25..2d848977435 100644 --- a/packages/dev/loaders/src/glTF/2.0/glTFLoader.ts +++ b/packages/dev/loaders/src/glTF/2.0/glTFLoader.ts @@ -1251,7 +1251,7 @@ export class GLTFLoader implements IGLTFLoader { loadAttribute("POSITION", VertexBuffer.PositionKind, (babylonVertexBuffer, data) => { const positions = new Float32Array(data.length); - babylonVertexBuffer.forEach(positions.length, (value, index) => { + babylonVertexBuffer.forEach(data.length, (value, index) => { positions[index] = data[index] + value; }); @@ -1270,7 +1270,7 @@ export class GLTFLoader implements IGLTFLoader { loadAttribute("TANGENT", VertexBuffer.TangentKind, (babylonVertexBuffer, data) => { const tangents = new Float32Array((data.length / 3) * 4); let dataIndex = 0; - babylonVertexBuffer.forEach(tangents.length, (value, index) => { + babylonVertexBuffer.forEach((data.length / 3) * 4, (value, index) => { // Tangent data for morph targets is stored as xyz delta. // The vertexData.tangent is stored as xyzw. // So we need to skip every fourth vertexData.tangent. @@ -2169,6 +2169,7 @@ export class GLTFLoader implements IGLTFLoader { const babylonMaterial = new PBRMaterial(name, this._babylonScene); babylonMaterial._parentContainer = this._assetContainer; this._babylonScene._blockEntityCollection = false; + // Moved to mesh so user can change materials on gltf meshes: babylonMaterial.sideOrientation = this._babylonScene.useRightHandedSystem ? Material.CounterClockWiseSideOrientation : Material.ClockWiseSideOrientation; babylonMaterial.fillMode = babylonDrawMode; babylonMaterial.enableSpecularAntiAliasing = true; babylonMaterial.useRadianceOverAlpha = !this._parent.transparencyAsCoverage; @@ -2528,7 +2529,11 @@ export class GLTFLoader implements IGLTFLoader { * @param pointer the JSON pointer */ public static AddPointerMetadata(babylonObject: IWithMetadata, pointer: string): void { - (((babylonObject._internalMetadata ||= {}).gltf ||= {}).pointers ||= []).push(pointer); + babylonObject.metadata = babylonObject.metadata || {}; + const metadata = (babylonObject._internalMetadata = babylonObject._internalMetadata || {}); + const gltf = (metadata.gltf = metadata.gltf || {}); + const pointers = (gltf.pointers = gltf.pointers || []); + pointers.push(pointer); } private static _GetTextureWrapMode(context: string, mode: TextureWrapMode | undefined): number { From ba017976113b9593276a0fdb65b8d981c08abcd7 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 16 Oct 2024 17:27:12 -0300 Subject: [PATCH 011/133] Added support for animations --- .../serializers/src/glTF/2.0/glTFAnimation.ts | 1995 ++++++++--------- .../serializers/src/glTF/2.0/glTFExporter.ts | 39 +- 2 files changed, 1035 insertions(+), 999 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts index 28f78043e69..d0888c188a6 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts @@ -1,1052 +1,1051 @@ -// import type { IAnimation, INode, IBufferView, IAccessor, IAnimationSampler, IAnimationChannel } from "babylonjs-gltf2interface"; -// import { AnimationSamplerInterpolation, AnimationChannelTargetPath, AccessorType, AccessorComponentType } from "babylonjs-gltf2interface"; -// import type { Node } from "core/node"; -// import type { Nullable } from "core/types"; -// import { Vector3, Quaternion } from "core/Maths/math.vector"; -// import { Tools } from "core/Misc/tools"; -// import { Animation } from "core/Animations/animation"; -// import { TransformNode } from "core/Meshes/transformNode"; -// import type { Scene } from "core/scene"; -// import { MorphTarget } from "core/Morph/morphTarget"; -// import { Mesh } from "core/Meshes/mesh"; +import type { IAnimation, INode, IBufferView, IAccessor, IAnimationSampler, IAnimationChannel } from "babylonjs-gltf2interface"; +import { AnimationSamplerInterpolation, AnimationChannelTargetPath, AccessorType, AccessorComponentType } from "babylonjs-gltf2interface"; +import type { Node } from "core/node"; +import type { Nullable } from "core/types"; +import { Vector3, Quaternion } from "core/Maths/math.vector"; +import { Tools } from "core/Misc/tools"; +import { Animation } from "core/Animations/animation"; +import { TransformNode } from "core/Meshes/transformNode"; +import type { Scene } from "core/scene"; +import { MorphTarget } from "core/Morph/morphTarget"; +import { Mesh } from "core/Meshes/mesh"; -// import type { _BinaryWriter } from "./glTFExporter"; -// import { _GLTFUtilities } from "./glTFUtilities"; -// import type { IAnimationKey } from "core/Animations/animationKey"; -// import { AnimationKeyInterpolation } from "core/Animations/animationKey"; +import type { IAnimationKey } from "core/Animations/animationKey"; +import { AnimationKeyInterpolation } from "core/Animations/animationKey"; -// import { Camera } from "core/Cameras/camera"; -// import { Light } from "core/Lights/light"; +import { Camera } from "core/Cameras/camera"; +import { Light } from "core/Lights/light"; +import type { DataWriter } from "./dataWriter"; +import { createAccessor, createBufferView, getAccessorElementCount } from "./glTFUtilities"; -// /** -// * @internal -// * Interface to store animation data. -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export interface _IAnimationData { -// /** -// * Keyframe data. -// */ -// inputs: number[]; -// /** -// * Value data. -// */ -// outputs: number[][]; -// /** -// * Animation interpolation data. -// */ -// samplerInterpolation: AnimationSamplerInterpolation; -// /** -// * Minimum keyframe value. -// */ -// inputsMin: number; -// /** -// * Maximum keyframe value. -// */ -// inputsMax: number; -// } +/** + * @internal + * Interface to store animation data. + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export interface _IAnimationData { + /** + * Keyframe data. + */ + inputs: number[]; + /** + * Value data. + */ + outputs: number[][]; + /** + * Animation interpolation data. + */ + samplerInterpolation: AnimationSamplerInterpolation; + /** + * Minimum keyframe value. + */ + inputsMin: number; + /** + * Maximum keyframe value. + */ + inputsMax: number; +} -// /** -// * @internal -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export interface _IAnimationInfo { -// /** -// * The target channel for the animation -// */ -// animationChannelTargetPath: AnimationChannelTargetPath; -// /** -// * The glTF accessor type for the data. -// */ -// dataAccessorType: AccessorType.VEC3 | AccessorType.VEC4 | AccessorType.SCALAR; -// /** -// * Specifies if quaternions should be used. -// */ -// useQuaternion: boolean; -// } +/** + * @internal + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export interface _IAnimationInfo { + /** + * The target channel for the animation + */ + animationChannelTargetPath: AnimationChannelTargetPath; + /** + * The glTF accessor type for the data. + */ + dataAccessorType: AccessorType.VEC3 | AccessorType.VEC4 | AccessorType.SCALAR; + /** + * Specifies if quaternions should be used. + */ + useQuaternion: boolean; +} -// /** -// * @internal -// * Enum for handling in tangent and out tangent. -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// enum _TangentType { -// /** -// * Specifies that input tangents are used. -// */ -// INTANGENT, -// /** -// * Specifies that output tangents are used. -// */ -// OUTTANGENT, -// } +/** + * @internal + * Enum for handling in tangent and out tangent. + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +enum _TangentType { + /** + * Specifies that input tangents are used. + */ + INTANGENT, + /** + * Specifies that output tangents are used. + */ + OUTTANGENT, +} -// /** -// * @internal -// * Utility class for generating glTF animation data from BabylonJS. -// */ -// export class _GLTFAnimation { -// /** -// * Determine if a node is transformable - ie has properties it should be part of animation of transformation. -// * @param babylonNode the node to test -// * @returns true if can be animated, false otherwise. False if the parameter is null or undefined. -// */ -// private static _IsTransformable(babylonNode: Node): boolean { -// return babylonNode && (babylonNode instanceof TransformNode || babylonNode instanceof Camera || babylonNode instanceof Light); -// } +/** + * @internal + * Utility class for generating glTF animation data from BabylonJS. + */ +export class _GLTFAnimation { + /** + * Determine if a node is transformable - ie has properties it should be part of animation of transformation. + * @param babylonNode the node to test + * @returns true if can be animated, false otherwise. False if the parameter is null or undefined. + */ + private static _IsTransformable(babylonNode: Node): boolean { + return babylonNode && (babylonNode instanceof TransformNode || babylonNode instanceof Camera || babylonNode instanceof Light); + } -// /** -// * @ignore -// * -// * Creates glTF channel animation from BabylonJS animation. -// * @param babylonTransformNode - BabylonJS mesh. -// * @param animation - animation. -// * @param animationChannelTargetPath - The target animation channel. -// * @param useQuaternion - Specifies if quaternions are used. -// * @returns nullable IAnimationData -// */ -// public static _CreateNodeAnimation( -// babylonTransformNode: Node, -// animation: Animation, -// animationChannelTargetPath: AnimationChannelTargetPath, -// useQuaternion: boolean, -// animationSampleRate: number -// ): Nullable<_IAnimationData> { -// if (this._IsTransformable(babylonTransformNode)) { -// const inputs: number[] = []; -// const outputs: number[][] = []; -// const keyFrames = animation.getKeys(); -// const minMaxKeyFrames = _GLTFAnimation._CalculateMinMaxKeyFrames(keyFrames); -// const interpolationOrBake = _GLTFAnimation._DeduceInterpolation(keyFrames, animationChannelTargetPath, useQuaternion); + /** + * @ignore + * + * Creates glTF channel animation from BabylonJS animation. + * @param babylonTransformNode - BabylonJS mesh. + * @param animation - animation. + * @param animationChannelTargetPath - The target animation channel. + * @param useQuaternion - Specifies if quaternions are used. + * @returns nullable IAnimationData + */ + public static _CreateNodeAnimation( + babylonTransformNode: Node, + animation: Animation, + animationChannelTargetPath: AnimationChannelTargetPath, + useQuaternion: boolean, + animationSampleRate: number + ): Nullable<_IAnimationData> { + if (this._IsTransformable(babylonTransformNode)) { + const inputs: number[] = []; + const outputs: number[][] = []; + const keyFrames = animation.getKeys(); + const minMaxKeyFrames = _GLTFAnimation._CalculateMinMaxKeyFrames(keyFrames); + const interpolationOrBake = _GLTFAnimation._DeduceInterpolation(keyFrames, animationChannelTargetPath, useQuaternion); -// const interpolation = interpolationOrBake.interpolationType; -// const shouldBakeAnimation = interpolationOrBake.shouldBakeAnimation; + const interpolation = interpolationOrBake.interpolationType; + const shouldBakeAnimation = interpolationOrBake.shouldBakeAnimation; -// if (shouldBakeAnimation) { -// _GLTFAnimation._CreateBakedAnimation( -// babylonTransformNode, -// animation, -// animationChannelTargetPath, -// minMaxKeyFrames.min, -// minMaxKeyFrames.max, -// animation.framePerSecond, -// animationSampleRate, -// inputs, -// outputs, -// minMaxKeyFrames, -// useQuaternion -// ); -// } else { -// if (interpolation === AnimationSamplerInterpolation.LINEAR || interpolation === AnimationSamplerInterpolation.STEP) { -// _GLTFAnimation._CreateLinearOrStepAnimation(babylonTransformNode, animation, animationChannelTargetPath, inputs, outputs, useQuaternion); -// } else if (interpolation === AnimationSamplerInterpolation.CUBICSPLINE) { -// _GLTFAnimation._CreateCubicSplineAnimation(babylonTransformNode, animation, animationChannelTargetPath, inputs, outputs, useQuaternion); -// } else { -// _GLTFAnimation._CreateBakedAnimation( -// babylonTransformNode, -// animation, -// animationChannelTargetPath, -// minMaxKeyFrames.min, -// minMaxKeyFrames.max, -// animation.framePerSecond, -// animationSampleRate, -// inputs, -// outputs, -// minMaxKeyFrames, -// useQuaternion -// ); -// } -// } + if (shouldBakeAnimation) { + _GLTFAnimation._CreateBakedAnimation( + babylonTransformNode, + animation, + animationChannelTargetPath, + minMaxKeyFrames.min, + minMaxKeyFrames.max, + animation.framePerSecond, + animationSampleRate, + inputs, + outputs, + minMaxKeyFrames, + useQuaternion + ); + } else { + if (interpolation === AnimationSamplerInterpolation.LINEAR || interpolation === AnimationSamplerInterpolation.STEP) { + _GLTFAnimation._CreateLinearOrStepAnimation(babylonTransformNode, animation, animationChannelTargetPath, inputs, outputs, useQuaternion); + } else if (interpolation === AnimationSamplerInterpolation.CUBICSPLINE) { + _GLTFAnimation._CreateCubicSplineAnimation(babylonTransformNode, animation, animationChannelTargetPath, inputs, outputs, useQuaternion); + } else { + _GLTFAnimation._CreateBakedAnimation( + babylonTransformNode, + animation, + animationChannelTargetPath, + minMaxKeyFrames.min, + minMaxKeyFrames.max, + animation.framePerSecond, + animationSampleRate, + inputs, + outputs, + minMaxKeyFrames, + useQuaternion + ); + } + } -// if (inputs.length && outputs.length) { -// const result: _IAnimationData = { -// inputs: inputs, -// outputs: outputs, -// samplerInterpolation: interpolation, -// inputsMin: shouldBakeAnimation ? minMaxKeyFrames.min : Tools.FloatRound(minMaxKeyFrames.min / animation.framePerSecond), -// inputsMax: shouldBakeAnimation ? minMaxKeyFrames.max : Tools.FloatRound(minMaxKeyFrames.max / animation.framePerSecond), -// }; + if (inputs.length && outputs.length) { + const result: _IAnimationData = { + inputs: inputs, + outputs: outputs, + samplerInterpolation: interpolation, + inputsMin: shouldBakeAnimation ? minMaxKeyFrames.min : Tools.FloatRound(minMaxKeyFrames.min / animation.framePerSecond), + inputsMax: shouldBakeAnimation ? minMaxKeyFrames.max : Tools.FloatRound(minMaxKeyFrames.max / animation.framePerSecond), + }; -// return result; -// } -// } + return result; + } + } -// return null; -// } + return null; + } -// private static _DeduceAnimationInfo(animation: Animation): Nullable<_IAnimationInfo> { -// let animationChannelTargetPath: Nullable = null; -// let dataAccessorType = AccessorType.VEC3; -// let useQuaternion: boolean = false; -// const property = animation.targetProperty.split("."); -// switch (property[0]) { -// case "scaling": { -// animationChannelTargetPath = AnimationChannelTargetPath.SCALE; -// break; -// } -// case "position": { -// animationChannelTargetPath = AnimationChannelTargetPath.TRANSLATION; -// break; -// } -// case "rotation": { -// dataAccessorType = AccessorType.VEC4; -// animationChannelTargetPath = AnimationChannelTargetPath.ROTATION; -// break; -// } -// case "rotationQuaternion": { -// dataAccessorType = AccessorType.VEC4; -// useQuaternion = true; -// animationChannelTargetPath = AnimationChannelTargetPath.ROTATION; -// break; -// } -// case "influence": { -// dataAccessorType = AccessorType.SCALAR; -// animationChannelTargetPath = AnimationChannelTargetPath.WEIGHTS; -// break; -// } -// default: { -// Tools.Error(`Unsupported animatable property ${property[0]}`); -// } -// } -// if (animationChannelTargetPath) { -// return { animationChannelTargetPath: animationChannelTargetPath, dataAccessorType: dataAccessorType, useQuaternion: useQuaternion }; -// } else { -// Tools.Error("animation channel target path and data accessor type could be deduced"); -// } -// return null; -// } + private static _DeduceAnimationInfo(animation: Animation): Nullable<_IAnimationInfo> { + let animationChannelTargetPath: Nullable = null; + let dataAccessorType = AccessorType.VEC3; + let useQuaternion: boolean = false; + const property = animation.targetProperty.split("."); + switch (property[0]) { + case "scaling": { + animationChannelTargetPath = AnimationChannelTargetPath.SCALE; + break; + } + case "position": { + animationChannelTargetPath = AnimationChannelTargetPath.TRANSLATION; + break; + } + case "rotation": { + dataAccessorType = AccessorType.VEC4; + animationChannelTargetPath = AnimationChannelTargetPath.ROTATION; + break; + } + case "rotationQuaternion": { + dataAccessorType = AccessorType.VEC4; + useQuaternion = true; + animationChannelTargetPath = AnimationChannelTargetPath.ROTATION; + break; + } + case "influence": { + dataAccessorType = AccessorType.SCALAR; + animationChannelTargetPath = AnimationChannelTargetPath.WEIGHTS; + break; + } + default: { + Tools.Error(`Unsupported animatable property ${property[0]}`); + } + } + if (animationChannelTargetPath) { + return { animationChannelTargetPath: animationChannelTargetPath, dataAccessorType: dataAccessorType, useQuaternion: useQuaternion }; + } else { + Tools.Error("animation channel target path and data accessor type could be deduced"); + } + return null; + } -// /** -// * @ignore -// * Create node animations from the transform node animations -// * @param babylonNode -// * @param runtimeGLTFAnimation -// * @param idleGLTFAnimations -// * @param nodeMap -// * @param nodes -// * @param binaryWriter -// * @param bufferViews -// * @param accessors -// * @param animationSampleRate -// */ -// public static _CreateNodeAnimationFromNodeAnimations( -// babylonNode: Node, -// runtimeGLTFAnimation: IAnimation, -// idleGLTFAnimations: IAnimation[], -// nodeMap: { [key: number]: number }, -// nodes: INode[], -// binaryWriter: _BinaryWriter, -// bufferViews: IBufferView[], -// accessors: IAccessor[], -// animationSampleRate: number, -// shouldExportAnimation?: (animation: Animation) => boolean -// ) { -// let glTFAnimation: IAnimation; -// if (_GLTFAnimation._IsTransformable(babylonNode)) { -// if (babylonNode.animations) { -// for (const animation of babylonNode.animations) { -// if (shouldExportAnimation && !shouldExportAnimation(animation)) { -// continue; -// } -// const animationInfo = _GLTFAnimation._DeduceAnimationInfo(animation); -// if (animationInfo) { -// glTFAnimation = { -// name: animation.name, -// samplers: [], -// channels: [], -// }; -// _GLTFAnimation._AddAnimation( -// `${animation.name}`, -// animation.hasRunningRuntimeAnimations ? runtimeGLTFAnimation : glTFAnimation, -// babylonNode, -// animation, -// animationInfo.dataAccessorType, -// animationInfo.animationChannelTargetPath, -// nodeMap, -// binaryWriter, -// bufferViews, -// accessors, -// animationInfo.useQuaternion, -// animationSampleRate -// ); -// if (glTFAnimation.samplers.length && glTFAnimation.channels.length) { -// idleGLTFAnimations.push(glTFAnimation); -// } -// } -// } -// } -// } -// } + /** + * @ignore + * Create node animations from the transform node animations + * @param babylonNode + * @param runtimeGLTFAnimation + * @param idleGLTFAnimations + * @param nodeMap + * @param nodes + * @param binaryWriter + * @param bufferViews + * @param accessors + * @param animationSampleRate + */ + public static _CreateNodeAnimationFromNodeAnimations( + babylonNode: Node, + runtimeGLTFAnimation: IAnimation, + idleGLTFAnimations: IAnimation[], + nodeMap: Map, + nodes: INode[], + binaryWriter: DataWriter, + bufferViews: IBufferView[], + accessors: IAccessor[], + animationSampleRate: number, + shouldExportAnimation?: (animation: Animation) => boolean + ) { + let glTFAnimation: IAnimation; + if (_GLTFAnimation._IsTransformable(babylonNode)) { + if (babylonNode.animations) { + for (const animation of babylonNode.animations) { + if (shouldExportAnimation && !shouldExportAnimation(animation)) { + continue; + } + const animationInfo = _GLTFAnimation._DeduceAnimationInfo(animation); + if (animationInfo) { + glTFAnimation = { + name: animation.name, + samplers: [], + channels: [], + }; + _GLTFAnimation._AddAnimation( + `${animation.name}`, + animation.hasRunningRuntimeAnimations ? runtimeGLTFAnimation : glTFAnimation, + babylonNode, + animation, + animationInfo.dataAccessorType, + animationInfo.animationChannelTargetPath, + nodeMap, + binaryWriter, + bufferViews, + accessors, + animationInfo.useQuaternion, + animationSampleRate + ); + if (glTFAnimation.samplers.length && glTFAnimation.channels.length) { + idleGLTFAnimations.push(glTFAnimation); + } + } + } + } + } + } -// /** -// * @ignore -// * Create individual morph animations from the mesh's morph target animation tracks -// * @param babylonNode -// * @param runtimeGLTFAnimation -// * @param idleGLTFAnimations -// * @param nodeMap -// * @param nodes -// * @param binaryWriter -// * @param bufferViews -// * @param accessors -// * @param animationSampleRate -// */ -// public static _CreateMorphTargetAnimationFromMorphTargetAnimations( -// babylonNode: Node, -// runtimeGLTFAnimation: IAnimation, -// idleGLTFAnimations: IAnimation[], -// nodeMap: { [key: number]: number }, -// nodes: INode[], -// binaryWriter: _BinaryWriter, -// bufferViews: IBufferView[], -// accessors: IAccessor[], -// animationSampleRate: number, -// shouldExportAnimation?: (animation: Animation) => boolean -// ) { -// let glTFAnimation: IAnimation; -// if (babylonNode instanceof Mesh) { -// const morphTargetManager = babylonNode.morphTargetManager; -// if (morphTargetManager) { -// for (let i = 0; i < morphTargetManager.numTargets; ++i) { -// const morphTarget = morphTargetManager.getTarget(i); -// for (const animation of morphTarget.animations) { -// if (shouldExportAnimation && !shouldExportAnimation(animation)) { -// continue; -// } -// const combinedAnimation = new Animation( -// `${animation.name}`, -// "influence", -// animation.framePerSecond, -// animation.dataType, -// animation.loopMode, -// animation.enableBlending -// ); -// const combinedAnimationKeys: IAnimationKey[] = []; -// const animationKeys = animation.getKeys(); + /** + * @ignore + * Create individual morph animations from the mesh's morph target animation tracks + * @param babylonNode + * @param runtimeGLTFAnimation + * @param idleGLTFAnimations + * @param nodeMap + * @param nodes + * @param binaryWriter + * @param bufferViews + * @param accessors + * @param animationSampleRate + */ + public static _CreateMorphTargetAnimationFromMorphTargetAnimations( + babylonNode: Node, + runtimeGLTFAnimation: IAnimation, + idleGLTFAnimations: IAnimation[], + nodeMap: Map, + nodes: INode[], + binaryWriter: DataWriter, + bufferViews: IBufferView[], + accessors: IAccessor[], + animationSampleRate: number, + shouldExportAnimation?: (animation: Animation) => boolean + ) { + let glTFAnimation: IAnimation; + if (babylonNode instanceof Mesh) { + const morphTargetManager = babylonNode.morphTargetManager; + if (morphTargetManager) { + for (let i = 0; i < morphTargetManager.numTargets; ++i) { + const morphTarget = morphTargetManager.getTarget(i); + for (const animation of morphTarget.animations) { + if (shouldExportAnimation && !shouldExportAnimation(animation)) { + continue; + } + const combinedAnimation = new Animation( + `${animation.name}`, + "influence", + animation.framePerSecond, + animation.dataType, + animation.loopMode, + animation.enableBlending + ); + const combinedAnimationKeys: IAnimationKey[] = []; + const animationKeys = animation.getKeys(); -// for (let j = 0; j < animationKeys.length; ++j) { -// const animationKey = animationKeys[j]; -// for (let k = 0; k < morphTargetManager.numTargets; ++k) { -// if (k == i) { -// combinedAnimationKeys.push(animationKey); -// } else { -// combinedAnimationKeys.push({ frame: animationKey.frame, value: 0 }); -// } -// } -// } -// combinedAnimation.setKeys(combinedAnimationKeys); -// const animationInfo = _GLTFAnimation._DeduceAnimationInfo(combinedAnimation); -// if (animationInfo) { -// glTFAnimation = { -// name: combinedAnimation.name, -// samplers: [], -// channels: [], -// }; -// _GLTFAnimation._AddAnimation( -// animation.name, -// animation.hasRunningRuntimeAnimations ? runtimeGLTFAnimation : glTFAnimation, -// babylonNode, -// combinedAnimation, -// animationInfo.dataAccessorType, -// animationInfo.animationChannelTargetPath, -// nodeMap, -// binaryWriter, -// bufferViews, -// accessors, -// animationInfo.useQuaternion, -// animationSampleRate, -// morphTargetManager.numTargets -// ); -// if (glTFAnimation.samplers.length && glTFAnimation.channels.length) { -// idleGLTFAnimations.push(glTFAnimation); -// } -// } -// } -// } -// } -// } -// } + for (let j = 0; j < animationKeys.length; ++j) { + const animationKey = animationKeys[j]; + for (let k = 0; k < morphTargetManager.numTargets; ++k) { + if (k == i) { + combinedAnimationKeys.push(animationKey); + } else { + combinedAnimationKeys.push({ frame: animationKey.frame, value: 0 }); + } + } + } + combinedAnimation.setKeys(combinedAnimationKeys); + const animationInfo = _GLTFAnimation._DeduceAnimationInfo(combinedAnimation); + if (animationInfo) { + glTFAnimation = { + name: combinedAnimation.name, + samplers: [], + channels: [], + }; + _GLTFAnimation._AddAnimation( + animation.name, + animation.hasRunningRuntimeAnimations ? runtimeGLTFAnimation : glTFAnimation, + babylonNode, + combinedAnimation, + animationInfo.dataAccessorType, + animationInfo.animationChannelTargetPath, + nodeMap, + binaryWriter, + bufferViews, + accessors, + animationInfo.useQuaternion, + animationSampleRate, + morphTargetManager.numTargets + ); + if (glTFAnimation.samplers.length && glTFAnimation.channels.length) { + idleGLTFAnimations.push(glTFAnimation); + } + } + } + } + } + } + } -// /** -// * @internal -// * Create node and morph animations from the animation groups -// * @param babylonScene -// * @param glTFAnimations -// * @param nodeMap -// * @param nodes -// * @param binaryWriter -// * @param bufferViews -// * @param accessors -// * @param animationSampleRate -// */ -// public static _CreateNodeAndMorphAnimationFromAnimationGroups( -// babylonScene: Scene, -// glTFAnimations: IAnimation[], -// nodeMap: { [key: number]: number }, -// binaryWriter: _BinaryWriter, -// bufferViews: IBufferView[], -// accessors: IAccessor[], -// animationSampleRate: number, -// shouldExportAnimation?: (animation: Animation) => boolean -// ) { -// let glTFAnimation: IAnimation; -// if (babylonScene.animationGroups) { -// const animationGroups = babylonScene.animationGroups; -// for (const animationGroup of animationGroups) { -// const morphAnimations: Map> = new Map(); -// const sampleAnimations: Map = new Map(); -// const morphAnimationMeshes: Set = new Set(); -// const animationGroupFrameDiff = animationGroup.to - animationGroup.from; -// glTFAnimation = { -// name: animationGroup.name, -// channels: [], -// samplers: [], -// }; -// for (let i = 0; i < animationGroup.targetedAnimations.length; ++i) { -// const targetAnimation = animationGroup.targetedAnimations[i]; -// const target = targetAnimation.target; -// const animation = targetAnimation.animation; -// if (shouldExportAnimation && !shouldExportAnimation(animation)) { -// continue; -// } -// if (this._IsTransformable(target) || (target.length === 1 && this._IsTransformable(target[0]))) { -// const animationInfo = _GLTFAnimation._DeduceAnimationInfo(targetAnimation.animation); -// if (animationInfo) { -// const babylonTransformNode = this._IsTransformable(target) ? target : this._IsTransformable(target[0]) ? target[0] : null; -// if (babylonTransformNode) { -// _GLTFAnimation._AddAnimation( -// `${animation.name}`, -// glTFAnimation, -// babylonTransformNode, -// animation, -// animationInfo.dataAccessorType, -// animationInfo.animationChannelTargetPath, -// nodeMap, -// binaryWriter, -// bufferViews, -// accessors, -// animationInfo.useQuaternion, -// animationSampleRate -// ); -// } -// } -// } else if (target instanceof MorphTarget || (target.length === 1 && target[0] instanceof MorphTarget)) { -// const animationInfo = _GLTFAnimation._DeduceAnimationInfo(targetAnimation.animation); -// if (animationInfo) { -// const babylonMorphTarget = target instanceof MorphTarget ? (target as MorphTarget) : (target[0] as MorphTarget); -// if (babylonMorphTarget) { -// const babylonMorphTargetManager = babylonScene.morphTargetManagers.find((morphTargetManager) => { -// for (let j = 0; j < morphTargetManager.numTargets; ++j) { -// if (morphTargetManager.getTarget(j) === babylonMorphTarget) { -// return true; -// } -// } -// return false; -// }); -// if (babylonMorphTargetManager) { -// const babylonMesh = babylonScene.meshes.find((mesh) => { -// return (mesh as Mesh).morphTargetManager === babylonMorphTargetManager; -// }) as Mesh; -// if (babylonMesh) { -// if (!morphAnimations.has(babylonMesh)) { -// morphAnimations.set(babylonMesh, new Map()); -// } -// morphAnimations.get(babylonMesh)?.set(babylonMorphTarget, animation); -// morphAnimationMeshes.add(babylonMesh); -// sampleAnimations.set(babylonMesh, animation); -// } -// } -// } -// } -// } else { -// // this is the place for the KHR_animation_pointer. -// } -// } -// morphAnimationMeshes.forEach((mesh) => { -// const morphTargetManager = mesh.morphTargetManager!; -// let combinedAnimationGroup: Nullable = null; -// const animationKeys: IAnimationKey[] = []; -// const sampleAnimation = sampleAnimations.get(mesh)!; -// const sampleAnimationKeys = sampleAnimation.getKeys(); -// const numAnimationKeys = sampleAnimationKeys.length; -// /* -// Due to how glTF expects morph target animation data to be formatted, we need to rearrange the individual morph target animation tracks, -// such that we have a single animation, where a given keyframe input value has successive output values for each morph target belonging to the manager. -// See: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations + /** + * @internal + * Create node and morph animations from the animation groups + * @param babylonScene + * @param glTFAnimations + * @param nodeMap + * @param nodes + * @param binaryWriter + * @param bufferViews + * @param accessors + * @param animationSampleRate + */ + public static _CreateNodeAndMorphAnimationFromAnimationGroups( + babylonScene: Scene, + glTFAnimations: IAnimation[], + nodeMap: Map, + binaryWriter: DataWriter, + bufferViews: IBufferView[], + accessors: IAccessor[], + animationSampleRate: number, + shouldExportAnimation?: (animation: Animation) => boolean + ) { + let glTFAnimation: IAnimation; + if (babylonScene.animationGroups) { + const animationGroups = babylonScene.animationGroups; + for (const animationGroup of animationGroups) { + const morphAnimations: Map> = new Map(); + const sampleAnimations: Map = new Map(); + const morphAnimationMeshes: Set = new Set(); + const animationGroupFrameDiff = animationGroup.to - animationGroup.from; + glTFAnimation = { + name: animationGroup.name, + channels: [], + samplers: [], + }; + for (let i = 0; i < animationGroup.targetedAnimations.length; ++i) { + const targetAnimation = animationGroup.targetedAnimations[i]; + const target = targetAnimation.target; + const animation = targetAnimation.animation; + if (shouldExportAnimation && !shouldExportAnimation(animation)) { + continue; + } + if (this._IsTransformable(target) || (target.length === 1 && this._IsTransformable(target[0]))) { + const animationInfo = _GLTFAnimation._DeduceAnimationInfo(targetAnimation.animation); + if (animationInfo) { + const babylonTransformNode = this._IsTransformable(target) ? target : this._IsTransformable(target[0]) ? target[0] : null; + if (babylonTransformNode) { + _GLTFAnimation._AddAnimation( + `${animation.name}`, + glTFAnimation, + babylonTransformNode, + animation, + animationInfo.dataAccessorType, + animationInfo.animationChannelTargetPath, + nodeMap, + binaryWriter, + bufferViews, + accessors, + animationInfo.useQuaternion, + animationSampleRate + ); + } + } + } else if (target instanceof MorphTarget || (target.length === 1 && target[0] instanceof MorphTarget)) { + const animationInfo = _GLTFAnimation._DeduceAnimationInfo(targetAnimation.animation); + if (animationInfo) { + const babylonMorphTarget = target instanceof MorphTarget ? (target as MorphTarget) : (target[0] as MorphTarget); + if (babylonMorphTarget) { + const babylonMorphTargetManager = babylonScene.morphTargetManagers.find((morphTargetManager) => { + for (let j = 0; j < morphTargetManager.numTargets; ++j) { + if (morphTargetManager.getTarget(j) === babylonMorphTarget) { + return true; + } + } + return false; + }); + if (babylonMorphTargetManager) { + const babylonMesh = babylonScene.meshes.find((mesh) => { + return (mesh as Mesh).morphTargetManager === babylonMorphTargetManager; + }) as Mesh; + if (babylonMesh) { + if (!morphAnimations.has(babylonMesh)) { + morphAnimations.set(babylonMesh, new Map()); + } + morphAnimations.get(babylonMesh)?.set(babylonMorphTarget, animation); + morphAnimationMeshes.add(babylonMesh); + sampleAnimations.set(babylonMesh, animation); + } + } + } + } + } else { + // this is the place for the KHR_animation_pointer. + } + } + morphAnimationMeshes.forEach((mesh) => { + const morphTargetManager = mesh.morphTargetManager!; + let combinedAnimationGroup: Nullable = null; + const animationKeys: IAnimationKey[] = []; + const sampleAnimation = sampleAnimations.get(mesh)!; + const sampleAnimationKeys = sampleAnimation.getKeys(); + const numAnimationKeys = sampleAnimationKeys.length; + /* + Due to how glTF expects morph target animation data to be formatted, we need to rearrange the individual morph target animation tracks, + such that we have a single animation, where a given keyframe input value has successive output values for each morph target belonging to the manager. + See: https://github.com/KhronosGroup/glTF/tree/master/specification/2.0#animations -// We do this via constructing a new Animation track, and interleaving the frames of each morph target animation track in the current Animation Group -// We reuse the Babylon Animation data structure for ease of handling export of cubic spline animation keys, and to reuse the -// existing _GLTFAnimation.AddAnimation codepath with minimal modification, however the constructed Babylon Animation is NOT intended for use in-engine. -// */ -// for (let i = 0; i < numAnimationKeys; ++i) { -// for (let j = 0; j < morphTargetManager.numTargets; ++j) { -// const morphTarget = morphTargetManager.getTarget(j); -// const animationsByMorphTarget = morphAnimations.get(mesh); -// if (animationsByMorphTarget) { -// const morphTargetAnimation = animationsByMorphTarget.get(morphTarget); -// if (morphTargetAnimation) { -// if (!combinedAnimationGroup) { -// combinedAnimationGroup = new Animation( -// `${animationGroup.name}_${mesh.name}_MorphWeightAnimation`, -// "influence", -// morphTargetAnimation.framePerSecond, -// Animation.ANIMATIONTYPE_FLOAT, -// morphTargetAnimation.loopMode, -// morphTargetAnimation.enableBlending -// ); -// } -// animationKeys.push(morphTargetAnimation.getKeys()[i]); -// } else { -// animationKeys.push({ -// frame: animationGroup.from + (animationGroupFrameDiff / numAnimationKeys) * i, -// value: morphTarget.influence, -// inTangent: sampleAnimationKeys[0].inTangent ? 0 : undefined, -// outTangent: sampleAnimationKeys[0].outTangent ? 0 : undefined, -// }); -// } -// } -// } -// } -// combinedAnimationGroup!.setKeys(animationKeys); -// const animationInfo = _GLTFAnimation._DeduceAnimationInfo(combinedAnimationGroup!); -// if (animationInfo) { -// _GLTFAnimation._AddAnimation( -// `${animationGroup.name}_${mesh.name}_MorphWeightAnimation`, -// glTFAnimation, -// mesh, -// combinedAnimationGroup!, -// animationInfo.dataAccessorType, -// animationInfo.animationChannelTargetPath, -// nodeMap, -// binaryWriter, -// bufferViews, -// accessors, -// animationInfo.useQuaternion, -// animationSampleRate, -// morphTargetManager?.numTargets -// ); -// } -// }); -// if (glTFAnimation.channels.length && glTFAnimation.samplers.length) { -// glTFAnimations.push(glTFAnimation); -// } -// } -// } -// } + We do this via constructing a new Animation track, and interleaving the frames of each morph target animation track in the current Animation Group + We reuse the Babylon Animation data structure for ease of handling export of cubic spline animation keys, and to reuse the + existing _GLTFAnimation.AddAnimation codepath with minimal modification, however the constructed Babylon Animation is NOT intended for use in-engine. + */ + for (let i = 0; i < numAnimationKeys; ++i) { + for (let j = 0; j < morphTargetManager.numTargets; ++j) { + const morphTarget = morphTargetManager.getTarget(j); + const animationsByMorphTarget = morphAnimations.get(mesh); + if (animationsByMorphTarget) { + const morphTargetAnimation = animationsByMorphTarget.get(morphTarget); + if (morphTargetAnimation) { + if (!combinedAnimationGroup) { + combinedAnimationGroup = new Animation( + `${animationGroup.name}_${mesh.name}_MorphWeightAnimation`, + "influence", + morphTargetAnimation.framePerSecond, + Animation.ANIMATIONTYPE_FLOAT, + morphTargetAnimation.loopMode, + morphTargetAnimation.enableBlending + ); + } + animationKeys.push(morphTargetAnimation.getKeys()[i]); + } else { + animationKeys.push({ + frame: animationGroup.from + (animationGroupFrameDiff / numAnimationKeys) * i, + value: morphTarget.influence, + inTangent: sampleAnimationKeys[0].inTangent ? 0 : undefined, + outTangent: sampleAnimationKeys[0].outTangent ? 0 : undefined, + }); + } + } + } + } + combinedAnimationGroup!.setKeys(animationKeys); + const animationInfo = _GLTFAnimation._DeduceAnimationInfo(combinedAnimationGroup!); + if (animationInfo) { + _GLTFAnimation._AddAnimation( + `${animationGroup.name}_${mesh.name}_MorphWeightAnimation`, + glTFAnimation, + mesh, + combinedAnimationGroup!, + animationInfo.dataAccessorType, + animationInfo.animationChannelTargetPath, + nodeMap, + binaryWriter, + bufferViews, + accessors, + animationInfo.useQuaternion, + animationSampleRate, + morphTargetManager?.numTargets + ); + } + }); + if (glTFAnimation.channels.length && glTFAnimation.samplers.length) { + glTFAnimations.push(glTFAnimation); + } + } + } + } -// private static _AddAnimation( -// name: string, -// glTFAnimation: IAnimation, -// babylonTransformNode: Node, -// animation: Animation, -// dataAccessorType: AccessorType, -// animationChannelTargetPath: AnimationChannelTargetPath, -// nodeMap: { [key: number]: number }, -// binaryWriter: _BinaryWriter, -// bufferViews: IBufferView[], -// accessors: IAccessor[], -// useQuaternion: boolean, -// animationSampleRate: number, -// morphAnimationChannels?: number -// ) { -// const animationData = _GLTFAnimation._CreateNodeAnimation(babylonTransformNode, animation, animationChannelTargetPath, useQuaternion, animationSampleRate); -// let bufferView: IBufferView; -// let accessor: IAccessor; -// let keyframeAccessorIndex: number; -// let dataAccessorIndex: number; -// let outputLength: number; -// let animationSampler: IAnimationSampler; -// let animationChannel: IAnimationChannel; + private static _AddAnimation( + name: string, + glTFAnimation: IAnimation, + babylonTransformNode: Node, + animation: Animation, + dataAccessorType: AccessorType, + animationChannelTargetPath: AnimationChannelTargetPath, + nodeMap: Map, + binaryWriter: DataWriter, + bufferViews: IBufferView[], + accessors: IAccessor[], + useQuaternion: boolean, + animationSampleRate: number, + morphAnimationChannels?: number + ) { + const animationData = _GLTFAnimation._CreateNodeAnimation(babylonTransformNode, animation, animationChannelTargetPath, useQuaternion, animationSampleRate); + let bufferView: IBufferView; + let accessor: IAccessor; + let keyframeAccessorIndex: number; + let dataAccessorIndex: number; + let outputLength: number; + let animationSampler: IAnimationSampler; + let animationChannel: IAnimationChannel; -// if (animationData) { -// /* -// * Now that we have the glTF converted morph target animation data, -// * we can remove redundant input data so that we have n input frames, -// * and morphAnimationChannels * n output frames -// */ -// if (morphAnimationChannels) { -// let index = 0; -// let currentInput: number = 0; -// const newInputs: number[] = []; -// while (animationData.inputs.length > 0) { -// currentInput = animationData.inputs.shift()!; -// if (index % morphAnimationChannels == 0) { -// newInputs.push(currentInput); -// } -// index++; -// } -// animationData.inputs = newInputs; -// } + if (animationData) { + /* + * Now that we have the glTF converted morph target animation data, + * we can remove redundant input data so that we have n input frames, + * and morphAnimationChannels * n output frames + */ + if (morphAnimationChannels) { + let index = 0; + let currentInput: number = 0; + const newInputs: number[] = []; + while (animationData.inputs.length > 0) { + currentInput = animationData.inputs.shift()!; + if (index % morphAnimationChannels == 0) { + newInputs.push(currentInput); + } + index++; + } + animationData.inputs = newInputs; + } -// const nodeIndex = nodeMap[babylonTransformNode.uniqueId]; + const nodeIndex = nodeMap.get(babylonTransformNode); -// // Creates buffer view and accessor for key frames. -// let byteLength = animationData.inputs.length * 4; -// bufferView = _GLTFUtilities._CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, `${name} keyframe data view`); -// bufferViews.push(bufferView); -// animationData.inputs.forEach(function (input) { -// binaryWriter.setFloat32(input); -// }); + // Creates buffer view and accessor for key frames. + let byteLength = animationData.inputs.length * 4; + const offset = binaryWriter.byteOffset; + bufferView = createBufferView(0, offset, byteLength); + //bufferView = _GLTFUtilities._CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, `${name} keyframe data view`); + bufferViews.push(bufferView); + animationData.inputs.forEach(function (input) { + binaryWriter.writeFloat32(input); + }); -// accessor = _GLTFUtilities._CreateAccessor( -// bufferViews.length - 1, -// `${name} keyframes`, -// AccessorType.SCALAR, -// AccessorComponentType.FLOAT, -// animationData.inputs.length, -// null, -// [animationData.inputsMin], -// [animationData.inputsMax] -// ); -// accessors.push(accessor); -// keyframeAccessorIndex = accessors.length - 1; + accessor = createAccessor(bufferViews.length - 1, AccessorType.SCALAR, AccessorComponentType.FLOAT, animationData.inputs.length, null, { + min: [animationData.inputsMin], + max: [animationData.inputsMax], + }); -// // create bufferview and accessor for keyed values. -// outputLength = animationData.outputs.length; -// byteLength = _GLTFUtilities._GetDataAccessorElementCount(dataAccessorType) * 4 * animationData.outputs.length; + accessors.push(accessor); + keyframeAccessorIndex = accessors.length - 1; -// // check for in and out tangents -// bufferView = _GLTFUtilities._CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, `${name} data view`); -// bufferViews.push(bufferView); + // create bufferview and accessor for keyed values. + outputLength = animationData.outputs.length; + byteLength = getAccessorElementCount(dataAccessorType) * 4 * animationData.outputs.length; -// animationData.outputs.forEach(function (output) { -// output.forEach(function (entry) { -// binaryWriter.setFloat32(entry); -// }); -// }); + // check for in and out tangents + bufferView = createBufferView(0, binaryWriter.byteOffset, byteLength); + //bufferView = _GLTFUtilities._CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, `${name} data view`); + bufferViews.push(bufferView); -// accessor = _GLTFUtilities._CreateAccessor(bufferViews.length - 1, `${name} data`, dataAccessorType, AccessorComponentType.FLOAT, outputLength, null, null, null); -// accessors.push(accessor); -// dataAccessorIndex = accessors.length - 1; + animationData.outputs.forEach(function (output) { + output.forEach(function (entry) { + binaryWriter.writeFloat32(entry); + }); + }); -// // create sampler -// animationSampler = { -// interpolation: animationData.samplerInterpolation, -// input: keyframeAccessorIndex, -// output: dataAccessorIndex, -// }; -// glTFAnimation.samplers.push(animationSampler); + accessor = createAccessor(bufferViews.length - 1, dataAccessorType, AccessorComponentType.FLOAT, outputLength, null); + //accessor = _GLTFUtilities._CreateAccessor(bufferViews.length - 1, `${name} data`, dataAccessorType, AccessorComponentType.FLOAT, outputLength, null, null, null); + accessors.push(accessor); + dataAccessorIndex = accessors.length - 1; -// // create channel -// animationChannel = { -// sampler: glTFAnimation.samplers.length - 1, -// target: { -// node: nodeIndex, -// path: animationChannelTargetPath, -// }, -// }; -// glTFAnimation.channels.push(animationChannel); -// } -// } + // create sampler + animationSampler = { + interpolation: animationData.samplerInterpolation, + input: keyframeAccessorIndex, + output: dataAccessorIndex, + }; + glTFAnimation.samplers.push(animationSampler); -// /** -// * Create a baked animation -// * @param babylonTransformNode BabylonJS mesh -// * @param animation BabylonJS animation corresponding to the BabylonJS mesh -// * @param animationChannelTargetPath animation target channel -// * @param minFrame minimum animation frame -// * @param maxFrame maximum animation frame -// * @param fps frames per second of the animation -// * @param sampleRate -// * @param inputs input key frames of the animation -// * @param outputs output key frame data of the animation -// * @param minMaxFrames -// * @param minMaxFrames.min -// * @param minMaxFrames.max -// * @param useQuaternion specifies if quaternions should be used -// */ -// private static _CreateBakedAnimation( -// babylonTransformNode: Node, -// animation: Animation, -// animationChannelTargetPath: AnimationChannelTargetPath, -// minFrame: number, -// maxFrame: number, -// fps: number, -// sampleRate: number, -// inputs: number[], -// outputs: number[][], -// minMaxFrames: { min: number; max: number }, -// useQuaternion: boolean -// ) { -// let value: number | Vector3 | Quaternion; -// const quaternionCache: Quaternion = Quaternion.Identity(); -// let previousTime: Nullable = null; -// let time: number; -// let maxUsedFrame: Nullable = null; -// let currKeyFrame: Nullable = null; -// let nextKeyFrame: Nullable = null; -// let prevKeyFrame: Nullable = null; -// let endFrame: Nullable = null; -// minMaxFrames.min = Tools.FloatRound(minFrame / fps); + // create channel + animationChannel = { + sampler: glTFAnimation.samplers.length - 1, + target: { + node: nodeIndex, + path: animationChannelTargetPath, + }, + }; + glTFAnimation.channels.push(animationChannel); + } + } -// const keyFrames = animation.getKeys(); + /** + * Create a baked animation + * @param babylonTransformNode BabylonJS mesh + * @param animation BabylonJS animation corresponding to the BabylonJS mesh + * @param animationChannelTargetPath animation target channel + * @param minFrame minimum animation frame + * @param maxFrame maximum animation frame + * @param fps frames per second of the animation + * @param sampleRate + * @param inputs input key frames of the animation + * @param outputs output key frame data of the animation + * @param minMaxFrames + * @param minMaxFrames.min + * @param minMaxFrames.max + * @param useQuaternion specifies if quaternions should be used + */ + private static _CreateBakedAnimation( + babylonTransformNode: Node, + animation: Animation, + animationChannelTargetPath: AnimationChannelTargetPath, + minFrame: number, + maxFrame: number, + fps: number, + sampleRate: number, + inputs: number[], + outputs: number[][], + minMaxFrames: { min: number; max: number }, + useQuaternion: boolean + ) { + let value: number | Vector3 | Quaternion; + const quaternionCache: Quaternion = Quaternion.Identity(); + let previousTime: Nullable = null; + let time: number; + let maxUsedFrame: Nullable = null; + let currKeyFrame: Nullable = null; + let nextKeyFrame: Nullable = null; + let prevKeyFrame: Nullable = null; + let endFrame: Nullable = null; + minMaxFrames.min = Tools.FloatRound(minFrame / fps); -// for (let i = 0, length = keyFrames.length; i < length; ++i) { -// endFrame = null; -// currKeyFrame = keyFrames[i]; + const keyFrames = animation.getKeys(); -// if (i + 1 < length) { -// nextKeyFrame = keyFrames[i + 1]; -// if ((currKeyFrame.value.equals && currKeyFrame.value.equals(nextKeyFrame.value)) || currKeyFrame.value === nextKeyFrame.value) { -// if (i === 0) { -// // set the first frame to itself -// endFrame = currKeyFrame.frame; -// } else { -// continue; -// } -// } else { -// endFrame = nextKeyFrame.frame; -// } -// } else { -// // at the last key frame -// prevKeyFrame = keyFrames[i - 1]; -// if ((currKeyFrame.value.equals && currKeyFrame.value.equals(prevKeyFrame.value)) || currKeyFrame.value === prevKeyFrame.value) { -// continue; -// } else { -// endFrame = maxFrame; -// } -// } -// if (endFrame) { -// for (let f = currKeyFrame.frame; f <= endFrame; f += sampleRate) { -// time = Tools.FloatRound(f / fps); -// if (time === previousTime) { -// continue; -// } -// previousTime = time; -// maxUsedFrame = time; -// const state = { -// key: 0, -// repeatCount: 0, -// loopMode: animation.loopMode, -// }; -// value = animation._interpolate(f, state); + for (let i = 0, length = keyFrames.length; i < length; ++i) { + endFrame = null; + currKeyFrame = keyFrames[i]; -// _GLTFAnimation._SetInterpolatedValue(babylonTransformNode, value, time, animation, animationChannelTargetPath, quaternionCache, inputs, outputs, useQuaternion); -// } -// } -// } -// if (maxUsedFrame) { -// minMaxFrames.max = maxUsedFrame; -// } -// } + if (i + 1 < length) { + nextKeyFrame = keyFrames[i + 1]; + if ((currKeyFrame.value.equals && currKeyFrame.value.equals(nextKeyFrame.value)) || currKeyFrame.value === nextKeyFrame.value) { + if (i === 0) { + // set the first frame to itself + endFrame = currKeyFrame.frame; + } else { + continue; + } + } else { + endFrame = nextKeyFrame.frame; + } + } else { + // at the last key frame + prevKeyFrame = keyFrames[i - 1]; + if ((currKeyFrame.value.equals && currKeyFrame.value.equals(prevKeyFrame.value)) || currKeyFrame.value === prevKeyFrame.value) { + continue; + } else { + endFrame = maxFrame; + } + } + if (endFrame) { + for (let f = currKeyFrame.frame; f <= endFrame; f += sampleRate) { + time = Tools.FloatRound(f / fps); + if (time === previousTime) { + continue; + } + previousTime = time; + maxUsedFrame = time; + const state = { + key: 0, + repeatCount: 0, + loopMode: animation.loopMode, + }; + value = animation._interpolate(f, state); -// private static _ConvertFactorToVector3OrQuaternion( -// factor: number, -// babylonTransformNode: Node, -// animation: Animation, -// animationChannelTargetPath: AnimationChannelTargetPath, -// useQuaternion: boolean -// ): Vector3 | Quaternion { -// const basePositionRotationOrScale = _GLTFAnimation._GetBasePositionRotationOrScale(babylonTransformNode, animationChannelTargetPath, useQuaternion); -// // handles single component x, y, z or w component animation by using a base property and animating over a component. -// const property = animation.targetProperty.split("."); -// const componentName = property ? property[1] : ""; // x, y, z, or w component -// const value = useQuaternion ? Quaternion.FromArray(basePositionRotationOrScale).normalize() : Vector3.FromArray(basePositionRotationOrScale); + _GLTFAnimation._SetInterpolatedValue(babylonTransformNode, value, time, animation, animationChannelTargetPath, quaternionCache, inputs, outputs, useQuaternion); + } + } + } + if (maxUsedFrame) { + minMaxFrames.max = maxUsedFrame; + } + } -// switch (componentName) { -// case "x": -// case "y": -// case "z": { -// value[componentName] = factor; -// break; -// } -// case "w": { -// (value as Quaternion).w = factor; -// break; -// } -// default: { -// Tools.Error(`glTFAnimation: Unsupported component name "${componentName}"!`); -// } -// } + private static _ConvertFactorToVector3OrQuaternion( + factor: number, + babylonTransformNode: Node, + animation: Animation, + animationChannelTargetPath: AnimationChannelTargetPath, + useQuaternion: boolean + ): Vector3 | Quaternion { + const basePositionRotationOrScale = _GLTFAnimation._GetBasePositionRotationOrScale(babylonTransformNode, animationChannelTargetPath, useQuaternion); + // handles single component x, y, z or w component animation by using a base property and animating over a component. + const property = animation.targetProperty.split("."); + const componentName = property ? property[1] : ""; // x, y, z, or w component + const value = useQuaternion ? Quaternion.FromArray(basePositionRotationOrScale).normalize() : Vector3.FromArray(basePositionRotationOrScale); -// return value; -// } + switch (componentName) { + case "x": + case "y": + case "z": { + value[componentName] = factor; + break; + } + case "w": { + (value as Quaternion).w = factor; + break; + } + default: { + Tools.Error(`glTFAnimation: Unsupported component name "${componentName}"!`); + } + } -// private static _SetInterpolatedValue( -// babylonTransformNode: Node, -// value: number | Vector3 | Quaternion, -// time: number, -// animation: Animation, -// animationChannelTargetPath: AnimationChannelTargetPath, -// quaternionCache: Quaternion, -// inputs: number[], -// outputs: number[][], -// useQuaternion: boolean -// ) { -// let cacheValue: Vector3 | Quaternion | number; -// inputs.push(time); + return value; + } -// if (animationChannelTargetPath === AnimationChannelTargetPath.WEIGHTS) { -// outputs.push([value as number]); -// return; -// } + private static _SetInterpolatedValue( + babylonTransformNode: Node, + value: number | Vector3 | Quaternion, + time: number, + animation: Animation, + animationChannelTargetPath: AnimationChannelTargetPath, + quaternionCache: Quaternion, + inputs: number[], + outputs: number[][], + useQuaternion: boolean + ) { + let cacheValue: Vector3 | Quaternion | number; + inputs.push(time); -// if (animation.dataType === Animation.ANIMATIONTYPE_FLOAT) { -// value = this._ConvertFactorToVector3OrQuaternion(value as number, babylonTransformNode, animation, animationChannelTargetPath, useQuaternion); -// } + if (animationChannelTargetPath === AnimationChannelTargetPath.WEIGHTS) { + outputs.push([value as number]); + return; + } -// if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { -// if (useQuaternion) { -// quaternionCache = value as Quaternion; -// } else { -// cacheValue = value as Vector3; -// Quaternion.RotationYawPitchRollToRef(cacheValue.y, cacheValue.x, cacheValue.z, quaternionCache); -// } -// outputs.push(quaternionCache.asArray()); -// } else { -// // scaling and position animation -// cacheValue = value as Vector3; -// outputs.push(cacheValue.asArray()); -// } -// } + if (animation.dataType === Animation.ANIMATIONTYPE_FLOAT) { + value = this._ConvertFactorToVector3OrQuaternion(value as number, babylonTransformNode, animation, animationChannelTargetPath, useQuaternion); + } -// /** -// * Creates linear animation from the animation key frames -// * @param babylonTransformNode BabylonJS mesh -// * @param animation BabylonJS animation -// * @param animationChannelTargetPath The target animation channel -// * @param inputs Array to store the key frame times -// * @param outputs Array to store the key frame data -// * @param useQuaternion Specifies if quaternions are used in the animation -// */ -// private static _CreateLinearOrStepAnimation( -// babylonTransformNode: Node, -// animation: Animation, -// animationChannelTargetPath: AnimationChannelTargetPath, -// inputs: number[], -// outputs: number[][], -// useQuaternion: boolean -// ) { -// for (const keyFrame of animation.getKeys()) { -// inputs.push(keyFrame.frame / animation.framePerSecond); // keyframes in seconds. -// _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonTransformNode, useQuaternion); -// } -// } + if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { + if (useQuaternion) { + quaternionCache = value as Quaternion; + } else { + cacheValue = value as Vector3; + Quaternion.RotationYawPitchRollToRef(cacheValue.y, cacheValue.x, cacheValue.z, quaternionCache); + } + outputs.push(quaternionCache.asArray()); + } else { + // scaling and position animation + cacheValue = value as Vector3; + outputs.push(cacheValue.asArray()); + } + } -// /** -// * Creates cubic spline animation from the animation key frames -// * @param babylonTransformNode BabylonJS mesh -// * @param animation BabylonJS animation -// * @param animationChannelTargetPath The target animation channel -// * @param inputs Array to store the key frame times -// * @param outputs Array to store the key frame data -// * @param useQuaternion Specifies if quaternions are used in the animation -// */ -// private static _CreateCubicSplineAnimation( -// babylonTransformNode: Node, -// animation: Animation, -// animationChannelTargetPath: AnimationChannelTargetPath, -// inputs: number[], -// outputs: number[][], -// useQuaternion: boolean -// ) { -// animation.getKeys().forEach(function (keyFrame) { -// inputs.push(keyFrame.frame / animation.framePerSecond); // keyframes in seconds. -// _GLTFAnimation._AddSplineTangent(_TangentType.INTANGENT, outputs, animationChannelTargetPath, AnimationSamplerInterpolation.CUBICSPLINE, keyFrame, useQuaternion); -// _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonTransformNode, useQuaternion); + /** + * Creates linear animation from the animation key frames + * @param babylonTransformNode BabylonJS mesh + * @param animation BabylonJS animation + * @param animationChannelTargetPath The target animation channel + * @param inputs Array to store the key frame times + * @param outputs Array to store the key frame data + * @param useQuaternion Specifies if quaternions are used in the animation + */ + private static _CreateLinearOrStepAnimation( + babylonTransformNode: Node, + animation: Animation, + animationChannelTargetPath: AnimationChannelTargetPath, + inputs: number[], + outputs: number[][], + useQuaternion: boolean + ) { + for (const keyFrame of animation.getKeys()) { + inputs.push(keyFrame.frame / animation.framePerSecond); // keyframes in seconds. + _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonTransformNode, useQuaternion); + } + } -// _GLTFAnimation._AddSplineTangent(_TangentType.OUTTANGENT, outputs, animationChannelTargetPath, AnimationSamplerInterpolation.CUBICSPLINE, keyFrame, useQuaternion); -// }); -// } + /** + * Creates cubic spline animation from the animation key frames + * @param babylonTransformNode BabylonJS mesh + * @param animation BabylonJS animation + * @param animationChannelTargetPath The target animation channel + * @param inputs Array to store the key frame times + * @param outputs Array to store the key frame data + * @param useQuaternion Specifies if quaternions are used in the animation + */ + private static _CreateCubicSplineAnimation( + babylonTransformNode: Node, + animation: Animation, + animationChannelTargetPath: AnimationChannelTargetPath, + inputs: number[], + outputs: number[][], + useQuaternion: boolean + ) { + animation.getKeys().forEach(function (keyFrame) { + inputs.push(keyFrame.frame / animation.framePerSecond); // keyframes in seconds. + _GLTFAnimation._AddSplineTangent(_TangentType.INTANGENT, outputs, animationChannelTargetPath, AnimationSamplerInterpolation.CUBICSPLINE, keyFrame, useQuaternion); + _GLTFAnimation._AddKeyframeValue(keyFrame, animation, outputs, animationChannelTargetPath, babylonTransformNode, useQuaternion); -// private static _GetBasePositionRotationOrScale(babylonTransformNode: Node, animationChannelTargetPath: AnimationChannelTargetPath, useQuaternion: boolean) { -// let basePositionRotationOrScale: number[]; -// if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { -// if (useQuaternion) { -// const q = (babylonTransformNode as TransformNode).rotationQuaternion; -// basePositionRotationOrScale = (q ?? Quaternion.Identity()).asArray(); -// } else { -// const r: Vector3 = (babylonTransformNode as TransformNode).rotation; -// basePositionRotationOrScale = (r ?? Vector3.Zero()).asArray(); -// } -// } else if (animationChannelTargetPath === AnimationChannelTargetPath.TRANSLATION) { -// const p: Vector3 = (babylonTransformNode as TransformNode).position; -// basePositionRotationOrScale = (p ?? Vector3.Zero()).asArray(); -// } else { -// // scale -// const s: Vector3 = (babylonTransformNode as TransformNode).scaling; -// basePositionRotationOrScale = (s ?? Vector3.One()).asArray(); -// } -// return basePositionRotationOrScale; -// } + _GLTFAnimation._AddSplineTangent(_TangentType.OUTTANGENT, outputs, animationChannelTargetPath, AnimationSamplerInterpolation.CUBICSPLINE, keyFrame, useQuaternion); + }); + } -// /** -// * Adds a key frame value -// * @param keyFrame -// * @param animation -// * @param outputs -// * @param animationChannelTargetPath -// * @param babylonTransformNode -// * @param useQuaternion -// */ -// private static _AddKeyframeValue( -// keyFrame: IAnimationKey, -// animation: Animation, -// outputs: number[][], -// animationChannelTargetPath: AnimationChannelTargetPath, -// babylonTransformNode: Node, -// useQuaternion: boolean -// ) { -// let newPositionRotationOrScale: Nullable; -// const animationType = animation.dataType; -// if (animationType === Animation.ANIMATIONTYPE_VECTOR3) { -// let value = keyFrame.value.asArray(); -// if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { -// const array = Vector3.FromArray(value); -// const rotationQuaternion = Quaternion.RotationYawPitchRoll(array.y, array.x, array.z); -// value = rotationQuaternion.asArray(); -// } -// outputs.push(value); // scale vector. -// } else if (animationType === Animation.ANIMATIONTYPE_FLOAT) { -// if (animationChannelTargetPath === AnimationChannelTargetPath.WEIGHTS) { -// outputs.push([keyFrame.value]); -// } else { -// // handles single component x, y, z or w component animation by using a base property and animating over a component. -// newPositionRotationOrScale = this._ConvertFactorToVector3OrQuaternion( -// keyFrame.value as number, -// babylonTransformNode, -// animation, -// animationChannelTargetPath, -// useQuaternion -// ); -// if (newPositionRotationOrScale) { -// if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { -// const posRotScale = useQuaternion -// ? (newPositionRotationOrScale as Quaternion) -// : Quaternion.RotationYawPitchRoll(newPositionRotationOrScale.y, newPositionRotationOrScale.x, newPositionRotationOrScale.z).normalize(); -// outputs.push(posRotScale.asArray()); -// } -// outputs.push(newPositionRotationOrScale.asArray()); -// } -// } -// } else if (animationType === Animation.ANIMATIONTYPE_QUATERNION) { -// outputs.push((keyFrame.value as Quaternion).normalize().asArray()); -// } else { -// Tools.Error("glTFAnimation: Unsupported key frame values for animation!"); -// } -// } + private static _GetBasePositionRotationOrScale(babylonTransformNode: Node, animationChannelTargetPath: AnimationChannelTargetPath, useQuaternion: boolean) { + let basePositionRotationOrScale: number[]; + if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { + if (useQuaternion) { + const q = (babylonTransformNode as TransformNode).rotationQuaternion; + basePositionRotationOrScale = (q ?? Quaternion.Identity()).asArray(); + } else { + const r: Vector3 = (babylonTransformNode as TransformNode).rotation; + basePositionRotationOrScale = (r ?? Vector3.Zero()).asArray(); + } + } else if (animationChannelTargetPath === AnimationChannelTargetPath.TRANSLATION) { + const p: Vector3 = (babylonTransformNode as TransformNode).position; + basePositionRotationOrScale = (p ?? Vector3.Zero()).asArray(); + } else { + // scale + const s: Vector3 = (babylonTransformNode as TransformNode).scaling; + basePositionRotationOrScale = (s ?? Vector3.One()).asArray(); + } + return basePositionRotationOrScale; + } -// /** -// * @internal -// * Determine the interpolation based on the key frames -// * @param keyFrames -// * @param animationChannelTargetPath -// * @param useQuaternion -// */ -// private static _DeduceInterpolation( -// keyFrames: IAnimationKey[], -// animationChannelTargetPath: AnimationChannelTargetPath, -// useQuaternion: boolean -// ): { interpolationType: AnimationSamplerInterpolation; shouldBakeAnimation: boolean } { -// let interpolationType: AnimationSamplerInterpolation | undefined; -// let shouldBakeAnimation = false; -// let key: IAnimationKey; + /** + * Adds a key frame value + * @param keyFrame + * @param animation + * @param outputs + * @param animationChannelTargetPath + * @param babylonTransformNode + * @param useQuaternion + */ + private static _AddKeyframeValue( + keyFrame: IAnimationKey, + animation: Animation, + outputs: number[][], + animationChannelTargetPath: AnimationChannelTargetPath, + babylonTransformNode: Node, + useQuaternion: boolean + ) { + let newPositionRotationOrScale: Nullable; + const animationType = animation.dataType; + if (animationType === Animation.ANIMATIONTYPE_VECTOR3) { + let value = keyFrame.value.asArray(); + if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { + const array = Vector3.FromArray(value); + const rotationQuaternion = Quaternion.RotationYawPitchRoll(array.y, array.x, array.z); + value = rotationQuaternion.asArray(); + } + outputs.push(value); // scale vector. + } else if (animationType === Animation.ANIMATIONTYPE_FLOAT) { + if (animationChannelTargetPath === AnimationChannelTargetPath.WEIGHTS) { + outputs.push([keyFrame.value]); + } else { + // handles single component x, y, z or w component animation by using a base property and animating over a component. + newPositionRotationOrScale = this._ConvertFactorToVector3OrQuaternion( + keyFrame.value as number, + babylonTransformNode, + animation, + animationChannelTargetPath, + useQuaternion + ); + if (newPositionRotationOrScale) { + if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { + const posRotScale = useQuaternion + ? (newPositionRotationOrScale as Quaternion) + : Quaternion.RotationYawPitchRoll(newPositionRotationOrScale.y, newPositionRotationOrScale.x, newPositionRotationOrScale.z).normalize(); + outputs.push(posRotScale.asArray()); + } + outputs.push(newPositionRotationOrScale.asArray()); + } + } + } else if (animationType === Animation.ANIMATIONTYPE_QUATERNION) { + outputs.push((keyFrame.value as Quaternion).normalize().asArray()); + } else { + Tools.Error("glTFAnimation: Unsupported key frame values for animation!"); + } + } -// if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION && !useQuaternion) { -// return { interpolationType: AnimationSamplerInterpolation.LINEAR, shouldBakeAnimation: true }; -// } + /** + * @internal + * Determine the interpolation based on the key frames + * @param keyFrames + * @param animationChannelTargetPath + * @param useQuaternion + */ + private static _DeduceInterpolation( + keyFrames: IAnimationKey[], + animationChannelTargetPath: AnimationChannelTargetPath, + useQuaternion: boolean + ): { interpolationType: AnimationSamplerInterpolation; shouldBakeAnimation: boolean } { + let interpolationType: AnimationSamplerInterpolation | undefined; + let shouldBakeAnimation = false; + let key: IAnimationKey; -// for (let i = 0, length = keyFrames.length; i < length; ++i) { -// key = keyFrames[i]; -// if (key.inTangent || key.outTangent) { -// if (interpolationType) { -// if (interpolationType !== AnimationSamplerInterpolation.CUBICSPLINE) { -// interpolationType = AnimationSamplerInterpolation.LINEAR; -// shouldBakeAnimation = true; -// break; -// } -// } else { -// interpolationType = AnimationSamplerInterpolation.CUBICSPLINE; -// } -// } else { -// if (interpolationType) { -// if ( -// interpolationType === AnimationSamplerInterpolation.CUBICSPLINE || -// (key.interpolation && key.interpolation === AnimationKeyInterpolation.STEP && interpolationType !== AnimationSamplerInterpolation.STEP) -// ) { -// interpolationType = AnimationSamplerInterpolation.LINEAR; -// shouldBakeAnimation = true; -// break; -// } -// } else { -// if (key.interpolation && key.interpolation === AnimationKeyInterpolation.STEP) { -// interpolationType = AnimationSamplerInterpolation.STEP; -// } else { -// interpolationType = AnimationSamplerInterpolation.LINEAR; -// } -// } -// } -// } -// if (!interpolationType) { -// interpolationType = AnimationSamplerInterpolation.LINEAR; -// } + if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION && !useQuaternion) { + return { interpolationType: AnimationSamplerInterpolation.LINEAR, shouldBakeAnimation: true }; + } -// return { interpolationType: interpolationType, shouldBakeAnimation: shouldBakeAnimation }; -// } + for (let i = 0, length = keyFrames.length; i < length; ++i) { + key = keyFrames[i]; + if (key.inTangent || key.outTangent) { + if (interpolationType) { + if (interpolationType !== AnimationSamplerInterpolation.CUBICSPLINE) { + interpolationType = AnimationSamplerInterpolation.LINEAR; + shouldBakeAnimation = true; + break; + } + } else { + interpolationType = AnimationSamplerInterpolation.CUBICSPLINE; + } + } else { + if (interpolationType) { + if ( + interpolationType === AnimationSamplerInterpolation.CUBICSPLINE || + (key.interpolation && key.interpolation === AnimationKeyInterpolation.STEP && interpolationType !== AnimationSamplerInterpolation.STEP) + ) { + interpolationType = AnimationSamplerInterpolation.LINEAR; + shouldBakeAnimation = true; + break; + } + } else { + if (key.interpolation && key.interpolation === AnimationKeyInterpolation.STEP) { + interpolationType = AnimationSamplerInterpolation.STEP; + } else { + interpolationType = AnimationSamplerInterpolation.LINEAR; + } + } + } + } + if (!interpolationType) { + interpolationType = AnimationSamplerInterpolation.LINEAR; + } -// /** -// * Adds an input tangent or output tangent to the output data -// * If an input tangent or output tangent is missing, it uses the zero vector or zero quaternion -// * @param tangentType Specifies which type of tangent to handle (inTangent or outTangent) -// * @param outputs The animation data by keyframe -// * @param animationChannelTargetPath The target animation channel -// * @param interpolation The interpolation type -// * @param keyFrame The key frame with the animation data -// * @param useQuaternion Specifies if quaternions are used -// */ -// private static _AddSplineTangent( -// tangentType: _TangentType, -// outputs: number[][], -// animationChannelTargetPath: AnimationChannelTargetPath, -// interpolation: AnimationSamplerInterpolation, -// keyFrame: IAnimationKey, -// useQuaternion: boolean -// ) { -// let tangent: number[]; -// const tangentValue: Vector3 | Quaternion | number = tangentType === _TangentType.INTANGENT ? keyFrame.inTangent : keyFrame.outTangent; -// if (interpolation === AnimationSamplerInterpolation.CUBICSPLINE) { -// if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { -// if (tangentValue) { -// if (useQuaternion) { -// tangent = (tangentValue as Quaternion).asArray(); -// } else { -// const array = tangentValue as Vector3; -// tangent = Quaternion.RotationYawPitchRoll(array.y, array.x, array.z).asArray(); -// } -// } else { -// tangent = [0, 0, 0, 0]; -// } -// } else if (animationChannelTargetPath === AnimationChannelTargetPath.WEIGHTS) { -// if (tangentValue) { -// tangent = [tangentValue as number]; -// } else { -// tangent = [0]; -// } -// } else { -// if (tangentValue) { -// tangent = (tangentValue as Vector3).asArray(); -// } else { -// tangent = [0, 0, 0]; -// } -// } + return { interpolationType: interpolationType, shouldBakeAnimation: shouldBakeAnimation }; + } -// outputs.push(tangent); -// } -// } + /** + * Adds an input tangent or output tangent to the output data + * If an input tangent or output tangent is missing, it uses the zero vector or zero quaternion + * @param tangentType Specifies which type of tangent to handle (inTangent or outTangent) + * @param outputs The animation data by keyframe + * @param animationChannelTargetPath The target animation channel + * @param interpolation The interpolation type + * @param keyFrame The key frame with the animation data + * @param useQuaternion Specifies if quaternions are used + */ + private static _AddSplineTangent( + tangentType: _TangentType, + outputs: number[][], + animationChannelTargetPath: AnimationChannelTargetPath, + interpolation: AnimationSamplerInterpolation, + keyFrame: IAnimationKey, + useQuaternion: boolean + ) { + let tangent: number[]; + const tangentValue: Vector3 | Quaternion | number = tangentType === _TangentType.INTANGENT ? keyFrame.inTangent : keyFrame.outTangent; + if (interpolation === AnimationSamplerInterpolation.CUBICSPLINE) { + if (animationChannelTargetPath === AnimationChannelTargetPath.ROTATION) { + if (tangentValue) { + if (useQuaternion) { + tangent = (tangentValue as Quaternion).asArray(); + } else { + const array = tangentValue as Vector3; + tangent = Quaternion.RotationYawPitchRoll(array.y, array.x, array.z).asArray(); + } + } else { + tangent = [0, 0, 0, 0]; + } + } else if (animationChannelTargetPath === AnimationChannelTargetPath.WEIGHTS) { + if (tangentValue) { + tangent = [tangentValue as number]; + } else { + tangent = [0]; + } + } else { + if (tangentValue) { + tangent = (tangentValue as Vector3).asArray(); + } else { + tangent = [0, 0, 0]; + } + } -// /** -// * Get the minimum and maximum key frames' frame values -// * @param keyFrames animation key frames -// * @returns the minimum and maximum key frame value -// */ -// private static _CalculateMinMaxKeyFrames(keyFrames: IAnimationKey[]): { min: number; max: number } { -// let min: number = Infinity; -// let max: number = -Infinity; -// keyFrames.forEach(function (keyFrame) { -// min = Math.min(min, keyFrame.frame); -// max = Math.max(max, keyFrame.frame); -// }); + outputs.push(tangent); + } + } -// return { min: min, max: max }; -// } -// } + /** + * Get the minimum and maximum key frames' frame values + * @param keyFrames animation key frames + * @returns the minimum and maximum key frame value + */ + private static _CalculateMinMaxKeyFrames(keyFrames: IAnimationKey[]): { min: number; max: number } { + let min: number = Infinity; + let max: number = -Infinity; + keyFrames.forEach(function (keyFrame) { + min = Math.min(min, keyFrame.frame); + max = Math.max(max, keyFrame.frame); + }); + + return { min: min, max: max }; + } +} diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 3cf20eae4d7..2abb68da15c 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -64,6 +64,7 @@ import { MultiMaterial, PBRMaterial, StandardMaterial } from "core/Materials"; import { Logger } from "core/Misc/logger"; import { enumerateFloatValues } from "core/Buffers/bufferUtils"; import type { Bone, Skeleton } from "core/Bones"; +import { _GLTFAnimation } from "./glTFAnimation"; // 180 degrees rotation in Y. const rotation180Y = new Quaternion(0, 1, 0, 0); @@ -199,7 +200,7 @@ export class GLTFExporter { // /** // * Baked animation sample rate // */ - // private _animationSampleRate: number; + private _animationSampleRate: number; private readonly _options: Required; @@ -1529,6 +1530,42 @@ export class GLTFExporter { } } + const runtimeGLTFAnimation: IAnimation = { + name: "runtime animations", + channels: [], + samplers: [], + }; + const idleGLTFAnimations: IAnimation[] = []; + + if (!this._babylonScene.animationGroups.length) { + _GLTFAnimation._CreateMorphTargetAnimationFromMorphTargetAnimations( + babylonNode, + runtimeGLTFAnimation, + idleGLTFAnimations, + this._nodeMap, + this._nodes, + this._dataWriter, + this._bufferViews, + this._accessors, + this._animationSampleRate, + this._options.shouldExportAnimation + ); + if (babylonNode.animations.length) { + _GLTFAnimation._CreateNodeAnimationFromNodeAnimations( + babylonNode, + runtimeGLTFAnimation, + idleGLTFAnimations, + this._nodeMap, + this._nodes, + this._dataWriter, + this._bufferViews, + this._accessors, + this._animationSampleRate, + this._options.shouldExportAnimation + ); + } + } + return nodeIndex; } From 0d7a705e2ed3c1ec57b34633810e6599d1b5f4c3 Mon Sep 17 00:00:00 2001 From: --local <--local> Date: Wed, 16 Oct 2024 18:15:49 -0300 Subject: [PATCH 012/133] Added animation support --- .../dev/serializers/src/glTF/2.0/glTFExporter.ts | 13 +++++++++++++ 1 file changed, 13 insertions(+) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 2abb68da15c..9f3fe36426a 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1245,6 +1245,19 @@ export class GLTFExporter { this._exportAndAssignCameras(); this._exportAndAssignSkeletons(); + if (this._babylonScene.animationGroups.length) { + _GLTFAnimation._CreateNodeAndMorphAnimationFromAnimationGroups( + this._babylonScene, + this._animations, + this._nodeMap, + this._dataWriter, + this._bufferViews, + this._accessors, + this._animationSampleRate, + this._options.shouldExportAnimation + ); + } + // return this._exportNodesAndAnimationsAsync(nodes, convertToRightHandedMap, dataWriter).then((nodeMap) => { // return this._createSkinsAsync(nodeMap, dataWriter).then((skinMap) => { // for (const babylonNode of nodes) { From 1cc8964f882486992b5788dba21ee42a9a66f38e Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 16 Oct 2024 18:20:38 -0300 Subject: [PATCH 013/133] Changed triangle flipping to XOR operator --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 9f3fe36426a..1d98d36b969 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1600,7 +1600,7 @@ export class GLTFExporter { // Flip if triangle winding order is not CCW as glTF is always CCW. const flip = isTriangleFillMode(fillMode) && sideOrientation !== Material.CounterClockWiseSideOrientation; - if (flip && convertToRightHanded) { + if ((flip && !convertToRightHanded) || (!flip && convertToRightHanded)) { if (fillMode === Material.TriangleStripDrawMode || fillMode === Material.TriangleFanDrawMode) { throw new Error("Triangle strip/fan fill mode is not implemented"); } From 6d45c5426e635fd0b3f9bdeb7696d982ec12d360 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 16 Oct 2024 18:42:13 -0300 Subject: [PATCH 014/133] Added initial convertion to right hand mode for animation --- .../serializers/src/glTF/2.0/glTFAnimation.ts | 38 +++++++++++++++---- .../serializers/src/glTF/2.0/glTFExporter.ts | 4 +- 2 files changed, 34 insertions(+), 8 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts index d0888c188a6..eed4aa4eea8 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts @@ -239,6 +239,7 @@ export class _GLTFAnimation { bufferViews: IBufferView[], accessors: IAccessor[], animationSampleRate: number, + useRightHanded: boolean, shouldExportAnimation?: (animation: Animation) => boolean ) { let glTFAnimation: IAnimation; @@ -267,7 +268,8 @@ export class _GLTFAnimation { bufferViews, accessors, animationInfo.useQuaternion, - animationSampleRate + animationSampleRate, + useRightHanded ); if (glTFAnimation.samplers.length && glTFAnimation.channels.length) { idleGLTFAnimations.push(glTFAnimation); @@ -301,6 +303,7 @@ export class _GLTFAnimation { bufferViews: IBufferView[], accessors: IAccessor[], animationSampleRate: number, + useRightHanded: boolean, shouldExportAnimation?: (animation: Animation) => boolean ) { let glTFAnimation: IAnimation; @@ -355,6 +358,7 @@ export class _GLTFAnimation { accessors, animationInfo.useQuaternion, animationSampleRate, + useRightHanded, morphTargetManager.numTargets ); if (glTFAnimation.samplers.length && glTFAnimation.channels.length) { @@ -387,6 +391,7 @@ export class _GLTFAnimation { bufferViews: IBufferView[], accessors: IAccessor[], animationSampleRate: number, + useRightHanded: boolean, shouldExportAnimation?: (animation: Animation) => boolean ) { let glTFAnimation: IAnimation; @@ -426,7 +431,8 @@ export class _GLTFAnimation { bufferViews, accessors, animationInfo.useQuaternion, - animationSampleRate + animationSampleRate, + useRightHanded ); } } @@ -523,6 +529,7 @@ export class _GLTFAnimation { accessors, animationInfo.useQuaternion, animationSampleRate, + useRightHanded, morphTargetManager?.numTargets ); } @@ -547,6 +554,7 @@ export class _GLTFAnimation { accessors: IAccessor[], useQuaternion: boolean, animationSampleRate: number, + useRightHanded: boolean, morphAnimationChannels?: number ) { const animationData = _GLTFAnimation._CreateNodeAnimation(babylonTransformNode, animation, animationChannelTargetPath, useQuaternion, animationSampleRate); @@ -604,17 +612,33 @@ export class _GLTFAnimation { // check for in and out tangents bufferView = createBufferView(0, binaryWriter.byteOffset, byteLength); - //bufferView = _GLTFUtilities._CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, `${name} data view`); bufferViews.push(bufferView); animationData.outputs.forEach(function (output) { - output.forEach(function (entry) { - binaryWriter.writeFloat32(entry); - }); + if (useRightHanded) { + switch (animationChannelTargetPath) { + case AnimationChannelTargetPath.TRANSLATION: + binaryWriter.writeFloat32(-output[0]); + binaryWriter.writeFloat32(output[1]); + binaryWriter.writeFloat32(output[2]); + break; + + default: + output.forEach(function (entry) { + binaryWriter.writeFloat32(entry); + }); + break; + } + } else { + output.forEach(function (entry) { + binaryWriter.writeFloat32(entry); + }); + } }); + //TODO: Handle right hand vs left hand here. + accessor = createAccessor(bufferViews.length - 1, dataAccessorType, AccessorComponentType.FLOAT, outputLength, null); - //accessor = _GLTFUtilities._CreateAccessor(bufferViews.length - 1, `${name} data`, dataAccessorType, AccessorComponentType.FLOAT, outputLength, null, null, null); accessors.push(accessor); dataAccessorIndex = accessors.length - 1; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 1d98d36b969..8285022ef88 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1254,7 +1254,7 @@ export class GLTFExporter { this._bufferViews, this._accessors, this._animationSampleRate, - this._options.shouldExportAnimation + this._babylonScene.useRightHandedSystem ); } @@ -1561,6 +1561,7 @@ export class GLTFExporter { this._bufferViews, this._accessors, this._animationSampleRate, + convertToRightHanded, this._options.shouldExportAnimation ); if (babylonNode.animations.length) { @@ -1574,6 +1575,7 @@ export class GLTFExporter { this._bufferViews, this._accessors, this._animationSampleRate, + convertToRightHanded, this._options.shouldExportAnimation ); } From d92181c8ccd51b769348439b357ed82f1d4a5239 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Fri, 18 Oct 2024 15:47:38 -0300 Subject: [PATCH 015/133] Added right hand transformation for rotation animation --- .../serializers/src/glTF/2.0/glTFAnimation.ts | 17 +++++++++++++++-- 1 file changed, 15 insertions(+), 2 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts index eed4aa4eea8..38549f211ed 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts @@ -16,7 +16,7 @@ import { AnimationKeyInterpolation } from "core/Animations/animationKey"; import { Camera } from "core/Cameras/camera"; import { Light } from "core/Lights/light"; import type { DataWriter } from "./dataWriter"; -import { createAccessor, createBufferView, getAccessorElementCount } from "./glTFUtilities"; +import { createAccessor, createBufferView, getAccessorElementCount, convertToRightHandedRotation } from "./glTFUtilities"; /** * @internal @@ -592,7 +592,6 @@ export class _GLTFAnimation { let byteLength = animationData.inputs.length * 4; const offset = binaryWriter.byteOffset; bufferView = createBufferView(0, offset, byteLength); - //bufferView = _GLTFUtilities._CreateBufferView(0, binaryWriter.getByteOffset(), byteLength, undefined, `${name} keyframe data view`); bufferViews.push(bufferView); animationData.inputs.forEach(function (input) { binaryWriter.writeFloat32(input); @@ -614,15 +613,29 @@ export class _GLTFAnimation { bufferView = createBufferView(0, binaryWriter.byteOffset, byteLength); bufferViews.push(bufferView); + const rotationQuaternion = new Quaternion(); + const tempQuaterionArray = [0, 0, 0, 0]; + animationData.outputs.forEach(function (output) { if (useRightHanded) { switch (animationChannelTargetPath) { case AnimationChannelTargetPath.TRANSLATION: + case AnimationChannelTargetPath.SCALE: binaryWriter.writeFloat32(-output[0]); binaryWriter.writeFloat32(output[1]); binaryWriter.writeFloat32(output[2]); break; + case AnimationChannelTargetPath.ROTATION: + Quaternion.FromArrayToRef(output, 0, rotationQuaternion); + convertToRightHandedRotation(rotationQuaternion); + rotationQuaternion.normalize().toArray(tempQuaterionArray); + binaryWriter.writeFloat32(tempQuaterionArray[0]); + binaryWriter.writeFloat32(tempQuaterionArray[1]); + binaryWriter.writeFloat32(tempQuaterionArray[2]); + binaryWriter.writeFloat32(tempQuaterionArray[2]); + break; + default: output.forEach(function (entry) { binaryWriter.writeFloat32(entry); From b6658d9a0a1867d993941bf96d2a2825dfd27d0c Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 23 Oct 2024 17:38:19 -0300 Subject: [PATCH 016/133] Got morph targets to work --- .../serializers/src/glTF/2.0/glTFExporter.ts | 85 ++++++++++++++----- .../src/glTF/2.0/glTFMorphTargetsUtilities.ts | 82 ++++++++++++++++++ 2 files changed, 146 insertions(+), 21 deletions(-) create mode 100644 packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 8285022ef88..fdff72da107 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -65,6 +65,9 @@ import { Logger } from "core/Misc/logger"; import { enumerateFloatValues } from "core/Buffers/bufferUtils"; import type { Bone, Skeleton } from "core/Bones"; import { _GLTFAnimation } from "./glTFAnimation"; +import type { MorphTarget } from "core/Morph"; +import { buildMorphTargetBuffers } from "./glTFMorphTargetsUtilities"; +import type { GlTFMorphTarget } from "./glTFMorphTargetsUtilities"; // 180 degrees rotation in Y. const rotation180Y = new Quaternion(0, 1, 0, 0); @@ -81,6 +84,8 @@ class ExporterState { private _remappedBufferView = new Map>(); + private _meshMorphTargetMap = new Map(); + // Babylon mesh -> glTF mesh index private _meshMap = new Map(); @@ -172,6 +177,18 @@ class ExporterState { public setMesh(mesh: Mesh, meshIndex: number): void { this._meshMap.set(mesh, meshIndex); } + + public bindMorphDataToMesh(mesh: Mesh, morphData: GlTFMorphTarget) { + const morphTargets = this._meshMorphTargetMap.get(mesh) || []; + this._meshMorphTargetMap.set(mesh, morphTargets); + if (morphTargets.indexOf(morphData) === -1) { + morphTargets.push(morphData); + } + } + + public getMorphTargetsFromMesh(mesh: Mesh): GlTFMorphTarget[] | undefined { + return this._meshMorphTargetMap.get(mesh); + } } /** @internal */ @@ -1336,7 +1353,12 @@ export class GLTFExporter { return nodes; } - private _collectBuffers(babylonNode: Node, bufferToVertexBuffersMap: Map, vertexBufferToMeshesMap: Map): void { + private _collectBuffers( + babylonNode: Node, + bufferToVertexBuffersMap: Map, + vertexBufferToMeshesMap: Map, + morphTargetsToMeshesMap: Map + ): void { if (!this._shouldExportNode(babylonNode)) { return; } @@ -1361,19 +1383,34 @@ export class GLTFExporter { } } } + + const morphTargetManager = babylonNode.morphTargetManager; + + if (morphTargetManager) { + for (let morphIndex = 0; morphIndex < morphTargetManager.numTargets; morphIndex++) { + const morphTarget = morphTargetManager.getTarget(morphIndex); + + const meshes = morphTargetsToMeshesMap.get(morphTarget) || []; + morphTargetsToMeshesMap.set(morphTarget, meshes); + if (meshes.indexOf(babylonNode) === -1) { + meshes.push(babylonNode); + } + } + } } for (const babylonChildNode of babylonNode.getChildren()) { - this._collectBuffers(babylonChildNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap); + this._collectBuffers(babylonChildNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap, morphTargetsToMeshesMap); } } private _exportBuffers(babylonRootNodes: Node[], convertToRightHanded: boolean, state: ExporterState): void { const bufferToVertexBuffersMap = new Map(); const vertexBufferToMeshesMap = new Map(); + const morphTagetsMeshesMap = new Map(); for (const babylonNode of babylonRootNodes) { - this._collectBuffers(babylonNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap); + this._collectBuffers(babylonNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap, morphTagetsMeshesMap); } for (const [buffer, vertexBuffers] of bufferToVertexBuffersMap) { @@ -1481,6 +1518,14 @@ export class GLTFExporter { state.setRemappedBufferView(buffer, vertexBuffer, this._bufferViews.length - 1); } } + + for (const [morphTarget, meshes] of morphTagetsMeshesMap) { + const glTFMorphTarget = buildMorphTargetBuffers(morphTarget, this._dataWriter, this._bufferViews, this._accessors, convertToRightHanded); + + for (const mesh of meshes) { + state.bindMorphDataToMesh(mesh, glTFMorphTarget); + } + } } private async _exportNodeAsync(babylonNode: Node, state: ExporterState, convertToRightHanded: boolean): Promise { @@ -1742,6 +1787,7 @@ export class GLTFExporter { const indices = babylonMesh.isUnIndexed ? null : babylonMesh.getIndices(); const vertexBuffers = babylonMesh.geometry?.getVertexBuffers(); + const morphTargets = state.getMorphTargetsFromMesh(babylonMesh); const subMeshes = babylonMesh.subMeshes; if (vertexBuffers && subMeshes && subMeshes.length > 0) { @@ -1763,27 +1809,24 @@ export class GLTFExporter { } mesh.primitives.push(primitive); + + if (morphTargets) { + primitive.targets = []; + for (const gltfMorphTarget of morphTargets) { + primitive.targets.push(gltfMorphTarget.attributes); + } + } } } - // TO DO: Add support for morph targets. - // const morphTargetManager = babylonMesh.morphTargetManager; - // if (morphTargetManager) { - // // By convention, morph target names are stored in the mesh extras. - // if (!mesh.extras) { - // mesh.extras = {}; - // } - // mesh.extras.targetNames = []; - - // for (let i = 0; i < morphTargetManager.numTargets; ++i) { - // const target = morphTargetManager.getTarget(i); - // this._setMorphTargetAttributes(submesh, meshPrimitive, target, dataWriter); - // mesh.extras.targetNames.push(target.name); - // } - // } - - // TODO: handle morph targets - // TODO: handle skeleton + if (morphTargets) { + mesh.weights = []; + mesh.extras.targetNames = []; + for (const gltfMorphTarget of morphTargets) { + mesh.weights.push(gltfMorphTarget.influence); + mesh.extras.targetNames.push(gltfMorphTarget.name); + } + } return meshIndex; } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts new file mode 100644 index 00000000000..971f4745bff --- /dev/null +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -0,0 +1,82 @@ +import type { IBufferView, IAccessor } from "babylonjs-gltf2interface"; +import { AccessorComponentType, AccessorType } from "babylonjs-gltf2interface"; +import type { MorphTarget } from "core/Morph/morphTarget"; +import type { DataWriter } from "./dataWriter"; + +import { createAccessor, createBufferView } from "./glTFUtilities"; + +/** + * Temporary structure to store morph target information. + */ +export class GlTFMorphTarget { + public attributes: { [name: string]: number }; + public influence: number; + public name: string; +} + +export function buildMorphTargetBuffers( + morphTarget: MorphTarget, + dataWriter: DataWriter, + bufferViews: IBufferView[], + accessors: IAccessor[], + convertToRightHanded: boolean +): GlTFMorphTarget { + const result = new GlTFMorphTarget(); + result.influence = morphTarget.influence; + result.name = morphTarget.name; + + const flipX = convertToRightHanded ? -1 : 1; + + if (morphTarget.hasPositions) { + const morphPositions = morphTarget.getPositions()!; + + const byteOffset = dataWriter.byteOffset; + + for (let index = 0; index < morphPositions.length; index += 3) { + dataWriter.writeFloat32(morphPositions[index] * flipX); + dataWriter.writeFloat32(morphPositions[index]); + dataWriter.writeFloat32(morphPositions[index]); + } + + bufferViews.push(createBufferView(0, byteOffset, morphPositions.length * 4, 4 * 3)); + const bufferViewIndex = bufferViews.length - 1; + accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphPositions.length / 3, 0)); + result.attributes.POSITION = accessors.length - 1; + } + + if (morphTarget.hasTangents) { + const morphTangents = morphTarget.getTangents()!; + + const byteOffset = dataWriter.byteOffset; + + for (let index = 0; index < morphTangents.length; index += 3) { + dataWriter.writeFloat32(morphTangents[index] * flipX); + dataWriter.writeFloat32(morphTangents[index]); + dataWriter.writeFloat32(morphTangents[index]); + } + + bufferViews.push(createBufferView(0, byteOffset, morphTangents.length * 4, 4 * 3)); + const bufferViewIndex = bufferViews.length - 1; + accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphTangents.length / 3, 0)); + result.attributes.TANGENT = accessors.length - 1; + } + + if (morphTarget.hasNormals) { + const morphNormals = morphTarget.getNormals()!; + + const byteOffset = dataWriter.byteOffset; + + for (let index = 0; index < morphNormals.length; index += 3) { + dataWriter.writeFloat32(morphNormals[index] * flipX); + dataWriter.writeFloat32(morphNormals[index]); + dataWriter.writeFloat32(morphNormals[index]); + } + + bufferViews.push(createBufferView(0, byteOffset, morphNormals.length * 4, 4 * 3)); + const bufferViewIndex = bufferViews.length - 1; + accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphNormals.length / 3, 0)); + result.attributes.NORMAL = accessors.length - 1; + } + + return result; +} From 3f5f480104603f9a77a63ca99a975c66bd706f22 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 23 Oct 2024 19:05:11 -0300 Subject: [PATCH 017/133] Got morph to export with no errors --- .../serializers/src/glTF/2.0/glTFExporter.ts | 30 ++++++++--- .../src/glTF/2.0/glTFMorphTargetsUtilities.ts | 53 +++++++++++++------ .../serializers/src/glTF/2.0/glTFUtilities.ts | 7 ++- 3 files changed, 66 insertions(+), 24 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index fdff72da107..d8f54989863 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -86,6 +86,8 @@ class ExporterState { private _meshMorphTargetMap = new Map(); + private _vertexMapColorAlpha = new Map(); + // Babylon mesh -> glTF mesh index private _meshMap = new Map(); @@ -170,6 +172,14 @@ class ExporterState { map2.set(count, accessorIndex); } + public hasVertexColorAlpha(vertexBuffer: VertexBuffer): boolean { + return this._vertexMapColorAlpha.get(vertexBuffer) || false; + } + + public setHasVertexColorAlpha(vertexBuffer: VertexBuffer, hasAlpha: boolean) { + return this._vertexMapColorAlpha.set(vertexBuffer, hasAlpha); + } + public getMesh(mesh: Mesh): number | undefined { return this._meshMap.get(mesh); } @@ -1357,7 +1367,8 @@ export class GLTFExporter { babylonNode: Node, bufferToVertexBuffersMap: Map, vertexBufferToMeshesMap: Map, - morphTargetsToMeshesMap: Map + morphTargetsToMeshesMap: Map, + state: ExporterState ): void { if (!this._shouldExportNode(babylonNode)) { return; @@ -1368,7 +1379,7 @@ export class GLTFExporter { if (vertexBuffers) { for (const kind in vertexBuffers) { const vertexBuffer = vertexBuffers[kind]; - + state.setHasVertexColorAlpha(vertexBuffer, babylonNode.hasVertexAlpha); const buffer = vertexBuffer._buffer; const vertexBufferArray = bufferToVertexBuffersMap.get(buffer) || []; bufferToVertexBuffersMap.set(buffer, vertexBufferArray); @@ -1400,7 +1411,7 @@ export class GLTFExporter { } for (const babylonChildNode of babylonNode.getChildren()) { - this._collectBuffers(babylonChildNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap, morphTargetsToMeshesMap); + this._collectBuffers(babylonChildNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap, morphTargetsToMeshesMap, state); } } @@ -1410,7 +1421,7 @@ export class GLTFExporter { const morphTagetsMeshesMap = new Map(); for (const babylonNode of babylonRootNodes) { - this._collectBuffers(babylonNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap, morphTagetsMeshesMap); + this._collectBuffers(babylonNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap, morphTagetsMeshesMap, state); } for (const [buffer, vertexBuffers] of bufferToVertexBuffersMap) { @@ -1718,7 +1729,9 @@ export class GLTFExporter { const bufferViewIndex = state.getRemappedBufferView(vertexBuffer._buffer, vertexBuffer); if (bufferViewIndex !== undefined) { const byteOffset = vertexBuffer.byteOffset + start * vertexBuffer.byteStride; - this._accessors.push(createAccessor(bufferViewIndex, getAccessorType(kind), VertexBuffer.UNSIGNED_BYTE, count, byteOffset, minMax)); + this._accessors.push( + createAccessor(bufferViewIndex, getAccessorType(kind, state.hasVertexColorAlpha(vertexBuffer)), VertexBuffer.UNSIGNED_BYTE, count, byteOffset, minMax) + ); accessorIndex = this._accessors.length - 1; state.setVertexAccessor(vertexBuffer, start, count, accessorIndex); primitive.attributes[getAttributeType(kind)] = accessorIndex; @@ -1726,7 +1739,7 @@ export class GLTFExporter { } else { const bufferViewIndex = state.getVertexBufferView(vertexBuffer._buffer)!; const byteOffset = vertexBuffer.byteOffset + start * vertexBuffer.byteStride; - this._accessors.push(createAccessor(bufferViewIndex, getAccessorType(kind), vertexBuffer.type, count, byteOffset, minMax)); + this._accessors.push(createAccessor(bufferViewIndex, getAccessorType(kind, state.hasVertexColorAlpha(vertexBuffer)), vertexBuffer.type, count, byteOffset, minMax)); accessorIndex = this._accessors.length - 1; state.setVertexAccessor(vertexBuffer, start, count, accessorIndex); primitive.attributes[getAttributeType(kind)] = accessorIndex; @@ -1821,7 +1834,12 @@ export class GLTFExporter { if (morphTargets) { mesh.weights = []; + + if (!mesh.extras) { + mesh.extras = {}; + } mesh.extras.targetNames = []; + for (const gltfMorphTarget of morphTargets) { mesh.weights.push(gltfMorphTarget.influence); mesh.extras.targetNames.push(gltfMorphTarget.name); diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts index 971f4745bff..8cf5cf4ec26 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -14,6 +14,7 @@ export class GlTFMorphTarget { public name: string; } +// TO DO: Convert Babylon morph (absolute) to GLTF (deltas) export function buildMorphTargetBuffers( morphTarget: MorphTarget, dataWriter: DataWriter, @@ -24,24 +25,42 @@ export function buildMorphTargetBuffers( const result = new GlTFMorphTarget(); result.influence = morphTarget.influence; result.name = morphTarget.name; + result.attributes = {}; const flipX = convertToRightHanded ? -1 : 1; + const floatSize = 4; if (morphTarget.hasPositions) { const morphPositions = morphTarget.getPositions()!; const byteOffset = dataWriter.byteOffset; + const min = new Array(3).fill(Infinity); + const max = new Array(3).fill(-Infinity); + for (let index = 0; index < morphPositions.length; index += 3) { - dataWriter.writeFloat32(morphPositions[index] * flipX); - dataWriter.writeFloat32(morphPositions[index]); - dataWriter.writeFloat32(morphPositions[index]); + const x = morphPositions[index] * flipX; + const y = morphPositions[index + 1]; + const z = morphPositions[index + 2]; + + min[0] = Math.min(min[0], x); + max[0] = Math.max(max[0], x); + + min[1] = Math.min(min[1], y); + max[1] = Math.max(max[1], y); + + min[2] = Math.min(min[2], z); + max[2] = Math.max(max[2], z); + + dataWriter.writeFloat32(x); + dataWriter.writeFloat32(y); + dataWriter.writeFloat32(z); } - bufferViews.push(createBufferView(0, byteOffset, morphPositions.length * 4, 4 * 3)); + bufferViews.push(createBufferView(0, byteOffset, morphPositions.length * floatSize, floatSize * 3)); const bufferViewIndex = bufferViews.length - 1; - accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphPositions.length / 3, 0)); - result.attributes.POSITION = accessors.length - 1; + accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphPositions.length / 3, 0, { min, max })); + result.attributes["POSITION"] = accessors.length - 1; } if (morphTarget.hasTangents) { @@ -49,16 +68,18 @@ export function buildMorphTargetBuffers( const byteOffset = dataWriter.byteOffset; - for (let index = 0; index < morphTangents.length; index += 3) { + for (let index = 0; index < morphTangents.length; index += 4) { dataWriter.writeFloat32(morphTangents[index] * flipX); - dataWriter.writeFloat32(morphTangents[index]); - dataWriter.writeFloat32(morphTangents[index]); + dataWriter.writeFloat32(morphTangents[index + 1]); + dataWriter.writeFloat32(morphTangents[index + 2]); } - bufferViews.push(createBufferView(0, byteOffset, morphTangents.length * 4, 4 * 3)); + const numberOfTangents = morphTangents.length / 4; + + bufferViews.push(createBufferView(0, byteOffset, numberOfTangents * floatSize * 3, floatSize * 3)); const bufferViewIndex = bufferViews.length - 1; - accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphTangents.length / 3, 0)); - result.attributes.TANGENT = accessors.length - 1; + accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, numberOfTangents, 0)); + result.attributes["TANGENT"] = accessors.length - 1; } if (morphTarget.hasNormals) { @@ -68,14 +89,14 @@ export function buildMorphTargetBuffers( for (let index = 0; index < morphNormals.length; index += 3) { dataWriter.writeFloat32(morphNormals[index] * flipX); - dataWriter.writeFloat32(morphNormals[index]); - dataWriter.writeFloat32(morphNormals[index]); + dataWriter.writeFloat32(morphNormals[index + 1]); + dataWriter.writeFloat32(morphNormals[index + 2]); } - bufferViews.push(createBufferView(0, byteOffset, morphNormals.length * 4, 4 * 3)); + bufferViews.push(createBufferView(0, byteOffset, morphNormals.length * floatSize, floatSize * 3)); const bufferViewIndex = bufferViews.length - 1; accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphNormals.length / 3, 0)); - result.attributes.NORMAL = accessors.length - 1; + result.attributes["NORMAL"] = accessors.length - 1; } return result; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index b3b049b4d65..038f90e9368 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -90,12 +90,15 @@ export function getAccessorElementCount(accessorType: AccessorType): number { } } -export function getAccessorType(kind: string): AccessorType { +export function getAccessorType(kind: string, hasVertexColorAlpha: boolean): AccessorType { + if (kind == VertexBuffer.ColorKind) { + return hasVertexColorAlpha ? AccessorType.VEC4 : AccessorType.VEC3; + } + switch (kind) { case VertexBuffer.PositionKind: case VertexBuffer.NormalKind: return AccessorType.VEC3; - case VertexBuffer.ColorKind: case VertexBuffer.TangentKind: case VertexBuffer.MatricesIndicesKind: case VertexBuffer.MatricesIndicesExtraKind: From c8b913c3dc7b81e6bf48d268f12021c85c441ef2 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Thu, 24 Oct 2024 10:49:32 -0300 Subject: [PATCH 018/133] Solved handyness problem --- .../serializers/src/glTF/2.0/glTFExporter.ts | 4 +- .../src/glTF/2.0/glTFMorphTargetsUtilities.ts | 134 ++++++++++++------ 2 files changed, 93 insertions(+), 45 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index d8f54989863..37278ff9bfa 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1531,7 +1531,7 @@ export class GLTFExporter { } for (const [morphTarget, meshes] of morphTagetsMeshesMap) { - const glTFMorphTarget = buildMorphTargetBuffers(morphTarget, this._dataWriter, this._bufferViews, this._accessors, convertToRightHanded); + const glTFMorphTarget = buildMorphTargetBuffers(morphTarget, meshes[0], this._dataWriter, this._bufferViews, this._accessors, convertToRightHanded); for (const mesh of meshes) { state.bindMorphDataToMesh(mesh, glTFMorphTarget); @@ -1658,7 +1658,7 @@ export class GLTFExporter { // Flip if triangle winding order is not CCW as glTF is always CCW. const flip = isTriangleFillMode(fillMode) && sideOrientation !== Material.CounterClockWiseSideOrientation; - if ((flip && !convertToRightHanded) || (!flip && convertToRightHanded)) { + if (flip && convertToRightHanded) { if (fillMode === Material.TriangleStripDrawMode || fillMode === Material.TriangleFanDrawMode) { throw new Error("Triangle strip/fan fill mode is not implemented"); } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts index 8cf5cf4ec26..dd0f33a2123 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -4,19 +4,42 @@ import type { MorphTarget } from "core/Morph/morphTarget"; import type { DataWriter } from "./dataWriter"; import { createAccessor, createBufferView } from "./glTFUtilities"; +import type { Mesh } from "core/Meshes"; +import { VertexBuffer } from "core/Buffers"; +import { Vector3 } from "core/Maths"; +import type { Vector4 } from "core/Maths"; /** * Temporary structure to store morph target information. */ export class GlTFMorphTarget { + /** + * + */ public attributes: { [name: string]: number }; + /** + * + */ public influence: number; + /** + * + */ public name: string; } +function _NormalizeTangentFromRef(tangent: Vector4 | Vector3) { + const length = Math.sqrt(tangent.x * tangent.x + tangent.y * tangent.y + tangent.z * tangent.z); + if (length > 0) { + tangent.x /= length; + tangent.y /= length; + tangent.z /= length; + } +} + // TO DO: Convert Babylon morph (absolute) to GLTF (deltas) export function buildMorphTargetBuffers( morphTarget: MorphTarget, + mesh: Mesh, dataWriter: DataWriter, bufferViews: IBufferView[], accessors: IAccessor[], @@ -29,68 +52,66 @@ export function buildMorphTargetBuffers( const flipX = convertToRightHanded ? -1 : 1; const floatSize = 4; + const difference = Vector3.Zero(); if (morphTarget.hasPositions) { const morphPositions = morphTarget.getPositions()!; + const meshPositions = mesh.getPositionData(); - const byteOffset = dataWriter.byteOffset; + if (meshPositions) { + const byteOffset = dataWriter.byteOffset; - const min = new Array(3).fill(Infinity); - const max = new Array(3).fill(-Infinity); + const min = new Array(3).fill(Infinity); + const max = new Array(3).fill(-Infinity); - for (let index = 0; index < morphPositions.length; index += 3) { - const x = morphPositions[index] * flipX; - const y = morphPositions[index + 1]; - const z = morphPositions[index + 2]; + for (let index = 0; index < morphPositions.length; index += 3) { + const morphX = morphPositions[index]; + const morphY = morphPositions[index + 1]; + const morphZ = morphPositions[index + 2]; - min[0] = Math.min(min[0], x); - max[0] = Math.max(max[0], x); + const meshX = meshPositions[index]; + const meshY = meshPositions[index + 1]; + const meshZ = meshPositions[index + 2]; - min[1] = Math.min(min[1], y); - max[1] = Math.max(max[1], y); + const deltaX = (morphX - meshX) * flipX; + const deltaY = morphY - meshY; + const deltaZ = morphZ - meshZ; - min[2] = Math.min(min[2], z); - max[2] = Math.max(max[2], z); + min[0] = Math.min(min[0], deltaX); + max[0] = Math.max(max[0], deltaX); - dataWriter.writeFloat32(x); - dataWriter.writeFloat32(y); - dataWriter.writeFloat32(z); - } - - bufferViews.push(createBufferView(0, byteOffset, morphPositions.length * floatSize, floatSize * 3)); - const bufferViewIndex = bufferViews.length - 1; - accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphPositions.length / 3, 0, { min, max })); - result.attributes["POSITION"] = accessors.length - 1; - } + min[1] = Math.min(min[1], deltaY); + max[1] = Math.max(max[1], deltaY); - if (morphTarget.hasTangents) { - const morphTangents = morphTarget.getTangents()!; + min[2] = Math.min(min[2], deltaZ); + max[2] = Math.max(max[2], deltaZ); - const byteOffset = dataWriter.byteOffset; + dataWriter.writeFloat32(deltaX); + dataWriter.writeFloat32(deltaY); + dataWriter.writeFloat32(deltaZ); + } - for (let index = 0; index < morphTangents.length; index += 4) { - dataWriter.writeFloat32(morphTangents[index] * flipX); - dataWriter.writeFloat32(morphTangents[index + 1]); - dataWriter.writeFloat32(morphTangents[index + 2]); + bufferViews.push(createBufferView(0, byteOffset, morphPositions.length * floatSize, floatSize * 3)); + const bufferViewIndex = bufferViews.length - 1; + accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphPositions.length / 3, 0, { min, max })); + result.attributes["POSITION"] = accessors.length - 1; } - - const numberOfTangents = morphTangents.length / 4; - - bufferViews.push(createBufferView(0, byteOffset, numberOfTangents * floatSize * 3, floatSize * 3)); - const bufferViewIndex = bufferViews.length - 1; - accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, numberOfTangents, 0)); - result.attributes["TANGENT"] = accessors.length - 1; } if (morphTarget.hasNormals) { - const morphNormals = morphTarget.getNormals()!; - const byteOffset = dataWriter.byteOffset; - for (let index = 0; index < morphNormals.length; index += 3) { - dataWriter.writeFloat32(morphNormals[index] * flipX); - dataWriter.writeFloat32(morphNormals[index + 1]); - dataWriter.writeFloat32(morphNormals[index + 2]); + const morphNormals = morphTarget.getNormals()!; + const originalNormals = mesh.getVerticesData(VertexBuffer.NormalKind, undefined, undefined, true)!; + const vertexStart = 0; + const vertexCount = originalNormals.length / 3; + for (let i = vertexStart; i < vertexCount; ++i) { + const originalNormal = Vector3.FromArray(originalNormals, i * 3).normalize(); + const morphNormal = Vector3.FromArray(morphNormals, i * 3).normalize(); + morphNormal.subtractToRef(originalNormal, difference); + dataWriter.writeFloat32(difference.x * flipX); + dataWriter.writeFloat32(difference.y); + dataWriter.writeFloat32(difference.z); } bufferViews.push(createBufferView(0, byteOffset, morphNormals.length * floatSize, floatSize * 3)); @@ -99,5 +120,32 @@ export function buildMorphTargetBuffers( result.attributes["NORMAL"] = accessors.length - 1; } + if (morphTarget.hasTangents) { + const byteOffset = dataWriter.byteOffset; + const morphTangents = morphTarget.getTangents()!; + const originalTangents = mesh.getVerticesData(VertexBuffer.TangentKind, undefined, undefined, true)!; + const vertexStart = 0; + const vertexCount = originalTangents.length / 4; + for (let i = vertexStart; i < vertexCount; ++i) { + // Only read the x, y, z components and ignore w + const originalTangent = Vector3.FromArray(originalTangents, i * 4); + _NormalizeTangentFromRef(originalTangent); + + // Morph target tangents omit the w component so it won't be present in the data + const morphTangent = Vector3.FromArray(morphTangents, i * 3); + _NormalizeTangentFromRef(morphTangent); + + morphTangent.subtractToRef(originalTangent, difference); + dataWriter.writeFloat32(difference.x * flipX); + dataWriter.writeFloat32(difference.y); + dataWriter.writeFloat32(difference.z); + } + + bufferViews.push(createBufferView(0, byteOffset, vertexCount * floatSize * 3, floatSize * 3)); + const bufferViewIndex = bufferViews.length - 1; + accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, vertexCount, 0)); + result.attributes["TANGENT"] = accessors.length - 1; + } + return result; } From 5969278cc1b814370896f56ce7e0b9fd404d8cf7 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Fri, 25 Oct 2024 17:50:24 -0400 Subject: [PATCH 019/133] Init WIP --- .../2.0/Extensions/KHR_lights_punctual.ts | 379 +++++++++--------- .../src/glTF/2.0/Extensions/index.ts | 2 +- .../serializers/src/glTF/2.0/glTFExporter.ts | 8 +- .../src/glTF/2.0/glTFExporterExtension.ts | 3 +- 4 files changed, 198 insertions(+), 194 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index 64575a5b8b2..7771985cc8b 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -1,190 +1,189 @@ -// import type { SpotLight } from "core/Lights/spotLight"; -// import type { Nullable } from "core/types"; -// import { Vector3, Quaternion, TmpVectors, Matrix } from "core/Maths/math.vector"; -// import { Color3 } from "core/Maths/math.color"; -// import { Light } from "core/Lights/light"; -// import type { Node } from "core/node"; -// import { ShadowLight } from "core/Lights/shadowLight"; -// import type { INode, IKHRLightsPunctual_LightReference, IKHRLightsPunctual_Light, IKHRLightsPunctual } from "babylonjs-gltf2interface"; -// import { KHRLightsPunctual_LightType } from "babylonjs-gltf2interface"; -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import { GLTFExporter } from "../glTFExporter"; -// import { Logger } from "core/Misc/logger"; -// import { _GLTFUtilities } from "../glTFUtilities"; - -// const NAME = "KHR_lights_punctual"; - -// /** -// * [Specification](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md) -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { -// /** The name of this extension. */ -// public readonly name = NAME; - -// /** Defines whether this extension is enabled. */ -// public enabled = true; - -// /** Defines whether this extension is required */ -// public required = false; - -// /** Reference to the glTF exporter */ -// private _exporter: GLTFExporter; - -// private _lights: IKHRLightsPunctual; - -// /** -// * @internal -// */ -// constructor(exporter: GLTFExporter) { -// this._exporter = exporter; -// } - -// /** @internal */ -// public dispose() { -// (this._lights as any) = null; -// } - -// /** @internal */ -// public get wasUsed() { -// return !!this._lights; -// } - -// /** @internal */ -// public onExporting(): void { -// this._exporter!._glTF.extensions![NAME] = this._lights; -// } -// /** -// * Define this method to modify the default behavior when exporting a node -// * @param context The context when exporting the node -// * @param node glTF node -// * @param babylonNode BabylonJS node -// * @param nodeMap Node mapping of unique id to glTF node index -// * @returns nullable INode promise -// */ -// public postExportNodeAsync(context: string, node: Nullable, babylonNode: Node, nodeMap: { [key: number]: number }): Promise> { -// return new Promise((resolve) => { -// if (node && babylonNode instanceof ShadowLight) { -// let light: IKHRLightsPunctual_Light; - -// const lightType = -// babylonNode.getTypeID() == Light.LIGHTTYPEID_POINTLIGHT -// ? KHRLightsPunctual_LightType.POINT -// : babylonNode.getTypeID() == Light.LIGHTTYPEID_DIRECTIONALLIGHT -// ? KHRLightsPunctual_LightType.DIRECTIONAL -// : babylonNode.getTypeID() == Light.LIGHTTYPEID_SPOTLIGHT -// ? KHRLightsPunctual_LightType.SPOT -// : null; -// if (lightType == null) { -// Logger.Warn(`${context}: Light ${babylonNode.name} is not supported in ${NAME}`); -// } else { -// if (!babylonNode.position.equalsToFloats(0, 0, 0)) { -// node.translation = babylonNode.position.asArray(); -// } -// if (lightType !== KHRLightsPunctual_LightType.POINT) { -// const localAxis = babylonNode.direction; -// const yaw = -Math.atan2(localAxis.z, localAxis.x) + Math.PI / 2; -// const len = Math.sqrt(localAxis.x * localAxis.x + localAxis.z * localAxis.z); -// const pitch = -Math.atan2(localAxis.y, len); -// const lightRotationQuaternion = Quaternion.RotationYawPitchRoll(yaw + Math.PI, pitch, 0); -// if (!Quaternion.IsIdentity(lightRotationQuaternion)) { -// node.rotation = lightRotationQuaternion.asArray(); -// } -// } - -// if (babylonNode.falloffType !== Light.FALLOFF_GLTF) { -// Logger.Warn(`${context}: Light falloff for ${babylonNode.name} does not match the ${NAME} specification!`); -// } -// light = { -// type: lightType, -// }; -// if (!babylonNode.diffuse.equals(Color3.White())) { -// light.color = babylonNode.diffuse.asArray(); -// } -// if (babylonNode.intensity !== 1.0) { -// light.intensity = babylonNode.intensity; -// } -// if (babylonNode.range !== Number.MAX_VALUE) { -// light.range = babylonNode.range; -// } - -// if (lightType === KHRLightsPunctual_LightType.SPOT) { -// const babylonSpotLight = babylonNode as SpotLight; -// if (babylonSpotLight.angle !== Math.PI / 2.0) { -// if (light.spot == null) { -// light.spot = {}; -// } -// light.spot.outerConeAngle = babylonSpotLight.angle / 2.0; -// } -// if (babylonSpotLight.innerAngle !== 0) { -// if (light.spot == null) { -// light.spot = {}; -// } -// light.spot.innerConeAngle = babylonSpotLight.innerAngle / 2.0; -// } -// } - -// this._lights ||= { -// lights: [], -// }; - -// this._lights.lights.push(light); - -// const lightReference: IKHRLightsPunctual_LightReference = { -// light: this._lights.lights.length - 1, -// }; - -// // Avoid duplicating the Light's parent node if possible. -// const parentBabylonNode = babylonNode.parent; -// if (parentBabylonNode && parentBabylonNode.getChildren().length == 1) { -// const parentNode = this._exporter._nodes[nodeMap[parentBabylonNode.uniqueId]]; -// if (parentNode) { -// const parentTranslation = Vector3.FromArrayToRef(parentNode.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); -// const parentRotation = Quaternion.FromArrayToRef(parentNode.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); -// const parentScale = Vector3.FromArrayToRef(parentNode.scale || [1, 1, 1], 0, TmpVectors.Vector3[1]); -// const parentMatrix = Matrix.ComposeToRef(parentScale, parentRotation, parentTranslation, TmpVectors.Matrix[0]); - -// const translation = Vector3.FromArrayToRef(node.translation || [0, 0, 0], 0, TmpVectors.Vector3[2]); -// const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); -// const matrix = Matrix.ComposeToRef(Vector3.OneReadOnly, rotation, translation, TmpVectors.Matrix[1]); - -// parentMatrix.multiplyToRef(matrix, matrix); -// matrix.decompose(parentScale, parentRotation, parentTranslation); - -// if (parentTranslation.equalsToFloats(0, 0, 0)) { -// delete parentNode.translation; -// } else { -// parentNode.translation = parentTranslation.asArray(); -// } - -// if (Quaternion.IsIdentity(parentRotation)) { -// delete parentNode.rotation; -// } else { -// parentNode.rotation = parentRotation.asArray(); -// } - -// if (parentScale.equalsToFloats(1, 1, 1)) { -// delete parentNode.scale; -// } else { -// parentNode.scale = parentScale.asArray(); -// } - -// parentNode.extensions ||= {}; -// parentNode.extensions[NAME] = lightReference; - -// // Do not export the original node -// resolve(null); -// return; -// } -// } - -// node.extensions ||= {}; -// node.extensions[NAME] = lightReference; -// } -// } -// resolve(node); -// }); -// } -// } - -// GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_lights_punctual(exporter)); +import type { SpotLight } from "core/Lights/spotLight"; +import type { Nullable } from "core/types"; +import { Vector3, Quaternion, TmpVectors, Matrix } from "core/Maths/math.vector"; +import { Color3 } from "core/Maths/math.color"; +import { Light } from "core/Lights/light"; +import type { Node } from "core/node"; +import { ShadowLight } from "core/Lights/shadowLight"; +import type { INode, IKHRLightsPunctual_LightReference, IKHRLightsPunctual_Light, IKHRLightsPunctual } from "babylonjs-gltf2interface"; +import { KHRLightsPunctual_LightType } from "babylonjs-gltf2interface"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { GLTFExporter } from "../glTFExporter"; +import { Logger } from "core/Misc/logger"; + +const NAME = "KHR_lights_punctual"; + +/** + * [Specification](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md) + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { + /** The name of this extension. */ + public readonly name = NAME; + + /** Defines whether this extension is enabled. */ + public enabled = true; + + /** Defines whether this extension is required */ + public required = false; + + /** Reference to the glTF exporter */ + private _exporter: GLTFExporter; + + private _lights: IKHRLightsPunctual; + + /** + * @internal + */ + constructor(exporter: GLTFExporter) { + this._exporter = exporter; + } + + /** @internal */ + public dispose() { + (this._lights as any) = null; + } + + /** @internal */ + public get wasUsed() { + return !!this._lights; + } + + /** @internal */ + public onExporting(): void { + this._exporter!._glTF.extensions![NAME] = this._lights; + } + /** + * Define this method to modify the default behavior when exporting a node + * @param context The context when exporting the node + * @param node glTF node + * @param babylonNode BabylonJS node + * @param nodeMap Node mapping of babylon node to glTF node index + * @returns nullable INode promise + */ + public postExportNodeAsync(context: string, node: Nullable, babylonNode: Node, nodeMap: Map): Promise> { + return new Promise((resolve) => { + if (node && babylonNode instanceof ShadowLight) { + let light: IKHRLightsPunctual_Light; + + const lightType = + babylonNode.getTypeID() == Light.LIGHTTYPEID_POINTLIGHT + ? KHRLightsPunctual_LightType.POINT + : babylonNode.getTypeID() == Light.LIGHTTYPEID_DIRECTIONALLIGHT + ? KHRLightsPunctual_LightType.DIRECTIONAL + : babylonNode.getTypeID() == Light.LIGHTTYPEID_SPOTLIGHT + ? KHRLightsPunctual_LightType.SPOT + : null; + if (lightType == null) { + Logger.Warn(`${context}: Light ${babylonNode.name} is not supported in ${NAME}`); + } else { + if (!babylonNode.position.equalsToFloats(0, 0, 0)) { + node.translation = babylonNode.position.asArray(); + } + if (lightType !== KHRLightsPunctual_LightType.POINT) { + const localAxis = babylonNode.direction; + const yaw = -Math.atan2(localAxis.z, localAxis.x) + Math.PI / 2; + const len = Math.sqrt(localAxis.x * localAxis.x + localAxis.z * localAxis.z); + const pitch = -Math.atan2(localAxis.y, len); + const lightRotationQuaternion = Quaternion.RotationYawPitchRoll(yaw + Math.PI, pitch, 0); + if (!Quaternion.IsIdentity(lightRotationQuaternion)) { + node.rotation = lightRotationQuaternion.asArray(); + } + } + + if (babylonNode.falloffType !== Light.FALLOFF_GLTF) { + Logger.Warn(`${context}: Light falloff for ${babylonNode.name} does not match the ${NAME} specification!`); + } + light = { + type: lightType, + }; + if (!babylonNode.diffuse.equals(Color3.White())) { + light.color = babylonNode.diffuse.asArray(); + } + if (babylonNode.intensity !== 1.0) { + light.intensity = babylonNode.intensity; + } + if (babylonNode.range !== Number.MAX_VALUE) { + light.range = babylonNode.range; + } + + if (lightType === KHRLightsPunctual_LightType.SPOT) { + const babylonSpotLight = babylonNode as SpotLight; + if (babylonSpotLight.angle !== Math.PI / 2.0) { + if (light.spot == null) { + light.spot = {}; + } + light.spot.outerConeAngle = babylonSpotLight.angle / 2.0; + } + if (babylonSpotLight.innerAngle !== 0) { + if (light.spot == null) { + light.spot = {}; + } + light.spot.innerConeAngle = babylonSpotLight.innerAngle / 2.0; + } + } + + this._lights ||= { + lights: [], + }; + + this._lights.lights.push(light); + + const lightReference: IKHRLightsPunctual_LightReference = { + light: this._lights.lights.length - 1, + }; + + // Avoid duplicating the Light's parent node if possible. + const parentBabylonNode = babylonNode.parent; + if (parentBabylonNode && parentBabylonNode.getChildren().length == 1) { + const parentNode = this._exporter._nodes[nodeMap.get(parentBabylonNode)!]; + if (parentNode) { + const parentTranslation = Vector3.FromArrayToRef(parentNode.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); + const parentRotation = Quaternion.FromArrayToRef(parentNode.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); + const parentScale = Vector3.FromArrayToRef(parentNode.scale || [1, 1, 1], 0, TmpVectors.Vector3[1]); + const parentMatrix = Matrix.ComposeToRef(parentScale, parentRotation, parentTranslation, TmpVectors.Matrix[0]); + + const translation = Vector3.FromArrayToRef(node.translation || [0, 0, 0], 0, TmpVectors.Vector3[2]); + const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); + const matrix = Matrix.ComposeToRef(Vector3.OneReadOnly, rotation, translation, TmpVectors.Matrix[1]); + + parentMatrix.multiplyToRef(matrix, matrix); + matrix.decompose(parentScale, parentRotation, parentTranslation); + + if (parentTranslation.equalsToFloats(0, 0, 0)) { + delete parentNode.translation; + } else { + parentNode.translation = parentTranslation.asArray(); + } + + if (Quaternion.IsIdentity(parentRotation)) { + delete parentNode.rotation; + } else { + parentNode.rotation = parentRotation.asArray(); + } + + if (parentScale.equalsToFloats(1, 1, 1)) { + delete parentNode.scale; + } else { + parentNode.scale = parentScale.asArray(); + } + + parentNode.extensions ||= {}; + parentNode.extensions[NAME] = lightReference; + + // Do not export the original node + resolve(null); + return; + } + } + + node.extensions ||= {}; + node.extensions[NAME] = lightReference; + } + } + resolve(node); + }); + } +} + +GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_lights_punctual(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts index 897a9e2d5f5..d38f5f2c1a4 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts @@ -1,5 +1,5 @@ // export * from "./EXT_mesh_gpu_instancing"; -// export * from "./KHR_lights_punctual"; +export * from "./KHR_lights_punctual"; // export * from "./KHR_materials_anisotropy"; // export * from "./KHR_materials_clearcoat"; // export * from "./KHR_materials_diffuse_transmission"; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 37278ff9bfa..3f2e65d30be 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -239,7 +239,9 @@ export class GLTFExporter { private readonly _shouldExportNodeMap = new Map(); - // Babylon node -> glTF node index + /** + * Babylon node -> glTF node index + */ private readonly _nodeMap = new Map(); // Babylon material -> glTF material index @@ -297,7 +299,7 @@ export class GLTFExporter { ); } - public _extensionsPostExportNodeAsync(context: string, node: Nullable, babylonNode: Node, nodeMap: { [key: number]: number }): Promise> { + public _extensionsPostExportNodeAsync(context: string, node: Nullable, babylonNode: Node, nodeMap: Map): Promise> { return this._applyExtensions(node, (extension, node) => extension.postExportNodeAsync && extension.postExportNodeAsync(context, node, babylonNode, nodeMap)); } @@ -1637,6 +1639,8 @@ export class GLTFExporter { } } + this._extensionsPostExportNodeAsync("exportNodeAsync", this._nodes[nodeIndex], babylonNode, this._nodeMap); + return nodeIndex; } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts index 5c88f397cf2..64e76dd310d 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts @@ -50,9 +50,10 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo * @param context The context when exporting the node * @param node glTF node * @param babylonNode BabylonJS node + * @param nodeMap Node mapping of babylon node to glTF node index * @returns nullable INode promise */ - postExportNodeAsync?(context: string, node: Nullable, babylonNode: Node, nodeMap: { [key: number]: number }): Promise>; + postExportNodeAsync?(context: string, node: Nullable, babylonNode: Node, nodeMap: Map): Promise>; /** * Define this method to modify the default behavior when exporting a material From 39263dbcaf0dfa242013b96f7cf94ac9fd766e21 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Fri, 25 Oct 2024 18:41:59 -0400 Subject: [PATCH 020/133] Add back glTF.extensions initialization --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 3f2e65d30be..82dee557d05 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -355,6 +355,7 @@ export class GLTFExporter { } } + this._glTF.extensions ||= {}; if (extension.onExporting) { extension.onExporting(); } From 81edf355eb220fae8913f7cb4e7f0629a02ccafc Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 28 Oct 2024 12:06:32 -0400 Subject: [PATCH 021/133] Add directional light test --- ...lTFSerializerKHRPunctualLightDirectional.png | Bin 0 -> 12772 bytes .../tools/tests/test/visualization/config.json | 5 +++++ 2 files changed, 5 insertions(+) create mode 100644 packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerKHRPunctualLightDirectional.png diff --git a/packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerKHRPunctualLightDirectional.png b/packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerKHRPunctualLightDirectional.png new file mode 100644 index 0000000000000000000000000000000000000000..41e9b0ea3f35e4ed5412372830810f36224d9985 GIT binary patch literal 12772 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²MN$x+7;{E|}VvFLnkDdYfAEvvh_*sRLC77wMS z>haPjKKR71;5f*W)J_g0#YW!@>zO&%m2>@Mz#aabe(}1rR~ZXdsDO; zhPRenB!bU(rl#zE5IW`mZ|dn~LZ*7p8(&@HyY$2Pe~ Date: Mon, 28 Oct 2024 13:52:22 -0300 Subject: [PATCH 022/133] Changed position calculation to match old implementation --- .../src/glTF/2.0/glTFMorphTargetsUtilities.ts | 81 +++++++++---------- 1 file changed, 37 insertions(+), 44 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts index dd0f33a2123..cfda9476d1a 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -53,58 +53,51 @@ export function buildMorphTargetBuffers( const flipX = convertToRightHanded ? -1 : 1; const floatSize = 4; const difference = Vector3.Zero(); + let vertexStart = 0; + let vertexCount = 0; + let byteOffset = 0; + let bufferViewIndex = 0; if (morphTarget.hasPositions) { const morphPositions = morphTarget.getPositions()!; - const meshPositions = mesh.getPositionData(); - - if (meshPositions) { - const byteOffset = dataWriter.byteOffset; - - const min = new Array(3).fill(Infinity); - const max = new Array(3).fill(-Infinity); - - for (let index = 0; index < morphPositions.length; index += 3) { - const morphX = morphPositions[index]; - const morphY = morphPositions[index + 1]; - const morphZ = morphPositions[index + 2]; - - const meshX = meshPositions[index]; - const meshY = meshPositions[index + 1]; - const meshZ = meshPositions[index + 2]; - - const deltaX = (morphX - meshX) * flipX; - const deltaY = morphY - meshY; - const deltaZ = morphZ - meshZ; - - min[0] = Math.min(min[0], deltaX); - max[0] = Math.max(max[0], deltaX); + const originalPositions = mesh.getVerticesData(VertexBuffer.PositionKind, undefined, undefined, true)!; + const min = new Array(3).fill(Infinity); + const max = new Array(3).fill(-Infinity); + vertexCount = originalPositions.length / 3; + byteOffset = dataWriter.byteOffset; + vertexStart = 0; + for (let i = vertexStart; i < vertexCount; ++i) { + const originalPosition = Vector3.FromArray(originalPositions, i * 3); + const morphPosition = Vector3.FromArray(morphPositions, i * 3); + morphPosition.subtractToRef(originalPosition, difference); + difference.x *= flipX; - min[1] = Math.min(min[1], deltaY); - max[1] = Math.max(max[1], deltaY); + min[0] = Math.min(min[0], difference.x); + max[0] = Math.max(max[0], difference.x); - min[2] = Math.min(min[2], deltaZ); - max[2] = Math.max(max[2], deltaZ); + min[1] = Math.min(min[1], difference.y); + max[1] = Math.max(max[1], difference.y); - dataWriter.writeFloat32(deltaX); - dataWriter.writeFloat32(deltaY); - dataWriter.writeFloat32(deltaZ); - } + min[2] = Math.min(min[2], difference.z); + max[2] = Math.max(max[2], difference.z); - bufferViews.push(createBufferView(0, byteOffset, morphPositions.length * floatSize, floatSize * 3)); - const bufferViewIndex = bufferViews.length - 1; - accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphPositions.length / 3, 0, { min, max })); - result.attributes["POSITION"] = accessors.length - 1; + dataWriter.writeFloat32(difference.x); + dataWriter.writeFloat32(difference.y); + dataWriter.writeFloat32(difference.z); } + + bufferViews.push(createBufferView(0, byteOffset, morphPositions.length * floatSize, floatSize * 3)); + bufferViewIndex = bufferViews.length - 1; + accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphPositions.length / 3, 0, { min, max })); + result.attributes["POSITION"] = accessors.length - 1; } if (morphTarget.hasNormals) { - const byteOffset = dataWriter.byteOffset; - const morphNormals = morphTarget.getNormals()!; const originalNormals = mesh.getVerticesData(VertexBuffer.NormalKind, undefined, undefined, true)!; - const vertexStart = 0; - const vertexCount = originalNormals.length / 3; + vertexCount = originalNormals.length / 3; + byteOffset = dataWriter.byteOffset; + vertexStart = 0; for (let i = vertexStart; i < vertexCount; ++i) { const originalNormal = Vector3.FromArray(originalNormals, i * 3).normalize(); const morphNormal = Vector3.FromArray(morphNormals, i * 3).normalize(); @@ -115,17 +108,17 @@ export function buildMorphTargetBuffers( } bufferViews.push(createBufferView(0, byteOffset, morphNormals.length * floatSize, floatSize * 3)); - const bufferViewIndex = bufferViews.length - 1; + bufferViewIndex = bufferViews.length - 1; accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphNormals.length / 3, 0)); result.attributes["NORMAL"] = accessors.length - 1; } if (morphTarget.hasTangents) { - const byteOffset = dataWriter.byteOffset; const morphTangents = morphTarget.getTangents()!; const originalTangents = mesh.getVerticesData(VertexBuffer.TangentKind, undefined, undefined, true)!; - const vertexStart = 0; - const vertexCount = originalTangents.length / 4; + vertexCount = originalTangents.length / 4; + vertexStart = 0; + byteOffset = dataWriter.byteOffset; for (let i = vertexStart; i < vertexCount; ++i) { // Only read the x, y, z components and ignore w const originalTangent = Vector3.FromArray(originalTangents, i * 4); @@ -142,7 +135,7 @@ export function buildMorphTargetBuffers( } bufferViews.push(createBufferView(0, byteOffset, vertexCount * floatSize * 3, floatSize * 3)); - const bufferViewIndex = bufferViews.length - 1; + bufferViewIndex = bufferViews.length - 1; accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, vertexCount, 0)); result.attributes["TANGENT"] = accessors.length - 1; } From 350be4738663560cc9840a41707a5c49dceb08bf Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 28 Oct 2024 13:59:00 -0300 Subject: [PATCH 023/133] Removed unecessary comments --- .../serializers/src/glTF/2.0/glTFExporter.ts | 528 +----------------- 1 file changed, 2 insertions(+), 526 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 37278ff9bfa..f8658b46618 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -424,108 +424,6 @@ export class GLTFExporter { return true; } - // /** - // * Writes mesh attribute data to a data buffer - // * Returns the bytelength of the data - // * @param vertexBufferKind Indicates what kind of vertex data is being passed in - // * @param attributeComponentKind - // * @param meshPrimitive - // * @param morphTarget - // * @param meshAttributeArray Array containing the attribute data - // * @param morphTargetAttributeArray - // * @param stride Specifies the space between data - // * @param dataWriter The buffer to write the binary data to - // * @param minMax - // */ - // public writeMorphTargetAttributeData( - // vertexBufferKind: string, - // attributeComponentKind: AccessorComponentType, - // meshPrimitive: SubMesh, - // meshAttributeArray: FloatArray, - // morphTargetAttributeArray: FloatArray, - // stride: number, - // dataWriter: DataWriter, - // minMax?: any - // ) { - // let vertexAttributes: number[][] = []; - // let index: number; - // let difference: Vector3 = new Vector3(); - // let difference4: Vector4 = new Vector4(0, 0, 0, 0); - - // switch (vertexBufferKind) { - // case VertexBuffer.PositionKind: { - // for (let k = meshPrimitive.verticesStart; k < meshPrimitive.verticesCount; ++k) { - // index = meshPrimitive.indexStart + k * stride; - // const vertexData = Vector3.FromArray(meshAttributeArray, index); - // const morphData = Vector3.FromArray(morphTargetAttributeArray, index); - // difference = morphData.subtractToRef(vertexData, difference); - // if (minMax) { - // minMax.min.copyFromFloats(Math.min(difference.x, minMax.min.x), Math.min(difference.y, minMax.min.y), Math.min(difference.z, minMax.min.z)); - // minMax.max.copyFromFloats(Math.max(difference.x, minMax.max.x), Math.max(difference.y, minMax.max.y), Math.max(difference.z, minMax.max.z)); - // } - // vertexAttributes.push(difference.asArray()); - // } - // break; - // } - // case VertexBuffer.NormalKind: { - // for (let k = meshPrimitive.verticesStart; k < meshPrimitive.verticesCount; ++k) { - // index = meshPrimitive.indexStart + k * stride; - // const vertexData = Vector3.FromArray(meshAttributeArray, index).normalize(); - // const morphData = Vector3.FromArray(morphTargetAttributeArray, index).normalize(); - // difference = morphData.subtractToRef(vertexData, difference); - // vertexAttributes.push(difference.asArray()); - // } - // break; - // } - // case VertexBuffer.TangentKind: { - // for (let k = meshPrimitive.verticesStart; k < meshPrimitive.verticesCount; ++k) { - // index = meshPrimitive.indexStart + k * (stride + 1); - // const vertexData = Vector4.FromArray(meshAttributeArray, index); - // normalizeTangent(vertexData); - // const morphData = Vector4.FromArray(morphTargetAttributeArray, index); - // normalizeTangent(morphData); - // difference4 = morphData.subtractToRef(vertexData, difference4); - // vertexAttributes.push([difference4.x, difference4.y, difference4.z]); - // } - // break; - // } - // default: { - // Tools.Warn("Unsupported Vertex Buffer Type: " + vertexBufferKind); - // vertexAttributes = []; - // } - // } - - // let writeBinaryFunc; - // switch (attributeComponentKind) { - // case AccessorComponentType.UNSIGNED_BYTE: { - // writeBinaryFunc = dataWriter.writeUInt8.bind(dataWriter); - // break; - // } - // case AccessorComponentType.UNSIGNED_SHORT: { - // writeBinaryFunc = dataWriter.writeUInt16.bind(dataWriter); - // break; - // } - // case AccessorComponentType.UNSIGNED_INT: { - // writeBinaryFunc = dataWriter.writeUInt32.bind(dataWriter); - // break; - // } - // case AccessorComponentType.FLOAT: { - // writeBinaryFunc = dataWriter.writeFloat32.bind(dataWriter); - // break; - // } - // default: { - // Tools.Warn("Unsupported Attribute Component kind: " + attributeComponentKind); - // return; - // } - // } - - // for (const vertexAttribute of vertexAttributes) { - // for (const component of vertexAttribute) { - // writeBinaryFunc(component); - // } - // } - // } - private _generateJSON(shouldUseGlb: boolean, bufferByteLength: number, fileName?: string, prettyPrint?: boolean): string { const buffer: IBuffer = { byteLength: bufferByteLength }; let imageName: string; @@ -933,304 +831,6 @@ export class GLTFExporter { } } - // /** - // * Creates a bufferview based on the vertices type for the Babylon mesh - // * @param babylonSubMesh The Babylon submesh that the morph target is applied to - // * @param meshPrimitive - // * @param babylonMorphTarget the morph target to be exported - // * @param dataWriter The buffer to write the bufferview data to - // */ - // private _setMorphTargetAttributes(babylonSubMesh: SubMesh, meshPrimitive: IMeshPrimitive, babylonMorphTarget: MorphTarget, dataWriter: DataWriter) { - // if (babylonMorphTarget) { - // if (!meshPrimitive.targets) { - // meshPrimitive.targets = []; - // } - // const target: { [attribute: string]: number } = {}; - // const mesh = babylonSubMesh.getMesh() as Mesh; - // if (babylonMorphTarget.hasNormals) { - // const vertexNormals = mesh.getVerticesData(VertexBuffer.NormalKind, undefined, undefined, true)!; - // const morphNormals = babylonMorphTarget.getNormals()!; - // const count = babylonSubMesh.verticesCount; - // const byteStride = 12; // 3 x 4 byte floats - // const byteLength = count * byteStride; - // const bufferView = createBufferView(0, dataWriter.byteOffset, byteLength, byteStride); - // this._bufferViews.push(bufferView); - - // const bufferViewIndex = this._bufferViews.length - 1; - // const accessor = createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, count, 0, null); - // this._accessors.push(accessor); - // target.NORMAL = this._accessors.length - 1; - - // this.writeMorphTargetAttributeData(VertexBuffer.NormalKind, AccessorComponentType.FLOAT, babylonSubMesh, vertexNormals, morphNormals, byteStride / 4, dataWriter); - // } - // if (babylonMorphTarget.hasPositions) { - // const vertexPositions = mesh.getVerticesData(VertexBuffer.PositionKind, undefined, undefined, true)!; - // const morphPositions = babylonMorphTarget.getPositions()!; - // const count = babylonSubMesh.verticesCount; - // const byteStride = 12; // 3 x 4 byte floats - // const byteLength = count * byteStride; - // const bufferView = createBufferView(0, dataWriter.byteOffset, byteLength, byteStride); - // this._bufferViews.push(bufferView); - - // const bufferViewIndex = this._bufferViews.length - 1; - // const minMax = { min: new Vector3(Infinity, Infinity, Infinity), max: new Vector3(-Infinity, -Infinity, -Infinity) }; - // const accessor = createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, count, 0, null); - // this._accessors.push(accessor); - // target.POSITION = this._accessors.length - 1; - - // this.writeMorphTargetAttributeData( - // VertexBuffer.PositionKind, - // AccessorComponentType.FLOAT, - // babylonSubMesh, - // vertexPositions, - // morphPositions, - // byteStride / 4, - // dataWriter, - // minMax - // ); - // accessor.min = minMax.min!.asArray(); - // accessor.max = minMax.max!.asArray(); - // } - // if (babylonMorphTarget.hasTangents) { - // const vertexTangents = mesh.getVerticesData(VertexBuffer.TangentKind, undefined, undefined, true)!; - // const morphTangents = babylonMorphTarget.getTangents()!; - // const count = babylonSubMesh.verticesCount; - // const byteStride = 12; // 3 x 4 byte floats - // const byteLength = count * byteStride; - // const bufferView = createBufferView(0, dataWriter.byteOffset, byteLength, byteStride); - // this._bufferViews.push(bufferView); - - // const bufferViewIndex = this._bufferViews.length - 1; - // const accessor = createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, count, 0, null); - // this._accessors.push(accessor); - // target.TANGENT = this._accessors.length - 1; - - // this.writeMorphTargetAttributeData( - // VertexBuffer.TangentKind, - // AccessorComponentType.FLOAT, - // babylonSubMesh, - // vertexTangents, - // morphTangents, - // byteStride / 4, - // dataWriter - // ); - // } - // meshPrimitive.targets.push(target); - // } - // } - - // /** - // * Sets data for the primitive attributes of each submesh - // * @param mesh glTF Mesh object to store the primitive attribute information - // * @param babylonTransformNode Babylon mesh to get the primitive attribute data from - // * @param convertToRightHanded Whether to convert from left-handed to right-handed - // * @param dataWriter Buffer to write the attribute data to - // */ - // private _setPrimitiveAttributesAsync(mesh: IMesh, babylonTransformNode: TransformNode, convertToRightHanded: boolean, dataWriter: DataWriter): Promise { - // const promises: Promise[] = []; - // let bufferMesh: Nullable = null; - // let bufferView: IBufferView; - // let minMax: { min: Nullable; max: Nullable }; - - // if (babylonTransformNode instanceof Mesh) { - // bufferMesh = babylonTransformNode as Mesh; - // } else if (babylonTransformNode instanceof InstancedMesh) { - // bufferMesh = (babylonTransformNode as InstancedMesh).sourceMesh; - // } - // const attributeData: _IVertexAttributeData[] = [ - // { kind: VertexBuffer.PositionKind, accessorType: AccessorType.VEC3, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 12 }, - // { kind: VertexBuffer.NormalKind, accessorType: AccessorType.VEC3, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 12 }, - // { kind: VertexBuffer.ColorKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 16 }, - // { kind: VertexBuffer.TangentKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 16 }, - // { kind: VertexBuffer.UVKind, accessorType: AccessorType.VEC2, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 8 }, - // { kind: VertexBuffer.UV2Kind, accessorType: AccessorType.VEC2, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 8 }, - // { kind: VertexBuffer.MatricesIndicesKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.UNSIGNED_SHORT, byteStride: 8 }, - // { kind: VertexBuffer.MatricesIndicesExtraKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.UNSIGNED_SHORT, byteStride: 8 }, - // { kind: VertexBuffer.MatricesWeightsKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 16 }, - // { kind: VertexBuffer.MatricesWeightsExtraKind, accessorType: AccessorType.VEC4, accessorComponentType: AccessorComponentType.FLOAT, byteStride: 16 }, - // ]; - - // if (bufferMesh) { - // let indexBufferViewIndex: Nullable = null; - // const primitiveMode = this._getMeshPrimitiveMode(bufferMesh); - // const vertexAttributeBufferViews: { [attributeKind: string]: number } = {}; - // const morphTargetManager = bufferMesh.morphTargetManager; - - // // For each BabylonMesh, create bufferviews for each 'kind' - // for (const attribute of attributeData) { - // const attributeKind = attribute.kind; - // const attributeComponentKind = attribute.accessorComponentType; - // if (bufferMesh.isVerticesDataPresent(attributeKind, true)) { - // const vertexBuffer = this._getVertexBuffer(attributeKind, bufferMesh); - // attribute.byteStride = vertexBuffer - // ? vertexBuffer.getSize() * VertexBuffer.GetTypeByteLength(attribute.accessorComponentType) - // : VertexBuffer.DeduceStride(attributeKind) * 4; - // if (attribute.byteStride === 12) { - // attribute.accessorType = AccessorType.VEC3; - // } - - // this._createBufferViewKind(attributeKind, attributeComponentKind, babylonTransformNode, dataWriter, attribute.byteStride); - // attribute.bufferViewIndex = this._bufferViews.length - 1; - // vertexAttributeBufferViews[attributeKind] = attribute.bufferViewIndex; - // } - // } - - // if (bufferMesh.getTotalIndices()) { - // const indices = bufferMesh.getIndices(); - // if (indices) { - // const byteLength = indices.length * 4; - // bufferView = createBufferView(0, dataWriter.getByteOffset(), byteLength, undefined, "Indices - " + bufferMesh.name); - // this._bufferViews.push(bufferView); - // indexBufferViewIndex = this._bufferViews.length - 1; - - // for (let k = 0, length = indices.length; k < length; ++k) { - // dataWriter.setUInt32(indices[k]); - // } - // } - // } - - // if (bufferMesh.subMeshes) { - // // go through all mesh primitives (submeshes) - // for (const submesh of bufferMesh.subMeshes) { - // let babylonMaterial = submesh.getMaterial() || bufferMesh.getScene().defaultMaterial; - - // let materialIndex: Nullable = null; - // if (babylonMaterial) { - // if (bufferMesh instanceof LinesMesh) { - // // get the color from the lines mesh and set it in the material - // const material: IMaterial = { - // name: bufferMesh.name + " material", - // }; - // if (!bufferMesh.color.equals(Color3.White()) || bufferMesh.alpha < 1) { - // material.pbrMetallicRoughness = { - // baseColorFactor: bufferMesh.color.asArray().concat([bufferMesh.alpha]), - // }; - // } - // this._materials.push(material); - // materialIndex = this._materials.length - 1; - // } else if (babylonMaterial instanceof MultiMaterial) { - // const subMaterial = babylonMaterial.subMaterials[submesh.materialIndex]; - // if (subMaterial) { - // babylonMaterial = subMaterial; - // materialIndex = this._materialMap[babylonMaterial.uniqueId]; - // } - // } else { - // materialIndex = this._materialMap[babylonMaterial.uniqueId]; - // } - // } - - // const glTFMaterial: Nullable = materialIndex != null ? this._materials[materialIndex] : null; - - // const meshPrimitive: IMeshPrimitive = { attributes: {} }; - // this._setPrimitiveMode(meshPrimitive, primitiveMode); - - // for (const attribute of attributeData) { - // const attributeKind = attribute.kind; - // if ((attributeKind === VertexBuffer.UVKind || attributeKind === VertexBuffer.UV2Kind) && !this._options.exportUnusedUVs) { - // if (!glTFMaterial || !this._glTFMaterialExporter._hasTexturesPresent(glTFMaterial)) { - // continue; - // } - // } - // const vertexData = bufferMesh.getVerticesData(attributeKind, undefined, undefined, true); - // if (vertexData) { - // const vertexBuffer = this._getVertexBuffer(attributeKind, bufferMesh); - // if (vertexBuffer) { - // const stride = vertexBuffer.getSize(); - // const bufferViewIndex = attribute.bufferViewIndex; - // if (bufferViewIndex != undefined) { - // // check to see if bufferviewindex has a numeric value assigned. - // minMax = { min: null, max: null }; - // if (attributeKind == VertexBuffer.PositionKind) { - // minMax = calculateMinMaxPositions(vertexData, 0, vertexData.length / stride); - // } - // const accessor = createAccessor( - // bufferViewIndex, - // attributeKind + " - " + babylonTransformNode.name, - // attribute.accessorType, - // attribute.accessorComponentType, - // vertexData.length / stride, - // 0, - // minMax.min, - // minMax.max - // ); - // this._accessors.push(accessor); - // this._setAttributeKind(meshPrimitive, attributeKind); - // } - // } - // } - // } - - // if (indexBufferViewIndex) { - // // Create accessor - // const accessor = createAccessor( - // indexBufferViewIndex, - // "indices - " + babylonTransformNode.name, - // AccessorType.SCALAR, - // AccessorComponentType.UNSIGNED_INT, - // submesh.indexCount, - // submesh.indexStart * 4, - // null, - // null - // ); - // this._accessors.push(accessor); - // meshPrimitive.indices = this._accessors.length - 1; - // } - - // if (Object.keys(meshPrimitive.attributes).length > 0) { - // const sideOrientation = bufferMesh.overrideMaterialSideOrientation !== null ? bufferMesh.overrideMaterialSideOrientation : babylonMaterial.sideOrientation; - - // if (sideOrientation === (this._babylonScene.useRightHandedSystem ? Material.ClockWiseSideOrientation : Material.CounterClockWiseSideOrientation)) { - // let byteOffset = indexBufferViewIndex != null ? this._bufferViews[indexBufferViewIndex].byteOffset : null; - // if (byteOffset == null) { - // byteOffset = 0; - // } - // let babylonIndices: Nullable = null; - // if (indexBufferViewIndex != null) { - // babylonIndices = bufferMesh.getIndices(); - // } - // if (babylonIndices) { - // this._reorderIndicesBasedOnPrimitiveMode(submesh, primitiveMode, babylonIndices, byteOffset, dataWriter); - // } else { - // for (const attribute of attributeData) { - // const vertexData = bufferMesh.getVerticesData(attribute.kind, undefined, undefined, true); - // if (vertexData) { - // const byteOffset = this._bufferViews[vertexAttributeBufferViews[attribute.kind]].byteOffset || 0; - // this._reorderVertexAttributeDataBasedOnPrimitiveMode(submesh, primitiveMode, attribute.kind, vertexData, byteOffset, dataWriter); - // } - // } - // } - // } - - // if (materialIndex != null) { - // meshPrimitive.material = materialIndex; - // } - // } - // if (morphTargetManager) { - // // By convention, morph target names are stored in the mesh extras. - // if (!mesh.extras) { - // mesh.extras = {}; - // } - // mesh.extras.targetNames = []; - - // for (let i = 0; i < morphTargetManager.numTargets; ++i) { - // const target = morphTargetManager.getTarget(i); - // this._setMorphTargetAttributes(submesh, meshPrimitive, target, dataWriter); - // mesh.extras.targetNames.push(target.name); - // } - // } - - // mesh.primitives.push(meshPrimitive); - - // this._extensionsPostExportMeshPrimitiveAsync("postExport", meshPrimitive, submesh, dataWriter); - // promises.push(); - // } - // } - // } - // return Promise.all(promises).then(() => { - // /* do nothing */ - // }); - // } - private async _exportSceneAsync(): Promise { const scene: IScene = { nodes: [] }; @@ -1243,8 +843,8 @@ export class GLTFExporter { } } - // TODO: - // deal with this from the loader: + // TODO: + // deal with this from the loader: // babylonMaterial.invertNormalMapX = !this._babylonScene.useRightHandedSystem; // babylonMaterial.invertNormalMapY = this._babylonScene.useRightHandedSystem; @@ -1284,57 +884,6 @@ export class GLTFExporter { this._babylonScene.useRightHandedSystem ); } - - // return this._exportNodesAndAnimationsAsync(nodes, convertToRightHandedMap, dataWriter).then((nodeMap) => { - // return this._createSkinsAsync(nodeMap, dataWriter).then((skinMap) => { - // for (const babylonNode of nodes) { - // const glTFNodeIndex = nodeMap[babylonNode.uniqueId]; - // if (glTFNodeIndex !== undefined) { - // const glTFNode = this._nodes[glTFNodeIndex]; - - // if (babylonNode.metadata) { - // if (this._options.metadataSelector) { - // glTFNode.extras = this._options.metadataSelector(babylonNode.metadata); - // } else if (babylonNode.metadata.gltf) { - // glTFNode.extras = babylonNode.metadata.gltf.extras; - // } - // } - - // if (babylonNode instanceof Camera) { - // glTFNode.camera = cameraMap.get(babylonNode); - // } - - // if (!babylonNode.parent || removedRootNodes.has(babylonNode.parent)) { - // scene.nodes.push(glTFNodeIndex); - // } - - // if (babylonNode instanceof Mesh) { - // if (babylonNode.skeleton) { - // glTFNode.skin = skinMap[babylonNode.skeleton.uniqueId]; - // } - // } - - // const directDescendents = babylonNode.getDescendants(true); - // if (!glTFNode.children && directDescendents && directDescendents.length) { - // const children: number[] = []; - // for (const descendent of directDescendents) { - // if (nodeMap[descendent.uniqueId] != null) { - // children.push(nodeMap[descendent.uniqueId]); - // } - // } - // if (children.length) { - // glTFNode.children = children; - // } - // } - // } - // } - - // if (scene.nodes.length) { - // this._scenes.push(scene); - // } - // }); - // }); - // }); } private _shouldExportNode(babylonNode: Node): boolean { @@ -1848,77 +1397,4 @@ export class GLTFExporter { return meshIndex; } - - // const promise = this._extensionsPostExportNodeAsync("createNodeAsync", node, babylonNode, nodeMap); - // if (promise == null) { - // Tools.Warn(`Not exporting node ${babylonNode.name}`); - // return Promise.resolve(); - // } else { - // return promise.then((node) => { - // if (!node) { - // return; - // } - - // if (!this._babylonScene.animationGroups.length) { - // _GLTFAnimation._CreateMorphTargetAnimationFromMorphTargetAnimations( - // babylonNode, - // runtimeGLTFAnimation, - // idleGLTFAnimations, - // nodeMap, - // this._nodes, - // dataWriter, - // this._bufferViews, - // this._accessors, - // this._animationSampleRate, - // this._options.shouldExportAnimation - // ); - // if (babylonNode.animations.length) { - // _GLTFAnimation._CreateNodeAnimationFromNodeAnimations( - // babylonNode, - // runtimeGLTFAnimation, - // idleGLTFAnimations, - // nodeMap, - // this._nodes, - // dataWriter, - // this._bufferViews, - // this._accessors, - // this._animationSampleRate, - // this._options.shouldExportAnimation - // ); - // } - // } - // }); - // } - // }); - // }); - // } - - // return promise.then(() => { - // if (runtimeGLTFAnimation.channels.length && runtimeGLTFAnimation.samplers.length) { - // this._animations.push(runtimeGLTFAnimation); - // } - // idleGLTFAnimations.forEach((idleGLTFAnimation) => { - // if (idleGLTFAnimation.channels.length && idleGLTFAnimation.samplers.length) { - // this._animations.push(idleGLTFAnimation); - // } - // }); - - // if (this._babylonScene.animationGroups.length) { - // _GLTFAnimation._CreateNodeAndMorphAnimationFromAnimationGroups( - // this._babylonScene, - // this._animations, - // nodeMap, - // dataWriter, - // this._bufferViews, - // this._accessors, - // this._animationSampleRate, - // this._options.shouldExportAnimation - // ); - // } - - // return nodeMap; - // }); - - // return nodeMap; - // } } From d825911c014a2c29d21fe52128463687e844708a Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 28 Oct 2024 15:38:04 -0400 Subject: [PATCH 024/133] replace comment --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 4 +--- 1 file changed, 1 insertion(+), 3 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 82dee557d05..867098bdd8e 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -239,9 +239,7 @@ export class GLTFExporter { private readonly _shouldExportNodeMap = new Map(); - /** - * Babylon node -> glTF node index - */ + // Babylon node -> glTF node index private readonly _nodeMap = new Map(); // Babylon material -> glTF material index From 3cd41f40c09194eda818469cc842f8c3102aea91 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 30 Oct 2024 17:22:24 -0300 Subject: [PATCH 025/133] Fixed mesh flipping and removed scale flipping on animations --- .../serializers/src/glTF/2.0/glTFAnimation.ts | 39 +++++++++++++------ .../serializers/src/glTF/2.0/glTFExporter.ts | 10 ++++- 2 files changed, 35 insertions(+), 14 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts index 38549f211ed..bd6632453fe 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts @@ -16,7 +16,7 @@ import { AnimationKeyInterpolation } from "core/Animations/animationKey"; import { Camera } from "core/Cameras/camera"; import { Light } from "core/Lights/light"; import type { DataWriter } from "./dataWriter"; -import { createAccessor, createBufferView, getAccessorElementCount, convertToRightHandedRotation } from "./glTFUtilities"; +import { createAccessor, createBufferView, getAccessorElementCount, convertToRightHandedRotation, convertToRightHandedPosition } from "./glTFUtilities"; /** * @internal @@ -614,26 +614,41 @@ export class _GLTFAnimation { bufferViews.push(bufferView); const rotationQuaternion = new Quaternion(); + const eulerVec3 = new Vector3(); + const position = new Vector3(); const tempQuaterionArray = [0, 0, 0, 0]; animationData.outputs.forEach(function (output) { if (useRightHanded) { switch (animationChannelTargetPath) { case AnimationChannelTargetPath.TRANSLATION: - case AnimationChannelTargetPath.SCALE: - binaryWriter.writeFloat32(-output[0]); - binaryWriter.writeFloat32(output[1]); - binaryWriter.writeFloat32(output[2]); + Vector3.FromArrayToRef(output, 0, position); + convertToRightHandedPosition(position); + binaryWriter.writeFloat32(position.x); + binaryWriter.writeFloat32(position.y); + binaryWriter.writeFloat32(position.z); break; case AnimationChannelTargetPath.ROTATION: - Quaternion.FromArrayToRef(output, 0, rotationQuaternion); - convertToRightHandedRotation(rotationQuaternion); - rotationQuaternion.normalize().toArray(tempQuaterionArray); - binaryWriter.writeFloat32(tempQuaterionArray[0]); - binaryWriter.writeFloat32(tempQuaterionArray[1]); - binaryWriter.writeFloat32(tempQuaterionArray[2]); - binaryWriter.writeFloat32(tempQuaterionArray[2]); + if (output.length === 4) { + Quaternion.FromArrayToRef(output, 0, rotationQuaternion); + convertToRightHandedRotation(rotationQuaternion); + rotationQuaternion.toArray(tempQuaterionArray); + binaryWriter.writeFloat32(tempQuaterionArray[0]); + binaryWriter.writeFloat32(tempQuaterionArray[1]); + binaryWriter.writeFloat32(tempQuaterionArray[2]); + binaryWriter.writeFloat32(tempQuaterionArray[3]); + } else { + Vector3.FromArrayToRef(output, 0, eulerVec3); + Quaternion.FromEulerVectorToRef(eulerVec3, rotationQuaternion); + convertToRightHandedRotation(rotationQuaternion); + rotationQuaternion.toArray(tempQuaterionArray); + binaryWriter.writeFloat32(tempQuaterionArray[0]); + binaryWriter.writeFloat32(tempQuaterionArray[1]); + binaryWriter.writeFloat32(tempQuaterionArray[2]); + binaryWriter.writeFloat32(tempQuaterionArray[3]); + } + break; default: diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index f8658b46618..e9313b2f061 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1206,8 +1206,13 @@ export class GLTFExporter { primitive.mode = getPrimitiveMode(fillMode); // Flip if triangle winding order is not CCW as glTF is always CCW. - const flip = isTriangleFillMode(fillMode) && sideOrientation !== Material.CounterClockWiseSideOrientation; - if (flip && convertToRightHanded) { + let flip = isTriangleFillMode(fillMode) && sideOrientation !== Material.CounterClockWiseSideOrientation; + + if (convertToRightHanded) { + flip = !flip; + } + + if (flip) { if (fillMode === Material.TriangleStripDrawMode || fillMode === Material.TriangleFanDrawMode) { throw new Error("Triangle strip/fan fill mode is not implemented"); } @@ -1362,6 +1367,7 @@ export class GLTFExporter { // Index buffer const fillMode = babylonMesh.overrideRenderingFillMode ?? babylonMaterial.fillMode; + const sideOrientation = babylonMaterial._getEffectiveOrientation(babylonMesh); this._exportIndices(indices, subMesh.indexStart, subMesh.indexCount, -subMesh.verticesStart, fillMode, sideOrientation, state, primitive, convertToRightHanded); From f70fce12131d4c7ef776fc1708b7f9c358956ce1 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Wed, 30 Oct 2024 16:51:44 -0400 Subject: [PATCH 026/133] Update test --- .../glTFSerializerKHRPunctualLight.png | Bin 0 -> 10358 bytes ...lTFSerializerKHRPunctualLightDirectional.png | Bin 12772 -> 0 bytes .../tools/tests/test/visualization/config.json | 6 +++--- 3 files changed, 3 insertions(+), 3 deletions(-) create mode 100644 packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerKHRPunctualLight.png delete mode 100644 packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerKHRPunctualLightDirectional.png diff --git a/packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerKHRPunctualLight.png b/packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerKHRPunctualLight.png new file mode 100644 index 0000000000000000000000000000000000000000..0f87ed387ffc4ececd6a883cf6930f1285923924 GIT binary patch literal 10358 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²MN$x+7;{E|}VvFLnkDdYfAEvvh_*sRLC77wMS z>haPjKKR71;5f*W)J_g0#YW!@>zO&%m2>@Mz#aabe(}1rR~ZXdsDO; zhPRenB!bU(rl#zE5IW`mZ|dn~LZ*7p8(&@HyY$2Pe~ Date: Wed, 30 Oct 2024 19:43:51 -0400 Subject: [PATCH 027/133] Refactor lights - WIP --- .../2.0/Extensions/KHR_lights_punctual.ts | 235 ++++++++++-------- .../serializers/src/glTF/2.0/glTFExporter.ts | 2 +- .../serializers/src/glTF/2.0/glTFUtilities.ts | 12 + .../tests/test/visualization/config.json | 5 + 4 files changed, 144 insertions(+), 110 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index 7771985cc8b..f173c8c66aa 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -10,9 +10,29 @@ import { KHRLightsPunctual_LightType } from "babylonjs-gltf2interface"; import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; import { GLTFExporter } from "../glTFExporter"; import { Logger } from "core/Misc/logger"; +import { omitDefaultValues } from "../glTFUtilities"; const NAME = "KHR_lights_punctual"; +const DEFAULTS: Partial = { + name: "", + color: [1, 1, 1], + intensity: 1, + range: Number.MAX_VALUE, +}; + +const SPOTDEFAULTS: IKHRLightsPunctual_Light["spot"] = { + innerConeAngle: 0, + outerConeAngle: Math.PI / 4.0, +}; + +// TODO: Move elsewhere, since this is common +const NODEDEFAULTS: Partial = { + translation: [0, 0, 0], + rotation: [0, 0, 0, 1], + scale: [1, 1, 1], +}; + /** * [Specification](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md) */ @@ -63,124 +83,121 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { */ public postExportNodeAsync(context: string, node: Nullable, babylonNode: Node, nodeMap: Map): Promise> { return new Promise((resolve) => { - if (node && babylonNode instanceof ShadowLight) { - let light: IKHRLightsPunctual_Light; - - const lightType = - babylonNode.getTypeID() == Light.LIGHTTYPEID_POINTLIGHT - ? KHRLightsPunctual_LightType.POINT - : babylonNode.getTypeID() == Light.LIGHTTYPEID_DIRECTIONALLIGHT - ? KHRLightsPunctual_LightType.DIRECTIONAL - : babylonNode.getTypeID() == Light.LIGHTTYPEID_SPOTLIGHT - ? KHRLightsPunctual_LightType.SPOT - : null; - if (lightType == null) { - Logger.Warn(`${context}: Light ${babylonNode.name} is not supported in ${NAME}`); - } else { - if (!babylonNode.position.equalsToFloats(0, 0, 0)) { - node.translation = babylonNode.position.asArray(); - } - if (lightType !== KHRLightsPunctual_LightType.POINT) { - const localAxis = babylonNode.direction; - const yaw = -Math.atan2(localAxis.z, localAxis.x) + Math.PI / 2; - const len = Math.sqrt(localAxis.x * localAxis.x + localAxis.z * localAxis.z); - const pitch = -Math.atan2(localAxis.y, len); - const lightRotationQuaternion = Quaternion.RotationYawPitchRoll(yaw + Math.PI, pitch, 0); - if (!Quaternion.IsIdentity(lightRotationQuaternion)) { - node.rotation = lightRotationQuaternion.asArray(); - } - } + if (!node || !(babylonNode instanceof ShadowLight)) { + resolve(node); + return; + } - if (babylonNode.falloffType !== Light.FALLOFF_GLTF) { - Logger.Warn(`${context}: Light falloff for ${babylonNode.name} does not match the ${NAME} specification!`); - } - light = { - type: lightType, - }; - if (!babylonNode.diffuse.equals(Color3.White())) { - light.color = babylonNode.diffuse.asArray(); - } - if (babylonNode.intensity !== 1.0) { - light.intensity = babylonNode.intensity; - } - if (babylonNode.range !== Number.MAX_VALUE) { - light.range = babylonNode.range; + const lightType = + babylonNode.getTypeID() == Light.LIGHTTYPEID_POINTLIGHT + ? KHRLightsPunctual_LightType.POINT + : babylonNode.getTypeID() == Light.LIGHTTYPEID_DIRECTIONALLIGHT + ? KHRLightsPunctual_LightType.DIRECTIONAL + : babylonNode.getTypeID() == Light.LIGHTTYPEID_SPOTLIGHT + ? KHRLightsPunctual_LightType.SPOT + : null; + if (!lightType) { + Logger.Warn(`${context}: Light ${babylonNode.name} is not supported in ${NAME}`); + resolve(node); + return; + } + + if (babylonNode.falloffType !== Light.FALLOFF_GLTF) { + Logger.Warn(`${context}: Light falloff for ${babylonNode.name} does not match the ${NAME} specification!`); + } + + // TODO: Is this needed? + // if (!babylonNode.position.equalsToFloats(0, 0, 0)) { + // node.translation = babylonNode.position.asArray(); + // } + + // Override the node's rotation with the light's direction, since glTF uses a constant light direction + if (lightType !== KHRLightsPunctual_LightType.POINT) { + const localAxis = babylonNode.direction; + const yaw = -Math.atan2(localAxis.z, localAxis.x) + Math.PI / 2; + const len = Math.sqrt(localAxis.x * localAxis.x + localAxis.z * localAxis.z); + const pitch = -Math.atan2(localAxis.y, len); + const lightRotationQuaternion = Quaternion.RotationYawPitchRoll(yaw + Math.PI, pitch, 0); + if (!Quaternion.IsIdentity(lightRotationQuaternion)) { + node.rotation = lightRotationQuaternion.asArray(); + } + } + + let light: IKHRLightsPunctual_Light = { + type: lightType, + name: babylonNode.name, + color: babylonNode.diffuse.asArray(), + intensity: babylonNode.intensity, + range: babylonNode.range, + }; + light = omitDefaultValues(light, DEFAULTS); + + if (lightType === KHRLightsPunctual_LightType.SPOT) { + const babylonSpotLight = babylonNode as SpotLight; + light.spot = { + innerConeAngle: babylonSpotLight.innerAngle, + outerConeAngle: babylonSpotLight.angle, + }; + light.spot = omitDefaultValues(light.spot, SPOTDEFAULTS!); + } + + this._lights ||= { + lights: [], + }; + this._lights.lights.push(light); + + const lightReference: IKHRLightsPunctual_LightReference = { + light: this._lights.lights.length - 1, + }; + + // Assign the light to its parent node, if possible + const parentBabylonNode = babylonNode.parent; + if (parentBabylonNode && parentBabylonNode.getChildren().length == 1) { + const parentNode = this._exporter._nodes[nodeMap.get(parentBabylonNode)!]; + if (parentNode) { + // Consolidate the light's transformation with the parent's + const parentTranslation = Vector3.FromArrayToRef(parentNode.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); + const parentRotation = Quaternion.FromArrayToRef(parentNode.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); + const parentScale = Vector3.FromArrayToRef(parentNode.scale || [1, 1, 1], 0, TmpVectors.Vector3[1]); + const parentMatrix = Matrix.ComposeToRef(parentScale, parentRotation, parentTranslation, TmpVectors.Matrix[0]); + + const translation = Vector3.FromArrayToRef(node.translation || [0, 0, 0], 0, TmpVectors.Vector3[2]); + const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); + const matrix = Matrix.ComposeToRef(Vector3.OneReadOnly, rotation, translation, TmpVectors.Matrix[1]); + + parentMatrix.multiplyToRef(matrix, matrix); + matrix.decompose(parentScale, parentRotation, parentTranslation); + + // Remove default values + if (parentTranslation.equalsToFloats(0, 0, 0)) { + delete parentNode.translation; + } else { + parentNode.translation = parentTranslation.asArray(); } - if (lightType === KHRLightsPunctual_LightType.SPOT) { - const babylonSpotLight = babylonNode as SpotLight; - if (babylonSpotLight.angle !== Math.PI / 2.0) { - if (light.spot == null) { - light.spot = {}; - } - light.spot.outerConeAngle = babylonSpotLight.angle / 2.0; - } - if (babylonSpotLight.innerAngle !== 0) { - if (light.spot == null) { - light.spot = {}; - } - light.spot.innerConeAngle = babylonSpotLight.innerAngle / 2.0; - } + if (Quaternion.IsIdentity(parentRotation)) { + delete parentNode.rotation; + } else { + parentNode.rotation = parentRotation.asArray(); } - this._lights ||= { - lights: [], - }; - - this._lights.lights.push(light); - - const lightReference: IKHRLightsPunctual_LightReference = { - light: this._lights.lights.length - 1, - }; - - // Avoid duplicating the Light's parent node if possible. - const parentBabylonNode = babylonNode.parent; - if (parentBabylonNode && parentBabylonNode.getChildren().length == 1) { - const parentNode = this._exporter._nodes[nodeMap.get(parentBabylonNode)!]; - if (parentNode) { - const parentTranslation = Vector3.FromArrayToRef(parentNode.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); - const parentRotation = Quaternion.FromArrayToRef(parentNode.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); - const parentScale = Vector3.FromArrayToRef(parentNode.scale || [1, 1, 1], 0, TmpVectors.Vector3[1]); - const parentMatrix = Matrix.ComposeToRef(parentScale, parentRotation, parentTranslation, TmpVectors.Matrix[0]); - - const translation = Vector3.FromArrayToRef(node.translation || [0, 0, 0], 0, TmpVectors.Vector3[2]); - const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); - const matrix = Matrix.ComposeToRef(Vector3.OneReadOnly, rotation, translation, TmpVectors.Matrix[1]); - - parentMatrix.multiplyToRef(matrix, matrix); - matrix.decompose(parentScale, parentRotation, parentTranslation); - - if (parentTranslation.equalsToFloats(0, 0, 0)) { - delete parentNode.translation; - } else { - parentNode.translation = parentTranslation.asArray(); - } - - if (Quaternion.IsIdentity(parentRotation)) { - delete parentNode.rotation; - } else { - parentNode.rotation = parentRotation.asArray(); - } - - if (parentScale.equalsToFloats(1, 1, 1)) { - delete parentNode.scale; - } else { - parentNode.scale = parentScale.asArray(); - } - - parentNode.extensions ||= {}; - parentNode.extensions[NAME] = lightReference; - - // Do not export the original node - resolve(null); - return; - } + if (parentScale.equalsToFloats(1, 1, 1)) { + delete parentNode.scale; + } else { + parentNode.scale = parentScale.asArray(); } - node.extensions ||= {}; - node.extensions[NAME] = lightReference; + parentNode.extensions ||= {}; + parentNode.extensions[NAME] = lightReference; + + // Do not export the original node + resolve(null); + return; } } + + node.extensions ||= {}; + node.extensions[NAME] = lightReference; resolve(node); }); } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 867098bdd8e..b27b736cf53 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1638,7 +1638,7 @@ export class GLTFExporter { } } - this._extensionsPostExportNodeAsync("exportNodeAsync", this._nodes[nodeIndex], babylonNode, this._nodeMap); + this._extensionsPostExportNodeAsync("exportNodeAsync", node, babylonNode, this._nodeMap); return nodeIndex; } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 038f90e9368..a14428b3311 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -337,3 +337,15 @@ export function getMinMax(data: DataArray, vertexBuffer: VertexBuffer, start: nu return { min, max }; } + +export function omitDefaultValues(object: T, defaultValues: Partial): T { + return Object.fromEntries( + Object.entries(object).filter(([key, value]) => { + const defaultValue = defaultValues[key as keyof T]; + if (Array.isArray(value) && Array.isArray(defaultValue) && value.length === defaultValue.length) { + return value.every((val, i) => val !== defaultValue[i]); + } + return value !== defaultValue; + }) + ) as T; +} diff --git a/packages/tools/tests/test/visualization/config.json b/packages/tools/tests/test/visualization/config.json index cfebfc3b8cf..b6a12f48916 100644 --- a/packages/tools/tests/test/visualization/config.json +++ b/packages/tools/tests/test/visualization/config.json @@ -913,6 +913,11 @@ "playgroundId": "#FLXW8B#15", "referenceImage": "glTFSerializerKHRPunctualLight.png" }, + // { + // "title": "GLTF Serializer KHR light handiness", + // "playgroundId": "#FLXW8B#4", + // "referenceImage": "glTFSerializerKHRPunctualLightDirectional.png" + // } { "title": "GLTF Buggy with Draco Mesh Compression", "playgroundId": "#JNW207#1", From 313d3e94f7578e6f356564b1c95757995a4c9240 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Thu, 31 Oct 2024 18:27:17 -0300 Subject: [PATCH 028/133] Fixed skeleton animations and fixed indices flipping --- .../serializers/src/glTF/2.0/glTFAnimation.ts | 13 ++-- .../serializers/src/glTF/2.0/glTFExporter.ts | 71 ++++++++++++------- 2 files changed, 54 insertions(+), 30 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts index bd6632453fe..0a752572a04 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts @@ -391,7 +391,7 @@ export class _GLTFAnimation { bufferViews: IBufferView[], accessors: IAccessor[], animationSampleRate: number, - useRightHanded: boolean, + leftHandedNodes: Set, shouldExportAnimation?: (animation: Animation) => boolean ) { let glTFAnimation: IAnimation; @@ -414,6 +414,9 @@ export class _GLTFAnimation { if (shouldExportAnimation && !shouldExportAnimation(animation)) { continue; } + + const convertToRightHanded = leftHandedNodes.has(target); + if (this._IsTransformable(target) || (target.length === 1 && this._IsTransformable(target[0]))) { const animationInfo = _GLTFAnimation._DeduceAnimationInfo(targetAnimation.animation); if (animationInfo) { @@ -432,7 +435,7 @@ export class _GLTFAnimation { accessors, animationInfo.useQuaternion, animationSampleRate, - useRightHanded + convertToRightHanded ); } } @@ -529,7 +532,7 @@ export class _GLTFAnimation { accessors, animationInfo.useQuaternion, animationSampleRate, - useRightHanded, + false, morphTargetManager?.numTargets ); } @@ -554,7 +557,7 @@ export class _GLTFAnimation { accessors: IAccessor[], useQuaternion: boolean, animationSampleRate: number, - useRightHanded: boolean, + convertToRightHanded: boolean, morphAnimationChannels?: number ) { const animationData = _GLTFAnimation._CreateNodeAnimation(babylonTransformNode, animation, animationChannelTargetPath, useQuaternion, animationSampleRate); @@ -619,7 +622,7 @@ export class _GLTFAnimation { const tempQuaterionArray = [0, 0, 0, 0]; animationData.outputs.forEach(function (output) { - if (useRightHanded) { + if (convertToRightHanded) { switch (animationChannelTargetPath) { case AnimationChannelTargetPath.TRANSLATION: Vector3.FromArrayToRef(output, 0, position); diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index e9313b2f061..a970f34c226 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -88,16 +88,21 @@ class ExporterState { private _vertexMapColorAlpha = new Map(); + private _exportedNodes = new Set(); + // Babylon mesh -> glTF mesh index private _meshMap = new Map(); - public constructor(convertToRightHanded: boolean, userUint16SkinIndex: boolean) { + public constructor(convertToRightHanded: boolean, userUint16SkinIndex: boolean, wasAddedByNoopNode: boolean) { this.convertToRightHanded = convertToRightHanded; this.userUint16SkinIndex = userUint16SkinIndex; + this.wasAddedByNoopNode = wasAddedByNoopNode; } public readonly convertToRightHanded: boolean; + public readonly wasAddedByNoopNode: boolean; + public readonly userUint16SkinIndex: boolean; // Only used when convertToRightHanded is true. @@ -135,6 +140,16 @@ class ExporterState { map4.set(flip, accessorIndex); } + public pushExportedNode(node: Node) { + if (!this._exportedNodes.has(node)) { + this._exportedNodes.add(node); + } + } + + public getNodesSet(): Set { + return this._exportedNodes; + } + public getVertexBufferView(buffer: Buffer): number | undefined { return this._vertexBufferViewMap.get(buffer); } @@ -850,10 +865,11 @@ export class GLTFExporter { const rootNodesRH = new Array(); const rootNodesLH = new Array(); + const rootNoopNodesRH = new Array(); for (const rootNode of this._babylonScene.rootNodes) { if (this._options.removeNoopRootNodes && !this._options.includeCoordinateSystemConversionNodes && isNoopNode(rootNode, this._babylonScene.useRightHandedSystem)) { - rootNodesRH.push(...rootNode.getChildren()); + rootNoopNodesRH.push(...rootNode.getChildren()); } else if (this._babylonScene.useRightHandedSystem) { rootNodesRH.push(rootNode); } else { @@ -865,8 +881,13 @@ export class GLTFExporter { this._listAvailableSkeletons(); // await this._materialExporter.convertMaterialsToGLTFAsync(this._getMaterials(nodes)); - scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, true, this._options.userUint16SkinIndex))); - scene.nodes.push(...(await this._exportNodesAsync(rootNodesRH, false, this._options.userUint16SkinIndex))); + const stateLH = new ExporterState(true, this._options.userUint16SkinIndex, false); + scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, stateLH))); + const stateRH = new ExporterState(false, this._options.userUint16SkinIndex, false); + scene.nodes.push(...(await this._exportNodesAsync(rootNodesRH, stateRH))); + const noopRH = new ExporterState(false, this._options.userUint16SkinIndex, true); + scene.nodes.push(...(await this._exportNodesAsync(rootNoopNodesRH, noopRH))); + this._scenes.push(scene); this._exportAndAssignCameras(); @@ -881,7 +902,7 @@ export class GLTFExporter { this._bufferViews, this._accessors, this._animationSampleRate, - this._babylonScene.useRightHandedSystem + stateLH.getNodesSet() ); } } @@ -897,15 +918,14 @@ export class GLTFExporter { return result; } - private async _exportNodesAsync(babylonRootNodes: Node[], convertToRightHanded: boolean, useUint16SkinIndex: boolean): Promise { + private async _exportNodesAsync(babylonRootNodes: Node[], state: ExporterState): Promise { const nodes = new Array(); - const state = new ExporterState(convertToRightHanded, useUint16SkinIndex); - this._exportBuffers(babylonRootNodes, convertToRightHanded, state); + this._exportBuffers(babylonRootNodes, state); for (const babylonNode of babylonRootNodes) { if (this._shouldExportNode(babylonNode)) { - nodes.push(await this._exportNodeAsync(babylonNode, state, convertToRightHanded)); + nodes.push(await this._exportNodeAsync(babylonNode, state)); } } @@ -964,7 +984,7 @@ export class GLTFExporter { } } - private _exportBuffers(babylonRootNodes: Node[], convertToRightHanded: boolean, state: ExporterState): void { + private _exportBuffers(babylonRootNodes: Node[], state: ExporterState): void { const bufferToVertexBuffersMap = new Map(); const vertexBufferToMeshesMap = new Map(); const morphTagetsMeshesMap = new Map(); @@ -1006,7 +1026,7 @@ export class GLTFExporter { } // Performs coordinate conversion if needed (only for position, normal and tanget). - if (convertToRightHanded) { + if (state.convertToRightHanded) { for (const vertexBuffer of vertexBuffers) { switch (vertexBuffer.getKind()) { case VertexBuffer.PositionKind: @@ -1080,7 +1100,7 @@ export class GLTFExporter { } for (const [morphTarget, meshes] of morphTagetsMeshesMap) { - const glTFMorphTarget = buildMorphTargetBuffers(morphTarget, meshes[0], this._dataWriter, this._bufferViews, this._accessors, convertToRightHanded); + const glTFMorphTarget = buildMorphTargetBuffers(morphTarget, meshes[0], this._dataWriter, this._bufferViews, this._accessors, state.convertToRightHanded); for (const mesh of meshes) { state.bindMorphDataToMesh(mesh, glTFMorphTarget); @@ -1088,7 +1108,7 @@ export class GLTFExporter { } } - private async _exportNodeAsync(babylonNode: Node, state: ExporterState, convertToRightHanded: boolean): Promise { + private async _exportNodeAsync(babylonNode: Node, state: ExporterState): Promise { let nodeIndex = this._nodeMap.get(babylonNode); if (nodeIndex !== undefined) { return nodeIndex; @@ -1109,7 +1129,7 @@ export class GLTFExporter { if (babylonNode instanceof Mesh || babylonNode instanceof InstancedMesh) { const babylonMesh = babylonNode instanceof Mesh ? babylonNode : babylonNode.sourceMesh; if (babylonMesh.subMeshes && babylonMesh.subMeshes.length > 0) { - node.mesh = await this._exportMeshAsync(babylonMesh, state, convertToRightHanded); + node.mesh = await this._exportMeshAsync(babylonMesh, state); } if (babylonNode.skeleton) { @@ -1137,14 +1157,14 @@ export class GLTFExporter { } this._nodesCameraMap.get(gltfCamera)?.push(node); - this._setCameraTransformation(node, babylonNode, convertToRightHanded); + this._setCameraTransformation(node, babylonNode, state.convertToRightHanded); } } for (const babylonChildNode of babylonNode.getChildren()) { if (this._shouldExportNode(babylonChildNode)) { node.children ||= []; - node.children.push(await this._exportNodeAsync(babylonChildNode, state, convertToRightHanded)); + node.children.push(await this._exportNodeAsync(babylonChildNode, state)); } } @@ -1166,7 +1186,7 @@ export class GLTFExporter { this._bufferViews, this._accessors, this._animationSampleRate, - convertToRightHanded, + state.convertToRightHanded, this._options.shouldExportAnimation ); if (babylonNode.animations.length) { @@ -1180,7 +1200,7 @@ export class GLTFExporter { this._bufferViews, this._accessors, this._animationSampleRate, - convertToRightHanded, + state.convertToRightHanded, this._options.shouldExportAnimation ); } @@ -1197,8 +1217,7 @@ export class GLTFExporter { fillMode: number, sideOrientation: number, state: ExporterState, - primitive: IMeshPrimitive, - convertToRightHanded: boolean + primitive: IMeshPrimitive ): void { const is32Bits = areIndices32Bits(indices, count); let indicesToExport = indices; @@ -1206,12 +1225,14 @@ export class GLTFExporter { primitive.mode = getPrimitiveMode(fillMode); // Flip if triangle winding order is not CCW as glTF is always CCW. - let flip = isTriangleFillMode(fillMode) && sideOrientation !== Material.CounterClockWiseSideOrientation; + let invertedMaterial = sideOrientation !== Material.CounterClockWiseSideOrientation; - if (convertToRightHanded) { - flip = !flip; + if (state.convertToRightHanded && !state.wasAddedByNoopNode) { + invertedMaterial = !invertedMaterial; } + const flip = isTriangleFillMode(fillMode) && invertedMaterial; + if (flip) { if (fillMode === Material.TriangleStripDrawMode || fillMode === Material.TriangleFanDrawMode) { throw new Error("Triangle strip/fan fill mode is not implemented"); @@ -1341,7 +1362,7 @@ export class GLTFExporter { primitive.material = materialIndex; } - private async _exportMeshAsync(babylonMesh: Mesh, state: ExporterState, convertToRightHanded: boolean): Promise { + private async _exportMeshAsync(babylonMesh: Mesh, state: ExporterState): Promise { let meshIndex = state.getMesh(babylonMesh); if (meshIndex !== undefined) { return meshIndex; @@ -1369,7 +1390,7 @@ export class GLTFExporter { const fillMode = babylonMesh.overrideRenderingFillMode ?? babylonMaterial.fillMode; const sideOrientation = babylonMaterial._getEffectiveOrientation(babylonMesh); - this._exportIndices(indices, subMesh.indexStart, subMesh.indexCount, -subMesh.verticesStart, fillMode, sideOrientation, state, primitive, convertToRightHanded); + this._exportIndices(indices, subMesh.indexStart, subMesh.indexCount, -subMesh.verticesStart, fillMode, sideOrientation, state, primitive); // Vertex buffers for (const vertexBuffer of Object.values(vertexBuffers)) { From 650ae0fee9d5339bf55d8f78cc5d2ef369b4bb99 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Thu, 31 Oct 2024 17:56:07 -0400 Subject: [PATCH 029/133] Ensure nodes are skippable via postExportNodeAsync, and that parent node is always "created" before children-- all to fix a light parenting issue --- .../2.0/Extensions/KHR_lights_punctual.ts | 26 +++++----- .../serializers/src/glTF/2.0/glTFExporter.ts | 48 ++++++++++++++----- .../src/glTF/2.0/glTFExporterExtension.ts | 2 +- .../serializers/src/glTF/2.0/glTFUtilities.ts | 7 +++ 4 files changed, 58 insertions(+), 25 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index f173c8c66aa..6123ee4699a 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -1,7 +1,6 @@ import type { SpotLight } from "core/Lights/spotLight"; import type { Nullable } from "core/types"; import { Vector3, Quaternion, TmpVectors, Matrix } from "core/Maths/math.vector"; -import { Color3 } from "core/Maths/math.color"; import { Light } from "core/Lights/light"; import type { Node } from "core/node"; import { ShadowLight } from "core/Lights/shadowLight"; @@ -26,12 +25,12 @@ const SPOTDEFAULTS: IKHRLightsPunctual_Light["spot"] = { outerConeAngle: Math.PI / 4.0, }; -// TODO: Move elsewhere, since this is common -const NODEDEFAULTS: Partial = { - translation: [0, 0, 0], - rotation: [0, 0, 0, 1], - scale: [1, 1, 1], -}; +// // TODO: Move elsewhere, since this is common +// const NODEDEFAULTS: Partial = { +// translation: [0, 0, 0], +// rotation: [0, 0, 0, 1], +// scale: [1, 1, 1], +// }; /** * [Specification](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md) @@ -83,6 +82,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { */ public postExportNodeAsync(context: string, node: Nullable, babylonNode: Node, nodeMap: Map): Promise> { return new Promise((resolve) => { + // If node was nullified (marked as skippable) earlier in the pipeline, or it's not a light, skip if (!node || !(babylonNode instanceof ShadowLight)) { resolve(node); return; @@ -111,6 +111,8 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { // node.translation = babylonNode.position.asArray(); // } + // TODO: Test light w/o parent node and handedness + // Override the node's rotation with the light's direction, since glTF uses a constant light direction if (lightType !== KHRLightsPunctual_LightType.POINT) { const localAxis = babylonNode.direction; @@ -132,6 +134,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { }; light = omitDefaultValues(light, DEFAULTS); + // Set the required 'spot' field for spot lights, then check its contents for defaults if (lightType === KHRLightsPunctual_LightType.SPOT) { const babylonSpotLight = babylonNode as SpotLight; light.spot = { @@ -150,12 +153,13 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { light: this._lights.lights.length - 1, }; - // Assign the light to its parent node, if possible + // Assign the light to its parent node, if possible, to condense the glTF + // Why and when: the glTF loader generates a new parent node for each light node, which isn't needed in glTF const parentBabylonNode = babylonNode.parent; - if (parentBabylonNode && parentBabylonNode.getChildren().length == 1) { + if (parentBabylonNode && parentBabylonNode.getChildren().length == 1 && babylonNode.getChildren().length == 0) { const parentNode = this._exporter._nodes[nodeMap.get(parentBabylonNode)!]; if (parentNode) { - // Consolidate the light's transformation with the parent's + // Combine the light's transformation with the parent's const parentTranslation = Vector3.FromArrayToRef(parentNode.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); const parentRotation = Quaternion.FromArrayToRef(parentNode.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); const parentScale = Vector3.FromArrayToRef(parentNode.scale || [1, 1, 1], 0, TmpVectors.Vector3[1]); @@ -168,7 +172,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { parentMatrix.multiplyToRef(matrix, matrix); matrix.decompose(parentScale, parentRotation, parentTranslation); - // Remove default values + // Remove default values if they are now default if (parentTranslation.equalsToFloats(0, 0, 0)) { delete parentNode.translation; } else { diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index b27b736cf53..1d24e41ca01 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1357,7 +1357,11 @@ export class GLTFExporter { for (const babylonNode of babylonRootNodes) { if (this._shouldExportNode(babylonNode)) { - nodes.push(await this._exportNodeAsync(babylonNode, state, convertToRightHanded)); + // TODO: Do we need to await this? + const nodeIndex = await this._exportNodeAsync(babylonNode, state, convertToRightHanded); + if (nodeIndex) { + nodes.push(nodeIndex); + } } } @@ -1540,16 +1544,19 @@ export class GLTFExporter { } } - private async _exportNodeAsync(babylonNode: Node, state: ExporterState, convertToRightHanded: boolean): Promise { + /** + * Processes a node to be exported to the glTF file + * @returns A promise that resolves with the node index when the processing is complete, or null if the node should not be exported + * @internal + */ + private async _exportNodeAsync(babylonNode: Node, state: ExporterState, convertToRightHanded: boolean): Promise> { let nodeIndex = this._nodeMap.get(babylonNode); if (nodeIndex !== undefined) { return nodeIndex; } + // Create node to hold translation/rotation/scale and the mesh const node: INode = {}; - nodeIndex = this._nodes.length; - this._nodes.push(node); - this._nodeMap.set(babylonNode, nodeIndex); if (babylonNode.name) { node.name = babylonNode.name; @@ -1581,6 +1588,7 @@ export class GLTFExporter { } if (babylonNode instanceof Camera) { + // TODO: Do light technique here. const gltfCamera = this._camerasMap.get(babylonNode); if (gltfCamera) { @@ -1593,13 +1601,6 @@ export class GLTFExporter { } } - for (const babylonChildNode of babylonNode.getChildren()) { - if (this._shouldExportNode(babylonChildNode)) { - node.children ||= []; - node.children.push(await this._exportNodeAsync(babylonChildNode, state, convertToRightHanded)); - } - } - const runtimeGLTFAnimation: IAnimation = { name: "runtime animations", channels: [], @@ -1638,7 +1639,28 @@ export class GLTFExporter { } } - this._extensionsPostExportNodeAsync("exportNodeAsync", node, babylonNode, this._nodeMap); + // Apply extensions to the node. If this resolves to null, it means we can skip exporting this node and its children. + const processedNode = await this._extensionsPostExportNodeAsync("exportNodeAsync", node, babylonNode, this._nodeMap); + if (!processedNode) { + Logger.Warn(`Not exporting node ${babylonNode.name}`); + return null; + } + + nodeIndex = this._nodes.length; + this._nodes.push(node); + this._nodeMap.set(babylonNode, nodeIndex); + + // Begin processing child nodes only after parent is finished + for (const babylonChildNode of babylonNode.getChildren()) { + if (this._shouldExportNode(babylonChildNode)) { + node.children ||= []; + // TODO: Do we need to await this? + const childNodeIndex = await this._exportNodeAsync(babylonChildNode, state, convertToRightHanded); + if (childNodeIndex) { + node.children.push(childNodeIndex); + } + } + } return nodeIndex; } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts index 64e76dd310d..3e7e029365b 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts @@ -50,7 +50,7 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo * @param context The context when exporting the node * @param node glTF node * @param babylonNode BabylonJS node - * @param nodeMap Node mapping of babylon node to glTF node index + * @param nodeMap Current node mapping of babylon node to glTF node index. Useful for combining a node with its parent. * @returns nullable INode promise */ postExportNodeAsync?(context: string, node: Nullable, babylonNode: Node, nodeMap: Map): Promise>; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index a14428b3311..13b15d3fd86 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -338,6 +338,13 @@ export function getMinMax(data: DataArray, vertexBuffer: VertexBuffer, start: nu return { min, max }; } +/** + * Removes keys from an object that have the same value as the default values. + * Useful for avoiding unnecessary properties in the glTF JSON. + * @param object the object to omit default values from + * @param defaultValues a partial object with default values + * @returns new object with default values omitted + */ export function omitDefaultValues(object: T, defaultValues: Partial): T { return Object.fromEntries( Object.entries(object).filter(([key, value]) => { From 54cacbdc17917f65948d203a92566bb297a0c7b2 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Thu, 31 Oct 2024 19:13:54 -0300 Subject: [PATCH 030/133] Added logic to handle triangle flipping --- .../dev/serializers/src/glTF/2.0/glTFExporter.ts | 14 +++++++++----- 1 file changed, 9 insertions(+), 5 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index a970f34c226..5e6a9ac18e8 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1225,13 +1225,17 @@ export class GLTFExporter { primitive.mode = getPrimitiveMode(fillMode); // Flip if triangle winding order is not CCW as glTF is always CCW. - let invertedMaterial = sideOrientation !== Material.CounterClockWiseSideOrientation; + const invertedMaterial = sideOrientation !== Material.CounterClockWiseSideOrientation; - if (state.convertToRightHanded && !state.wasAddedByNoopNode) { - invertedMaterial = !invertedMaterial; - } + // Temporary logic to handle indices flipping. Not sure how this is working yet. + const A = state.wasAddedByNoopNode; + const B = state.convertToRightHanded; + const C = invertedMaterial; + + const result1 = !A && !B; + const result2 = !A && C; - const flip = isTriangleFillMode(fillMode) && invertedMaterial; + const flip = isTriangleFillMode(fillMode) && (result1 || result2); if (flip) { if (fillMode === Material.TriangleStripDrawMode || fillMode === Material.TriangleFanDrawMode) { From 943870a7f71dfd8531f09c2b6cd7dba2128e4d5c Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Fri, 1 Nov 2024 15:37:34 -0300 Subject: [PATCH 031/133] Code cleanup, added missing code to push nodes to node table in the context --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 9 +++------ 1 file changed, 3 insertions(+), 6 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 5e6a9ac18e8..8906baeb424 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1118,6 +1118,7 @@ export class GLTFExporter { nodeIndex = this._nodes.length; this._nodes.push(node); this._nodeMap.set(babylonNode, nodeIndex); + state.pushExportedNode(babylonNode); if (babylonNode.name) { node.name = babylonNode.name; @@ -1228,12 +1229,8 @@ export class GLTFExporter { const invertedMaterial = sideOrientation !== Material.CounterClockWiseSideOrientation; // Temporary logic to handle indices flipping. Not sure how this is working yet. - const A = state.wasAddedByNoopNode; - const B = state.convertToRightHanded; - const C = invertedMaterial; - - const result1 = !A && !B; - const result2 = !A && C; + const result1 = !state.wasAddedByNoopNode && !state.convertToRightHanded; + const result2 = !state.wasAddedByNoopNode && invertedMaterial; const flip = isTriangleFillMode(fillMode) && (result1 || result2); From 34783e4cc4a38de7722c1f326bd0f113df4db1e6 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 4 Nov 2024 10:15:18 -0500 Subject: [PATCH 032/133] Fix skipping node at index 0; convert handiness of light WIP --- .../2.0/Extensions/KHR_lights_punctual.ts | 26 +++++++++++-------- .../serializers/src/glTF/2.0/glTFExporter.ts | 21 ++++++++++----- .../src/glTF/2.0/glTFExporterExtension.ts | 3 ++- .../tests/test/visualization/config.json | 14 +++++----- 4 files changed, 40 insertions(+), 24 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index 6123ee4699a..70944317090 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -9,7 +9,7 @@ import { KHRLightsPunctual_LightType } from "babylonjs-gltf2interface"; import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; import { GLTFExporter } from "../glTFExporter"; import { Logger } from "core/Misc/logger"; -import { omitDefaultValues } from "../glTFUtilities"; +import { convertToRightHandedPosition, omitDefaultValues } from "../glTFUtilities"; const NAME = "KHR_lights_punctual"; @@ -78,9 +78,10 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { * @param node glTF node * @param babylonNode BabylonJS node * @param nodeMap Node mapping of babylon node to glTF node index + * @param convertToRightHanded Flag to convert the values to right-handed * @returns nullable INode promise */ - public postExportNodeAsync(context: string, node: Nullable, babylonNode: Node, nodeMap: Map): Promise> { + public postExportNodeAsync(context: string, node: Nullable, babylonNode: Node, nodeMap: Map, convertToRightHanded: boolean): Promise> { return new Promise((resolve) => { // If node was nullified (marked as skippable) earlier in the pipeline, or it's not a light, skip if (!node || !(babylonNode instanceof ShadowLight)) { @@ -106,14 +107,17 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { Logger.Warn(`${context}: Light falloff for ${babylonNode.name} does not match the ${NAME} specification!`); } - // TODO: Is this needed? - // if (!babylonNode.position.equalsToFloats(0, 0, 0)) { - // node.translation = babylonNode.position.asArray(); - // } - - // TODO: Test light w/o parent node and handedness + // Set the node's translation and rotation here, since lights are not handled in exportNodeAsync + if (!babylonNode.position.equalsToFloats(0, 0, 0)) { + const translation = TmpVectors.Vector3[0].copyFrom(babylonNode.position); + if (convertToRightHanded) { + convertToRightHandedPosition(translation); + } + node.translation = translation.asArray(); + } - // Override the node's rotation with the light's direction, since glTF uses a constant light direction + // Use the light's direction as the node's rotation, since in glTF, lights have a constant direction + // TODO: Fix. This was wrong originally, anyway. if (lightType !== KHRLightsPunctual_LightType.POINT) { const localAxis = babylonNode.direction; const yaw = -Math.atan2(localAxis.z, localAxis.x) + Math.PI / 2; @@ -121,7 +125,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { const pitch = -Math.atan2(localAxis.y, len); const lightRotationQuaternion = Quaternion.RotationYawPitchRoll(yaw + Math.PI, pitch, 0); if (!Quaternion.IsIdentity(lightRotationQuaternion)) { - node.rotation = lightRotationQuaternion.asArray(); + node.rotation = lightRotationQuaternion.normalize().asArray(); } } @@ -154,7 +158,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { }; // Assign the light to its parent node, if possible, to condense the glTF - // Why and when: the glTF loader generates a new parent node for each light node, which isn't needed in glTF + // Why and when: the glTF loader generates a new parent TransformNode for each light node, which isn't needed for glTF const parentBabylonNode = babylonNode.parent; if (parentBabylonNode && parentBabylonNode.getChildren().length == 1 && babylonNode.getChildren().length == 0) { const parentNode = this._exporter._nodes[nodeMap.get(parentBabylonNode)!]; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 1d24e41ca01..6a2bae01419 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -297,8 +297,17 @@ export class GLTFExporter { ); } - public _extensionsPostExportNodeAsync(context: string, node: Nullable, babylonNode: Node, nodeMap: Map): Promise> { - return this._applyExtensions(node, (extension, node) => extension.postExportNodeAsync && extension.postExportNodeAsync(context, node, babylonNode, nodeMap)); + public _extensionsPostExportNodeAsync( + context: string, + node: Nullable, + babylonNode: Node, + nodeMap: Map, + convertToRightHanded: boolean + ): Promise> { + return this._applyExtensions( + node, + (extension, node) => extension.postExportNodeAsync && extension.postExportNodeAsync(context, node, babylonNode, nodeMap, convertToRightHanded) + ); } public _extensionsPostExportMaterialAsync(context: string, material: Nullable, babylonMaterial: Material): Promise> { @@ -1359,7 +1368,7 @@ export class GLTFExporter { if (this._shouldExportNode(babylonNode)) { // TODO: Do we need to await this? const nodeIndex = await this._exportNodeAsync(babylonNode, state, convertToRightHanded); - if (nodeIndex) { + if (nodeIndex !== null) { nodes.push(nodeIndex); } } @@ -1588,7 +1597,7 @@ export class GLTFExporter { } if (babylonNode instanceof Camera) { - // TODO: Do light technique here. + // TODO: Do light technique here (don't duplicate parent node) const gltfCamera = this._camerasMap.get(babylonNode); if (gltfCamera) { @@ -1640,7 +1649,7 @@ export class GLTFExporter { } // Apply extensions to the node. If this resolves to null, it means we can skip exporting this node and its children. - const processedNode = await this._extensionsPostExportNodeAsync("exportNodeAsync", node, babylonNode, this._nodeMap); + const processedNode = await this._extensionsPostExportNodeAsync("exportNodeAsync", node, babylonNode, this._nodeMap, convertToRightHanded); if (!processedNode) { Logger.Warn(`Not exporting node ${babylonNode.name}`); return null; @@ -1656,7 +1665,7 @@ export class GLTFExporter { node.children ||= []; // TODO: Do we need to await this? const childNodeIndex = await this._exportNodeAsync(babylonChildNode, state, convertToRightHanded); - if (childNodeIndex) { + if (childNodeIndex !== null) { node.children.push(childNodeIndex); } } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts index 3e7e029365b..bd8fc1e6a80 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts @@ -51,9 +51,10 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo * @param node glTF node * @param babylonNode BabylonJS node * @param nodeMap Current node mapping of babylon node to glTF node index. Useful for combining a node with its parent. + * @param convertToRightHanded Flag indicating whether to convert values to right-handed * @returns nullable INode promise */ - postExportNodeAsync?(context: string, node: Nullable, babylonNode: Node, nodeMap: Map): Promise>; + postExportNodeAsync?(context: string, node: Nullable, babylonNode: Node, nodeMap: Map, convertToRightHanded: boolean): Promise>; /** * Define this method to modify the default behavior when exporting a material diff --git a/packages/tools/tests/test/visualization/config.json b/packages/tools/tests/test/visualization/config.json index b6a12f48916..f22c8575627 100644 --- a/packages/tools/tests/test/visualization/config.json +++ b/packages/tools/tests/test/visualization/config.json @@ -910,14 +910,16 @@ }, { "title": "GLTF Serializer KHR punctual light", - "playgroundId": "#FLXW8B#15", + "playgroundId": "#FLXW8B#24", + "replace": "//options//, roundtrip = true; useRightHandedSystem = false;", + "referenceImage": "glTFSerializerKHRPunctualLight.png" + }, + { + "title": "GLTF Serializer KHR punctual light, right-handed", + "playgroundId": "#FLXW8B#24", + "replace": "//options//, roundtrip = true; useRightHandedSystem = true;", "referenceImage": "glTFSerializerKHRPunctualLight.png" }, - // { - // "title": "GLTF Serializer KHR light handiness", - // "playgroundId": "#FLXW8B#4", - // "referenceImage": "glTFSerializerKHRPunctualLightDirectional.png" - // } { "title": "GLTF Buggy with Draco Mesh Compression", "playgroundId": "#JNW207#1", From 07dd1e2c6744513e9fdd566386b86171726f0e7b Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:14:32 -0500 Subject: [PATCH 033/133] Fix light rotation for both LH and RH --- .../2.0/Extensions/KHR_lights_punctual.ts | 31 ++++++++----------- 1 file changed, 13 insertions(+), 18 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index 70944317090..9c1e71cdb76 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -12,25 +12,17 @@ import { Logger } from "core/Misc/logger"; import { convertToRightHandedPosition, omitDefaultValues } from "../glTFUtilities"; const NAME = "KHR_lights_punctual"; - const DEFAULTS: Partial = { name: "", color: [1, 1, 1], intensity: 1, range: Number.MAX_VALUE, }; - const SPOTDEFAULTS: IKHRLightsPunctual_Light["spot"] = { innerConeAngle: 0, outerConeAngle: Math.PI / 4.0, }; - -// // TODO: Move elsewhere, since this is common -// const NODEDEFAULTS: Partial = { -// translation: [0, 0, 0], -// rotation: [0, 0, 0, 1], -// scale: [1, 1, 1], -// }; +const LIGHTDIRECTION = Vector3.Backward(); /** * [Specification](https://github.com/KhronosGroup/glTF/blob/master/extensions/2.0/Khronos/KHR_lights_punctual/README.md) @@ -116,14 +108,17 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { node.translation = translation.asArray(); } - // Use the light's direction as the node's rotation, since in glTF, lights have a constant direction - // TODO: Fix. This was wrong originally, anyway. + // Babylon lights have "constant" rotation and variable direction, while + // glTF lights have variable rotation and constant direction. Therefore, + // compute a quaternion that aligns the Babylon light's direction with glTF's constant one. if (lightType !== KHRLightsPunctual_LightType.POINT) { - const localAxis = babylonNode.direction; - const yaw = -Math.atan2(localAxis.z, localAxis.x) + Math.PI / 2; - const len = Math.sqrt(localAxis.x * localAxis.x + localAxis.z * localAxis.z); - const pitch = -Math.atan2(localAxis.y, len); - const lightRotationQuaternion = Quaternion.RotationYawPitchRoll(yaw + Math.PI, pitch, 0); + const direction = babylonNode.direction.normalize(); + if (convertToRightHanded) { + convertToRightHandedPosition(direction); + } + const angle = Math.acos(Vector3.Dot(LIGHTDIRECTION, direction)); + const axis = Vector3.Cross(LIGHTDIRECTION, direction); + const lightRotationQuaternion = Quaternion.RotationAxis(axis, angle); if (!Quaternion.IsIdentity(lightRotationQuaternion)) { node.rotation = lightRotationQuaternion.normalize().asArray(); } @@ -138,7 +133,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { }; light = omitDefaultValues(light, DEFAULTS); - // Set the required 'spot' field for spot lights, then check its contents for defaults + // Separately handle the required 'spot' field for spot lights if (lightType === KHRLightsPunctual_LightType.SPOT) { const babylonSpotLight = babylonNode as SpotLight; light.spot = { @@ -158,7 +153,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { }; // Assign the light to its parent node, if possible, to condense the glTF - // Why and when: the glTF loader generates a new parent TransformNode for each light node, which isn't needed for glTF + // Why and when: the glTF loader generates a new parent TransformNode for each light node, which we should undo on export const parentBabylonNode = babylonNode.parent; if (parentBabylonNode && parentBabylonNode.getChildren().length == 1 && babylonNode.getChildren().length == 0) { const parentNode = this._exporter._nodes[nodeMap.get(parentBabylonNode)!]; From 7be933e1493794abbaa0da4fb41e8268af3dd37c Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 4 Nov 2024 11:46:39 -0500 Subject: [PATCH 034/133] Fix possible empty children array (invalid glTF) if nodeIndex is null --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 6a2bae01419..5159d2191c7 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1662,10 +1662,10 @@ export class GLTFExporter { // Begin processing child nodes only after parent is finished for (const babylonChildNode of babylonNode.getChildren()) { if (this._shouldExportNode(babylonChildNode)) { - node.children ||= []; // TODO: Do we need to await this? const childNodeIndex = await this._exportNodeAsync(babylonChildNode, state, convertToRightHanded); if (childNodeIndex !== null) { + node.children ||= []; node.children.push(childNodeIndex); } } From 0d9aae37d260fa1ea5eb0b91b061c34fd2e55e8d Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 4 Nov 2024 12:01:09 -0500 Subject: [PATCH 035/133] Update vis tests for lights --- .../glTFSerializerKHRPunctualLight.png | Bin 10358 -> 0 bytes .../glTFSerializerKHRPunctualLightLH.png | Bin 0 -> 11159 bytes .../glTFSerializerKHRPunctualLightRH.png | Bin 0 -> 11664 bytes .../tools/tests/test/visualization/config.json | 10 +++++----- 4 files changed, 5 insertions(+), 5 deletions(-) delete mode 100644 packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerKHRPunctualLight.png create mode 100644 packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerKHRPunctualLightLH.png create mode 100644 packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerKHRPunctualLightRH.png diff --git a/packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerKHRPunctualLight.png b/packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerKHRPunctualLight.png deleted file mode 100644 index 0f87ed387ffc4ececd6a883cf6930f1285923924..0000000000000000000000000000000000000000 GIT binary patch literal 0 HcmV?d00001 literal 10358 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 Dzm#%3 literal 0 HcmV?d00001 diff --git a/packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerKHRPunctualLightRH.png b/packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerKHRPunctualLightRH.png new file mode 100644 index 0000000000000000000000000000000000000000..c8fe73e3b1d871e4ec638e4da2e7a039aa212068 GIT binary patch literal 11664 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 j|Ic&#|IdN^` Date: Mon, 4 Nov 2024 12:53:38 -0500 Subject: [PATCH 036/133] Add specificity to light parent check --- .../src/glTF/2.0/Extensions/KHR_lights_punctual.ts | 8 +++++--- 1 file changed, 5 insertions(+), 3 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index 9c1e71cdb76..95afeb429cd 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -5,6 +5,7 @@ import { Light } from "core/Lights/light"; import type { Node } from "core/node"; import { ShadowLight } from "core/Lights/shadowLight"; import type { INode, IKHRLightsPunctual_LightReference, IKHRLightsPunctual_Light, IKHRLightsPunctual } from "babylonjs-gltf2interface"; +import { TransformNode } from "core/Meshes/transformNode"; import { KHRLightsPunctual_LightType } from "babylonjs-gltf2interface"; import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; import { GLTFExporter } from "../glTFExporter"; @@ -155,10 +156,11 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { // Assign the light to its parent node, if possible, to condense the glTF // Why and when: the glTF loader generates a new parent TransformNode for each light node, which we should undo on export const parentBabylonNode = babylonNode.parent; - if (parentBabylonNode && parentBabylonNode.getChildren().length == 1 && babylonNode.getChildren().length == 0) { - const parentNode = this._exporter._nodes[nodeMap.get(parentBabylonNode)!]; - if (parentNode) { + if (parentBabylonNode && parentBabylonNode instanceof TransformNode && parentBabylonNode.getChildren().length == 1 && babylonNode.getChildren().length == 0) { + const parentNodeIndex = nodeMap.get(parentBabylonNode); + if (parentNodeIndex) { // Combine the light's transformation with the parent's + const parentNode = this._exporter._nodes[parentNodeIndex]; const parentTranslation = Vector3.FromArrayToRef(parentNode.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); const parentRotation = Quaternion.FromArrayToRef(parentNode.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); const parentScale = Vector3.FromArrayToRef(parentNode.scale || [1, 1, 1], 0, TmpVectors.Vector3[1]); From b72975e4a1b65ba590a76dcce190ccb2c3bc9bea Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:42:05 -0500 Subject: [PATCH 037/133] comments --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 35019b1d891..1a00303aecb 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1216,7 +1216,7 @@ export class GLTFExporter { } } - // Apply extensions to the node. If this resolves to null, it means we can skip exporting this node and its children. + // Apply extensions to the node. If this resolves to null, it means we should skip exporting this node (NOTE: This will also skip its children) const processedNode = await this._extensionsPostExportNodeAsync("exportNodeAsync", node, babylonNode, this._nodeMap, state.convertToRightHanded); if (!processedNode) { Logger.Warn(`Not exporting node ${babylonNode.name}`); @@ -1228,7 +1228,7 @@ export class GLTFExporter { this._nodeMap.set(babylonNode, nodeIndex); state.pushExportedNode(babylonNode); - // Begin processing child nodes once parent is finished + // Begin processing child nodes once parent has been added to the node map for (const babylonChildNode of babylonNode.getChildren()) { if (this._shouldExportNode(babylonChildNode)) { const childNodeIndex = await this._exportNodeAsync(babylonChildNode, state); From d8dab33def8c8cfd0fcd7fef46ae1bc4d3846383 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Tue, 5 Nov 2024 15:48:37 -0500 Subject: [PATCH 038/133] Ok, more comments --- .../src/glTF/2.0/Extensions/KHR_lights_punctual.ts | 2 ++ packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 5 ++--- .../dev/serializers/src/glTF/2.0/glTFExporterExtension.ts | 2 +- 3 files changed, 5 insertions(+), 4 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index 95afeb429cd..fcb4d2e555f 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -156,6 +156,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { // Assign the light to its parent node, if possible, to condense the glTF // Why and when: the glTF loader generates a new parent TransformNode for each light node, which we should undo on export const parentBabylonNode = babylonNode.parent; + // TODO: May be able to simplify this logic by using our previous check for the root node if (parentBabylonNode && parentBabylonNode instanceof TransformNode && parentBabylonNode.getChildren().length == 1 && babylonNode.getChildren().length == 0) { const parentNodeIndex = nodeMap.get(parentBabylonNode); if (parentNodeIndex) { @@ -174,6 +175,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { matrix.decompose(parentScale, parentRotation, parentTranslation); // Remove default values if they are now default + // TODO: Find good, common place to store node transformation defaults and use them here if (parentTranslation.equalsToFloats(0, 0, 0)) { delete parentNode.translation; } else { diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 1a00303aecb..74a36412949 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1132,7 +1132,6 @@ export class GLTFExporter { return nodeIndex; } - // Create node to hold translation/rotation/scale and the mesh const node: INode = {}; if (babylonNode.name) { @@ -1165,7 +1164,7 @@ export class GLTFExporter { } if (babylonNode instanceof Camera) { - // TODO: Do light technique here (don't duplicate parent node) + // TODO: Combine any TransformNode parent with child camera node, like we do for lights const gltfCamera = this._camerasMap.get(babylonNode); if (gltfCamera) { @@ -1228,7 +1227,7 @@ export class GLTFExporter { this._nodeMap.set(babylonNode, nodeIndex); state.pushExportedNode(babylonNode); - // Begin processing child nodes once parent has been added to the node map + // Begin processing child nodes once parent has been added to the node list for (const babylonChildNode of babylonNode.getChildren()) { if (this._shouldExportNode(babylonChildNode)) { const childNodeIndex = await this._exportNodeAsync(babylonChildNode, state); diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts index bd8fc1e6a80..65918e2e845 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts @@ -50,7 +50,7 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo * @param context The context when exporting the node * @param node glTF node * @param babylonNode BabylonJS node - * @param nodeMap Current node mapping of babylon node to glTF node index. Useful for combining a node with its parent. + * @param nodeMap Current node mapping of babylon node to glTF node index. Useful for combining nodes together. * @param convertToRightHanded Flag indicating whether to convert values to right-handed * @returns nullable INode promise */ From b67e087b53a18d074d4fe01e998e8c32b8f58a28 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Tue, 12 Nov 2024 18:03:35 -0300 Subject: [PATCH 039/133] Added fix for camera roundtrip --- .../2.0/Extensions/KHR_lights_punctual.ts | 41 +----- .../serializers/src/glTF/2.0/glTFAnimation.ts | 63 ++++++--- .../serializers/src/glTF/2.0/glTFExporter.ts | 133 +++++++++++------- .../serializers/src/glTF/2.0/glTFUtilities.ts | 68 ++++++++- 4 files changed, 199 insertions(+), 106 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index fcb4d2e555f..fcecfdf5c8b 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -1,16 +1,15 @@ import type { SpotLight } from "core/Lights/spotLight"; import type { Nullable } from "core/types"; -import { Vector3, Quaternion, TmpVectors, Matrix } from "core/Maths/math.vector"; +import { Vector3, Quaternion, TmpVectors } from "core/Maths/math.vector"; import { Light } from "core/Lights/light"; import type { Node } from "core/node"; import { ShadowLight } from "core/Lights/shadowLight"; import type { INode, IKHRLightsPunctual_LightReference, IKHRLightsPunctual_Light, IKHRLightsPunctual } from "babylonjs-gltf2interface"; -import { TransformNode } from "core/Meshes/transformNode"; import { KHRLightsPunctual_LightType } from "babylonjs-gltf2interface"; import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; import { GLTFExporter } from "../glTFExporter"; import { Logger } from "core/Misc/logger"; -import { convertToRightHandedPosition, omitDefaultValues } from "../glTFUtilities"; +import { convertToRightHandedPosition, omitDefaultValues, colapseParentNode, isImporterAddedNode } from "../glTFUtilities"; const NAME = "KHR_lights_punctual"; const DEFAULTS: Partial = { @@ -156,44 +155,14 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { // Assign the light to its parent node, if possible, to condense the glTF // Why and when: the glTF loader generates a new parent TransformNode for each light node, which we should undo on export const parentBabylonNode = babylonNode.parent; + // TODO: May be able to simplify this logic by using our previous check for the root node - if (parentBabylonNode && parentBabylonNode instanceof TransformNode && parentBabylonNode.getChildren().length == 1 && babylonNode.getChildren().length == 0) { + if (parentBabylonNode && isImporterAddedNode(babylonNode, parentBabylonNode)) { const parentNodeIndex = nodeMap.get(parentBabylonNode); if (parentNodeIndex) { // Combine the light's transformation with the parent's const parentNode = this._exporter._nodes[parentNodeIndex]; - const parentTranslation = Vector3.FromArrayToRef(parentNode.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); - const parentRotation = Quaternion.FromArrayToRef(parentNode.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); - const parentScale = Vector3.FromArrayToRef(parentNode.scale || [1, 1, 1], 0, TmpVectors.Vector3[1]); - const parentMatrix = Matrix.ComposeToRef(parentScale, parentRotation, parentTranslation, TmpVectors.Matrix[0]); - - const translation = Vector3.FromArrayToRef(node.translation || [0, 0, 0], 0, TmpVectors.Vector3[2]); - const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); - const matrix = Matrix.ComposeToRef(Vector3.OneReadOnly, rotation, translation, TmpVectors.Matrix[1]); - - parentMatrix.multiplyToRef(matrix, matrix); - matrix.decompose(parentScale, parentRotation, parentTranslation); - - // Remove default values if they are now default - // TODO: Find good, common place to store node transformation defaults and use them here - if (parentTranslation.equalsToFloats(0, 0, 0)) { - delete parentNode.translation; - } else { - parentNode.translation = parentTranslation.asArray(); - } - - if (Quaternion.IsIdentity(parentRotation)) { - delete parentNode.rotation; - } else { - parentNode.rotation = parentRotation.asArray(); - } - - if (parentScale.equalsToFloats(1, 1, 1)) { - delete parentNode.scale; - } else { - parentNode.scale = parentScale.asArray(); - } - + colapseParentNode(node, parentNode); parentNode.extensions ||= {}; parentNode.extensions[NAME] = lightReference; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts index 0a752572a04..3e9ea4c1d54 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts @@ -16,7 +16,14 @@ import { AnimationKeyInterpolation } from "core/Animations/animationKey"; import { Camera } from "core/Cameras/camera"; import { Light } from "core/Lights/light"; import type { DataWriter } from "./dataWriter"; -import { createAccessor, createBufferView, getAccessorElementCount, convertToRightHandedRotation, convertToRightHandedPosition } from "./glTFUtilities"; +import { + createAccessor, + createBufferView, + getAccessorElementCount, + convertToRightHandedPosition, + convertCameraRotationToGLTF, + convertToRightHandedRotation, +} from "./glTFUtilities"; /** * @internal @@ -620,6 +627,7 @@ export class _GLTFAnimation { const eulerVec3 = new Vector3(); const position = new Vector3(); const tempQuaterionArray = [0, 0, 0, 0]; + const isCamera = babylonTransformNode instanceof Camera; animationData.outputs.forEach(function (output) { if (convertToRightHanded) { @@ -635,23 +643,25 @@ export class _GLTFAnimation { case AnimationChannelTargetPath.ROTATION: if (output.length === 4) { Quaternion.FromArrayToRef(output, 0, rotationQuaternion); - convertToRightHandedRotation(rotationQuaternion); - rotationQuaternion.toArray(tempQuaterionArray); - binaryWriter.writeFloat32(tempQuaterionArray[0]); - binaryWriter.writeFloat32(tempQuaterionArray[1]); - binaryWriter.writeFloat32(tempQuaterionArray[2]); - binaryWriter.writeFloat32(tempQuaterionArray[3]); } else { Vector3.FromArrayToRef(output, 0, eulerVec3); Quaternion.FromEulerVectorToRef(eulerVec3, rotationQuaternion); + } + + if (isCamera) { + convertCameraRotationToGLTF(rotationQuaternion); + } + + if (!Quaternion.IsIdentity(rotationQuaternion)) { convertToRightHandedRotation(rotationQuaternion); - rotationQuaternion.toArray(tempQuaterionArray); - binaryWriter.writeFloat32(tempQuaterionArray[0]); - binaryWriter.writeFloat32(tempQuaterionArray[1]); - binaryWriter.writeFloat32(tempQuaterionArray[2]); - binaryWriter.writeFloat32(tempQuaterionArray[3]); } + rotationQuaternion.toArray(tempQuaterionArray); + binaryWriter.writeFloat32(tempQuaterionArray[0]); + binaryWriter.writeFloat32(tempQuaterionArray[1]); + binaryWriter.writeFloat32(tempQuaterionArray[2]); + binaryWriter.writeFloat32(tempQuaterionArray[3]); + break; default: @@ -661,14 +671,35 @@ export class _GLTFAnimation { break; } } else { - output.forEach(function (entry) { - binaryWriter.writeFloat32(entry); - }); + switch (animationChannelTargetPath) { + case AnimationChannelTargetPath.ROTATION: + if (output.length === 4) { + Quaternion.FromArrayToRef(output, 0, rotationQuaternion); + } else { + Vector3.FromArrayToRef(output, 0, eulerVec3); + Quaternion.FromEulerVectorToRef(eulerVec3, rotationQuaternion); + } + if (isCamera) { + convertCameraRotationToGLTF(rotationQuaternion); + } + rotationQuaternion.toArray(tempQuaterionArray); + binaryWriter.writeFloat32(tempQuaterionArray[0]); + binaryWriter.writeFloat32(tempQuaterionArray[1]); + binaryWriter.writeFloat32(tempQuaterionArray[2]); + binaryWriter.writeFloat32(tempQuaterionArray[3]); + + break; + + default: + output.forEach(function (entry) { + binaryWriter.writeFloat32(entry); + }); + break; + } } }); //TODO: Handle right hand vs left hand here. - accessor = createAccessor(bufferViews.length - 1, dataAccessorType, AccessorComponentType.FLOAT, outputLength, null); accessors.push(accessor); dataAccessorIndex = accessors.length - 1; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 74a36412949..956caf78a3c 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -22,8 +22,8 @@ import type { import { AccessorComponentType, AccessorType, CameraType, ImageMimeType } from "babylonjs-gltf2interface"; import type { FloatArray, IndicesArray, Nullable } from "core/types"; +import { Matrix } from "core/Maths/math.vector"; import { TmpVectors, Quaternion } from "core/Maths/math.vector"; -import type { Matrix } from "core/Maths/math.vector"; import { Tools } from "core/Misc/tools"; import type { Buffer } from "core/Buffers/buffer"; import { VertexBuffer } from "core/Buffers/buffer"; @@ -57,6 +57,10 @@ import { indicesArrayToUint8Array, isNoopNode, isTriangleFillMode, + colapseParentNode, + isImporterAddedNode, + rotateNodeMinus180Z, + convertCameraRotationToGLTF, } from "./glTFUtilities"; import { DataWriter } from "./dataWriter"; import { Camera } from "core/Cameras/camera"; @@ -69,9 +73,6 @@ import type { MorphTarget } from "core/Morph"; import { buildMorphTargetBuffers } from "./glTFMorphTargetsUtilities"; import type { GlTFMorphTarget } from "./glTFMorphTargetsUtilities"; -// 180 degrees rotation in Y. -const rotation180Y = new Quaternion(0, 1, 0, 0); - class ExporterState { // Babylon indices array, start, count, offset, flip -> glTF accessor index private _indicesAccessorMap = new Map, Map>>>>(); @@ -699,10 +700,18 @@ export class GLTFExporter { } } - private _setCameraTransformation(node: INode, babylonCamera: Camera, convertToRightHanded: boolean): void { + private _setCameraTransformation(node: INode, babylonCamera: Camera, convertToRightHanded: boolean, parent: Nullable): void { const translation = TmpVectors.Vector3[0]; const rotation = TmpVectors.Quaternion[0]; - babylonCamera.getWorldMatrix().decompose(undefined, rotation, translation); + + if (parent !== null) { + const parentWorldMatrix = Matrix.Invert(parent.getWorldMatrix()); + const cameraWorldMatrix = babylonCamera.getWorldMatrix(); + const cameraLocal = cameraWorldMatrix.multiply(parentWorldMatrix); + cameraLocal.decompose(undefined, rotation, translation); + } else { + babylonCamera.getWorldMatrix().decompose(undefined, rotation, translation); + } if (!translation.equalsToFloats(0, 0, 0)) { if (convertToRightHanded) { @@ -712,8 +721,7 @@ export class GLTFExporter { node.translation = translation.asArray(); } - // Rotation by 180 as glTF has a different convention than Babylon. - rotation.multiplyInPlace(rotation180Y); + convertCameraRotationToGLTF(rotation); if (!Quaternion.IsIdentity(rotation)) { if (convertToRightHanded) { @@ -1163,8 +1171,9 @@ export class GLTFExporter { } } + let skipNode = false; + if (babylonNode instanceof Camera) { - // TODO: Combine any TransformNode parent with child camera node, like we do for lights const gltfCamera = this._camerasMap.get(babylonNode); if (gltfCamera) { @@ -1172,34 +1181,35 @@ export class GLTFExporter { this._nodesCameraMap.set(gltfCamera, []); } - this._nodesCameraMap.get(gltfCamera)?.push(node); - this._setCameraTransformation(node, babylonNode, state.convertToRightHanded); + const parentBabylonNode = babylonNode.parent; + this._setCameraTransformation(node, babylonNode, state.convertToRightHanded, parentBabylonNode); + + if (parentBabylonNode && isImporterAddedNode(babylonNode, parentBabylonNode)) { + rotateNodeMinus180Z(node); + const parentNodeIndex = this._nodeMap.get(parentBabylonNode); + if (parentNodeIndex) { + const parentNode = this._nodes[parentNodeIndex]; + colapseParentNode(node, parentNode); + //rotateNode180Y(parentNode); + this._nodesCameraMap.get(gltfCamera)?.push(parentNode); + skipNode = true; + } + } else { + this._nodesCameraMap.get(gltfCamera)?.push(node); + } } } - const runtimeGLTFAnimation: IAnimation = { - name: "runtime animations", - channels: [], - samplers: [], - }; - const idleGLTFAnimations: IAnimation[] = []; + if (!skipNode) { + const runtimeGLTFAnimation: IAnimation = { + name: "runtime animations", + channels: [], + samplers: [], + }; + const idleGLTFAnimations: IAnimation[] = []; - if (!this._babylonScene.animationGroups.length) { - _GLTFAnimation._CreateMorphTargetAnimationFromMorphTargetAnimations( - babylonNode, - runtimeGLTFAnimation, - idleGLTFAnimations, - this._nodeMap, - this._nodes, - this._dataWriter, - this._bufferViews, - this._accessors, - this._animationSampleRate, - state.convertToRightHanded, - this._options.shouldExportAnimation - ); - if (babylonNode.animations.length) { - _GLTFAnimation._CreateNodeAnimationFromNodeAnimations( + if (!this._babylonScene.animationGroups.length) { + _GLTFAnimation._CreateMorphTargetAnimationFromMorphTargetAnimations( babylonNode, runtimeGLTFAnimation, idleGLTFAnimations, @@ -1212,33 +1222,50 @@ export class GLTFExporter { state.convertToRightHanded, this._options.shouldExportAnimation ); + if (babylonNode.animations.length) { + _GLTFAnimation._CreateNodeAnimationFromNodeAnimations( + babylonNode, + runtimeGLTFAnimation, + idleGLTFAnimations, + this._nodeMap, + this._nodes, + this._dataWriter, + this._bufferViews, + this._accessors, + this._animationSampleRate, + state.convertToRightHanded, + this._options.shouldExportAnimation + ); + } } - } - // Apply extensions to the node. If this resolves to null, it means we should skip exporting this node (NOTE: This will also skip its children) - const processedNode = await this._extensionsPostExportNodeAsync("exportNodeAsync", node, babylonNode, this._nodeMap, state.convertToRightHanded); - if (!processedNode) { - Logger.Warn(`Not exporting node ${babylonNode.name}`); - return null; - } - - nodeIndex = this._nodes.length; - this._nodes.push(node); - this._nodeMap.set(babylonNode, nodeIndex); - state.pushExportedNode(babylonNode); + // Apply extensions to the node. If this resolves to null, it means we should skip exporting this node (NOTE: This will also skip its children) + const processedNode = await this._extensionsPostExportNodeAsync("exportNodeAsync", node, babylonNode, this._nodeMap, state.convertToRightHanded); + if (!processedNode) { + Logger.Warn(`Not exporting node ${babylonNode.name}`); + return null; + } - // Begin processing child nodes once parent has been added to the node list - for (const babylonChildNode of babylonNode.getChildren()) { - if (this._shouldExportNode(babylonChildNode)) { - const childNodeIndex = await this._exportNodeAsync(babylonChildNode, state); - if (childNodeIndex !== null) { - node.children ||= []; - node.children.push(childNodeIndex); + nodeIndex = this._nodes.length; + this._nodes.push(node); + this._nodeMap.set(babylonNode, nodeIndex); + state.pushExportedNode(babylonNode); + + // Begin processing child nodes once parent has been added to the node list + for (const babylonChildNode of babylonNode.getChildren()) { + if (this._shouldExportNode(babylonChildNode)) { + const childNodeIndex = await this._exportNodeAsync(babylonChildNode, state); + if (childNodeIndex !== null) { + node.children ||= []; + node.children.push(childNodeIndex); + } } } + + return nodeIndex; } - return nodeIndex; + return null; } private _exportIndices( diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 13b15d3fd86..2c09aeeada6 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -1,6 +1,6 @@ /* eslint-disable jsdoc/require-jsdoc */ -import type { IBufferView, AccessorComponentType, IAccessor } from "babylonjs-gltf2interface"; +import type { IBufferView, AccessorComponentType, IAccessor, INode } from "babylonjs-gltf2interface"; import { AccessorType, MeshPrimitiveMode } from "babylonjs-gltf2interface"; import type { DataArray, IndicesArray, Nullable } from "core/types"; @@ -17,6 +17,10 @@ import type { Node } from "core/node"; // Matrix that converts handedness on the X-axis. const convertHandednessMatrix = Matrix.Compose(new Vector3(-1, 1, 1), Quaternion.Identity(), Vector3.Zero()); +// 180 degrees rotation in Y. +const rotation180Y = new Quaternion(0, 1, 0, 0); +const rotation180Z = new Quaternion(0, 0, 1, 0); + /** * Creates a buffer view based on the supplied arguments * @param bufferIndex index value of the specified buffer @@ -205,6 +209,68 @@ export function convertToRightHandedRotation(value: Quaternion): Quaternion { return value; } +/** + * Rotation by 180 as glTF has a different convention than Babylon. + * @param rotation Target camera rotation. + * @returns Ref to camera rotation. + */ +export function convertCameraRotationToGLTF(rotation: Quaternion): Quaternion { + return rotation.multiplyInPlace(rotation180Y); +} + +export function rotateNode180Y(node: INode) { + if (node.rotation) { + const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); + rotation.multiplyInPlace(rotation180Y); + node.rotation = rotation.asArray(); + } +} + +export function rotateNodeMinus180Z(node: INode) { + if (node.rotation) { + const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); + rotation.multiplyInPlace(Quaternion.Inverse(rotation180Z)); + node.rotation = rotation.asArray(); + } +} + +export function colapseParentNode(node: INode, parentNode: INode) { + const parentTranslation = Vector3.FromArrayToRef(parentNode.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); + const parentRotation = Quaternion.FromArrayToRef(parentNode.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); + const parentScale = Vector3.FromArrayToRef(parentNode.scale || [1, 1, 1], 0, TmpVectors.Vector3[1]); + const parentMatrix = Matrix.ComposeToRef(parentScale, parentRotation, parentTranslation, TmpVectors.Matrix[0]); + + const translation = Vector3.FromArrayToRef(node.translation || [0, 0, 0], 0, TmpVectors.Vector3[2]); + const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); + const scale = Vector3.FromArrayToRef(node.scale || [1, 1, 1], 0, TmpVectors.Vector3[1]); + const matrix = Matrix.ComposeToRef(scale, rotation, translation, TmpVectors.Matrix[1]); + + parentMatrix.multiplyToRef(matrix, matrix); + matrix.decompose(parentScale, parentRotation, parentTranslation); + + if (parentTranslation.equalsToFloats(0, 0, 0)) { + delete parentNode.translation; + } else { + parentNode.translation = parentTranslation.asArray(); + } + + if (Quaternion.IsIdentity(parentRotation)) { + delete parentNode.rotation; + } else { + parentNode.rotation = parentRotation.asArray(); + } + + if (parentScale.equalsToFloats(1, 1, 1)) { + delete parentNode.scale; + } else { + parentNode.scale = parentScale.asArray(); + } +} + +export function isImporterAddedNode(babylonNode: Node, parentBabylonNode: Node) { + return parentBabylonNode instanceof TransformNode && parentBabylonNode.getChildren().length == 1 && babylonNode.getChildren().length == 0; +} + // /** // * Converts a new right-handed Vector3 // * @param vector vector3 array From 2dc9a406be63dbd5c720b8acd224998d36466d06 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 13 Nov 2024 10:51:41 -0300 Subject: [PATCH 040/133] Fixed camera transformation based on parent --- packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts | 8 ++++---- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 1 - 2 files changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts index 3e9ea4c1d54..d29f1b2af1c 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts @@ -650,10 +650,10 @@ export class _GLTFAnimation { if (isCamera) { convertCameraRotationToGLTF(rotationQuaternion); - } - - if (!Quaternion.IsIdentity(rotationQuaternion)) { - convertToRightHandedRotation(rotationQuaternion); + } else { + if (!Quaternion.IsIdentity(rotationQuaternion)) { + convertToRightHandedRotation(rotationQuaternion); + } } rotationQuaternion.toArray(tempQuaterionArray); diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 956caf78a3c..3634051f9ed 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1190,7 +1190,6 @@ export class GLTFExporter { if (parentNodeIndex) { const parentNode = this._nodes[parentNodeIndex]; colapseParentNode(node, parentNode); - //rotateNode180Y(parentNode); this._nodesCameraMap.get(gltfCamera)?.push(parentNode); skipNode = true; } From 1c2ff00042b66b0435a23e38428bec2e15fbd4dc Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 13 Nov 2024 14:01:20 -0300 Subject: [PATCH 041/133] Added camera roundtrip tests --- packages/tools/tests/test/visualization/config.json | 12 ++++++++++++ 1 file changed, 12 insertions(+) diff --git a/packages/tools/tests/test/visualization/config.json b/packages/tools/tests/test/visualization/config.json index 0afd7eca72e..95458a176d7 100644 --- a/packages/tools/tests/test/visualization/config.json +++ b/packages/tools/tests/test/visualization/config.json @@ -924,6 +924,18 @@ "replace": "//options//, roundtrip = true; useRightHandedSystem = true;", "referenceImage": "glTFSerializerKHRPunctualLightRH.png" }, + { + "title": "GLTF Serializer Camera, left-handed", + "playgroundId": "#O0M0J9", + "replace": "//options//, roundtrip = true; useRightHandedSystem = false;", + "referenceImage": "glTFSerializerKHRPunctualLightLH.png" + }, + { + "title": "GLTF Serializer Camera, right-handed", + "playgroundId": "#O0M0J9", + "replace": "//options//, roundtrip = true; useRightHandedSystem = true;", + "referenceImage": "glTFSerializerKHRPunctualLightRH.png" + }, { "title": "GLTF Buggy with Draco Mesh Compression", "playgroundId": "#JNW207#1", From 7b8bfafc92c69c346604fe0702f606a03d2733fd Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 13 Nov 2024 14:38:25 -0300 Subject: [PATCH 042/133] Added validation tests for camera transform --- .../glTF/2.0/Extensions/KHR_lights_punctual.ts | 4 ++-- .../serializers/src/glTF/2.0/glTFExporter.ts | 14 +++++--------- .../serializers/src/glTF/2.0/glTFUtilities.ts | 13 ++++++++++++- .../ReferenceImages/glTFSerializerCamera.png | Bin 0 -> 10556 bytes .../tools/tests/test/visualization/config.json | 8 ++++---- 5 files changed, 23 insertions(+), 16 deletions(-) create mode 100644 packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerCamera.png diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index fcecfdf5c8b..4a1fae3b3dc 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -9,7 +9,7 @@ import { KHRLightsPunctual_LightType } from "babylonjs-gltf2interface"; import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; import { GLTFExporter } from "../glTFExporter"; import { Logger } from "core/Misc/logger"; -import { convertToRightHandedPosition, omitDefaultValues, colapseParentNode, isImporterAddedNode } from "../glTFUtilities"; +import { convertToRightHandedPosition, omitDefaultValues, colapseParentNode, isParentAddedByImporter } from "../glTFUtilities"; const NAME = "KHR_lights_punctual"; const DEFAULTS: Partial = { @@ -157,7 +157,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { const parentBabylonNode = babylonNode.parent; // TODO: May be able to simplify this logic by using our previous check for the root node - if (parentBabylonNode && isImporterAddedNode(babylonNode, parentBabylonNode)) { + if (parentBabylonNode && isParentAddedByImporter(babylonNode, parentBabylonNode)) { const parentNodeIndex = nodeMap.get(parentBabylonNode); if (parentNodeIndex) { // Combine the light's transformation with the parent's diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 3634051f9ed..e86895afeb3 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -58,7 +58,7 @@ import { isNoopNode, isTriangleFillMode, colapseParentNode, - isImporterAddedNode, + isParentAddedByImporter, rotateNodeMinus180Z, convertCameraRotationToGLTF, } from "./glTFUtilities"; @@ -721,13 +721,11 @@ export class GLTFExporter { node.translation = translation.asArray(); } - convertCameraRotationToGLTF(rotation); + if (convertToRightHanded) { + convertCameraRotationToGLTF(rotation); + } if (!Quaternion.IsIdentity(rotation)) { - if (convertToRightHanded) { - convertToRightHandedRotation(rotation); - } - node.rotation = rotation.asArray(); } } @@ -1166,8 +1164,6 @@ export class GLTFExporter { this._nodesSkinMap.get(skin)?.push(node); } } - } else { - // TODO: handle other Babylon node types } } @@ -1184,7 +1180,7 @@ export class GLTFExporter { const parentBabylonNode = babylonNode.parent; this._setCameraTransformation(node, babylonNode, state.convertToRightHanded, parentBabylonNode); - if (parentBabylonNode && isImporterAddedNode(babylonNode, parentBabylonNode)) { + if (parentBabylonNode && isParentAddedByImporter(babylonNode, parentBabylonNode)) { rotateNodeMinus180Z(node); const parentNodeIndex = this._nodeMap.get(parentBabylonNode); if (parentNodeIndex) { diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 2c09aeeada6..c064a04e2c1 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -234,6 +234,11 @@ export function rotateNodeMinus180Z(node: INode) { } } +/** + * Colapses GLTF parent and node into a single node. This is useful for removing nodes that were added by the GLTF importer. + * @param node Target parent node. + * @param parentNode Original GLTF node (Light or Camera). + */ export function colapseParentNode(node: INode, parentNode: INode) { const parentTranslation = Vector3.FromArrayToRef(parentNode.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); const parentRotation = Quaternion.FromArrayToRef(parentNode.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); @@ -267,7 +272,13 @@ export function colapseParentNode(node: INode, parentNode: INode) { } } -export function isImporterAddedNode(babylonNode: Node, parentBabylonNode: Node) { +/** + * Sometimes the GLTF Importer can add extra transform nodes (for lights and cameras). This checks if a parent node was added by the GLTF Importer. If so, it should be removed during serialization. + * @param babylonNode Original GLTF node (Light or Camera). + * @param parentBabylonNode Target parent node. + * @returns True if the parent node was added by the GLTF importer. + */ +export function isParentAddedByImporter(babylonNode: Node, parentBabylonNode: Node): boolean { return parentBabylonNode instanceof TransformNode && parentBabylonNode.getChildren().length == 1 && babylonNode.getChildren().length == 0; } diff --git a/packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerCamera.png b/packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerCamera.png new file mode 100644 index 0000000000000000000000000000000000000000..60c14b7e8d45cba2106a2a7a3fdca3a709eb5411 GIT binary patch literal 10556 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 literal 0 HcmV?d00001 diff --git a/packages/tools/tests/test/visualization/config.json b/packages/tools/tests/test/visualization/config.json index 95458a176d7..bc49410be14 100644 --- a/packages/tools/tests/test/visualization/config.json +++ b/packages/tools/tests/test/visualization/config.json @@ -926,15 +926,15 @@ }, { "title": "GLTF Serializer Camera, left-handed", - "playgroundId": "#O0M0J9", + "playgroundId": "#O0M0J9#3", "replace": "//options//, roundtrip = true; useRightHandedSystem = false;", - "referenceImage": "glTFSerializerKHRPunctualLightLH.png" + "referenceImage": "glTFSerializerCamera.png" }, { "title": "GLTF Serializer Camera, right-handed", - "playgroundId": "#O0M0J9", + "playgroundId": "#O0M0J9#3", "replace": "//options//, roundtrip = true; useRightHandedSystem = true;", - "referenceImage": "glTFSerializerKHRPunctualLightRH.png" + "referenceImage": "glTFSerializerCamera.png" }, { "title": "GLTF Buggy with Draco Mesh Compression", From 710dd60bfcef3ecbaff02c5382681f1f2e358e9c Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:33:53 -0500 Subject: [PATCH 043/133] Make materialExporter public for extensions to use --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 74a36412949..8b61cdf544b 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -246,7 +246,7 @@ export class GLTFExporter { private readonly _options: Required; - private readonly _materialExporter = new GLTFMaterialExporter(this); + public readonly _materialExporter = new GLTFMaterialExporter(this); private readonly _extensions: { [name: string]: IGLTFExporterExtensionV2 } = {}; From 8d0029b245933b0214e3f71bce474cf43d61bd17 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:56:42 -0500 Subject: [PATCH 044/133] Make exportTextureAsync public (for now) for transmission --- packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index e3598d8de83..077ac53fda8 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -898,7 +898,7 @@ export class GLTFMaterialExporter { return pixels; } - private async _exportTextureAsync(babylonTexture: BaseTexture, mimeType: ImageMimeType): Promise> { + public async _exportTextureAsync(babylonTexture: BaseTexture, mimeType: ImageMimeType): Promise> { const extensionPromise = this._exporter._extensionsPreExportTextureAsync("exporter", babylonTexture as Texture, mimeType); if (!extensionPromise) { return this._exportTextureInfoAsync(babylonTexture, mimeType); From 61831d1d29bbfd110330b286fa7649b2e68c2179 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Wed, 13 Nov 2024 16:59:10 -0500 Subject: [PATCH 045/133] Restore all material/texture extensions to previous working state --- .../Extensions/KHR_materials_anisotropy.ts | 167 +++++------ .../2.0/Extensions/KHR_materials_clearcoat.ts | 219 +++++++------- .../KHR_materials_diffuse_transmission.ts | 246 ++++++++-------- .../Extensions/KHR_materials_dispersion.ts | 126 ++++---- .../KHR_materials_emissive_strength.ts | 140 ++++----- .../glTF/2.0/Extensions/KHR_materials_ior.ts | 110 +++---- .../Extensions/KHR_materials_iridescence.ts | 183 ++++++------ .../2.0/Extensions/KHR_materials_sheen.ts | 175 +++++------ .../2.0/Extensions/KHR_materials_specular.ts | 238 +++++++-------- .../Extensions/KHR_materials_transmission.ts | 272 ++++++++---------- .../2.0/Extensions/KHR_materials_unlit.ts | 90 +++--- .../2.0/Extensions/KHR_materials_volume.ts | 240 ++++++++-------- .../2.0/Extensions/KHR_texture_transform.ts | 206 ++++++------- .../src/glTF/2.0/Extensions/index.ts | 26 +- 14 files changed, 1206 insertions(+), 1232 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_anisotropy.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_anisotropy.ts index 14418fd7486..2941ce70713 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_anisotropy.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_anisotropy.ts @@ -1,83 +1,84 @@ -// import type { IMaterial, IKHRMaterialsAnisotropy } from "babylonjs-gltf2interface"; -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import { GLTFExporter } from "../glTFExporter"; -// import type { Material } from "core/Materials/material"; -// import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; -// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; - -// const NAME = "KHR_materials_anisotropy"; - -// /** -// * @internal -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export class KHR_materials_anisotropy implements IGLTFExporterExtensionV2 { -// /** Name of this extension */ -// public readonly name = NAME; - -// /** Defines whether this extension is enabled */ -// public enabled = true; - -// /** Defines whether this extension is required */ -// public required = false; - -// private _exporter: GLTFExporter; - -// private _wasUsed = false; - -// constructor(exporter: GLTFExporter) { -// this._exporter = exporter; -// } - -// public dispose() {} - -// /** @internal */ -// public get wasUsed() { -// return this._wasUsed; -// } - -// public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { -// const additionalTextures: BaseTexture[] = []; -// if (babylonMaterial instanceof PBRBaseMaterial) { -// if (babylonMaterial.anisotropy.isEnabled && !babylonMaterial.anisotropy.legacy) { -// if (babylonMaterial.anisotropy.texture) { -// additionalTextures.push(babylonMaterial.anisotropy.texture); -// } -// return additionalTextures; -// } -// } - -// return []; -// } - -// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { -// return new Promise((resolve) => { -// if (babylonMaterial instanceof PBRBaseMaterial) { -// if (!babylonMaterial.anisotropy.isEnabled || babylonMaterial.anisotropy.legacy) { -// resolve(node); -// return; -// } - -// this._wasUsed = true; - -// node.extensions = node.extensions || {}; - -// const anisotropyTextureInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.anisotropy.texture); - -// const anisotropyInfo: IKHRMaterialsAnisotropy = { -// anisotropyStrength: babylonMaterial.anisotropy.intensity, -// anisotropyRotation: babylonMaterial.anisotropy.angle, -// anisotropyTexture: anisotropyTextureInfo ?? undefined, -// hasTextures: () => { -// return anisotropyInfo.anisotropyTexture !== null; -// }, -// }; - -// node.extensions[NAME] = anisotropyInfo; -// } -// resolve(node); -// }); -// } -// } - -// GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_anisotropy(exporter)); +import type { IMaterial, IKHRMaterialsAnisotropy } from "babylonjs-gltf2interface"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { GLTFExporter } from "../glTFExporter"; +import type { Material } from "core/Materials/material"; +import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; +import type { BaseTexture } from "core/Materials/Textures/baseTexture"; + +const NAME = "KHR_materials_anisotropy"; + +/** + * @internal + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_anisotropy implements IGLTFExporterExtensionV2 { + /** Name of this extension */ + public readonly name = NAME; + + /** Defines whether this extension is enabled */ + public enabled = true; + + /** Defines whether this extension is required */ + public required = false; + + private _exporter: GLTFExporter; + + private _wasUsed = false; + + constructor(exporter: GLTFExporter) { + this._exporter = exporter; + } + + public dispose() {} + + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } + + public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { + const additionalTextures: BaseTexture[] = []; + if (babylonMaterial instanceof PBRBaseMaterial) { + if (babylonMaterial.anisotropy.isEnabled && !babylonMaterial.anisotropy.legacy) { + if (babylonMaterial.anisotropy.texture) { + additionalTextures.push(babylonMaterial.anisotropy.texture); + } + return additionalTextures; + } + } + + return []; + } + + public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { + return new Promise((resolve) => { + if (babylonMaterial instanceof PBRBaseMaterial) { + if (!babylonMaterial.anisotropy.isEnabled || babylonMaterial.anisotropy.legacy) { + resolve(node); + return; + } + + this._wasUsed = true; + + node.extensions = node.extensions || {}; + + const anisotropyTextureInfo = this._exporter._materialExporter.getTextureInfo(babylonMaterial.anisotropy.texture); + + const anisotropyInfo: IKHRMaterialsAnisotropy = { + anisotropyStrength: babylonMaterial.anisotropy.intensity, + anisotropyRotation: babylonMaterial.anisotropy.angle, + anisotropyTexture: anisotropyTextureInfo ?? undefined, + }; + + if (anisotropyInfo.anisotropyTexture !== null) { + this._exporter._materialNeedsUVsSet.add(babylonMaterial); + } + + node.extensions[NAME] = anisotropyInfo; + } + resolve(node); + }); + } +} + +GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_anisotropy(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat.ts index 5b765c6e386..38a1b092589 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_clearcoat.ts @@ -1,109 +1,110 @@ -// import type { IMaterial, IKHRMaterialsClearcoat } from "babylonjs-gltf2interface"; -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import { GLTFExporter } from "../glTFExporter"; -// import type { Material } from "core/Materials/material"; -// import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; -// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; - -// import { Tools } from "core/Misc/tools"; - -// const NAME = "KHR_materials_clearcoat"; - -// /** -// * @internal -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export class KHR_materials_clearcoat implements IGLTFExporterExtensionV2 { -// /** Name of this extension */ -// public readonly name = NAME; - -// /** Defines whether this extension is enabled */ -// public enabled = true; - -// /** Defines whether this extension is required */ -// public required = false; - -// private _exporter: GLTFExporter; - -// private _wasUsed = false; - -// constructor(exporter: GLTFExporter) { -// this._exporter = exporter; -// } - -// public dispose() {} - -// /** @internal */ -// public get wasUsed() { -// return this._wasUsed; -// } - -// public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { -// const additionalTextures: BaseTexture[] = []; -// if (babylonMaterial instanceof PBRBaseMaterial) { -// if (babylonMaterial.clearCoat.isEnabled) { -// if (babylonMaterial.clearCoat.texture) { -// additionalTextures.push(babylonMaterial.clearCoat.texture); -// } -// if (!babylonMaterial.clearCoat.useRoughnessFromMainTexture && babylonMaterial.clearCoat.textureRoughness) { -// additionalTextures.push(babylonMaterial.clearCoat.textureRoughness); -// } -// if (babylonMaterial.clearCoat.bumpTexture) { -// additionalTextures.push(babylonMaterial.clearCoat.bumpTexture); -// } -// return additionalTextures; -// } -// } - -// return []; -// } - -// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { -// return new Promise((resolve) => { -// if (babylonMaterial instanceof PBRBaseMaterial) { -// if (!babylonMaterial.clearCoat.isEnabled) { -// resolve(node); -// return; -// } - -// this._wasUsed = true; - -// node.extensions = node.extensions || {}; - -// const clearCoatTextureInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.clearCoat.texture); -// let clearCoatTextureRoughnessInfo; -// if (babylonMaterial.clearCoat.useRoughnessFromMainTexture) { -// clearCoatTextureRoughnessInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.clearCoat.texture); -// } else { -// clearCoatTextureRoughnessInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.clearCoat.textureRoughness); -// } - -// if (babylonMaterial.clearCoat.isTintEnabled) { -// Tools.Warn(`Clear Color tint is not supported for glTF export. Ignoring for: ${babylonMaterial.name}`); -// } - -// if (babylonMaterial.clearCoat.remapF0OnInterfaceChange) { -// Tools.Warn(`Clear Color F0 remapping is not supported for glTF export. Ignoring for: ${babylonMaterial.name}`); -// } - -// const clearCoatNormalTextureInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.clearCoat.bumpTexture); - -// const clearCoatInfo: IKHRMaterialsClearcoat = { -// clearcoatFactor: babylonMaterial.clearCoat.intensity, -// clearcoatTexture: clearCoatTextureInfo ?? undefined, -// clearcoatRoughnessFactor: babylonMaterial.clearCoat.roughness, -// clearcoatRoughnessTexture: clearCoatTextureRoughnessInfo ?? undefined, -// clearcoatNormalTexture: clearCoatNormalTextureInfo ?? undefined, -// hasTextures: () => { -// return clearCoatInfo.clearcoatTexture !== null || clearCoatInfo.clearcoatRoughnessTexture !== null || clearCoatInfo.clearcoatRoughnessTexture !== null; -// }, -// }; - -// node.extensions[NAME] = clearCoatInfo; -// } -// resolve(node); -// }); -// } -// } - -// GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_clearcoat(exporter)); +import type { IMaterial, IKHRMaterialsClearcoat } from "babylonjs-gltf2interface"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { GLTFExporter } from "../glTFExporter"; +import type { Material } from "core/Materials/material"; +import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; +import type { BaseTexture } from "core/Materials/Textures/baseTexture"; + +import { Tools } from "core/Misc/tools"; + +const NAME = "KHR_materials_clearcoat"; + +/** + * @internal + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_clearcoat implements IGLTFExporterExtensionV2 { + /** Name of this extension */ + public readonly name = NAME; + + /** Defines whether this extension is enabled */ + public enabled = true; + + /** Defines whether this extension is required */ + public required = false; + + private _exporter: GLTFExporter; + + private _wasUsed = false; + + constructor(exporter: GLTFExporter) { + this._exporter = exporter; + } + + public dispose() {} + + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } + + public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { + const additionalTextures: BaseTexture[] = []; + if (babylonMaterial instanceof PBRBaseMaterial) { + if (babylonMaterial.clearCoat.isEnabled) { + if (babylonMaterial.clearCoat.texture) { + additionalTextures.push(babylonMaterial.clearCoat.texture); + } + if (!babylonMaterial.clearCoat.useRoughnessFromMainTexture && babylonMaterial.clearCoat.textureRoughness) { + additionalTextures.push(babylonMaterial.clearCoat.textureRoughness); + } + if (babylonMaterial.clearCoat.bumpTexture) { + additionalTextures.push(babylonMaterial.clearCoat.bumpTexture); + } + return additionalTextures; + } + } + + return []; + } + + public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { + return new Promise((resolve) => { + if (babylonMaterial instanceof PBRBaseMaterial) { + if (!babylonMaterial.clearCoat.isEnabled) { + resolve(node); + return; + } + + this._wasUsed = true; + + node.extensions = node.extensions || {}; + + const clearCoatTextureInfo = this._exporter._materialExporter.getTextureInfo(babylonMaterial.clearCoat.texture); + let clearCoatTextureRoughnessInfo; + if (babylonMaterial.clearCoat.useRoughnessFromMainTexture) { + clearCoatTextureRoughnessInfo = this._exporter._materialExporter.getTextureInfo(babylonMaterial.clearCoat.texture); + } else { + clearCoatTextureRoughnessInfo = this._exporter._materialExporter.getTextureInfo(babylonMaterial.clearCoat.textureRoughness); + } + + if (babylonMaterial.clearCoat.isTintEnabled) { + Tools.Warn(`Clear Color tint is not supported for glTF export. Ignoring for: ${babylonMaterial.name}`); + } + + if (babylonMaterial.clearCoat.remapF0OnInterfaceChange) { + Tools.Warn(`Clear Color F0 remapping is not supported for glTF export. Ignoring for: ${babylonMaterial.name}`); + } + + const clearCoatNormalTextureInfo = this._exporter._materialExporter.getTextureInfo(babylonMaterial.clearCoat.bumpTexture); + + const clearCoatInfo: IKHRMaterialsClearcoat = { + clearcoatFactor: babylonMaterial.clearCoat.intensity, + clearcoatTexture: clearCoatTextureInfo ?? undefined, + clearcoatRoughnessFactor: babylonMaterial.clearCoat.roughness, + clearcoatRoughnessTexture: clearCoatTextureRoughnessInfo ?? undefined, + clearcoatNormalTexture: clearCoatNormalTextureInfo ?? undefined, + }; + + if (clearCoatInfo.clearcoatTexture !== null || clearCoatInfo.clearcoatRoughnessTexture !== null || clearCoatInfo.clearcoatRoughnessTexture !== null) { + this._exporter._materialNeedsUVsSet.add(babylonMaterial); + } + + node.extensions[NAME] = clearCoatInfo; + } + resolve(node); + }); + } +} + +GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_clearcoat(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.ts index 756c107501f..562dc5f2750 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_diffuse_transmission.ts @@ -1,122 +1,124 @@ -// import type { IMaterial, IKHRMaterialsDiffuseTransmission } from "babylonjs-gltf2interface"; -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import { _Exporter } from "../glTFExporter"; -// import type { Material } from "core/Materials/material"; -// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; - -// const NAME = "KHR_materials_diffuse_transmission"; - -// /** -// * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1825) -// * !!! Experimental Extension Subject to Changes !!! -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export class KHR_materials_diffuse_transmission implements IGLTFExporterExtensionV2 { -// /** Name of this extension */ -// public readonly name = NAME; - -// /** Defines whether this extension is enabled */ -// public enabled = true; - -// /** Defines whether this extension is required */ -// public required = false; - -// private _exporter: _Exporter; - -// private _wasUsed = false; - -// constructor(exporter: _Exporter) { -// this._exporter = exporter; -// } - -// public dispose() {} - -// /** @internal */ -// public get wasUsed() { -// return this._wasUsed; -// } - -// /** -// * After exporting a material, deal with additional textures -// * @param context GLTF context of the material -// * @param node exported GLTF node -// * @param babylonMaterial corresponding babylon material -// * @returns array of additional textures to export -// */ -// public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { -// const additionalTextures: BaseTexture[] = []; - -// if (babylonMaterial instanceof PBRMaterial) { -// if (this._isExtensionEnabled(babylonMaterial)) { -// if (babylonMaterial.subSurface.thicknessTexture) { -// additionalTextures.push(babylonMaterial.subSurface.thicknessTexture); -// } -// return additionalTextures; -// } -// } - -// return additionalTextures; -// } - -// private _isExtensionEnabled(mat: PBRMaterial): boolean { -// // This extension must not be used on a material that also uses KHR_materials_unlit -// if (mat.unlit) { -// return false; -// } -// const subs = mat.subSurface; -// if (!subs.isTranslucencyEnabled) { -// return false; -// } - -// return ( -// !mat.unlit && -// !subs.useAlbedoToTintTranslucency && -// subs.useGltfStyleTextures && -// subs.volumeIndexOfRefraction === 1 && -// subs.minimumThickness === 0 && -// subs.maximumThickness === 0 -// ); -// } - -// private _hasTexturesExtension(mat: PBRMaterial): boolean { -// return mat.subSurface.translucencyIntensityTexture != null || mat.subSurface.translucencyColorTexture != null; -// } - -// /** -// * After exporting a material -// * @param context GLTF context of the material -// * @param node exported GLTF node -// * @param babylonMaterial corresponding babylon material -// * @returns promise that resolves with the updated node -// */ -// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { -// return new Promise((resolve) => { -// if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { -// this._wasUsed = true; - -// const subs = babylonMaterial.subSurface; - -// const diffuseTransmissionFactor = subs.translucencyIntensity == 1 ? undefined : subs.translucencyIntensity; -// const diffuseTransmissionTexture = this._exporter._glTFMaterialExporter._getTextureInfo(subs.translucencyIntensityTexture) ?? undefined; -// const diffuseTransmissionColorFactor = !subs.translucencyColor || subs.translucencyColor.equalsFloats(1.0, 1.0, 1.0) ? undefined : subs.translucencyColor.asArray(); -// const diffuseTransmissionColorTexture = this._exporter._glTFMaterialExporter._getTextureInfo(subs.translucencyColorTexture) ?? undefined; - -// const diffuseTransmissionInfo: IKHRMaterialsDiffuseTransmission = { -// diffuseTransmissionFactor, -// diffuseTransmissionTexture, -// diffuseTransmissionColorFactor, -// diffuseTransmissionColorTexture, -// hasTextures: () => { -// return this._hasTexturesExtension(babylonMaterial); -// }, -// }; -// node.extensions = node.extensions || {}; -// node.extensions[NAME] = diffuseTransmissionInfo; -// } -// resolve(node); -// }); -// } -// } - -// _Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_diffuse_transmission(exporter)); +import type { IMaterial, IKHRMaterialsDiffuseTransmission } from "babylonjs-gltf2interface"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { GLTFExporter } from "../glTFExporter"; +import type { Material } from "core/Materials/material"; +import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +import type { BaseTexture } from "core/Materials/Textures/baseTexture"; + +const NAME = "KHR_materials_diffuse_transmission"; + +/** + * [Proposed Specification](https://github.com/KhronosGroup/glTF/pull/1825) + * !!! Experimental Extension Subject to Changes !!! + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_diffuse_transmission implements IGLTFExporterExtensionV2 { + /** Name of this extension */ + public readonly name = NAME; + + /** Defines whether this extension is enabled */ + public enabled = true; + + /** Defines whether this extension is required */ + public required = false; + + private _exporter: GLTFExporter; + + private _wasUsed = false; + + constructor(exporter: GLTFExporter) { + this._exporter = exporter; + } + + public dispose() {} + + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } + + /** + * After exporting a material, deal with additional textures + * @param context GLTF context of the material + * @param node exported GLTF node + * @param babylonMaterial corresponding babylon material + * @returns array of additional textures to export + */ + public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { + const additionalTextures: BaseTexture[] = []; + + if (babylonMaterial instanceof PBRMaterial) { + if (this._isExtensionEnabled(babylonMaterial)) { + if (babylonMaterial.subSurface.thicknessTexture) { + additionalTextures.push(babylonMaterial.subSurface.thicknessTexture); + } + return additionalTextures; + } + } + + return additionalTextures; + } + + private _isExtensionEnabled(mat: PBRMaterial): boolean { + // This extension must not be used on a material that also uses KHR_materials_unlit + if (mat.unlit) { + return false; + } + const subs = mat.subSurface; + if (!subs.isTranslucencyEnabled) { + return false; + } + + return ( + !mat.unlit && + !subs.useAlbedoToTintTranslucency && + subs.useGltfStyleTextures && + subs.volumeIndexOfRefraction === 1 && + subs.minimumThickness === 0 && + subs.maximumThickness === 0 + ); + } + + private _hasTexturesExtension(mat: PBRMaterial): boolean { + return mat.subSurface.translucencyIntensityTexture != null || mat.subSurface.translucencyColorTexture != null; + } + + /** + * After exporting a material + * @param context GLTF context of the material + * @param node exported GLTF node + * @param babylonMaterial corresponding babylon material + * @returns promise that resolves with the updated node + */ + public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { + return new Promise((resolve) => { + if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { + this._wasUsed = true; + + const subs = babylonMaterial.subSurface; + + const diffuseTransmissionFactor = subs.translucencyIntensity == 1 ? undefined : subs.translucencyIntensity; + const diffuseTransmissionTexture = this._exporter._materialExporter.getTextureInfo(subs.translucencyIntensityTexture) ?? undefined; + const diffuseTransmissionColorFactor = !subs.translucencyColor || subs.translucencyColor.equalsFloats(1.0, 1.0, 1.0) ? undefined : subs.translucencyColor.asArray(); + const diffuseTransmissionColorTexture = this._exporter._materialExporter.getTextureInfo(subs.translucencyColorTexture) ?? undefined; + + const diffuseTransmissionInfo: IKHRMaterialsDiffuseTransmission = { + diffuseTransmissionFactor, + diffuseTransmissionTexture, + diffuseTransmissionColorFactor, + diffuseTransmissionColorTexture, + }; + + if (this._hasTexturesExtension(babylonMaterial)) { + this._exporter._materialNeedsUVsSet.add(babylonMaterial); + } + + node.extensions = node.extensions || {}; + node.extensions[NAME] = diffuseTransmissionInfo; + } + resolve(node); + }); + } +} + +GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_diffuse_transmission(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_dispersion.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_dispersion.ts index 42722a2400a..a11b41e2342 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_dispersion.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_dispersion.ts @@ -1,76 +1,76 @@ -// import type { IMaterial, IKHRMaterialsDispersion } from "babylonjs-gltf2interface"; -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import { _Exporter } from "../glTFExporter"; -// import type { Material } from "core/Materials/material"; -// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +import type { IMaterial, IKHRMaterialsDispersion } from "babylonjs-gltf2interface"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { GLTFExporter } from "../glTFExporter"; +import type { Material } from "core/Materials/material"; +import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -// const NAME = "KHR_materials_dispersion"; +const NAME = "KHR_materials_dispersion"; -// /** -// * [Specification](https://github.com/KhronosGroup/glTF/blob/87bd64a7f5e23c84b6aef2e6082069583ed0ddb4/extensions/2.0/Khronos/KHR_materials_dispersion/README.md) -// * @experimental -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export class KHR_materials_dispersion implements IGLTFExporterExtensionV2 { -// /** Name of this extension */ -// public readonly name = NAME; +/** + * [Specification](https://github.com/KhronosGroup/glTF/blob/87bd64a7f5e23c84b6aef2e6082069583ed0ddb4/extensions/2.0/Khronos/KHR_materials_dispersion/README.md) + * @experimental + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_dispersion implements IGLTFExporterExtensionV2 { + /** Name of this extension */ + public readonly name = NAME; -// /** Defines whether this extension is enabled */ -// public enabled = true; + /** Defines whether this extension is enabled */ + public enabled = true; -// /** Defines whether this extension is required */ -// public required = false; + /** Defines whether this extension is required */ + public required = false; -// private _wasUsed = false; + private _wasUsed = false; -// /** Constructor */ -// constructor() {} + /** Constructor */ + constructor() {} -// /** Dispose */ -// public dispose() {} + /** Dispose */ + public dispose() {} -// /** @internal */ -// public get wasUsed() { -// return this._wasUsed; -// } + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } -// private _isExtensionEnabled(mat: PBRMaterial): boolean { -// // This extension must not be used on a material that also uses KHR_materials_unlit -// if (mat.unlit) { -// return false; -// } -// const subs = mat.subSurface; -// // this extension requires refraction to be enabled. -// if (!subs.isRefractionEnabled && !subs.isDispersionEnabled) { -// return false; -// } -// return true; -// } + private _isExtensionEnabled(mat: PBRMaterial): boolean { + // This extension must not be used on a material that also uses KHR_materials_unlit + if (mat.unlit) { + return false; + } + const subs = mat.subSurface; + // this extension requires refraction to be enabled. + if (!subs.isRefractionEnabled && !subs.isDispersionEnabled) { + return false; + } + return true; + } -// /** -// * After exporting a material -// * @param context GLTF context of the material -// * @param node exported GLTF node -// * @param babylonMaterial corresponding babylon material -// * @returns promise, resolves with the material -// */ -// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { -// return new Promise((resolve) => { -// if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { -// this._wasUsed = true; + /** + * After exporting a material + * @param context GLTF context of the material + * @param node exported GLTF node + * @param babylonMaterial corresponding babylon material + * @returns promise, resolves with the material + */ + public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { + return new Promise((resolve) => { + if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { + this._wasUsed = true; -// const subs = babylonMaterial.subSurface; -// const dispersion = subs.dispersion; + const subs = babylonMaterial.subSurface; + const dispersion = subs.dispersion; -// const dispersionInfo: IKHRMaterialsDispersion = { -// dispersion: dispersion, -// }; -// node.extensions = node.extensions || {}; -// node.extensions[NAME] = dispersionInfo; -// } -// resolve(node); -// }); -// } -// } + const dispersionInfo: IKHRMaterialsDispersion = { + dispersion: dispersion, + }; + node.extensions = node.extensions || {}; + node.extensions[NAME] = dispersionInfo; + } + resolve(node); + }); + } +} -// _Exporter.RegisterExtension(NAME, () => new KHR_materials_dispersion()); +GLTFExporter.RegisterExtension(NAME, () => new KHR_materials_dispersion()); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_emissive_strength.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_emissive_strength.ts index edaff975fa0..0443db6d6ce 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_emissive_strength.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_emissive_strength.ts @@ -1,70 +1,70 @@ -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import { _Exporter } from "../glTFExporter"; -// import type { Material } from "core/Materials/material"; -// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -// import type { IMaterial, IKHRMaterialsEmissiveStrength } from "babylonjs-gltf2interface"; - -// const NAME = "KHR_materials_emissive_strength"; - -// /** -// * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md) -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export class KHR_materials_emissive_strength implements IGLTFExporterExtensionV2 { -// /** Name of this extension */ -// public readonly name = NAME; - -// /** Defines whether this extension is enabled */ -// public enabled = true; - -// /** Defines whether this extension is required */ -// public required = false; - -// private _wasUsed = false; - -// /** Dispose */ -// public dispose() {} - -// /** @internal */ -// public get wasUsed() { -// return this._wasUsed; -// } - -// /** -// * After exporting a material -// * @param context GLTF context of the material -// * @param node exported GLTF node -// * @param babylonMaterial corresponding babylon material -// * @returns promise, resolves with the material -// */ -// public postExportMaterialAsync(context: string, node: IMaterial, babylonMaterial: Material): Promise { -// return new Promise((resolve) => { -// if (!(babylonMaterial instanceof PBRMaterial)) { -// return resolve(node); -// } - -// const emissiveColor = babylonMaterial.emissiveColor.asArray(); -// const tempEmissiveStrength = Math.max(...emissiveColor); - -// if (tempEmissiveStrength > 1) { -// this._wasUsed = true; - -// node.extensions ||= {}; - -// const emissiveStrengthInfo: IKHRMaterialsEmissiveStrength = { -// emissiveStrength: tempEmissiveStrength, -// }; - -// // Normalize each value of the emissive factor to have a max value of 1 -// const newEmissiveFactor = babylonMaterial.emissiveColor.scale(1 / emissiveStrengthInfo.emissiveStrength); - -// node.emissiveFactor = newEmissiveFactor.asArray(); -// node.extensions[NAME] = emissiveStrengthInfo; -// } - -// return resolve(node); -// }); -// } -// } - -// _Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_emissive_strength()); +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { GLTFExporter } from "../glTFExporter"; +import type { Material } from "core/Materials/material"; +import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +import type { IMaterial, IKHRMaterialsEmissiveStrength } from "babylonjs-gltf2interface"; + +const NAME = "KHR_materials_emissive_strength"; + +/** + * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_emissive_strength/README.md) + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_emissive_strength implements IGLTFExporterExtensionV2 { + /** Name of this extension */ + public readonly name = NAME; + + /** Defines whether this extension is enabled */ + public enabled = true; + + /** Defines whether this extension is required */ + public required = false; + + private _wasUsed = false; + + /** Dispose */ + public dispose() {} + + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } + + /** + * After exporting a material + * @param context GLTF context of the material + * @param node exported GLTF node + * @param babylonMaterial corresponding babylon material + * @returns promise, resolves with the material + */ + public postExportMaterialAsync(context: string, node: IMaterial, babylonMaterial: Material): Promise { + return new Promise((resolve) => { + if (!(babylonMaterial instanceof PBRMaterial)) { + return resolve(node); + } + + const emissiveColor = babylonMaterial.emissiveColor.asArray(); + const tempEmissiveStrength = Math.max(...emissiveColor); + + if (tempEmissiveStrength > 1) { + this._wasUsed = true; + + node.extensions ||= {}; + + const emissiveStrengthInfo: IKHRMaterialsEmissiveStrength = { + emissiveStrength: tempEmissiveStrength, + }; + + // Normalize each value of the emissive factor to have a max value of 1 + const newEmissiveFactor = babylonMaterial.emissiveColor.scale(1 / emissiveStrengthInfo.emissiveStrength); + + node.emissiveFactor = newEmissiveFactor.asArray(); + node.extensions[NAME] = emissiveStrengthInfo; + } + + return resolve(node); + }); + } +} + +GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_emissive_strength()); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_ior.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_ior.ts index bc5bb85b43d..ae2f34bad63 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_ior.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_ior.ts @@ -1,67 +1,67 @@ -// import type { IMaterial, IKHRMaterialsIor } from "babylonjs-gltf2interface"; -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import { _Exporter } from "../glTFExporter"; -// import type { Material } from "core/Materials/material"; -// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +import type { IMaterial, IKHRMaterialsIor } from "babylonjs-gltf2interface"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { GLTFExporter } from "../glTFExporter"; +import type { Material } from "core/Materials/material"; +import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -// const NAME = "KHR_materials_ior"; +const NAME = "KHR_materials_ior"; -// /** -// * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_ior/README.md) -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export class KHR_materials_ior implements IGLTFExporterExtensionV2 { -// /** Name of this extension */ -// public readonly name = NAME; +/** + * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_ior/README.md) + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_ior implements IGLTFExporterExtensionV2 { + /** Name of this extension */ + public readonly name = NAME; -// /** Defines whether this extension is enabled */ -// public enabled = true; + /** Defines whether this extension is enabled */ + public enabled = true; -// /** Defines whether this extension is required */ -// public required = false; + /** Defines whether this extension is required */ + public required = false; -// private _wasUsed = false; + private _wasUsed = false; -// constructor() {} + constructor() {} -// /** Dispose */ -// public dispose() {} + /** Dispose */ + public dispose() {} -// /** @internal */ -// public get wasUsed() { -// return this._wasUsed; -// } + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } -// private _isExtensionEnabled(mat: PBRMaterial): boolean { -// // This extension must not be used on a material that also uses KHR_materials_unlit -// if (mat.unlit) { -// return false; -// } -// return mat.indexOfRefraction != undefined && mat.indexOfRefraction != 1.5; // 1.5 is normative default value. -// } + private _isExtensionEnabled(mat: PBRMaterial): boolean { + // This extension must not be used on a material that also uses KHR_materials_unlit + if (mat.unlit) { + return false; + } + return mat.indexOfRefraction != undefined && mat.indexOfRefraction != 1.5; // 1.5 is normative default value. + } -// /** -// * After exporting a material -// * @param context GLTF context of the material -// * @param node exported GLTF node -// * @param babylonMaterial corresponding babylon material -// * @returns promise, resolves with the material -// */ -// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { -// return new Promise((resolve) => { -// if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { -// this._wasUsed = true; + /** + * After exporting a material + * @param context GLTF context of the material + * @param node exported GLTF node + * @param babylonMaterial corresponding babylon material + * @returns promise, resolves with the material + */ + public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { + return new Promise((resolve) => { + if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { + this._wasUsed = true; -// const iorInfo: IKHRMaterialsIor = { -// ior: babylonMaterial.indexOfRefraction, -// }; -// node.extensions = node.extensions || {}; -// node.extensions[NAME] = iorInfo; -// } -// resolve(node); -// }); -// } -// } + const iorInfo: IKHRMaterialsIor = { + ior: babylonMaterial.indexOfRefraction, + }; + node.extensions = node.extensions || {}; + node.extensions[NAME] = iorInfo; + } + resolve(node); + }); + } +} -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// _Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_ior()); +// eslint-disable-next-line @typescript-eslint/no-unused-vars +GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_ior()); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_iridescence.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_iridescence.ts index 599a4c307aa..5bd01b5f29d 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_iridescence.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_iridescence.ts @@ -1,91 +1,92 @@ -// import type { IMaterial, IKHRMaterialsIridescence } from "babylonjs-gltf2interface"; -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import { GLTFExporter } from "../glTFExporter"; -// import type { Material } from "core/Materials/material"; -// import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; -// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; - -// const NAME = "KHR_materials_iridescence"; - -// /** -// * @internal -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export class KHR_materials_iridescence implements IGLTFExporterExtensionV2 { -// /** Name of this extension */ -// public readonly name = NAME; - -// /** Defines whether this extension is enabled */ -// public enabled = true; - -// /** Defines whether this extension is required */ -// public required = false; - -// private _exporter: GLTFExporter; - -// private _wasUsed = false; - -// constructor(exporter: GLTFExporter) { -// this._exporter = exporter; -// } - -// public dispose() {} - -// /** @internal */ -// public get wasUsed() { -// return this._wasUsed; -// } - -// public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { -// const additionalTextures: BaseTexture[] = []; -// if (babylonMaterial instanceof PBRBaseMaterial) { -// if (babylonMaterial.iridescence.isEnabled) { -// if (babylonMaterial.iridescence.texture) { -// additionalTextures.push(babylonMaterial.iridescence.texture); -// } -// if (babylonMaterial.iridescence.thicknessTexture && babylonMaterial.iridescence.thicknessTexture !== babylonMaterial.iridescence.texture) { -// additionalTextures.push(babylonMaterial.iridescence.thicknessTexture); -// } -// return additionalTextures; -// } -// } - -// return []; -// } - -// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { -// return new Promise((resolve) => { -// if (babylonMaterial instanceof PBRBaseMaterial) { -// if (!babylonMaterial.iridescence.isEnabled) { -// resolve(node); -// return; -// } - -// this._wasUsed = true; - -// node.extensions = node.extensions || {}; - -// const iridescenceTextureInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.iridescence.texture); -// const iridescenceThicknessTextureInfo = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.iridescence.thicknessTexture); - -// const iridescenceInfo: IKHRMaterialsIridescence = { -// iridescenceFactor: babylonMaterial.iridescence.intensity, -// iridescenceIor: babylonMaterial.iridescence.indexOfRefraction, -// iridescenceThicknessMinimum: babylonMaterial.iridescence.minimumThickness, -// iridescenceThicknessMaximum: babylonMaterial.iridescence.maximumThickness, - -// iridescenceTexture: iridescenceTextureInfo ?? undefined, -// iridescenceThicknessTexture: iridescenceThicknessTextureInfo ?? undefined, -// hasTextures: () => { -// return iridescenceInfo.iridescenceTexture !== null || iridescenceInfo.iridescenceThicknessTexture !== null; -// }, -// }; - -// node.extensions[NAME] = iridescenceInfo; -// } -// resolve(node); -// }); -// } -// } - -// GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_iridescence(exporter)); +import type { IMaterial, IKHRMaterialsIridescence } from "babylonjs-gltf2interface"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { GLTFExporter } from "../glTFExporter"; +import type { Material } from "core/Materials/material"; +import { PBRBaseMaterial } from "core/Materials/PBR/pbrBaseMaterial"; +import type { BaseTexture } from "core/Materials/Textures/baseTexture"; + +const NAME = "KHR_materials_iridescence"; + +/** + * @internal + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_iridescence implements IGLTFExporterExtensionV2 { + /** Name of this extension */ + public readonly name = NAME; + + /** Defines whether this extension is enabled */ + public enabled = true; + + /** Defines whether this extension is required */ + public required = false; + + private _exporter: GLTFExporter; + + private _wasUsed = false; + + constructor(exporter: GLTFExporter) { + this._exporter = exporter; + } + + public dispose() {} + + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } + + public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { + const additionalTextures: BaseTexture[] = []; + if (babylonMaterial instanceof PBRBaseMaterial) { + if (babylonMaterial.iridescence.isEnabled) { + if (babylonMaterial.iridescence.texture) { + additionalTextures.push(babylonMaterial.iridescence.texture); + } + if (babylonMaterial.iridescence.thicknessTexture && babylonMaterial.iridescence.thicknessTexture !== babylonMaterial.iridescence.texture) { + additionalTextures.push(babylonMaterial.iridescence.thicknessTexture); + } + return additionalTextures; + } + } + + return []; + } + + public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { + return new Promise((resolve) => { + if (babylonMaterial instanceof PBRBaseMaterial) { + if (!babylonMaterial.iridescence.isEnabled) { + resolve(node); + return; + } + + this._wasUsed = true; + + node.extensions = node.extensions || {}; + + const iridescenceTextureInfo = this._exporter._materialExporter.getTextureInfo(babylonMaterial.iridescence.texture); + const iridescenceThicknessTextureInfo = this._exporter._materialExporter.getTextureInfo(babylonMaterial.iridescence.thicknessTexture); + + const iridescenceInfo: IKHRMaterialsIridescence = { + iridescenceFactor: babylonMaterial.iridescence.intensity, + iridescenceIor: babylonMaterial.iridescence.indexOfRefraction, + iridescenceThicknessMinimum: babylonMaterial.iridescence.minimumThickness, + iridescenceThicknessMaximum: babylonMaterial.iridescence.maximumThickness, + + iridescenceTexture: iridescenceTextureInfo ?? undefined, + iridescenceThicknessTexture: iridescenceThicknessTextureInfo ?? undefined, + }; + + if (iridescenceInfo.iridescenceTexture !== null || iridescenceInfo.iridescenceThicknessTexture !== null) { + this._exporter._materialNeedsUVsSet.add(babylonMaterial); + } + + node.extensions[NAME] = iridescenceInfo; + } + resolve(node); + }); + } +} + +GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_iridescence(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_sheen.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_sheen.ts index a26dd3f2a52..3a1e682ba30 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_sheen.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_sheen.ts @@ -1,87 +1,88 @@ -// import type { IMaterial, IKHRMaterialsSheen } from "babylonjs-gltf2interface"; -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import { GLTFExporter } from "../glTFExporter"; -// import type { Material } from "core/Materials/material"; -// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; - -// const NAME = "KHR_materials_sheen"; - -// /** -// * @internal -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export class KHR_materials_sheen implements IGLTFExporterExtensionV2 { -// /** Name of this extension */ -// public readonly name = NAME; - -// /** Defines whether this extension is enabled */ -// public enabled = true; - -// /** Defines whether this extension is required */ -// public required = false; - -// private _wasUsed = false; - -// private _exporter: GLTFExporter; - -// constructor(exporter: GLTFExporter) { -// this._exporter = exporter; -// } - -// public dispose() {} - -// /** @internal */ -// public get wasUsed() { -// return this._wasUsed; -// } - -// public postExportMaterialAdditionalTextures(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { -// if (babylonMaterial instanceof PBRMaterial) { -// if (babylonMaterial.sheen.isEnabled && babylonMaterial.sheen.texture) { -// return [babylonMaterial.sheen.texture]; -// } -// } - -// return []; -// } - -// public postExportMaterialAsync(context: string, node: IMaterial, babylonMaterial: Material): Promise { -// return new Promise((resolve) => { -// if (babylonMaterial instanceof PBRMaterial) { -// if (!babylonMaterial.sheen.isEnabled) { -// resolve(node); -// return; -// } - -// this._wasUsed = true; - -// if (node.extensions == null) { -// node.extensions = {}; -// } -// const sheenInfo: IKHRMaterialsSheen = { -// sheenColorFactor: babylonMaterial.sheen.color.asArray(), -// sheenRoughnessFactor: babylonMaterial.sheen.roughness ?? 0, -// hasTextures: () => { -// return sheenInfo.sheenColorTexture !== null || sheenInfo.sheenRoughnessTexture !== null; -// }, -// }; - -// if (babylonMaterial.sheen.texture) { -// sheenInfo.sheenColorTexture = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.sheen.texture) ?? undefined; -// } - -// if (babylonMaterial.sheen.textureRoughness && !babylonMaterial.sheen.useRoughnessFromMainTexture) { -// sheenInfo.sheenRoughnessTexture = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.sheen.textureRoughness) ?? undefined; -// } else if (babylonMaterial.sheen.texture && babylonMaterial.sheen.useRoughnessFromMainTexture) { -// sheenInfo.sheenRoughnessTexture = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.sheen.texture) ?? undefined; -// } - -// node.extensions[NAME] = sheenInfo; -// } -// resolve(node); -// }); -// } -// } - -// GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_sheen(exporter)); +import type { IMaterial, IKHRMaterialsSheen } from "babylonjs-gltf2interface"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { GLTFExporter } from "../glTFExporter"; +import type { Material } from "core/Materials/material"; +import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +import type { BaseTexture } from "core/Materials/Textures/baseTexture"; + +const NAME = "KHR_materials_sheen"; + +/** + * @internal + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_sheen implements IGLTFExporterExtensionV2 { + /** Name of this extension */ + public readonly name = NAME; + + /** Defines whether this extension is enabled */ + public enabled = true; + + /** Defines whether this extension is required */ + public required = false; + + private _wasUsed = false; + + private _exporter: GLTFExporter; + + constructor(exporter: GLTFExporter) { + this._exporter = exporter; + } + + public dispose() {} + + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } + + public postExportMaterialAdditionalTextures(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { + if (babylonMaterial instanceof PBRMaterial) { + if (babylonMaterial.sheen.isEnabled && babylonMaterial.sheen.texture) { + return [babylonMaterial.sheen.texture]; + } + } + + return []; + } + + public postExportMaterialAsync(context: string, node: IMaterial, babylonMaterial: Material): Promise { + return new Promise((resolve) => { + if (babylonMaterial instanceof PBRMaterial) { + if (!babylonMaterial.sheen.isEnabled) { + resolve(node); + return; + } + + this._wasUsed = true; + + if (node.extensions == null) { + node.extensions = {}; + } + const sheenInfo: IKHRMaterialsSheen = { + sheenColorFactor: babylonMaterial.sheen.color.asArray(), + sheenRoughnessFactor: babylonMaterial.sheen.roughness ?? 0, + }; + + if (sheenInfo.sheenColorTexture !== null || sheenInfo.sheenRoughnessTexture !== null) { + this._exporter._materialNeedsUVsSet.add(babylonMaterial); + } + + if (babylonMaterial.sheen.texture) { + sheenInfo.sheenColorTexture = this._exporter._materialExporter.getTextureInfo(babylonMaterial.sheen.texture) ?? undefined; + } + + if (babylonMaterial.sheen.textureRoughness && !babylonMaterial.sheen.useRoughnessFromMainTexture) { + sheenInfo.sheenRoughnessTexture = this._exporter._materialExporter.getTextureInfo(babylonMaterial.sheen.textureRoughness) ?? undefined; + } else if (babylonMaterial.sheen.texture && babylonMaterial.sheen.useRoughnessFromMainTexture) { + sheenInfo.sheenRoughnessTexture = this._exporter._materialExporter.getTextureInfo(babylonMaterial.sheen.texture) ?? undefined; + } + + node.extensions[NAME] = sheenInfo; + } + resolve(node); + }); + } +} + +GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_sheen(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_specular.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_specular.ts index 9221c9ac3b8..95b0b1a09b0 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_specular.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_specular.ts @@ -1,118 +1,120 @@ -// import type { IMaterial, IKHRMaterialsSpecular } from "babylonjs-gltf2interface"; -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import { _Exporter } from "../glTFExporter"; -// import type { Material } from "core/Materials/material"; -// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; - -// const NAME = "KHR_materials_specular"; - -// /** -// * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_specular/README.md) -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export class KHR_materials_specular implements IGLTFExporterExtensionV2 { -// /** Name of this extension */ -// public readonly name = NAME; - -// /** Defines whether this extension is enabled */ -// public enabled = true; - -// /** Defines whether this extension is required */ -// public required = false; - -// private _exporter: _Exporter; - -// private _wasUsed = false; - -// constructor(exporter: _Exporter) { -// this._exporter = exporter; -// } - -// /** Dispose */ -// public dispose() {} - -// /** @internal */ -// public get wasUsed() { -// return this._wasUsed; -// } - -// /** -// * After exporting a material, deal with the additional textures -// * @param context GLTF context of the material -// * @param node exported GLTF node -// * @param babylonMaterial corresponding babylon material -// * @returns array of additional textures to export -// */ -// public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { -// const additionalTextures: BaseTexture[] = []; - -// if (babylonMaterial instanceof PBRMaterial) { -// if (this._isExtensionEnabled(babylonMaterial)) { -// if (babylonMaterial.metallicReflectanceTexture) { -// additionalTextures.push(babylonMaterial.metallicReflectanceTexture); -// } -// if (babylonMaterial.reflectanceTexture) { -// additionalTextures.push(babylonMaterial.reflectanceTexture); -// } -// return additionalTextures; -// } -// } - -// return additionalTextures; -// } - -// private _isExtensionEnabled(mat: PBRMaterial): boolean { -// // This extension must not be used on a material that also uses KHR_materials_unlit -// if (mat.unlit) { -// return false; -// } -// return ( -// (mat.metallicF0Factor != undefined && mat.metallicF0Factor != 1.0) || -// (mat.metallicReflectanceColor != undefined && !mat.metallicReflectanceColor.equalsFloats(1.0, 1.0, 1.0)) || -// this._hasTexturesExtension(mat) -// ); -// } - -// private _hasTexturesExtension(mat: PBRMaterial): boolean { -// return mat.metallicReflectanceTexture != null || mat.reflectanceTexture != null; -// } - -// /** -// * After exporting a material -// * @param context GLTF context of the material -// * @param node exported GLTF node -// * @param babylonMaterial corresponding babylon material -// * @returns promise, resolves with the material -// */ -// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { -// return new Promise((resolve) => { -// if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { -// this._wasUsed = true; - -// node.extensions = node.extensions || {}; - -// const metallicReflectanceTexture = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.metallicReflectanceTexture) ?? undefined; -// const reflectanceTexture = this._exporter._glTFMaterialExporter._getTextureInfo(babylonMaterial.reflectanceTexture) ?? undefined; -// const metallicF0Factor = babylonMaterial.metallicF0Factor == 1.0 ? undefined : babylonMaterial.metallicF0Factor; -// const metallicReflectanceColor = babylonMaterial.metallicReflectanceColor.equalsFloats(1.0, 1.0, 1.0) -// ? undefined -// : babylonMaterial.metallicReflectanceColor.asArray(); - -// const specularInfo: IKHRMaterialsSpecular = { -// specularFactor: metallicF0Factor, -// specularTexture: metallicReflectanceTexture, -// specularColorFactor: metallicReflectanceColor, -// specularColorTexture: reflectanceTexture, -// hasTextures: () => { -// return this._hasTexturesExtension(babylonMaterial); -// }, -// }; -// node.extensions[NAME] = specularInfo; -// } -// resolve(node); -// }); -// } -// } - -// _Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_specular(exporter)); +import type { IMaterial, IKHRMaterialsSpecular } from "babylonjs-gltf2interface"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { GLTFExporter } from "../glTFExporter"; +import type { Material } from "core/Materials/material"; +import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +import type { BaseTexture } from "core/Materials/Textures/baseTexture"; + +const NAME = "KHR_materials_specular"; + +/** + * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_specular/README.md) + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_specular implements IGLTFExporterExtensionV2 { + /** Name of this extension */ + public readonly name = NAME; + + /** Defines whether this extension is enabled */ + public enabled = true; + + /** Defines whether this extension is required */ + public required = false; + + private _exporter: GLTFExporter; + + private _wasUsed = false; + + constructor(exporter: GLTFExporter) { + this._exporter = exporter; + } + + /** Dispose */ + public dispose() {} + + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } + + /** + * After exporting a material, deal with the additional textures + * @param context GLTF context of the material + * @param node exported GLTF node + * @param babylonMaterial corresponding babylon material + * @returns array of additional textures to export + */ + public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { + const additionalTextures: BaseTexture[] = []; + + if (babylonMaterial instanceof PBRMaterial) { + if (this._isExtensionEnabled(babylonMaterial)) { + if (babylonMaterial.metallicReflectanceTexture) { + additionalTextures.push(babylonMaterial.metallicReflectanceTexture); + } + if (babylonMaterial.reflectanceTexture) { + additionalTextures.push(babylonMaterial.reflectanceTexture); + } + return additionalTextures; + } + } + + return additionalTextures; + } + + private _isExtensionEnabled(mat: PBRMaterial): boolean { + // This extension must not be used on a material that also uses KHR_materials_unlit + if (mat.unlit) { + return false; + } + return ( + (mat.metallicF0Factor != undefined && mat.metallicF0Factor != 1.0) || + (mat.metallicReflectanceColor != undefined && !mat.metallicReflectanceColor.equalsFloats(1.0, 1.0, 1.0)) || + this._hasTexturesExtension(mat) + ); + } + + private _hasTexturesExtension(mat: PBRMaterial): boolean { + return mat.metallicReflectanceTexture != null || mat.reflectanceTexture != null; + } + + /** + * After exporting a material + * @param context GLTF context of the material + * @param node exported GLTF node + * @param babylonMaterial corresponding babylon material + * @returns promise, resolves with the material + */ + public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { + return new Promise((resolve) => { + if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { + this._wasUsed = true; + + node.extensions = node.extensions || {}; + + const metallicReflectanceTexture = this._exporter._materialExporter.getTextureInfo(babylonMaterial.metallicReflectanceTexture) ?? undefined; + const reflectanceTexture = this._exporter._materialExporter.getTextureInfo(babylonMaterial.reflectanceTexture) ?? undefined; + const metallicF0Factor = babylonMaterial.metallicF0Factor == 1.0 ? undefined : babylonMaterial.metallicF0Factor; + const metallicReflectanceColor = babylonMaterial.metallicReflectanceColor.equalsFloats(1.0, 1.0, 1.0) + ? undefined + : babylonMaterial.metallicReflectanceColor.asArray(); + + const specularInfo: IKHRMaterialsSpecular = { + specularFactor: metallicF0Factor, + specularTexture: metallicReflectanceTexture, + specularColorFactor: metallicReflectanceColor, + specularColorTexture: reflectanceTexture, + }; + + if (this._hasTexturesExtension(babylonMaterial)) { + this._exporter._materialNeedsUVsSet.add(babylonMaterial); + } + + node.extensions[NAME] = specularInfo; + } + resolve(node); + }); + } +} + +GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_specular(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_transmission.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_transmission.ts index 1a70f1ab383..f35cb919e46 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_transmission.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_transmission.ts @@ -1,154 +1,118 @@ -// import type { IMaterial, IKHRMaterialsTransmission } from "babylonjs-gltf2interface"; -// import { ImageMimeType } from "babylonjs-gltf2interface"; -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import { _Exporter } from "../glTFExporter"; -// import type { Material } from "core/Materials/material"; -// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; -// import { Logger } from "core/Misc/logger"; -// import type { IMaterial, IKHRMaterialsTransmission } from "babylonjs-gltf2interface"; -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import { _Exporter } from "../glTFExporter"; -// import type { Material } from "core/Materials/material"; -// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; - -// const NAME = "KHR_materials_transmission"; - -// /** -// * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_transmission/README.md) -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export class KHR_materials_transmission implements IGLTFExporterExtensionV2 { -// /** Name of this extension */ -// public readonly name = NAME; - -// /** Defines whether this extension is enabled */ -// public enabled = true; - -// /** Defines whether this extension is required */ -// public required = false; - -// private _exporter: _Exporter; - -// private _wasUsed = false; - -// constructor(exporter: _Exporter) { -// this._exporter = exporter; -// } - -// /** Dispose */ -// public dispose() {} - -// /** @internal */ -// public get wasUsed() { -// return this._wasUsed; -// } - -// /** -// * After exporting a material, deal with additional textures -// * @param context GLTF context of the material -// * @param node exported GLTF node -// * @param babylonMaterial corresponding babylon material -// * @returns array of additional textures to export -// */ -// public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { -// const additionalTextures: BaseTexture[] = []; - -// if (babylonMaterial instanceof PBRMaterial) { -// if (this._isExtensionEnabled(babylonMaterial)) { -// if (babylonMaterial.subSurface.thicknessTexture) { -// additionalTextures.push(babylonMaterial.subSurface.thicknessTexture); -// } -// return additionalTextures; -// } -// } - -// return additionalTextures; -// } - -// private _isExtensionEnabled(mat: PBRMaterial): boolean { -// // This extension must not be used on a material that also uses KHR_materials_unlit -// if (mat.unlit) { -// return false; -// } -// const subs = mat.subSurface; -// return (subs.isRefractionEnabled && subs.refractionIntensity != undefined && subs.refractionIntensity != 0) || this._hasTexturesExtension(mat); -// } - -// private _hasTexturesExtension(mat: PBRMaterial): boolean { -// return mat.subSurface.refractionIntensityTexture != null; -// } - -// /** -// * After exporting a material -// * @param context GLTF context of the material -// * @param node exported GLTF node -// * @param babylonMaterial corresponding babylon material -// * @returns true if successful -// */ -// public async postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { -// if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { -// this._wasUsed = true; - -// const subSurface = babylonMaterial.subSurface; -// const transmissionFactor = subSurface.refractionIntensity === 0 ? undefined : subSurface.refractionIntensity; - -// const volumeInfo: IKHRMaterialsTransmission = { -// transmissionFactor: transmissionFactor, -// hasTextures: () => { -// return this._hasTexturesExtension(babylonMaterial); -// }, -// }; - -// if (subSurface.refractionIntensityTexture) { -// if (subSurface.useGltfStyleTextures) { -// const transmissionTexture = await this._exporter._glTFMaterialExporter._exportTextureInfoAsync(subSurface.refractionIntensityTexture, ImageMimeType.PNG); -// if (transmissionTexture) { -// volumeInfo.transmissionTexture = transmissionTexture; -// } -// } else { -// Logger.Warn(`${context}: Exporting a subsurface refraction intensity texture without \`useGltfStyleTextures\` is not supported`); -// } -// } - -// node.extensions ||= {}; -// node.extensions[NAME] = volumeInfo; -// } - -// return node; -// } -// } -// /** -// * After exporting a material -// * @param context GLTF context of the material -// * @param node exported GLTF node -// * @param babylonMaterial corresponding babylon material -// * @returns true if successful -// */ -// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { -// return new Promise((resolve) => { -// if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { -// this._wasUsed = true; - -// const subs = babylonMaterial.subSurface; -// const transmissionFactor = subs.refractionIntensity === 0 ? undefined : subs.refractionIntensity; - -// const transmissionTexture = this._exporter._glTFMaterialExporter._getTextureInfo(subs.refractionIntensityTexture) ?? undefined; - -// const volumeInfo: IKHRMaterialsTransmission = { -// transmissionFactor: transmissionFactor, -// transmissionTexture: transmissionTexture, -// hasTextures: () => { -// return this._hasTexturesExtension(babylonMaterial); -// }, -// }; -// node.extensions = node.extensions || {}; -// node.extensions[NAME] = volumeInfo; -// } -// resolve(node); -// }); -// } -// } - -// _Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_transmission(exporter)); +import type { IMaterial, IKHRMaterialsTransmission } from "babylonjs-gltf2interface"; +import { ImageMimeType } from "babylonjs-gltf2interface"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { GLTFExporter } from "../glTFExporter"; +import type { Material } from "core/Materials/material"; +import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +import type { BaseTexture } from "core/Materials/Textures/baseTexture"; +import { Logger } from "core/Misc/logger"; + +const NAME = "KHR_materials_transmission"; + +/** + * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_transmission/README.md) + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_transmission implements IGLTFExporterExtensionV2 { + /** Name of this extension */ + public readonly name = NAME; + + /** Defines whether this extension is enabled */ + public enabled = true; + + /** Defines whether this extension is required */ + public required = false; + + private _exporter: GLTFExporter; + + private _wasUsed = false; + + constructor(exporter: GLTFExporter) { + this._exporter = exporter; + } + + /** Dispose */ + public dispose() {} + + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } + + /** + * After exporting a material, deal with additional textures + * @param context GLTF context of the material + * @param node exported GLTF node + * @param babylonMaterial corresponding babylon material + * @returns array of additional textures to export + */ + public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { + const additionalTextures: BaseTexture[] = []; + + if (babylonMaterial instanceof PBRMaterial) { + if (this._isExtensionEnabled(babylonMaterial)) { + if (babylonMaterial.subSurface.thicknessTexture) { + additionalTextures.push(babylonMaterial.subSurface.thicknessTexture); + } + return additionalTextures; + } + } + + return additionalTextures; + } + + private _isExtensionEnabled(mat: PBRMaterial): boolean { + // This extension must not be used on a material that also uses KHR_materials_unlit + if (mat.unlit) { + return false; + } + const subs = mat.subSurface; + return (subs.isRefractionEnabled && subs.refractionIntensity != undefined && subs.refractionIntensity != 0) || this._hasTexturesExtension(mat); + } + + private _hasTexturesExtension(mat: PBRMaterial): boolean { + return mat.subSurface.refractionIntensityTexture != null; + } + + /** + * After exporting a material + * @param context GLTF context of the material + * @param node exported GLTF node + * @param babylonMaterial corresponding babylon material + * @returns true if successful + */ + public async postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { + if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { + this._wasUsed = true; + + const subSurface = babylonMaterial.subSurface; + const transmissionFactor = subSurface.refractionIntensity === 0 ? undefined : subSurface.refractionIntensity; + + const volumeInfo: IKHRMaterialsTransmission = { + transmissionFactor: transmissionFactor, + }; + + if (this._hasTexturesExtension(babylonMaterial)) { + this._exporter._materialNeedsUVsSet.add(babylonMaterial); + } + + if (subSurface.refractionIntensityTexture) { + if (subSurface.useGltfStyleTextures) { + const transmissionTexture = await this._exporter._materialExporter._exportTextureAsync(subSurface.refractionIntensityTexture, ImageMimeType.PNG); + if (transmissionTexture) { + volumeInfo.transmissionTexture = transmissionTexture; + } + } else { + Logger.Warn(`${context}: Exporting a subsurface refraction intensity texture without \`useGltfStyleTextures\` is not supported`); + } + } + + node.extensions ||= {}; + node.extensions[NAME] = volumeInfo; + } + + return node; + } +} + +GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_transmission(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_unlit.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_unlit.ts index fcce751059f..9d2aece1665 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_unlit.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_unlit.ts @@ -1,60 +1,60 @@ -// import type { IMaterial } from "babylonjs-gltf2interface"; -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import { GLTFExporter } from "../glTFExporter"; -// import type { Material } from "core/Materials/material"; -// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -// import { StandardMaterial } from "core/Materials/standardMaterial"; +import type { IMaterial } from "babylonjs-gltf2interface"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { GLTFExporter } from "../glTFExporter"; +import type { Material } from "core/Materials/material"; +import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +import { StandardMaterial } from "core/Materials/standardMaterial"; -// const NAME = "KHR_materials_unlit"; +const NAME = "KHR_materials_unlit"; -// /** -// * @internal -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export class KHR_materials_unlit implements IGLTFExporterExtensionV2 { -// /** Name of this extension */ -// public readonly name = NAME; +/** + * @internal + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_unlit implements IGLTFExporterExtensionV2 { + /** Name of this extension */ + public readonly name = NAME; -// /** Defines whether this extension is enabled */ -// public enabled = true; + /** Defines whether this extension is enabled */ + public enabled = true; -// /** Defines whether this extension is required */ -// public required = false; + /** Defines whether this extension is required */ + public required = false; -// private _wasUsed = false; + private _wasUsed = false; -// constructor() {} + constructor() {} -// /** @internal */ -// public get wasUsed() { -// return this._wasUsed; -// } + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } -// public dispose() {} + public dispose() {} -// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { -// return new Promise((resolve) => { -// let unlitMaterial = false; + public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { + return new Promise((resolve) => { + let unlitMaterial = false; -// if (babylonMaterial instanceof PBRMaterial) { -// unlitMaterial = babylonMaterial.unlit; -// } else if (babylonMaterial instanceof StandardMaterial) { -// unlitMaterial = babylonMaterial.disableLighting; -// } + if (babylonMaterial instanceof PBRMaterial) { + unlitMaterial = babylonMaterial.unlit; + } else if (babylonMaterial instanceof StandardMaterial) { + unlitMaterial = babylonMaterial.disableLighting; + } -// if (unlitMaterial) { -// this._wasUsed = true; + if (unlitMaterial) { + this._wasUsed = true; -// if (node.extensions == null) { -// node.extensions = {}; -// } + if (node.extensions == null) { + node.extensions = {}; + } -// node.extensions[NAME] = {}; -// } + node.extensions[NAME] = {}; + } -// resolve(node); -// }); -// } -// } + resolve(node); + }); + } +} -// GLTFExporter.RegisterExtension(NAME, () => new KHR_materials_unlit()); +GLTFExporter.RegisterExtension(NAME, () => new KHR_materials_unlit()); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_volume.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_volume.ts index ef0ad58abfc..214361ef8e4 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_volume.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_volume.ts @@ -1,119 +1,121 @@ -// import type { IMaterial, IKHRMaterialsVolume } from "babylonjs-gltf2interface"; -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import { _Exporter } from "../glTFExporter"; -// import type { Material } from "core/Materials/material"; -// import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; -// import type { BaseTexture } from "core/Materials/Textures/baseTexture"; -// import { Color3 } from "core/Maths/math.color"; - -// const NAME = "KHR_materials_volume"; - -// /** -// * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_volume/README.md) -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export class KHR_materials_volume implements IGLTFExporterExtensionV2 { -// /** Name of this extension */ -// public readonly name = NAME; - -// /** Defines whether this extension is enabled */ -// public enabled = true; - -// /** Defines whether this extension is required */ -// public required = false; - -// private _exporter: _Exporter; - -// private _wasUsed = false; - -// constructor(exporter: _Exporter) { -// this._exporter = exporter; -// } - -// public dispose() {} - -// /** @internal */ -// public get wasUsed() { -// return this._wasUsed; -// } - -// /** -// * After exporting a material, deal with additional textures -// * @param context GLTF context of the material -// * @param node exported GLTF node -// * @param babylonMaterial corresponding babylon material -// * @returns array of additional textures to export -// */ -// public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { -// const additionalTextures: BaseTexture[] = []; - -// if (babylonMaterial instanceof PBRMaterial) { -// if (this._isExtensionEnabled(babylonMaterial)) { -// if (babylonMaterial.subSurface.thicknessTexture) { -// additionalTextures.push(babylonMaterial.subSurface.thicknessTexture); -// } -// return additionalTextures; -// } -// } - -// return additionalTextures; -// } - -// private _isExtensionEnabled(mat: PBRMaterial): boolean { -// // This extension must not be used on a material that also uses KHR_materials_unlit -// if (mat.unlit) { -// return false; -// } -// const subs = mat.subSurface; -// // this extension requires either the KHR_materials_transmission or KHR_materials_diffuse_transmission extensions. -// if (!subs.isRefractionEnabled && !subs.isTranslucencyEnabled) { -// return false; -// } -// return ( -// (subs.maximumThickness != undefined && subs.maximumThickness != 0) || -// (subs.tintColorAtDistance != undefined && subs.tintColorAtDistance != Number.POSITIVE_INFINITY) || -// (subs.tintColor != undefined && subs.tintColor != Color3.White()) || -// this._hasTexturesExtension(mat) -// ); -// } - -// private _hasTexturesExtension(mat: PBRMaterial): boolean { -// return mat.subSurface.thicknessTexture != null; -// } - -// /** -// * After exporting a material -// * @param context GLTF context of the material -// * @param node exported GLTF node -// * @param babylonMaterial corresponding babylon material -// * @returns promise that resolves with the updated node -// */ -// public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { -// return new Promise((resolve) => { -// if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { -// this._wasUsed = true; - -// const subs = babylonMaterial.subSurface; -// const thicknessFactor = subs.maximumThickness == 0 ? undefined : subs.maximumThickness; -// const thicknessTexture = this._exporter._glTFMaterialExporter._getTextureInfo(subs.thicknessTexture) ?? undefined; -// const attenuationDistance = subs.tintColorAtDistance == Number.POSITIVE_INFINITY ? undefined : subs.tintColorAtDistance; -// const attenuationColor = subs.tintColor.equalsFloats(1.0, 1.0, 1.0) ? undefined : subs.tintColor.asArray(); - -// const volumeInfo: IKHRMaterialsVolume = { -// thicknessFactor: thicknessFactor, -// thicknessTexture: thicknessTexture, -// attenuationDistance: attenuationDistance, -// attenuationColor: attenuationColor, -// hasTextures: () => { -// return this._hasTexturesExtension(babylonMaterial); -// }, -// }; -// node.extensions = node.extensions || {}; -// node.extensions[NAME] = volumeInfo; -// } -// resolve(node); -// }); -// } -// } - -// _Exporter.RegisterExtension(NAME, (exporter) => new KHR_materials_volume(exporter)); +import type { IMaterial, IKHRMaterialsVolume } from "babylonjs-gltf2interface"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { GLTFExporter } from "../glTFExporter"; +import type { Material } from "core/Materials/material"; +import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +import type { BaseTexture } from "core/Materials/Textures/baseTexture"; +import { Color3 } from "core/Maths/math.color"; + +const NAME = "KHR_materials_volume"; + +/** + * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Khronos/KHR_materials_volume/README.md) + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_materials_volume implements IGLTFExporterExtensionV2 { + /** Name of this extension */ + public readonly name = NAME; + + /** Defines whether this extension is enabled */ + public enabled = true; + + /** Defines whether this extension is required */ + public required = false; + + private _exporter: GLTFExporter; + + private _wasUsed = false; + + constructor(exporter: GLTFExporter) { + this._exporter = exporter; + } + + public dispose() {} + + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } + + /** + * After exporting a material, deal with additional textures + * @param context GLTF context of the material + * @param node exported GLTF node + * @param babylonMaterial corresponding babylon material + * @returns array of additional textures to export + */ + public postExportMaterialAdditionalTextures?(context: string, node: IMaterial, babylonMaterial: Material): BaseTexture[] { + const additionalTextures: BaseTexture[] = []; + + if (babylonMaterial instanceof PBRMaterial) { + if (this._isExtensionEnabled(babylonMaterial)) { + if (babylonMaterial.subSurface.thicknessTexture) { + additionalTextures.push(babylonMaterial.subSurface.thicknessTexture); + } + return additionalTextures; + } + } + + return additionalTextures; + } + + private _isExtensionEnabled(mat: PBRMaterial): boolean { + // This extension must not be used on a material that also uses KHR_materials_unlit + if (mat.unlit) { + return false; + } + const subs = mat.subSurface; + // this extension requires either the KHR_materials_transmission or KHR_materials_diffuse_transmission extensions. + if (!subs.isRefractionEnabled && !subs.isTranslucencyEnabled) { + return false; + } + return ( + (subs.maximumThickness != undefined && subs.maximumThickness != 0) || + (subs.tintColorAtDistance != undefined && subs.tintColorAtDistance != Number.POSITIVE_INFINITY) || + (subs.tintColor != undefined && subs.tintColor != Color3.White()) || + this._hasTexturesExtension(mat) + ); + } + + private _hasTexturesExtension(mat: PBRMaterial): boolean { + return mat.subSurface.thicknessTexture != null; + } + + /** + * After exporting a material + * @param context GLTF context of the material + * @param node exported GLTF node + * @param babylonMaterial corresponding babylon material + * @returns promise that resolves with the updated node + */ + public postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise { + return new Promise((resolve) => { + if (babylonMaterial instanceof PBRMaterial && this._isExtensionEnabled(babylonMaterial)) { + this._wasUsed = true; + + const subs = babylonMaterial.subSurface; + const thicknessFactor = subs.maximumThickness == 0 ? undefined : subs.maximumThickness; + const thicknessTexture = this._exporter._materialExporter.getTextureInfo(subs.thicknessTexture) ?? undefined; + const attenuationDistance = subs.tintColorAtDistance == Number.POSITIVE_INFINITY ? undefined : subs.tintColorAtDistance; + const attenuationColor = subs.tintColor.equalsFloats(1.0, 1.0, 1.0) ? undefined : subs.tintColor.asArray(); + + const volumeInfo: IKHRMaterialsVolume = { + thicknessFactor: thicknessFactor, + thicknessTexture: thicknessTexture, + attenuationDistance: attenuationDistance, + attenuationColor: attenuationColor, + }; + + if (this._hasTexturesExtension(babylonMaterial)) { + this._exporter._materialNeedsUVsSet.add(babylonMaterial); + } + + node.extensions = node.extensions || {}; + node.extensions[NAME] = volumeInfo; + } + resolve(node); + }); + } +} + +GLTFExporter.RegisterExtension(NAME, (exporter) => new KHR_materials_volume(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_texture_transform.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_texture_transform.ts index 6983646ae7f..a5a09ea5923 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_texture_transform.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_texture_transform.ts @@ -1,103 +1,103 @@ -// import type { ITextureInfo, IKHRTextureTransform } from "babylonjs-gltf2interface"; -// import { Tools } from "core/Misc/tools"; -// import type { Texture } from "core/Materials/Textures/texture"; -// import type { Nullable } from "core/types"; -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import { GLTFExporter } from "../glTFExporter"; - -// const NAME = "KHR_texture_transform"; - -// /** -// * @internal -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export class KHR_texture_transform implements IGLTFExporterExtensionV2 { -// /** Name of this extension */ -// public readonly name = NAME; - -// /** Defines whether this extension is enabled */ -// public enabled = true; - -// /** Defines whether this extension is required */ -// public required = false; - -// /** Reference to the glTF exporter */ -// private _wasUsed = false; - -// constructor() {} - -// public dispose() {} - -// /** @internal */ -// public get wasUsed() { -// return this._wasUsed; -// } - -// public postExportTexture?(context: string, textureInfo: ITextureInfo, babylonTexture: Texture): void { -// const canUseExtension = -// babylonTexture && -// ((babylonTexture.uAng === 0 && babylonTexture.wAng === 0 && babylonTexture.vAng === 0) || -// (babylonTexture.uRotationCenter === 0 && babylonTexture.vRotationCenter === 0)); - -// if (canUseExtension) { -// const textureTransform: IKHRTextureTransform = {}; -// let transformIsRequired = false; - -// if (babylonTexture.uOffset !== 0 || babylonTexture.vOffset !== 0) { -// textureTransform.offset = [babylonTexture.uOffset, babylonTexture.vOffset]; -// transformIsRequired = true; -// } - -// if (babylonTexture.uScale !== 1 || babylonTexture.vScale !== 1) { -// textureTransform.scale = [babylonTexture.uScale, babylonTexture.vScale]; -// transformIsRequired = true; -// } - -// if (babylonTexture.wAng !== 0) { -// textureTransform.rotation = -babylonTexture.wAng; -// transformIsRequired = true; -// } - -// if (babylonTexture.coordinatesIndex !== 0) { -// textureTransform.texCoord = babylonTexture.coordinatesIndex; -// transformIsRequired = true; -// } - -// if (!transformIsRequired) { -// return; -// } - -// this._wasUsed = true; -// if (!textureInfo.extensions) { -// textureInfo.extensions = {}; -// } -// textureInfo.extensions[NAME] = textureTransform; -// } -// } - -// public preExportTextureAsync(context: string, babylonTexture: Texture): Promise> { -// return new Promise((resolve, reject) => { -// const scene = babylonTexture.getScene(); -// if (!scene) { -// reject(`${context}: "scene" is not defined for Babylon texture ${babylonTexture.name}!`); -// return; -// } - -// /* -// * The KHR_texture_transform schema only supports w rotation around the origin. -// * See https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform#gltf-schema-updates. -// */ -// if (babylonTexture.uAng !== 0 || babylonTexture.vAng !== 0) { -// Tools.Warn(`${context}: Texture ${babylonTexture.name} with rotation in the u or v axis is not supported in glTF.`); -// resolve(null); -// } else if (babylonTexture.wAng !== 0 && (babylonTexture.uRotationCenter !== 0 || babylonTexture.vRotationCenter !== 0)) { -// Tools.Warn(`${context}: Texture ${babylonTexture.name} with rotation not centered at the origin cannot be exported with ${NAME}`); -// resolve(null); -// } else { -// resolve(babylonTexture); -// } -// }); -// } -// } - -// GLTFExporter.RegisterExtension(NAME, () => new KHR_texture_transform()); +import type { ITextureInfo, IKHRTextureTransform } from "babylonjs-gltf2interface"; +import { Tools } from "core/Misc/tools"; +import type { Texture } from "core/Materials/Textures/texture"; +import type { Nullable } from "core/types"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import { GLTFExporter } from "../glTFExporter"; + +const NAME = "KHR_texture_transform"; + +/** + * @internal + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class KHR_texture_transform implements IGLTFExporterExtensionV2 { + /** Name of this extension */ + public readonly name = NAME; + + /** Defines whether this extension is enabled */ + public enabled = true; + + /** Defines whether this extension is required */ + public required = false; + + /** Reference to the glTF exporter */ + private _wasUsed = false; + + constructor() {} + + public dispose() {} + + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } + + public postExportTexture?(context: string, textureInfo: ITextureInfo, babylonTexture: Texture): void { + const canUseExtension = + babylonTexture && + ((babylonTexture.uAng === 0 && babylonTexture.wAng === 0 && babylonTexture.vAng === 0) || + (babylonTexture.uRotationCenter === 0 && babylonTexture.vRotationCenter === 0)); + + if (canUseExtension) { + const textureTransform: IKHRTextureTransform = {}; + let transformIsRequired = false; + + if (babylonTexture.uOffset !== 0 || babylonTexture.vOffset !== 0) { + textureTransform.offset = [babylonTexture.uOffset, babylonTexture.vOffset]; + transformIsRequired = true; + } + + if (babylonTexture.uScale !== 1 || babylonTexture.vScale !== 1) { + textureTransform.scale = [babylonTexture.uScale, babylonTexture.vScale]; + transformIsRequired = true; + } + + if (babylonTexture.wAng !== 0) { + textureTransform.rotation = -babylonTexture.wAng; + transformIsRequired = true; + } + + if (babylonTexture.coordinatesIndex !== 0) { + textureTransform.texCoord = babylonTexture.coordinatesIndex; + transformIsRequired = true; + } + + if (!transformIsRequired) { + return; + } + + this._wasUsed = true; + if (!textureInfo.extensions) { + textureInfo.extensions = {}; + } + textureInfo.extensions[NAME] = textureTransform; + } + } + + public preExportTextureAsync(context: string, babylonTexture: Texture): Promise> { + return new Promise((resolve, reject) => { + const scene = babylonTexture.getScene(); + if (!scene) { + reject(`${context}: "scene" is not defined for Babylon texture ${babylonTexture.name}!`); + return; + } + + /* + * The KHR_texture_transform schema only supports w rotation around the origin. + * See https://github.com/KhronosGroup/glTF/tree/main/extensions/2.0/Khronos/KHR_texture_transform#gltf-schema-updates. + */ + if (babylonTexture.uAng !== 0 || babylonTexture.vAng !== 0) { + Tools.Warn(`${context}: Texture ${babylonTexture.name} with rotation in the u or v axis is not supported in glTF.`); + resolve(null); + } else if (babylonTexture.wAng !== 0 && (babylonTexture.uRotationCenter !== 0 || babylonTexture.vRotationCenter !== 0)) { + Tools.Warn(`${context}: Texture ${babylonTexture.name} with rotation not centered at the origin cannot be exported with ${NAME}`); + resolve(null); + } else { + resolve(babylonTexture); + } + }); + } +} + +GLTFExporter.RegisterExtension(NAME, () => new KHR_texture_transform()); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts index d38f5f2c1a4..4673cd070e3 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts @@ -1,15 +1,15 @@ // export * from "./EXT_mesh_gpu_instancing"; export * from "./KHR_lights_punctual"; -// export * from "./KHR_materials_anisotropy"; -// export * from "./KHR_materials_clearcoat"; -// export * from "./KHR_materials_diffuse_transmission"; -// export * from "./KHR_materials_dispersion"; -// export * from "./KHR_materials_emissive_strength"; -// export * from "./KHR_materials_ior"; -// export * from "./KHR_materials_iridescence"; -// export * from "./KHR_materials_sheen"; -// export * from "./KHR_materials_specular"; -// export * from "./KHR_materials_transmission"; -// export * from "./KHR_materials_unlit"; -// export * from "./KHR_materials_volume"; -// export * from "./KHR_texture_transform"; +export * from "./KHR_materials_anisotropy"; +export * from "./KHR_materials_clearcoat"; +export * from "./KHR_materials_diffuse_transmission"; +export * from "./KHR_materials_dispersion"; +export * from "./KHR_materials_emissive_strength"; +export * from "./KHR_materials_ior"; +export * from "./KHR_materials_iridescence"; +export * from "./KHR_materials_sheen"; +export * from "./KHR_materials_specular"; +export * from "./KHR_materials_transmission"; +export * from "./KHR_materials_unlit"; +export * from "./KHR_materials_volume"; +export * from "./KHR_texture_transform"; From f3afff1888ab4d674b808bc90f7e2439b10081ac Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Thu, 14 Nov 2024 11:16:27 -0300 Subject: [PATCH 046/133] Added possible fix to round trip --- .../serializers/src/glTF/2.0/glTFExporter.ts | 3 +++ .../serializers/src/glTF/2.0/glTFUtilities.ts | 23 +++++++++++++++++++ 2 files changed, 26 insertions(+) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index e86895afeb3..f6474069c24 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -61,6 +61,7 @@ import { isParentAddedByImporter, rotateNodeMinus180Z, convertCameraRotationToGLTF, + convertNode, } from "./glTFUtilities"; import { DataWriter } from "./dataWriter"; import { Camera } from "core/Cameras/camera"; @@ -1187,6 +1188,8 @@ export class GLTFExporter { const parentNode = this._nodes[parentNodeIndex]; colapseParentNode(node, parentNode); this._nodesCameraMap.get(gltfCamera)?.push(parentNode); + convertNode(parentNode); + rotateNodeMinus180Z(parentNode); skipNode = true; } } else { diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index c064a04e2c1..28224b792c1 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -234,6 +234,29 @@ export function rotateNodeMinus180Z(node: INode) { } } +export function convertNode(node: INode) { + const translation = Vector3.FromArrayToRef(node.translation || [0, 0, 0], 0, TmpVectors.Vector3[2]); + const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); + + translation.y *= -1; + translation.x *= -1; + + //rotation.x *= -1; + //rotation.y *= -1; + + if (translation.equalsToFloats(0, 0, 0)) { + delete node.translation; + } else { + node.translation = translation.asArray(); + } + + if (Quaternion.IsIdentity(rotation)) { + delete node.rotation; + } else { + node.rotation = rotation.asArray(); + } +} + /** * Colapses GLTF parent and node into a single node. This is useful for removing nodes that were added by the GLTF importer. * @param node Target parent node. From 6786e3785374db1285ee6887c379fbb1f1543724 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 18 Nov 2024 11:24:58 -0300 Subject: [PATCH 047/133] Fixed camera round trip --- .../dev/serializers/src/glTF/2.0/glTFExporter.ts | 3 --- .../dev/serializers/src/glTF/2.0/glTFUtilities.ts | 5 ++--- .../tools/tests/test/visualization/config.json | 14 ++++++++++---- 3 files changed, 12 insertions(+), 10 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index ce595f59b52..2bf65528a82 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -57,7 +57,6 @@ import { indicesArrayToUint8Array, isNoopNode, isTriangleFillMode, - colapseParentNode, isParentAddedByImporter, rotateNodeMinus180Z, convertCameraRotationToGLTF, @@ -1186,10 +1185,8 @@ export class GLTFExporter { const parentNodeIndex = this._nodeMap.get(parentBabylonNode); if (parentNodeIndex) { const parentNode = this._nodes[parentNodeIndex]; - colapseParentNode(node, parentNode); this._nodesCameraMap.get(gltfCamera)?.push(parentNode); convertNode(parentNode); - rotateNodeMinus180Z(parentNode); skipNode = true; } } else { diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 28224b792c1..40f42140e1f 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -236,13 +236,12 @@ export function rotateNodeMinus180Z(node: INode) { export function convertNode(node: INode) { const translation = Vector3.FromArrayToRef(node.translation || [0, 0, 0], 0, TmpVectors.Vector3[2]); - const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); + let rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); translation.y *= -1; translation.x *= -1; - //rotation.x *= -1; - //rotation.y *= -1; + rotation = convertToRightHandedRotation(rotation); if (translation.equalsToFloats(0, 0, 0)) { delete node.translation; diff --git a/packages/tools/tests/test/visualization/config.json b/packages/tools/tests/test/visualization/config.json index bc49410be14..c0fa76b21c9 100644 --- a/packages/tools/tests/test/visualization/config.json +++ b/packages/tools/tests/test/visualization/config.json @@ -926,14 +926,20 @@ }, { "title": "GLTF Serializer Camera, left-handed", - "playgroundId": "#O0M0J9#3", - "replace": "//options//, roundtrip = true; useRightHandedSystem = false;", + "playgroundId": "#O0M0J9#7", + "replace": "//options//, roundtripCount = 3; useRightHandedSystem = false;", "referenceImage": "glTFSerializerCamera.png" }, { "title": "GLTF Serializer Camera, right-handed", - "playgroundId": "#O0M0J9#3", - "replace": "//options//, roundtrip = true; useRightHandedSystem = true;", + "playgroundId": "#O0M0J9#7", + "replace": "//options//, roundtripCount = 3; useRightHandedSystem = true;", + "referenceImage": "glTFSerializerCamera.png" + }, + { + "title": "GLTF Serializer Camera, right-handed, round trip twice", + "playgroundId": "#O0M0J9#7", + "replace": "//options//, roundtripCount = 4; useRightHandedSystem = false;", "referenceImage": "glTFSerializerCamera.png" }, { From 79b23a46497c5af97c55ebbd62268e0e4d65e4f1 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 18 Nov 2024 11:39:15 -0300 Subject: [PATCH 048/133] Fixed camera round trip --- .../serializers/src/glTF/2.0/glTFExporter.ts | 2 -- .../serializers/src/glTF/2.0/glTFUtilities.ts | 22 ------------------- 2 files changed, 24 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 2bf65528a82..56df7d205ff 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -60,7 +60,6 @@ import { isParentAddedByImporter, rotateNodeMinus180Z, convertCameraRotationToGLTF, - convertNode, } from "./glTFUtilities"; import { DataWriter } from "./dataWriter"; import { Camera } from "core/Cameras/camera"; @@ -1186,7 +1185,6 @@ export class GLTFExporter { if (parentNodeIndex) { const parentNode = this._nodes[parentNodeIndex]; this._nodesCameraMap.get(gltfCamera)?.push(parentNode); - convertNode(parentNode); skipNode = true; } } else { diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 40f42140e1f..c064a04e2c1 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -234,28 +234,6 @@ export function rotateNodeMinus180Z(node: INode) { } } -export function convertNode(node: INode) { - const translation = Vector3.FromArrayToRef(node.translation || [0, 0, 0], 0, TmpVectors.Vector3[2]); - let rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); - - translation.y *= -1; - translation.x *= -1; - - rotation = convertToRightHandedRotation(rotation); - - if (translation.equalsToFloats(0, 0, 0)) { - delete node.translation; - } else { - node.translation = translation.asArray(); - } - - if (Quaternion.IsIdentity(rotation)) { - delete node.rotation; - } else { - node.rotation = rotation.asArray(); - } -} - /** * Colapses GLTF parent and node into a single node. This is useful for removing nodes that were added by the GLTF importer. * @param node Target parent node. From 8149420ea61adc3ba639bef5f091f6b722f04561 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 18 Nov 2024 12:02:04 -0300 Subject: [PATCH 049/133] Finished camera work --- .../dev/serializers/src/glTF/2.0/glTFExporter.ts | 14 ++++++-------- .../dev/serializers/src/glTF/2.0/glTFUtilities.ts | 9 --------- 2 files changed, 6 insertions(+), 17 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 56df7d205ff..8364f037be3 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -58,8 +58,7 @@ import { isNoopNode, isTriangleFillMode, isParentAddedByImporter, - rotateNodeMinus180Z, - convertCameraRotationToGLTF, + rotateNode180Y, } from "./glTFUtilities"; import { DataWriter } from "./dataWriter"; import { Camera } from "core/Cameras/camera"; @@ -701,7 +700,7 @@ export class GLTFExporter { private _setCameraTransformation(node: INode, babylonCamera: Camera, convertToRightHanded: boolean, parent: Nullable): void { const translation = TmpVectors.Vector3[0]; - const rotation = TmpVectors.Quaternion[0]; + let rotation = TmpVectors.Quaternion[0]; if (parent !== null) { const parentWorldMatrix = Matrix.Invert(parent.getWorldMatrix()); @@ -715,15 +714,12 @@ export class GLTFExporter { if (!translation.equalsToFloats(0, 0, 0)) { if (convertToRightHanded) { convertToRightHandedPosition(translation); + translation.x *= -1; } node.translation = translation.asArray(); } - if (convertToRightHanded) { - convertCameraRotationToGLTF(rotation); - } - if (!Quaternion.IsIdentity(rotation)) { node.rotation = rotation.asArray(); } @@ -1180,7 +1176,6 @@ export class GLTFExporter { this._setCameraTransformation(node, babylonNode, state.convertToRightHanded, parentBabylonNode); if (parentBabylonNode && isParentAddedByImporter(babylonNode, parentBabylonNode)) { - rotateNodeMinus180Z(node); const parentNodeIndex = this._nodeMap.get(parentBabylonNode); if (parentNodeIndex) { const parentNode = this._nodes[parentNodeIndex]; @@ -1188,6 +1183,9 @@ export class GLTFExporter { skipNode = true; } } else { + if (state.convertToRightHanded) { + rotateNode180Y(node); + } this._nodesCameraMap.get(gltfCamera)?.push(node); } } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index c064a04e2c1..d8750624586 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -19,7 +19,6 @@ const convertHandednessMatrix = Matrix.Compose(new Vector3(-1, 1, 1), Quaternion // 180 degrees rotation in Y. const rotation180Y = new Quaternion(0, 1, 0, 0); -const rotation180Z = new Quaternion(0, 0, 1, 0); /** * Creates a buffer view based on the supplied arguments @@ -226,14 +225,6 @@ export function rotateNode180Y(node: INode) { } } -export function rotateNodeMinus180Z(node: INode) { - if (node.rotation) { - const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); - rotation.multiplyInPlace(Quaternion.Inverse(rotation180Z)); - node.rotation = rotation.asArray(); - } -} - /** * Colapses GLTF parent and node into a single node. This is useful for removing nodes that were added by the GLTF importer. * @param node Target parent node. From 70661647ac41cb62a8da7e5890dd7373857adae5 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 18 Nov 2024 12:16:27 -0300 Subject: [PATCH 050/133] Finished working on camera rotation --- .../serializers/src/glTF/2.0/glTFExporter.ts | 2 +- .../ReferenceImages/glTFSerializerCamera.png | Bin 10556 -> 5873 bytes .../tests/test/visualization/config.json | 18 ++++++++++++------ 3 files changed, 13 insertions(+), 7 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 8364f037be3..3fa158e3182 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -700,7 +700,7 @@ export class GLTFExporter { private _setCameraTransformation(node: INode, babylonCamera: Camera, convertToRightHanded: boolean, parent: Nullable): void { const translation = TmpVectors.Vector3[0]; - let rotation = TmpVectors.Quaternion[0]; + const rotation = TmpVectors.Quaternion[0]; if (parent !== null) { const parentWorldMatrix = Matrix.Invert(parent.getWorldMatrix()); diff --git a/packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerCamera.png b/packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerCamera.png index 60c14b7e8d45cba2106a2a7a3fdca3a709eb5411..820b2ebf7877da63c8aebbc0b50bd9644026ba56 100644 GIT binary patch literal 5873 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 diff --git a/packages/tools/tests/test/visualization/config.json b/packages/tools/tests/test/visualization/config.json index c0fa76b21c9..1a46ff318fb 100644 --- a/packages/tools/tests/test/visualization/config.json +++ b/packages/tools/tests/test/visualization/config.json @@ -926,20 +926,26 @@ }, { "title": "GLTF Serializer Camera, left-handed", - "playgroundId": "#O0M0J9#7", - "replace": "//options//, roundtripCount = 3; useRightHandedSystem = false;", + "playgroundId": "#O0M0J9#8", + "replace": "//options//, roundtripCount = 1; useRightHandedSystem = false;", "referenceImage": "glTFSerializerCamera.png" }, { "title": "GLTF Serializer Camera, right-handed", - "playgroundId": "#O0M0J9#7", - "replace": "//options//, roundtripCount = 3; useRightHandedSystem = true;", + "playgroundId": "#O0M0J9#8", + "replace": "//options//, roundtripCount = 1; useRightHandedSystem = true;", + "referenceImage": "glTFSerializerCamera.png" + }, + { + "title": "GLTF Serializer Camera, left-handed, round trip twice", + "playgroundId": "#O0M0J9#8", + "replace": "//options//, roundtripCount = 2; useRightHandedSystem = false;", "referenceImage": "glTFSerializerCamera.png" }, { "title": "GLTF Serializer Camera, right-handed, round trip twice", - "playgroundId": "#O0M0J9#7", - "replace": "//options//, roundtripCount = 4; useRightHandedSystem = false;", + "playgroundId": "#O0M0J9#8", + "replace": "//options//, roundtripCount = 2; useRightHandedSystem = true;", "referenceImage": "glTFSerializerCamera.png" }, { From df96f4962c8e73bf86c1f83bfecf9589218bd508 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 18 Nov 2024 12:36:13 -0300 Subject: [PATCH 051/133] Removed unecessary transform --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 6 +----- 1 file changed, 1 insertion(+), 5 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 3fa158e3182..39cd7c59604 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -703,6 +703,7 @@ export class GLTFExporter { const rotation = TmpVectors.Quaternion[0]; if (parent !== null) { + // We need local coordinates. If camera has parent we need to que local translation/rotation. const parentWorldMatrix = Matrix.Invert(parent.getWorldMatrix()); const cameraWorldMatrix = babylonCamera.getWorldMatrix(); const cameraLocal = cameraWorldMatrix.multiply(parentWorldMatrix); @@ -712,11 +713,6 @@ export class GLTFExporter { } if (!translation.equalsToFloats(0, 0, 0)) { - if (convertToRightHanded) { - convertToRightHandedPosition(translation); - translation.x *= -1; - } - node.translation = translation.asArray(); } From 4aed5298db11fee37f8e89d6a6c75d2c1ddc537e Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 18 Nov 2024 12:57:43 -0300 Subject: [PATCH 052/133] Fixed transforms, added proper test for left handed scene --- .../serializers/src/glTF/2.0/glTFExporter.ts | 7 ++-- .../serializers/src/glTF/2.0/glTFUtilities.ts | 32 ++++++++++++++++++ .../glTFSerializerCameraLeftHand.png | Bin 0 -> 6127 bytes ....png => glTFSerializerCameraRightHand.png} | Bin .../tests/test/visualization/config.json | 8 ++--- 5 files changed, 41 insertions(+), 6 deletions(-) create mode 100644 packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerCameraLeftHand.png rename packages/tools/tests/test/visualization/ReferenceImages/{glTFSerializerCamera.png => glTFSerializerCameraRightHand.png} (100%) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 39cd7c59604..2bbea4e5aa0 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -58,7 +58,8 @@ import { isNoopNode, isTriangleFillMode, isParentAddedByImporter, - rotateNode180Y, + convertToRightHandedNode, + rotateNodeMinus180Y, } from "./glTFUtilities"; import { DataWriter } from "./dataWriter"; import { Camera } from "core/Cameras/camera"; @@ -1171,6 +1172,7 @@ export class GLTFExporter { const parentBabylonNode = babylonNode.parent; this._setCameraTransformation(node, babylonNode, state.convertToRightHanded, parentBabylonNode); + // If a camera has a node that was added by the GLTF importer, we can just use the parent node transform as the "camera" transform. if (parentBabylonNode && isParentAddedByImporter(babylonNode, parentBabylonNode)) { const parentNodeIndex = this._nodeMap.get(parentBabylonNode); if (parentNodeIndex) { @@ -1180,7 +1182,8 @@ export class GLTFExporter { } } else { if (state.convertToRightHanded) { - rotateNode180Y(node); + convertToRightHandedNode(node); + rotateNodeMinus180Y(node); } this._nodesCameraMap.get(gltfCamera)?.push(node); } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index d8750624586..5516d6d5b29 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -19,6 +19,7 @@ const convertHandednessMatrix = Matrix.Compose(new Vector3(-1, 1, 1), Quaternion // 180 degrees rotation in Y. const rotation180Y = new Quaternion(0, 1, 0, 0); +const rotation180MinusY = new Quaternion(0, 1, 0, 0).invert(); /** * Creates a buffer view based on the supplied arguments @@ -208,6 +209,29 @@ export function convertToRightHandedRotation(value: Quaternion): Quaternion { return value; } +export function convertToRightHandedNode(value: INode) { + let translation = Vector3.FromArrayToRef(value.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); + let rotation = Quaternion.FromArrayToRef(value.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); + + translation = convertToRightHandedPosition(translation); + rotation = convertToRightHandedRotation(rotation); + + value.rotation = rotation.asArray(); + value.translation = translation.asArray(); + + if (translation.equalsToFloats(0, 0, 0)) { + delete value.translation; + } else { + value.translation = translation.asArray(); + } + + if (Quaternion.IsIdentity(rotation)) { + delete value.rotation; + } else { + value.rotation = rotation.asArray(); + } +} + /** * Rotation by 180 as glTF has a different convention than Babylon. * @param rotation Target camera rotation. @@ -225,6 +249,14 @@ export function rotateNode180Y(node: INode) { } } +export function rotateNodeMinus180Y(node: INode) { + if (node.rotation) { + const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); + rotation180MinusY.multiplyToRef(rotation, rotation); + node.rotation = rotation.asArray(); + } +} + /** * Colapses GLTF parent and node into a single node. This is useful for removing nodes that were added by the GLTF importer. * @param node Target parent node. diff --git a/packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerCameraLeftHand.png b/packages/tools/tests/test/visualization/ReferenceImages/glTFSerializerCameraLeftHand.png new file mode 100644 index 0000000000000000000000000000000000000000..30567177134e07df1435a27e2ec39fa321f7b89c GIT binary patch literal 6127 zcmbtY`9D|#~vNoth*}vDjKjC|Snfo~F^Lg$$_jT^Xo0}T2vmR%K!C>r0hPsw87~Bp9V@N^3 zA<3R?d@uCF;BRRF!m4_NmtZh~awFY~SAug_rk3i3uM??1S95akf^#*jOy}6d+NI~oR!kxc^^0o79{6F|2#9zFcz-QEO_bkp08p%<)s z7uHXftzDj3z2xT;v0f4KJY;K@Gs5qpR`W)sS=H$GIY0Y7W6!CEjBk%y{!k9+OQ&I_ z9;fkPf9F*WDnCv=cdIT_3P0F682-=-H%S5l=|VGqN9oeC?_cbCTZ_0+oJK?V)tCU9 z{8Yq&<1q8<)KHt#uIcmEzoSKWgKRmEVAQ~|#GSR4id_y3#%zktmb*+$&8~L`qbCYL zqv+JpmVJc@(YkT2#=VZBiHTRlTZ*Umf5aXP{=LN@bqV2MBw3@o@in4jB~_^Wvt)gO z%xh9ohxjcNAcXQ-z2&^KFxjh;hp>8U96q)0J0ayW?pz;HpDnt#7$FparjqH26b7q+ znXr7j>d}Um_5!CGCpO(J|JXNQUqi=ooKU~JX#Q)~uQO2Y)@{3y9-TZG33t-#OY@GV zMR@E{X~c~(>I$n=i&Q!}#XN_YG#P;IGk~nihM;FewAX_l7^y$w7XlE*@>nZ=<4-YdaN_JvaWPPcPHyu3#-OYX7y21T=0w**A z=|u_h>LI^9&YqBp<+L)(+^J#bML#~0rH*>>EiY8FZV%T+pPN?PanO@Bw8|f!c7CF6 znN|6@r%$=YKOUg+vdnr1&FuBdbjyA;=?JUSvidz&G3;53C3cTF$vp{sNUxU6aU?>Z zzY0Q@lXo2JeX_6Il?m%-;hSJ|PJJK!-=*l@lY1d_^YdqyjSRZn*s%SkP|)bs~QB`vx({ z>7Y*si|E1w8C$4pxXb25CV znJJk`3h0eA2fyjOqT%khP(i$nBzU;N# z=%bRwIx8dIFoN~mxV%~%pofe)HYOU%2;XQ^*mqoZYfdaG`d@CV?cMsJa;^^djLx}0 zYMnsm3!93`gj;1JM1JB`=Y0cMT-u=Yuk`%T%+Q0Y8f!NDa~-@{?RmP7j{`xP!SxXq zhZXh~1zcPL6X*ZZ1oo9TvN{LnzRwXR?08L+$v_k*RmW71)MmA?7 z{M};9WCoq;{c)>PsdT=|q|;l!H21@~%jargJYNKHpBA(ZR616!XkC)OX5&{c^QTMG zLZ-Dm)Mk=9mXRUw)h(r!5r-Ml9?rJRjC_wg`ARu!Gi2qNVdsJ+qeH6~u12`=;4xKPwB z;{Us&TDN`TXiBGU@bu1f(Cw}3y?z1B2IO&K>z#?)YxjOzKyi;r(fl>nvs=122+`<_ z@}2g%-m|PEF^mNy_io<$)SZaq?^HtCiFHpbWjIXlja}xBR!pY`A*?4$wi$x!;RZm(vFHoVK}TqswCqxoT*dywVfP){`OG5 zPriY(-}TyuvU+cb90|?OM>09(sF&gGlK6y46*%3u#Uq-|UgJ!jo7E>W-e|4rU$+cB zp0gr=zBg8|nJ^Oa5Nq~i(wzhdi1|v<+Ip;(LLJX4cJ37kz4aEPD)Y>`?nr6`EuPMg z^-R2+7+a}6h@}{KUt1Uq+a8SQ-%5kug`m9tUd4GuExmbNQjsW(d=c=>CyT3F3}KQ0 z$XoIW`?YxN-1>mON=&I|7enC%nifI>hdu5cG&;(C8Vu>*uQCu1pt8Mw_vuTFP}QZ9 zqH*gH{f2_TNC8LV;h&{Brn|cfy~*`zFpVrkkXe+qerulmGm2sWs@DzWBcu%?1lUcV z`PAIH`@g-Su-*jgy2~64)YfAPK+ z;g4!YZW7Rcl9b!^RW%HatKt=@Bt|7Eyc*w&H-Y3Ykz*e|^}`g?rk=u7xFNC&(>dV_ zZP%X=?L}#y`mtt1vx>{27#7ej@KMcA2m+()a?fzf(~n>2O4`e00FiH9oRB81Ff-{9 z5tiL-n-bF$N8S~j-570I?~ryDzF!OrROFI>c%zvkC`H-sqWU+@0dF<^!%EL9WGn?T zFHv_4jYUY=M;%hbM&EDdF!V8_y6jJ2e{w6XZ~`2@+qw})o(DvlBcfc?^j`R3{y}$cjJ-+$dVgUeu z%T4ot{=&1|;q9{Q^~MY7&1K6?P`Bd=W`Ympo(hJ{%X6jdCNe< z$fZ-I*yAA3cpO@IZlRe#DZIegvE5&a6f7JE+`avtaD!-C(F)|)=TakA}<}!)zX?N1tMi_9WVnpai zspd6dCYm(c5xAsHY2Fy~YK~eKl=f#l=0kDv{_2Q>0{R%M6{G9I`j4>l}FK3JYv?n zWqUZk>dmUh2LFh zvQdc@S}C>*`{UK&Zpo*_0Utbxw%W87fj_Er}BXpDaNq)`suIwHa&H^9MZov%yxSzaa~Z- zRkZ}YC?jrKj42V@yiQygeLSBOGwlD`LZq!HD6wwxbmIxyLjIcX8`x1vd^hC>tx&f5 z{FtUwRyS@lFo#k+xAajncm@pZ|*en5S_-L&iQokJPTg*HmUYYx<2}_wlMGb~vh%{7=b^8+`Ih zG-*3E;wVOPs&XJt}H&x7w9 z+v&DR%He^L+6bJAQ`p8>n?Oq)C7JxK>mi+?$%|L$s#N9Mi*JjV73`0bwgPIglA~iKB9b_W+ z+$EO9L}}~%vn4rdfWGEXTNXlouQ$j0S;MH>*Ighog0=HI;EM9I7jTI_NXwr z+y%!nRc4y*zqAw9+#T?1keji>mJp`A9V#o{do}%he|^fEQCkdO8#$I4|25N)T=DB00&3aRe7^?;d8=Lv3%76q_E~)&j9NjcWChkINT`B zArz+zzwd_n4Yq-CAD0}_YLR+;>g|U1? z$$}Ou)eV7*lc)S1S>bdJS(>F6zAFOwDR(~e*-oBmuVaKrnQB|Kni98LhN7sqs?NCuFvDWVGsBDld_&6b1S1>OkvIex_fmw>r1J$Rh`4Yx z3qa*o0wHqmq4g$3gaBN-aP%V!YFz+KZWDYUiN`RViGdDH5mzqo5enM1xcb3%?*Fg? z0y?Ot2$Ze?5|;yjkH`Qwh}+W;95q|HT_2^280MwL1cV30AKJx z-Qfm5pGQl8AmXYvGCmQ2-^3bT!2|7ugm*gs9Jbg9x*#G7#2T{Ufm6kVt~>y);emp_ zL8;!@_D3uz^LYNVwjkn4385?XU&%Z%yl1sQ#1Lfs2!Pk#MERe@Yx7{+y%4Cr6#lcE zAVR5(&=m>5D?LzF(*Kn^s*a6d0#*D(A6ROPi1PM8@krve-N`AZxxoTsZ2LFJ^$gk# ziw73-_|KyLJ+nnZVM;jw7ARobB_O>xTH-#4_-%}2$pzq)K@d98sQ@{}g$0EH&=My9 zd<2sUQ(%zf6k0+FM6g{(vOEOf;(Z_udKsybQ?wB%%yF~?6Nq?Zggi$4$4CFhDYvVT z#0+`tF#x~V2fl~+oFb=KAW#5+b_)g(cs=B?ENJEF2V*4u@d1vp9)rk74S+Eyy!LBy z3Ik-UgnsG+8Ji-P(g8S60pYg+BI;%z7;^HTPX~r~fEmS9s*7BL81NJm?n0$l>Ic;& z@KlMLC`T^P%k)0Ywg0tI`?1B!XZfOsH|$gdCe z9T@`+Sxp};iv>kh!wO2Hm{iS>;!vRk`en2VD4-};E zzt=`EeDs*5LyjC+Rj76~Is82k!NbXY_Rd@t>O1pv{vN3Bh6)Jl;&|AG$7~k@rF|Q# zDh(p~@ccb709@%QVO{LsP(M-R?ScA^TtqnRyW2NspF#^2GugKh$|i(WwSat%p>t27 zm=;s{O_ToRS*f z&6e@cRWPm)1N9w77|oUfz}YS#bs&g3gR}w!ii#j(gdr6N8X7D3BYmV!jn?K-Oho6L zAN{h2H^nKL!lK<`f*&Hi@S$J!maNlKXjSA*GDWC;sJL&td)YFRKgk$zZ8ktnX9UnDqieD-TU9c&|vS`HhqT zl6n3-t?(ZENv82N9d{FMG!HI$*DROiC*?S=&TI<4M$yS2>)6b|*8$pF1Ww0EJT7Hz zE9&Mf24BNH9xbOOz}QD)K;SgBru!mAb2UA1GkD)wt-3xQEgQ#vnv>pfP%9sEUhD2{ z4_t(+T5=z!2$ST7_>!EyzS2Z|Ul%jam$F(`9h}(>K2XaT!?-Wt03vrVE5LSC)cCq$ zUJ-$qV~0rRbe;bYp@V#LE(+KlVSaTo;(8yA2Ca*Xj+p0U)%m+t>6Atcta7ye~XRGb=BLVJ}OA=)_O-tszsC)Y$BN99hc zFlpOL{f0jy1gxkg`*I&F!=jCx_=s7mLTWs-ynl|u*yO%tUedv#7jy*aEvK|U!|(46 z(Uv#iyMGw7o)tJ*)YcT4%Qv!Pq@#E(kQD$JDU#@FvMV7xoT^m`DgR|-$_qg z{F#|#K0f^M#cL7}YhfD62_yO7@- znyHROU+p0p)LtfL(t1NQ_607uNmd%ygd!Kb>mrGv-~1FFMjCL*EazuMQQ3xNT)&}t z!AP}=tkx_CNVRTzUHefT0ZR7x*U9(eAc`r@!h%EGt8g8`@MTU(mj?w)7>X2}I1H1U zCVcVHbI4ZvlV8JbPmreKrVX1ef}vf&2uT1U!82rY1j8xC9Vin_qwCF~;o+e>b>loQ zBuVe8J(O6Jyb1xhdFw6~ia4Z5IyA2BEpO(VhJJRa5=WFBh8x-! Date: Mon, 18 Nov 2024 13:08:49 -0300 Subject: [PATCH 053/133] Small cleanup --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 4 ++-- .../dev/serializers/src/glTF/2.0/glTFUtilities.ts | 11 +---------- 2 files changed, 3 insertions(+), 12 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 2bbea4e5aa0..54c6899019b 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -59,7 +59,7 @@ import { isTriangleFillMode, isParentAddedByImporter, convertToRightHandedNode, - rotateNodeMinus180Y, + rotateNode180Y, } from "./glTFUtilities"; import { DataWriter } from "./dataWriter"; import { Camera } from "core/Cameras/camera"; @@ -1183,7 +1183,7 @@ export class GLTFExporter { } else { if (state.convertToRightHanded) { convertToRightHandedNode(node); - rotateNodeMinus180Y(node); + rotateNode180Y(node); } this._nodesCameraMap.get(gltfCamera)?.push(node); } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 5516d6d5b29..a2c824b5839 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -19,7 +19,6 @@ const convertHandednessMatrix = Matrix.Compose(new Vector3(-1, 1, 1), Quaternion // 180 degrees rotation in Y. const rotation180Y = new Quaternion(0, 1, 0, 0); -const rotation180MinusY = new Quaternion(0, 1, 0, 0).invert(); /** * Creates a buffer view based on the supplied arguments @@ -244,15 +243,7 @@ export function convertCameraRotationToGLTF(rotation: Quaternion): Quaternion { export function rotateNode180Y(node: INode) { if (node.rotation) { const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); - rotation.multiplyInPlace(rotation180Y); - node.rotation = rotation.asArray(); - } -} - -export function rotateNodeMinus180Y(node: INode) { - if (node.rotation) { - const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); - rotation180MinusY.multiplyToRef(rotation, rotation); + rotation180Y.multiplyToRef(rotation, rotation); node.rotation = rotation.asArray(); } } From 0593cedc2502e0c6515949aabe4a96977d3468cf Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 18 Nov 2024 13:35:41 -0300 Subject: [PATCH 054/133] Fixed tipos and removed comments --- .../src/glTF/2.0/Extensions/KHR_lights_punctual.ts | 5 ++--- packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts | 2 +- 2 files changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index 4a1fae3b3dc..9b282e9aa84 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -9,7 +9,7 @@ import { KHRLightsPunctual_LightType } from "babylonjs-gltf2interface"; import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; import { GLTFExporter } from "../glTFExporter"; import { Logger } from "core/Misc/logger"; -import { convertToRightHandedPosition, omitDefaultValues, colapseParentNode, isParentAddedByImporter } from "../glTFUtilities"; +import { convertToRightHandedPosition, omitDefaultValues, collapseParentNode, isParentAddedByImporter } from "../glTFUtilities"; const NAME = "KHR_lights_punctual"; const DEFAULTS: Partial = { @@ -156,13 +156,12 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { // Why and when: the glTF loader generates a new parent TransformNode for each light node, which we should undo on export const parentBabylonNode = babylonNode.parent; - // TODO: May be able to simplify this logic by using our previous check for the root node if (parentBabylonNode && isParentAddedByImporter(babylonNode, parentBabylonNode)) { const parentNodeIndex = nodeMap.get(parentBabylonNode); if (parentNodeIndex) { // Combine the light's transformation with the parent's const parentNode = this._exporter._nodes[parentNodeIndex]; - colapseParentNode(node, parentNode); + collapseParentNode(node, parentNode); parentNode.extensions ||= {}; parentNode.extensions[NAME] = lightReference; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index a2c824b5839..6e32699ac40 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -253,7 +253,7 @@ export function rotateNode180Y(node: INode) { * @param node Target parent node. * @param parentNode Original GLTF node (Light or Camera). */ -export function colapseParentNode(node: INode, parentNode: INode) { +export function collapseParentNode(node: INode, parentNode: INode) { const parentTranslation = Vector3.FromArrayToRef(parentNode.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); const parentRotation = Quaternion.FromArrayToRef(parentNode.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); const parentScale = Vector3.FromArrayToRef(parentNode.scale || [1, 1, 1], 0, TmpVectors.Vector3[1]); From 8120e5354d3b68ded4043ee474f123f2b4b92375 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 18 Nov 2024 13:22:35 -0500 Subject: [PATCH 055/133] wip --- .../serializers/src/glTF/2.0/glTFExporter.ts | 38 ++++++++++++++++++- 1 file changed, 37 insertions(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 8906baeb424..93c4be82807 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -68,6 +68,7 @@ import { _GLTFAnimation } from "./glTFAnimation"; import type { MorphTarget } from "core/Morph"; import { buildMorphTargetBuffers } from "./glTFMorphTargetsUtilities"; import type { GlTFMorphTarget } from "./glTFMorphTargetsUtilities"; +import { Color3, Color4 } from "core/Maths/math.color"; // 180 degrees rotation in Y. const rotation180Y = new Quaternion(0, 1, 0, 0); @@ -1006,11 +1007,12 @@ export class GLTFExporter { const bytes = dataArrayToUint8Array(data).slice(); - // Normalize normals and tangents. for (const vertexBuffer of vertexBuffers) { switch (vertexBuffer.getKind()) { + // Normalize normals and tangents. case VertexBuffer.NormalKind: case VertexBuffer.TangentKind: { + // Q: Why do we iterate through all the meshes for each vertex buffer? for (const mesh of vertexBufferToMeshesMap.get(vertexBuffer)!) { const { byteOffset, byteStride, type, normalized } = vertexBuffer; const size = vertexBuffer.getSize(); @@ -1021,6 +1023,40 @@ export class GLTFExporter { values[2] *= invLength; }); } + break; + } + // Convert StandardMaterial vertex colors from gamma to linear space. + case VertexBuffer.ColorKind: { + const meshes = vertexBufferToMeshesMap.get(vertexBuffer)!; + const stdMatCount = meshes.filter((mesh) => mesh.material instanceof StandardMaterial).length; + // If buffer is used by only PBR materials, nothing to do. + if (stdMatCount === 0) { + break; + } + // If buffer is shared by both PBR and non-PBR materials, we won't convert for now. + // TODO: Implement this case. + if (stdMatCount < meshes.length) { + Logger.Warn("Not converting StandardMaterial's vertex color, as buffer is shared with non-StandardMaterial meshes. Results may look incorrect."); + break; + } + // Otherwise, buffer is used by only StandardMaterials, so we convert to linear. + const { byteOffset, byteStride, type, normalized } = vertexBuffer; + if (vertexBuffer.type == VertexBuffer.UNSIGNED_SHORT) { + const size = vertexBuffer.getSize(); + const vertexData: Color3 | Color4 = byteStride === 3 ? new Color3() : new Color4(); + const useExactSrgbConversions = this._babylonScene.getEngine().useExactSrgbConversions; + enumerateFloatValues(bytes, byteOffset, byteStride, size, type, meshes[0].getTotalVertices() * size, normalized, (values) => { + // Cast values to Color3 or Color4 to ensure TS calls correct functions + if (values.length === 4) { + (vertexData as Color4).fromArray(values, 0); + (vertexData as Color4).toLinearSpaceToRef(vertexData as Color4, useExactSrgbConversions); + (vertexData as Color4).toArray(values, 0); + } else if (values.length === 3) { + (vertexData as Color3).fromArray(values, 0); + (vertexData as Color3).toLinearSpaceToRef(vertexData as Color3); + (vertexData as Color3).toArray(values, 0); + } + }); } } } From 50f359992a19604d3134b4b49376439b25268c7d Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 18 Nov 2024 18:07:07 -0500 Subject: [PATCH 056/133] wip --- .../serializers/src/glTF/2.0/glTFExporter.ts | 29 ++++++++++++++++--- 1 file changed, 25 insertions(+), 4 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 93c4be82807..a5b5498693c 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1028,7 +1028,8 @@ export class GLTFExporter { // Convert StandardMaterial vertex colors from gamma to linear space. case VertexBuffer.ColorKind: { const meshes = vertexBufferToMeshesMap.get(vertexBuffer)!; - const stdMatCount = meshes.filter((mesh) => mesh.material instanceof StandardMaterial).length; + // Count the number of StandardMaterials, including null materials (which default to ) + const stdMatCount = meshes.filter((mesh) => mesh.material instanceof StandardMaterial || mesh.material === null).length; // If buffer is used by only PBR materials, nothing to do. if (stdMatCount === 0) { break; @@ -1039,13 +1040,33 @@ export class GLTFExporter { Logger.Warn("Not converting StandardMaterial's vertex color, as buffer is shared with non-StandardMaterial meshes. Results may look incorrect."); break; } - // Otherwise, buffer is used by only StandardMaterials, so we convert to linear. + + // Otherwise, buffer is used by only StandardMaterials, so we convert to linear.. + const { byteOffset, byteStride, type, normalized } = vertexBuffer; - if (vertexBuffer.type == VertexBuffer.UNSIGNED_SHORT) { + + if (type == VertexBuffer.BYTE || type == VertexBuffer.UNSIGNED_BYTE) { + Logger.Warn("Converting UINT8 vertex colors to linear space. Results may look incorrect."); + } + const size = vertexBuffer.getSize(); + const maxTotalVertices = meshes.reduce((max, current) => { + return current.getTotalVertices() > max ? current.getTotalVertices() : max; + }, -Number.MAX_VALUE); + /** + * say we have 4 available elements in our vertex buffer (indices [0,1,2,3]) + and say 2 meshes point to this vertex buffer, having the following EBOs: + + mesh 1 EBO: [0,1,2] + mesh 2 EBO: [0,1,3] + + maxTotalVertices(mesh1, mesh2) = 3 + + so if we enumerate until we've processed 3 elements, [0,1,2], we never hit 3?? + */ const vertexData: Color3 | Color4 = byteStride === 3 ? new Color3() : new Color4(); const useExactSrgbConversions = this._babylonScene.getEngine().useExactSrgbConversions; - enumerateFloatValues(bytes, byteOffset, byteStride, size, type, meshes[0].getTotalVertices() * size, normalized, (values) => { + enumerateFloatValues(bytes, byteOffset, byteStride, size, type, maxTotalVertices * size, normalized, (values) => { // Cast values to Color3 or Color4 to ensure TS calls correct functions if (values.length === 4) { (vertexData as Color4).fromArray(values, 0); From 2e31915f87399b9137ee51953d58e0ff479819ad Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Tue, 19 Nov 2024 10:45:53 -0500 Subject: [PATCH 057/133] Clean up --- .../serializers/src/glTF/2.0/glTFExporter.ts | 51 +++++++------------ 1 file changed, 19 insertions(+), 32 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index a5b5498693c..a757678014a 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -60,7 +60,7 @@ import { } from "./glTFUtilities"; import { DataWriter } from "./dataWriter"; import { Camera } from "core/Cameras/camera"; -import { MultiMaterial, PBRMaterial, StandardMaterial } from "core/Materials"; +import { MultiMaterial, PBRBaseMaterial, PBRMaterial, StandardMaterial } from "core/Materials"; import { Logger } from "core/Misc/logger"; import { enumerateFloatValues } from "core/Buffers/bufferUtils"; import type { Bone, Skeleton } from "core/Bones"; @@ -1012,7 +1012,6 @@ export class GLTFExporter { // Normalize normals and tangents. case VertexBuffer.NormalKind: case VertexBuffer.TangentKind: { - // Q: Why do we iterate through all the meshes for each vertex buffer? for (const mesh of vertexBufferToMeshesMap.get(vertexBuffer)!) { const { byteOffset, byteStride, type, normalized } = vertexBuffer; const size = vertexBuffer.getSize(); @@ -1028,54 +1027,42 @@ export class GLTFExporter { // Convert StandardMaterial vertex colors from gamma to linear space. case VertexBuffer.ColorKind: { const meshes = vertexBufferToMeshesMap.get(vertexBuffer)!; - // Count the number of StandardMaterials, including null materials (which default to ) - const stdMatCount = meshes.filter((mesh) => mesh.material instanceof StandardMaterial || mesh.material === null).length; - // If buffer is used by only PBR materials, nothing to do. - if (stdMatCount === 0) { - break; + const pbrCount = meshes.filter((mesh) => mesh.material instanceof PBRBaseMaterial).length; + + if (pbrCount === meshes.length) { + break; // Only PBR materials, so no conversion needed. } - // If buffer is shared by both PBR and non-PBR materials, we won't convert for now. + // TODO: Implement this case. - if (stdMatCount < meshes.length) { - Logger.Warn("Not converting StandardMaterial's vertex color, as buffer is shared with non-StandardMaterial meshes. Results may look incorrect."); + if (pbrCount !== 0) { + Logger.Warn("Not converting vertex color space, as buffer is shared by both PBR and StandardMaterials. Results may look incorrect."); break; } - // Otherwise, buffer is used by only StandardMaterials, so we convert to linear.. - + // Proceed with conversion const { byteOffset, byteStride, type, normalized } = vertexBuffer; - - if (type == VertexBuffer.BYTE || type == VertexBuffer.UNSIGNED_BYTE) { - Logger.Warn("Converting UINT8 vertex colors to linear space. Results may look incorrect."); - } - const size = vertexBuffer.getSize(); const maxTotalVertices = meshes.reduce((max, current) => { return current.getTotalVertices() > max ? current.getTotalVertices() : max; }, -Number.MAX_VALUE); - /** - * say we have 4 available elements in our vertex buffer (indices [0,1,2,3]) - and say 2 meshes point to this vertex buffer, having the following EBOs: - mesh 1 EBO: [0,1,2] - mesh 2 EBO: [0,1,3] - - maxTotalVertices(mesh1, mesh2) = 3 + if (type == VertexBuffer.BYTE || type == VertexBuffer.UNSIGNED_BYTE) { + Logger.Warn("Converting uint8 vertex colors to linear space. Results may look incorrect."); + } - so if we enumerate until we've processed 3 elements, [0,1,2], we never hit 3?? - */ - const vertexData: Color3 | Color4 = byteStride === 3 ? new Color3() : new Color4(); + const vertexData: Color3 | Color4 = size === 3 ? new Color3() : new Color4(); const useExactSrgbConversions = this._babylonScene.getEngine().useExactSrgbConversions; + enumerateFloatValues(bytes, byteOffset, byteStride, size, type, maxTotalVertices * size, normalized, (values) => { // Cast values to Color3 or Color4 to ensure TS calls correct functions - if (values.length === 4) { + if (values.length === 3) { + (vertexData as Color3).fromArray(values, 0); + (vertexData as Color3).toLinearSpaceToRef(vertexData as Color3, useExactSrgbConversions); + (vertexData as Color3).toArray(values, 0); + } else { (vertexData as Color4).fromArray(values, 0); (vertexData as Color4).toLinearSpaceToRef(vertexData as Color4, useExactSrgbConversions); (vertexData as Color4).toArray(values, 0); - } else if (values.length === 3) { - (vertexData as Color3).fromArray(values, 0); - (vertexData as Color3).toLinearSpaceToRef(vertexData as Color3); - (vertexData as Color3).toArray(values, 0); } }); } From d06ffb3de4aaf0efbe48fc04af8114fd705ba74d Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Tue, 19 Nov 2024 14:59:24 -0300 Subject: [PATCH 058/133] Added handle for linesMesh --- .../serializers/src/glTF/2.0/glTFExporter.ts | 47 ++++++++++++------- 1 file changed, 29 insertions(+), 18 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 54c6899019b..4919da66a45 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -71,6 +71,8 @@ import { _GLTFAnimation } from "./glTFAnimation"; import type { MorphTarget } from "core/Morph"; import { buildMorphTargetBuffers } from "./glTFMorphTargetsUtilities"; import type { GlTFMorphTarget } from "./glTFMorphTargetsUtilities"; +import { LinesMesh } from "core/Meshes/linesMesh"; +import { Color3 } from "core/Maths/math.color"; class ExporterState { // Babylon indices array, start, count, offset, flip -> glTF accessor index @@ -1378,22 +1380,6 @@ export class GLTFExporter { private async _exportMaterialAsync(babylonMaterial: Material, vertexBuffers: { [kind: string]: VertexBuffer }, subMesh: SubMesh, primitive: IMeshPrimitive): Promise { let materialIndex = this._materialMap.get(babylonMaterial); if (materialIndex === undefined) { - // TODO: Handle LinesMesh - // if (babylonMesh instanceof LinesMesh) { - // const material: IMaterial = { - // name: babylonMaterial.name, - // }; - - // if (!babylonMesh.color.equals(Color3.White()) || babylonMesh.alpha < 1) { - // material.pbrMetallicRoughness = { - // baseColorFactor: [...babylonMesh.color.asArray(), babylonMesh.alpha], - // }; - // } - - // this._materials.push(material); - // materialIndex = this._materials.length - 1; - // } - const hasUVs = vertexBuffers && Object.keys(vertexBuffers).some((kind) => kind.startsWith("uv")); babylonMaterial = babylonMaterial instanceof MultiMaterial ? babylonMaterial.subMaterials[subMesh.materialIndex]! : babylonMaterial; if (babylonMaterial instanceof PBRMaterial) { @@ -1426,14 +1412,39 @@ export class GLTFExporter { const vertexBuffers = babylonMesh.geometry?.getVertexBuffers(); const morphTargets = state.getMorphTargetsFromMesh(babylonMesh); + let isLinesMesh = false; + + if (babylonMesh instanceof LinesMesh) { + isLinesMesh = true; + } + const subMeshes = babylonMesh.subMeshes; if (vertexBuffers && subMeshes && subMeshes.length > 0) { for (const subMesh of subMeshes) { const primitive: IMeshPrimitive = { attributes: {} }; - // Material const babylonMaterial = subMesh.getMaterial() || this._babylonScene.defaultMaterial; - await this._exportMaterialAsync(babylonMaterial, vertexBuffers, subMesh, primitive); + + // Special case for LinesMesh + if (isLinesMesh) { + const material: IMaterial = { + name: babylonMaterial.name, + }; + + const babylonLinesMesh = babylonMesh as LinesMesh; + + if (!babylonLinesMesh.color.equals(Color3.White()) || babylonLinesMesh.alpha < 1) { + material.pbrMetallicRoughness = { + baseColorFactor: [...babylonLinesMesh.color.asArray(), babylonLinesMesh.alpha], + }; + } + + this._materials.push(material); + primitive.material = this._materials.length - 1; + } else { + // Material + await this._exportMaterialAsync(babylonMaterial, vertexBuffers, subMesh, primitive); + } // Index buffer const fillMode = babylonMesh.overrideRenderingFillMode ?? babylonMaterial.fillMode; From 1a6d77a2b2dd5dab6a0a12345e0ab08a1934849f Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Tue, 19 Nov 2024 15:41:11 -0300 Subject: [PATCH 059/133] Added small change for fillMode when mesh is LinesMesh --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 4919da66a45..a0749738483 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1447,9 +1447,10 @@ export class GLTFExporter { } // Index buffer - const fillMode = babylonMesh.overrideRenderingFillMode ?? babylonMaterial.fillMode; + const fillMode = isLinesMesh ? Material.LineListDrawMode : (babylonMesh.overrideRenderingFillMode ?? babylonMaterial.fillMode); const sideOrientation = babylonMaterial._getEffectiveOrientation(babylonMesh); + this._exportIndices(indices, subMesh.indexStart, subMesh.indexCount, -subMesh.verticesStart, fillMode, sideOrientation, state, primitive); // Vertex buffers From 16a9918570dc57d1f6e17bcddfcaac7804563cf8 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:21:34 -0500 Subject: [PATCH 060/133] Factor out common variables; extra comments --- .../serializers/src/glTF/2.0/glTFExporter.ts | 33 +++++++++---------- 1 file changed, 15 insertions(+), 18 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index a757678014a..b39d9580c8b 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1007,30 +1007,33 @@ export class GLTFExporter { const bytes = dataArrayToUint8Array(data).slice(); + // Apply conversions to buffer data in-place. for (const vertexBuffer of vertexBuffers) { + const { byteOffset, byteStride, type, normalized } = vertexBuffer; + const size = vertexBuffer.getSize(); + const meshes = vertexBufferToMeshesMap.get(vertexBuffer)!; + const maxTotalVertices = meshes.reduce((max, current) => { + return current.getTotalVertices() > max ? current.getTotalVertices() : max; + }, -Number.MAX_VALUE); // To ensure nothing is missed when enumerating, but may not be necessary. + switch (vertexBuffer.getKind()) { // Normalize normals and tangents. case VertexBuffer.NormalKind: case VertexBuffer.TangentKind: { - for (const mesh of vertexBufferToMeshesMap.get(vertexBuffer)!) { - const { byteOffset, byteStride, type, normalized } = vertexBuffer; - const size = vertexBuffer.getSize(); - enumerateFloatValues(bytes, byteOffset, byteStride, size, type, mesh.getTotalVertices() * size, normalized, (values) => { - const invLength = 1 / Math.sqrt(values[0] * values[0] + values[1] * values[1] + values[2] * values[2]); - values[0] *= invLength; - values[1] *= invLength; - values[2] *= invLength; - }); - } + enumerateFloatValues(bytes, byteOffset, byteStride, size, type, maxTotalVertices * size, normalized, (values) => { + const invLength = 1 / Math.sqrt(values[0] * values[0] + values[1] * values[1] + values[2] * values[2]); + values[0] *= invLength; + values[1] *= invLength; + values[2] *= invLength; + }); break; } // Convert StandardMaterial vertex colors from gamma to linear space. case VertexBuffer.ColorKind: { - const meshes = vertexBufferToMeshesMap.get(vertexBuffer)!; const pbrCount = meshes.filter((mesh) => mesh.material instanceof PBRBaseMaterial).length; if (pbrCount === meshes.length) { - break; // Only PBR materials, so no conversion needed. + break; // Buffer used by only PBR materials, so no conversion needed. } // TODO: Implement this case. @@ -1040,12 +1043,6 @@ export class GLTFExporter { } // Proceed with conversion - const { byteOffset, byteStride, type, normalized } = vertexBuffer; - const size = vertexBuffer.getSize(); - const maxTotalVertices = meshes.reduce((max, current) => { - return current.getTotalVertices() > max ? current.getTotalVertices() : max; - }, -Number.MAX_VALUE); - if (type == VertexBuffer.BYTE || type == VertexBuffer.UNSIGNED_BYTE) { Logger.Warn("Converting uint8 vertex colors to linear space. Results may look incorrect."); } From 895f625021c6533a2bea6ebb83bfccb8df774d6b Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:26:35 -0500 Subject: [PATCH 061/133] Count stdMaterials, not just non-stdMaterials, since not sure what other materials like Multimaterial do --- .../dev/serializers/src/glTF/2.0/glTFExporter.ts | 12 ++++++------ 1 file changed, 6 insertions(+), 6 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index b39d9580c8b..57a9210bee0 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -60,7 +60,7 @@ import { } from "./glTFUtilities"; import { DataWriter } from "./dataWriter"; import { Camera } from "core/Cameras/camera"; -import { MultiMaterial, PBRBaseMaterial, PBRMaterial, StandardMaterial } from "core/Materials"; +import { MultiMaterial, PBRMaterial, StandardMaterial } from "core/Materials"; import { Logger } from "core/Misc/logger"; import { enumerateFloatValues } from "core/Buffers/bufferUtils"; import type { Bone, Skeleton } from "core/Bones"; @@ -1030,15 +1030,15 @@ export class GLTFExporter { } // Convert StandardMaterial vertex colors from gamma to linear space. case VertexBuffer.ColorKind: { - const pbrCount = meshes.filter((mesh) => mesh.material instanceof PBRBaseMaterial).length; + const stdMaterialCount = meshes.filter((mesh) => mesh.material instanceof StandardMaterial || mesh.material == null).length; - if (pbrCount === meshes.length) { - break; // Buffer used by only PBR materials, so no conversion needed. + if (stdMaterialCount == 0) { + break; // Buffer not used by StandardMaterials, so no conversion needed. } // TODO: Implement this case. - if (pbrCount !== 0) { - Logger.Warn("Not converting vertex color space, as buffer is shared by both PBR and StandardMaterials. Results may look incorrect."); + if (stdMaterialCount != meshes.length) { + Logger.Warn("Not converting vertex color space, as buffer is shared by StandardMaterials and other material types. Results may look incorrect."); break; } From 6cf9810743d032ecf96ce7561d186a97ae9b5c6d Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:30:25 -0500 Subject: [PATCH 062/133] Remove casting to Color3/4, use separate Color3/4 variables --- .../serializers/src/glTF/2.0/glTFExporter.ts | 17 +++++++++-------- 1 file changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 57a9210bee0..f5611d49db8 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1047,19 +1047,20 @@ export class GLTFExporter { Logger.Warn("Converting uint8 vertex colors to linear space. Results may look incorrect."); } - const vertexData: Color3 | Color4 = size === 3 ? new Color3() : new Color4(); + const vertexData3 = new Color3(); + const vertexData4 = new Color4(); const useExactSrgbConversions = this._babylonScene.getEngine().useExactSrgbConversions; enumerateFloatValues(bytes, byteOffset, byteStride, size, type, maxTotalVertices * size, normalized, (values) => { - // Cast values to Color3 or Color4 to ensure TS calls correct functions + // Using separate Color3 and Color4 objects to ensure the right functions are called. if (values.length === 3) { - (vertexData as Color3).fromArray(values, 0); - (vertexData as Color3).toLinearSpaceToRef(vertexData as Color3, useExactSrgbConversions); - (vertexData as Color3).toArray(values, 0); + vertexData3.fromArray(values, 0); + vertexData3.toLinearSpaceToRef(vertexData3, useExactSrgbConversions); + vertexData3.toArray(values, 0); } else { - (vertexData as Color4).fromArray(values, 0); - (vertexData as Color4).toLinearSpaceToRef(vertexData as Color4, useExactSrgbConversions); - (vertexData as Color4).toArray(values, 0); + vertexData4.fromArray(values, 0); + vertexData4.toLinearSpaceToRef(vertexData4, useExactSrgbConversions); + vertexData4.toArray(values, 0); } }); } From 23625d6f7e65a8c69120f22302cfee3cd28ce728 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:32:08 -0500 Subject: [PATCH 063/133] Remove int8 check, just need uint8 --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index f5611d49db8..32076efde06 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1043,7 +1043,7 @@ export class GLTFExporter { } // Proceed with conversion - if (type == VertexBuffer.BYTE || type == VertexBuffer.UNSIGNED_BYTE) { + if (type == VertexBuffer.UNSIGNED_BYTE) { Logger.Warn("Converting uint8 vertex colors to linear space. Results may look incorrect."); } From fe053d1a21d4d9f94b7077aacb38c5ee567d73c8 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Tue, 19 Nov 2024 14:33:13 -0500 Subject: [PATCH 064/133] remove unneeded comment --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 32076efde06..0fc9a7df986 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1042,7 +1042,6 @@ export class GLTFExporter { break; } - // Proceed with conversion if (type == VertexBuffer.UNSIGNED_BYTE) { Logger.Warn("Converting uint8 vertex colors to linear space. Results may look incorrect."); } From 5384a0d30e113f6459f917ede8305503ddf07557 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Tue, 19 Nov 2024 16:58:16 -0300 Subject: [PATCH 065/133] Added support for GPU instancing --- .../2.0/Extensions/EXT_mesh_gpu_instancing.ts | 366 +++++++++--------- .../src/glTF/2.0/Extensions/index.ts | 2 +- .../serializers/src/glTF/2.0/glTFExporter.ts | 13 +- .../src/glTF/2.0/glTFExporterExtension.ts | 10 +- 4 files changed, 199 insertions(+), 192 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts index a14818e2498..0c4d5366d2d 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts @@ -1,185 +1,181 @@ -// import type { IBufferView, IAccessor, INode, IEXTMeshGpuInstancing } from "babylonjs-gltf2interface"; -// import { AccessorType, AccessorComponentType } from "babylonjs-gltf2interface"; -// import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; -// import type { _BinaryWriter } from "../glTFExporter"; -// import { _Exporter } from "../glTFExporter"; -// import type { Nullable } from "core/types"; -// import type { Node } from "core/node"; -// import { Mesh } from "core/Meshes/mesh"; -// import "core/Meshes/thinInstanceMesh"; -// import { TmpVectors, Quaternion, Vector3 } from "core/Maths/math.vector"; -// import { VertexBuffer } from "core/Buffers/buffer"; - -// const NAME = "EXT_mesh_gpu_instancing"; - -// /** -// * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_mesh_gpu_instancing/README.md) -// */ -// // eslint-disable-next-line @typescript-eslint/naming-convention -// export class EXT_mesh_gpu_instancing implements IGLTFExporterExtensionV2 { -// /** Name of this extension */ -// public readonly name = NAME; - -// /** Defines whether this extension is enabled */ -// public enabled = true; - -// /** Defines whether this extension is required */ -// public required = false; - -// private _exporter: _Exporter; - -// private _wasUsed = false; - -// constructor(exporter: _Exporter) { -// this._exporter = exporter; -// } - -// public dispose() {} - -// /** @internal */ -// public get wasUsed() { -// return this._wasUsed; -// } - -// /** -// * After node is exported -// * @param context the GLTF context when loading the asset -// * @param node the node exported -// * @param babylonNode the corresponding babylon node -// * @param nodeMap map from babylon node id to node index -// * @param binaryWriter binary writer -// * @returns nullable promise, resolves with the node -// */ -// public postExportNodeAsync( -// context: string, -// node: Nullable, -// babylonNode: Node, -// nodeMap: { [key: number]: number }, -// binaryWriter: _BinaryWriter -// ): Promise> { -// return new Promise((resolve) => { -// if (node && babylonNode instanceof Mesh) { -// if (babylonNode.hasThinInstances && binaryWriter) { -// this._wasUsed = true; - -// const noTranslation = Vector3.Zero(); -// const noRotation = Quaternion.Identity(); -// const noScale = Vector3.One(); - -// // retrieve all the instance world matrix -// const matrix = babylonNode.thinInstanceGetWorldMatrices(); - -// const iwt = TmpVectors.Vector3[2]; -// const iwr = TmpVectors.Quaternion[1]; -// const iws = TmpVectors.Vector3[3]; - -// let hasAnyInstanceWorldTranslation = false; -// let hasAnyInstanceWorldRotation = false; -// let hasAnyInstanceWorldScale = false; - -// // prepare temp buffers -// const translationBuffer = new Float32Array(babylonNode.thinInstanceCount * 3); -// const rotationBuffer = new Float32Array(babylonNode.thinInstanceCount * 4); -// const scaleBuffer = new Float32Array(babylonNode.thinInstanceCount * 3); - -// let i = 0; -// for (const m of matrix) { -// m.decompose(iws, iwr, iwt); - -// // fill the temp buffer -// translationBuffer.set(iwt.asArray(), i * 3); -// rotationBuffer.set(iwr.normalize().asArray(), i * 4); // ensure the quaternion is normalized -// scaleBuffer.set(iws.asArray(), i * 3); - -// // this is where we decide if there is any transformation -// hasAnyInstanceWorldTranslation = hasAnyInstanceWorldTranslation || !iwt.equalsWithEpsilon(noTranslation); -// hasAnyInstanceWorldRotation = hasAnyInstanceWorldRotation || !iwr.equalsWithEpsilon(noRotation); -// hasAnyInstanceWorldScale = hasAnyInstanceWorldScale || !iws.equalsWithEpsilon(noScale); - -// i++; -// } - -// const extension: IEXTMeshGpuInstancing = { -// attributes: {}, -// }; - -// // do we need to write TRANSLATION ? -// if (hasAnyInstanceWorldTranslation) { -// extension.attributes["TRANSLATION"] = this._buildAccessor( -// translationBuffer, -// AccessorType.VEC3, -// babylonNode.thinInstanceCount, -// binaryWriter, -// AccessorComponentType.FLOAT -// ); -// } -// // do we need to write ROTATION ? -// if (hasAnyInstanceWorldRotation) { -// const componentType = AccessorComponentType.FLOAT; // we decided to stay on FLOAT for now see https://github.com/BabylonJS/Babylon.js/pull/12495 -// extension.attributes["ROTATION"] = this._buildAccessor(rotationBuffer, AccessorType.VEC4, babylonNode.thinInstanceCount, binaryWriter, componentType); -// } -// // do we need to write SCALE ? -// if (hasAnyInstanceWorldScale) { -// extension.attributes["SCALE"] = this._buildAccessor( -// scaleBuffer, -// AccessorType.VEC3, -// babylonNode.thinInstanceCount, -// binaryWriter, -// AccessorComponentType.FLOAT -// ); -// } - -// /* eslint-enable @typescript-eslint/naming-convention*/ -// node.extensions = node.extensions || {}; -// node.extensions[NAME] = extension; -// } -// } -// resolve(node); -// }); -// } - -// private _buildAccessor(buffer: Float32Array, type: AccessorType, count: number, binaryWriter: _BinaryWriter, componentType: AccessorComponentType): number { -// // write the buffer -// const bufferOffset = binaryWriter.getByteOffset(); -// switch (componentType) { -// case AccessorComponentType.FLOAT: { -// for (let i = 0; i != buffer.length; i++) { -// binaryWriter.setFloat32(buffer[i]); -// } -// break; -// } -// case AccessorComponentType.BYTE: { -// for (let i = 0; i != buffer.length; i++) { -// binaryWriter.setByte(buffer[i] * 127); -// } -// break; -// } -// case AccessorComponentType.SHORT: { -// for (let i = 0; i != buffer.length; i++) { -// binaryWriter.setInt16(buffer[i] * 32767); -// } - -// break; -// } -// } -// // build the buffer view -// const bv: IBufferView = { buffer: 0, byteOffset: bufferOffset, byteLength: buffer.length * VertexBuffer.GetTypeByteLength(componentType) }; -// const bufferViewIndex = this._exporter._bufferViews.length; -// this._exporter._bufferViews.push(bv); - -// // finally build the accessor -// const accessorIndex = this._exporter._accessors.length; -// const accessor: IAccessor = { -// bufferView: bufferViewIndex, -// componentType: componentType, -// count: count, -// type: type, -// normalized: componentType == AccessorComponentType.BYTE || componentType == AccessorComponentType.SHORT, -// }; -// this._exporter._accessors.push(accessor); -// return accessorIndex; -// } -// } - -// // eslint-disable-next-line @typescript-eslint/no-unused-vars -// _Exporter.RegisterExtension(NAME, (exporter) => new EXT_mesh_gpu_instancing(exporter)); +import type { IBufferView, IAccessor, INode, IEXTMeshGpuInstancing } from "babylonjs-gltf2interface"; +import { AccessorType, AccessorComponentType } from "babylonjs-gltf2interface"; +import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; +import type { DataWriter } from "../dataWriter"; +import { GLTFExporter } from "../glTFExporter"; +import type { Nullable } from "core/types"; +import type { Node } from "core/node"; +import { Mesh } from "core/Meshes/mesh"; +import "core/Meshes/thinInstanceMesh"; +import { TmpVectors, Quaternion, Vector3 } from "core/Maths/math.vector"; +import { VertexBuffer } from "core/Buffers/buffer"; + +const NAME = "EXT_mesh_gpu_instancing"; + +/** + * [Specification](https://github.com/KhronosGroup/glTF/blob/main/extensions/2.0/Vendor/EXT_mesh_gpu_instancing/README.md) + */ +// eslint-disable-next-line @typescript-eslint/naming-convention +export class EXT_mesh_gpu_instancing implements IGLTFExporterExtensionV2 { + /** Name of this extension */ + public readonly name = NAME; + + /** Defines whether this extension is enabled */ + public enabled = true; + + /** Defines whether this extension is required */ + public required = false; + + private _exporter: GLTFExporter; + + private _wasUsed = false; + + constructor(exporter: GLTFExporter) { + this._exporter = exporter; + } + + public dispose() {} + + /** @internal */ + public get wasUsed() { + return this._wasUsed; + } + + /** + * After node is exported + * @param context the GLTF context when loading the asset + * @param node the node exported + * @param babylonNode the corresponding babylon node + * @param nodeMap map from babylon node id to node index + * @param convertToRightHanded true if we need to convert data from left hand to right hand system. + * @param dataWriter binary writer + * @returns nullable promise, resolves with the node + */ + public postExportNodeAsync( + context: string, + node: Nullable, + babylonNode: Node, + nodeMap: Map, + convertToRightHanded: boolean, + dataWriter: DataWriter + ): Promise> { + return new Promise((resolve) => { + if (node && babylonNode instanceof Mesh) { + if (babylonNode.hasThinInstances && this._exporter) { + this._wasUsed = true; + + const noTranslation = Vector3.Zero(); + const noRotation = Quaternion.Identity(); + const noScale = Vector3.One(); + + // retrieve all the instance world matrix + const matrix = babylonNode.thinInstanceGetWorldMatrices(); + + const iwt = TmpVectors.Vector3[2]; + const iwr = TmpVectors.Quaternion[1]; + const iws = TmpVectors.Vector3[3]; + + let hasAnyInstanceWorldTranslation = false; + let hasAnyInstanceWorldRotation = false; + let hasAnyInstanceWorldScale = false; + + // prepare temp buffers + const translationBuffer = new Float32Array(babylonNode.thinInstanceCount * 3); + const rotationBuffer = new Float32Array(babylonNode.thinInstanceCount * 4); + const scaleBuffer = new Float32Array(babylonNode.thinInstanceCount * 3); + + let i = 0; + for (const m of matrix) { + m.decompose(iws, iwr, iwt); + + // fill the temp buffer + translationBuffer.set(iwt.asArray(), i * 3); + rotationBuffer.set(iwr.normalize().asArray(), i * 4); // ensure the quaternion is normalized + scaleBuffer.set(iws.asArray(), i * 3); + + // this is where we decide if there is any transformation + hasAnyInstanceWorldTranslation = hasAnyInstanceWorldTranslation || !iwt.equalsWithEpsilon(noTranslation); + hasAnyInstanceWorldRotation = hasAnyInstanceWorldRotation || !iwr.equalsWithEpsilon(noRotation); + hasAnyInstanceWorldScale = hasAnyInstanceWorldScale || !iws.equalsWithEpsilon(noScale); + + i++; + } + + const extension: IEXTMeshGpuInstancing = { + attributes: {}, + }; + + // do we need to write TRANSLATION ? + if (hasAnyInstanceWorldTranslation) { + extension.attributes["TRANSLATION"] = this._buildAccessor( + translationBuffer, + AccessorType.VEC3, + babylonNode.thinInstanceCount, + dataWriter, + AccessorComponentType.FLOAT + ); + } + // do we need to write ROTATION ? + if (hasAnyInstanceWorldRotation) { + const componentType = AccessorComponentType.FLOAT; // we decided to stay on FLOAT for now see https://github.com/BabylonJS/Babylon.js/pull/12495 + extension.attributes["ROTATION"] = this._buildAccessor(rotationBuffer, AccessorType.VEC4, babylonNode.thinInstanceCount, dataWriter, componentType); + } + // do we need to write SCALE ? + if (hasAnyInstanceWorldScale) { + extension.attributes["SCALE"] = this._buildAccessor(scaleBuffer, AccessorType.VEC3, babylonNode.thinInstanceCount, dataWriter, AccessorComponentType.FLOAT); + } + + /* eslint-enable @typescript-eslint/naming-convention*/ + node.extensions = node.extensions || {}; + node.extensions[NAME] = extension; + } + } + resolve(node); + }); + } + + private _buildAccessor(buffer: Float32Array, type: AccessorType, count: number, binaryWriter: DataWriter, componentType: AccessorComponentType): number { + // write the buffer + const bufferOffset = binaryWriter.byteOffset; + switch (componentType) { + case AccessorComponentType.FLOAT: { + for (let i = 0; i != buffer.length; i++) { + binaryWriter.writeFloat32(buffer[i]); + } + break; + } + case AccessorComponentType.BYTE: { + for (let i = 0; i != buffer.length; i++) { + binaryWriter.writeUInt8(buffer[i] * 127); + } + break; + } + case AccessorComponentType.SHORT: { + for (let i = 0; i != buffer.length; i++) { + binaryWriter.writeInt16(buffer[i] * 32767); + } + + break; + } + } + // build the buffer view + const bv: IBufferView = { buffer: 0, byteOffset: bufferOffset, byteLength: buffer.length * VertexBuffer.GetTypeByteLength(componentType) }; + const bufferViewIndex = this._exporter._bufferViews.length; + this._exporter._bufferViews.push(bv); + + // finally build the accessor + const accessorIndex = this._exporter._accessors.length; + const accessor: IAccessor = { + bufferView: bufferViewIndex, + componentType: componentType, + count: count, + type: type, + normalized: componentType == AccessorComponentType.BYTE || componentType == AccessorComponentType.SHORT, + }; + this._exporter._accessors.push(accessor); + return accessorIndex; + } +} + +// eslint-disable-next-line @typescript-eslint/no-unused-vars +GLTFExporter.RegisterExtension(NAME, (exporter) => new EXT_mesh_gpu_instancing(exporter)); diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts index 4673cd070e3..57c28d537f0 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/index.ts @@ -1,4 +1,4 @@ -// export * from "./EXT_mesh_gpu_instancing"; +export * from "./EXT_mesh_gpu_instancing"; export * from "./KHR_lights_punctual"; export * from "./KHR_materials_anisotropy"; export * from "./KHR_materials_clearcoat"; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index a0749738483..6172ef899a2 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -323,7 +323,7 @@ export class GLTFExporter { ): Promise> { return this._applyExtensions( node, - (extension, node) => extension.postExportNodeAsync && extension.postExportNodeAsync(context, node, babylonNode, nodeMap, convertToRightHanded) + (extension, node) => extension.postExportNodeAsync && extension.postExportNodeAsync(context, node, babylonNode, nodeMap, convertToRightHanded, this._dataWriter) ); } @@ -1338,6 +1338,11 @@ export class GLTFExporter { private _exportVertexBuffer(vertexBuffer: VertexBuffer, babylonMaterial: Material, start: number, count: number, state: ExporterState, primitive: IMeshPrimitive): void { const kind = vertexBuffer.getKind(); + + if (kind.startsWith("world")) { + return; + } + if (kind.startsWith("uv") && !this._options.exportUnusedUVs) { if (!babylonMaterial || !this._materialNeedsUVsSet.has(babylonMaterial)) { return; @@ -1370,11 +1375,9 @@ export class GLTFExporter { state.setVertexAccessor(vertexBuffer, start, count, accessorIndex); primitive.attributes[getAttributeType(kind)] = accessorIndex; } + } else { + primitive.attributes[getAttributeType(kind)] = accessorIndex; } - - // TODO: StandardMaterial color spaces - // probably have to create new buffer view to store new colors during collectBuffers and figure out if only standardMaterial is using it - // separate map by color space } private async _exportMaterialAsync(babylonMaterial: Material, vertexBuffers: { [kind: string]: VertexBuffer }, subMesh: SubMesh, primitive: IMeshPrimitive): Promise { diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts index 65918e2e845..1ba60c736bb 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts @@ -9,6 +9,7 @@ import type { IDisposable } from "core/scene"; import type { IGLTFExporterExtension } from "../glTFFileExporter"; import type { Material } from "core/Materials/material"; import type { BaseTexture } from "core/Materials/Textures/baseTexture"; +import type { DataWriter } from "./dataWriter"; /** @internal */ // eslint-disable-next-line no-var, @typescript-eslint/naming-convention @@ -54,7 +55,14 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo * @param convertToRightHanded Flag indicating whether to convert values to right-handed * @returns nullable INode promise */ - postExportNodeAsync?(context: string, node: Nullable, babylonNode: Node, nodeMap: Map, convertToRightHanded: boolean): Promise>; + postExportNodeAsync?( + context: string, + node: Nullable, + babylonNode: Node, + nodeMap: Map, + convertToRightHanded: boolean, + dataWriter: DataWriter + ): Promise>; /** * Define this method to modify the default behavior when exporting a material From 826aeebb60532dc7e8e45b9ded7cd9f2cf3e10b6 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Tue, 19 Nov 2024 17:25:58 -0300 Subject: [PATCH 066/133] Got hand rotation working --- .../src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts index 0c4d5366d2d..a6ffabf6293 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts @@ -9,6 +9,7 @@ import { Mesh } from "core/Meshes/mesh"; import "core/Meshes/thinInstanceMesh"; import { TmpVectors, Quaternion, Vector3 } from "core/Maths/math.vector"; import { VertexBuffer } from "core/Buffers/buffer"; +import { convertToRightHandedPosition, convertToRightHandedRotation } from "../glTFUtilities"; const NAME = "EXT_mesh_gpu_instancing"; @@ -71,8 +72,8 @@ export class EXT_mesh_gpu_instancing implements IGLTFExporterExtensionV2 { // retrieve all the instance world matrix const matrix = babylonNode.thinInstanceGetWorldMatrices(); - const iwt = TmpVectors.Vector3[2]; - const iwr = TmpVectors.Quaternion[1]; + let iwt = TmpVectors.Vector3[2]; + let iwr = TmpVectors.Quaternion[1]; const iws = TmpVectors.Vector3[3]; let hasAnyInstanceWorldTranslation = false; @@ -88,6 +89,11 @@ export class EXT_mesh_gpu_instancing implements IGLTFExporterExtensionV2 { for (const m of matrix) { m.decompose(iws, iwr, iwt); + if (convertToRightHanded) { + iwt = convertToRightHandedPosition(iwt); + iwr = convertToRightHandedRotation(iwr); + } + // fill the temp buffer translationBuffer.set(iwt.asArray(), i * 3); rotationBuffer.set(iwr.normalize().asArray(), i * 4); // ensure the quaternion is normalized From 7cba726825b74476513a119fb7c9961eaefc21fa Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Wed, 20 Nov 2024 12:07:22 -0500 Subject: [PATCH 067/133] Pass "should serialize empty Babylon window.scene to glTF with only asset property" test --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 6172ef899a2..cd15e94b5c3 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -898,7 +898,9 @@ export class GLTFExporter { const noopRH = new ExporterState(false, this._options.userUint16SkinIndex, true); scene.nodes.push(...(await this._exportNodesAsync(rootNoopNodesRH, noopRH))); - this._scenes.push(scene); + if (scene.nodes.length) { + this._scenes.push(scene); + } this._exportAndAssignCameras(); this._exportAndAssignSkeletons(); From ade6fcb0973e40c4ac1c2d66d649b612b3243c34 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Wed, 20 Nov 2024 13:25:00 -0500 Subject: [PATCH 068/133] nit: remove skipNode, instead early return null --- .../serializers/src/glTF/2.0/glTFExporter.ts | 92 +++++++++---------- 1 file changed, 43 insertions(+), 49 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index cd15e94b5c3..55c559a08d6 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1163,8 +1163,6 @@ export class GLTFExporter { } } - let skipNode = false; - if (babylonNode instanceof Camera) { const gltfCamera = this._camerasMap.get(babylonNode); @@ -1182,7 +1180,7 @@ export class GLTFExporter { if (parentNodeIndex) { const parentNode = this._nodes[parentNodeIndex]; this._nodesCameraMap.get(gltfCamera)?.push(parentNode); - skipNode = true; + return null; // Skip exporting this node } } else { if (state.convertToRightHanded) { @@ -1194,16 +1192,29 @@ export class GLTFExporter { } } - if (!skipNode) { - const runtimeGLTFAnimation: IAnimation = { - name: "runtime animations", - channels: [], - samplers: [], - }; - const idleGLTFAnimations: IAnimation[] = []; + const runtimeGLTFAnimation: IAnimation = { + name: "runtime animations", + channels: [], + samplers: [], + }; + const idleGLTFAnimations: IAnimation[] = []; - if (!this._babylonScene.animationGroups.length) { - _GLTFAnimation._CreateMorphTargetAnimationFromMorphTargetAnimations( + if (!this._babylonScene.animationGroups.length) { + _GLTFAnimation._CreateMorphTargetAnimationFromMorphTargetAnimations( + babylonNode, + runtimeGLTFAnimation, + idleGLTFAnimations, + this._nodeMap, + this._nodes, + this._dataWriter, + this._bufferViews, + this._accessors, + this._animationSampleRate, + state.convertToRightHanded, + this._options.shouldExportAnimation + ); + if (babylonNode.animations.length) { + _GLTFAnimation._CreateNodeAnimationFromNodeAnimations( babylonNode, runtimeGLTFAnimation, idleGLTFAnimations, @@ -1216,50 +1227,33 @@ export class GLTFExporter { state.convertToRightHanded, this._options.shouldExportAnimation ); - if (babylonNode.animations.length) { - _GLTFAnimation._CreateNodeAnimationFromNodeAnimations( - babylonNode, - runtimeGLTFAnimation, - idleGLTFAnimations, - this._nodeMap, - this._nodes, - this._dataWriter, - this._bufferViews, - this._accessors, - this._animationSampleRate, - state.convertToRightHanded, - this._options.shouldExportAnimation - ); - } } + } - // Apply extensions to the node. If this resolves to null, it means we should skip exporting this node (NOTE: This will also skip its children) - const processedNode = await this._extensionsPostExportNodeAsync("exportNodeAsync", node, babylonNode, this._nodeMap, state.convertToRightHanded); - if (!processedNode) { - Logger.Warn(`Not exporting node ${babylonNode.name}`); - return null; - } + // Apply extensions to the node. If this resolves to null, it means we should skip exporting this node (NOTE: This will also skip its children) + const processedNode = await this._extensionsPostExportNodeAsync("exportNodeAsync", node, babylonNode, this._nodeMap, state.convertToRightHanded); + if (!processedNode) { + Logger.Warn(`Not exporting node ${babylonNode.name}`); + return null; + } - nodeIndex = this._nodes.length; - this._nodes.push(node); - this._nodeMap.set(babylonNode, nodeIndex); - state.pushExportedNode(babylonNode); - - // Begin processing child nodes once parent has been added to the node list - for (const babylonChildNode of babylonNode.getChildren()) { - if (this._shouldExportNode(babylonChildNode)) { - const childNodeIndex = await this._exportNodeAsync(babylonChildNode, state); - if (childNodeIndex !== null) { - node.children ||= []; - node.children.push(childNodeIndex); - } + nodeIndex = this._nodes.length; + this._nodes.push(node); + this._nodeMap.set(babylonNode, nodeIndex); + state.pushExportedNode(babylonNode); + + // Begin processing child nodes once parent has been added to the node list + for (const babylonChildNode of babylonNode.getChildren()) { + if (this._shouldExportNode(babylonChildNode)) { + const childNodeIndex = await this._exportNodeAsync(babylonChildNode, state); + if (childNodeIndex !== null) { + node.children ||= []; + node.children.push(childNodeIndex); } } - - return nodeIndex; } - return null; + return nodeIndex; } private _exportIndices( From a52a871da57a7e7756121a80102720d476df3305 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Wed, 20 Nov 2024 17:25:36 -0500 Subject: [PATCH 069/133] Pass all animation-related tests --- .../serializers/src/glTF/2.0/glTFExporter.ts | 30 ++++++++++++------- 1 file changed, 20 insertions(+), 10 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 55c559a08d6..bb75f9f503d 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1192,6 +1192,19 @@ export class GLTFExporter { } } + // Apply extensions to the node. If this resolves to null, it means we should skip exporting this node (NOTE: This will also skip its children) + const processedNode = await this._extensionsPostExportNodeAsync("exportNodeAsync", node, babylonNode, this._nodeMap, state.convertToRightHanded); + if (!processedNode) { + Logger.Warn(`Not exporting node ${babylonNode.name}`); + return null; + } + + nodeIndex = this._nodes.length; + this._nodes.push(node); + this._nodeMap.set(babylonNode, nodeIndex); + state.pushExportedNode(babylonNode); + + // Process node's animations once the node has been added to nodeMap (TODO: This should be refactored) const runtimeGLTFAnimation: IAnimation = { name: "runtime animations", channels: [], @@ -1230,17 +1243,14 @@ export class GLTFExporter { } } - // Apply extensions to the node. If this resolves to null, it means we should skip exporting this node (NOTE: This will also skip its children) - const processedNode = await this._extensionsPostExportNodeAsync("exportNodeAsync", node, babylonNode, this._nodeMap, state.convertToRightHanded); - if (!processedNode) { - Logger.Warn(`Not exporting node ${babylonNode.name}`); - return null; + if (runtimeGLTFAnimation.channels.length && runtimeGLTFAnimation.samplers.length) { + this._animations.push(runtimeGLTFAnimation); } - - nodeIndex = this._nodes.length; - this._nodes.push(node); - this._nodeMap.set(babylonNode, nodeIndex); - state.pushExportedNode(babylonNode); + idleGLTFAnimations.forEach((idleGLTFAnimation) => { + if (idleGLTFAnimation.channels.length && idleGLTFAnimation.samplers.length) { + this._animations.push(idleGLTFAnimation); + } + }); // Begin processing child nodes once parent has been added to the node list for (const babylonChildNode of babylonNode.getChildren()) { From ed1deac43a1e1d1fce4411529cf4c04ba52f58e4 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Wed, 20 Nov 2024 18:16:13 -0500 Subject: [PATCH 070/133] Pass all light-related tests --- .../src/glTF/2.0/Extensions/KHR_lights_punctual.ts | 4 ++-- packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts | 2 +- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index 9b282e9aa84..e963e49c0ca 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -137,8 +137,8 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { if (lightType === KHRLightsPunctual_LightType.SPOT) { const babylonSpotLight = babylonNode as SpotLight; light.spot = { - innerConeAngle: babylonSpotLight.innerAngle, - outerConeAngle: babylonSpotLight.angle, + innerConeAngle: babylonSpotLight.innerAngle / 2.0, + outerConeAngle: babylonSpotLight.angle / 2.0, }; light.spot = omitDefaultValues(light.spot, SPOTDEFAULTS!); } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 6e32699ac40..52962088043 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -441,7 +441,7 @@ export function omitDefaultValues(object: T, defaultValues: Pa Object.entries(object).filter(([key, value]) => { const defaultValue = defaultValues[key as keyof T]; if (Array.isArray(value) && Array.isArray(defaultValue) && value.length === defaultValue.length) { - return value.every((val, i) => val !== defaultValue[i]); + return !value.every((val, i) => val === defaultValue[i]); } return value !== defaultValue; }) From 4c35273a40e57174aa3d9112d29b430c44a2e770 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Wed, 20 Nov 2024 18:46:36 -0500 Subject: [PATCH 071/133] Update class and method names to pass metal-rough-related tests --- .../dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts | 5 +++-- packages/dev/serializers/src/glTF/2.0/index.ts | 7 +++++++ .../serializers/test/integration/glTFSerializer.test.ts | 6 +++--- 3 files changed, 13 insertions(+), 5 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index 077ac53fda8..29031991d21 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -65,13 +65,14 @@ function getFileExtensionFromMimeType(mimeType: ImageMimeType): string { } /** - * Computes the metallic factor + * Computes the metallic factor. + * Exported to be testable in unit tests. * @param diffuse diffused value * @param specular specular value * @param oneMinusSpecularStrength one minus the specular strength * @returns metallic value */ -function solveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number { +export function solveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number { if (specular < dielectricSpecular.r) { dielectricSpecular; return 0; diff --git a/packages/dev/serializers/src/glTF/2.0/index.ts b/packages/dev/serializers/src/glTF/2.0/index.ts index b5b53fa542b..6ce87eb2bab 100644 --- a/packages/dev/serializers/src/glTF/2.0/index.ts +++ b/packages/dev/serializers/src/glTF/2.0/index.ts @@ -1,2 +1,9 @@ +/* eslint-disable import/no-internal-modules */ +export * from "./glTFAnimation"; export * from "./glTFData"; +export * from "./glTFExporter"; +export * from "./glTFExporterExtension"; +export * from "./glTFMaterialExporter"; export * from "./glTFSerializer"; +export * from "./glTFUtilities"; +export * from "./Extensions/index"; diff --git a/packages/dev/serializers/test/integration/glTFSerializer.test.ts b/packages/dev/serializers/test/integration/glTFSerializer.test.ts index c2595096669..4c1fe3dbc5d 100644 --- a/packages/dev/serializers/test/integration/glTFSerializer.test.ts +++ b/packages/dev/serializers/test/integration/glTFSerializer.test.ts @@ -46,7 +46,7 @@ describe("Babylon glTF Serializer", () => { babylonStandardMaterial.specularColor = BABYLON.Color3.Black(); babylonStandardMaterial.specularPower = 64; babylonStandardMaterial.alpha = 1; - const materialExporter = new BABYLON.GLTF2.Exporter._GLTFMaterialExporter(new BABYLON.GLTF2.Exporter._Exporter(window.scene)); + const materialExporter = new BABYLON.GLTF2.Exporter.GLTFMaterialExporter(new BABYLON.GLTF2.Exporter.GLTFExporter(window.scene)); const metalRough = materialExporter._convertToGLTFPBRMetallicRoughness(babylonStandardMaterial); return { @@ -62,8 +62,8 @@ describe("Babylon glTF Serializer", () => { }); it("should solve for metallic", async () => { const assertionData = await page.evaluate(() => { - const solveZero = BABYLON.GLTF2.Exporter._GLTFMaterialExporter._SolveMetallic(1.0, 0.0, 1.0); - const solveAproxOne = BABYLON.GLTF2.Exporter._GLTFMaterialExporter._SolveMetallic(0.0, 1.0, 1.0); + const solveZero = BABYLON.GLTF2.Exporter.solveMetallic(1.0, 0.0, 1.0); + const solveAproxOne = BABYLON.GLTF2.Exporter.solveMetallic(0.0, 1.0, 1.0); return { solveZero, solveAproxOne, From 9a4655db365826201a51411041268c56bd342782 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:34:11 -0500 Subject: [PATCH 072/133] Add optional `normalized` to accessor factory --- packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 6e32699ac40..ecce0b08738 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -50,6 +50,7 @@ export function createBufferView(bufferIndex: number, byteOffset: number, byteLe * @param count The number of attributes referenced by this accessor * @param byteOffset The offset relative to the start of the bufferView in bytes * @param minMax Minimum and maximum value of each component in this attribute + * @param normalized Specifies whether integer data values are normalized before usage * @returns accessor for glTF */ export function createAccessor( @@ -58,7 +59,8 @@ export function createAccessor( componentType: AccessorComponentType, count: number, byteOffset: Nullable, - minMax: Nullable<{ min: number[]; max: number[] }> = null + minMax: Nullable<{ min: number[]; max: number[] }> = null, + normalized?: boolean ): IAccessor { const accessor: IAccessor = { bufferView: bufferViewIndex, componentType: componentType, count: count, type: type }; @@ -67,6 +69,10 @@ export function createAccessor( accessor.max = minMax.max; } + if (normalized) { + accessor.normalized = normalized; + } + if (byteOffset != null) { accessor.byteOffset = byteOffset; } From c32f7487523a36547f6009639c7f1fde335fbf6a Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Thu, 21 Nov 2024 11:34:57 -0500 Subject: [PATCH 073/133] Use `normalized` when creating most accessors --- .../dev/serializers/src/glTF/2.0/glTFExporter.ts | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 6172ef899a2..cdff78006c7 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1370,7 +1370,17 @@ export class GLTFExporter { } else { const bufferViewIndex = state.getVertexBufferView(vertexBuffer._buffer)!; const byteOffset = vertexBuffer.byteOffset + start * vertexBuffer.byteStride; - this._accessors.push(createAccessor(bufferViewIndex, getAccessorType(kind, state.hasVertexColorAlpha(vertexBuffer)), vertexBuffer.type, count, byteOffset, minMax)); + this._accessors.push( + createAccessor( + bufferViewIndex, + getAccessorType(kind, state.hasVertexColorAlpha(vertexBuffer)), + vertexBuffer.type, + count, + byteOffset, + minMax, + vertexBuffer.normalized // TODO: Find other places where this is needed. + ) + ); accessorIndex = this._accessors.length - 1; state.setVertexAccessor(vertexBuffer, start, count, accessorIndex); primitive.attributes[getAttributeType(kind)] = accessorIndex; From e04d827d55196fe6d76d851dae81fe9419d97c39 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Thu, 21 Nov 2024 12:18:04 -0500 Subject: [PATCH 074/133] Add test to export instances pointing to same mesh --- .../test/integration/glTFSerializer.test.ts | 20 +++++++++++++++++++ 1 file changed, 20 insertions(+) diff --git a/packages/dev/serializers/test/integration/glTFSerializer.test.ts b/packages/dev/serializers/test/integration/glTFSerializer.test.ts index 4c1fe3dbc5d..88694c57460 100644 --- a/packages/dev/serializers/test/integration/glTFSerializer.test.ts +++ b/packages/dev/serializers/test/integration/glTFSerializer.test.ts @@ -535,5 +535,25 @@ describe("Babylon glTF Serializer", () => { expect(assertionData.extensions["KHR_lights_punctual"].lights).toHaveLength(3); expect(assertionData.nodes).toHaveLength(3); }); + it("should export instances as nodes pointing to same mesh", async () => { + const instanceCount = 3; + const assertionData = await page.evaluate((instanceCount) => { + const mesh = BABYLON.MeshBuilder.CreateBox("box", {}, window.scene!); + for (let i = 0; i < instanceCount; i++) { + mesh.createInstance("boxInstance" + i); + } + return BABYLON.GLTF2Export.GLTFAsync(window.scene!, "test").then((glTFData) => { + const jsonString = glTFData.glTFFiles["test.gltf"] as string; + const jsonData = JSON.parse(jsonString); + return jsonData; + }); + }, instanceCount); + expect(Object.keys(assertionData)).toHaveLength(9); + expect(assertionData.nodes).toHaveLength(instanceCount + 1); + expect(assertionData.meshes).toHaveLength(1); + for (const node of assertionData.nodes) { + expect(node.mesh).toEqual(0); + } + }); }); }); From fe0e516892cdd1968ef7bde14a58cc51facbade9 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Thu, 21 Nov 2024 17:22:53 -0300 Subject: [PATCH 075/133] Removed old TODO --- packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts index d29f1b2af1c..193caa11b68 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts @@ -699,7 +699,6 @@ export class _GLTFAnimation { } }); - //TODO: Handle right hand vs left hand here. accessor = createAccessor(bufferViews.length - 1, dataAccessorType, AccessorComponentType.FLOAT, outputLength, null); accessors.push(accessor); dataAccessorIndex = accessors.length - 1; From 5298bbd173d6d00c47bc32ce97b4eac89e2b5c3e Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Thu, 21 Nov 2024 17:58:51 -0300 Subject: [PATCH 076/133] Fixed bad importing --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 4 +++- .../serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts | 6 +++--- 2 files changed, 6 insertions(+), 4 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 78dc658d1f9..db2af3809aa 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -63,7 +63,9 @@ import { } from "./glTFUtilities"; import { DataWriter } from "./dataWriter"; import { Camera } from "core/Cameras/camera"; -import { MultiMaterial, PBRMaterial, StandardMaterial } from "core/Materials"; +import { MultiMaterial } from "core/Materials/multiMaterial"; +import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; +import { StandardMaterial } from "core/Materials/standardMaterial"; import { Logger } from "core/Misc/logger"; import { enumerateFloatValues } from "core/Buffers/bufferUtils"; import type { Bone, Skeleton } from "core/Bones"; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts index cfda9476d1a..d74ae8beac8 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -5,9 +5,9 @@ import type { DataWriter } from "./dataWriter"; import { createAccessor, createBufferView } from "./glTFUtilities"; import type { Mesh } from "core/Meshes"; -import { VertexBuffer } from "core/Buffers"; -import { Vector3 } from "core/Maths"; -import type { Vector4 } from "core/Maths"; +import { VertexBuffer } from "core/Buffers/buffer"; +import { Vector3 } from "core/Maths/math.vector"; +import type { Vector4 } from "core/Maths/math.vector"; /** * Temporary structure to store morph target information. From 90a2a4d019a4dbbfa9fb7e11fc4d9cf0d2316f11 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Thu, 21 Nov 2024 18:00:39 -0300 Subject: [PATCH 077/133] Fixed bad importing --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 3 +-- .../dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts | 3 +-- 2 files changed, 2 insertions(+), 4 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index db2af3809aa..3295d94f79e 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -22,8 +22,7 @@ import type { import { AccessorComponentType, AccessorType, CameraType, ImageMimeType } from "babylonjs-gltf2interface"; import type { FloatArray, IndicesArray, Nullable } from "core/types"; -import { Matrix } from "core/Maths/math.vector"; -import { TmpVectors, Quaternion } from "core/Maths/math.vector"; +import { TmpVectors, Quaternion, Matrix } from "core/Maths/math.vector"; import { Tools } from "core/Misc/tools"; import type { Buffer } from "core/Buffers/buffer"; import { VertexBuffer } from "core/Buffers/buffer"; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts index d74ae8beac8..bb3a8eae40c 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -4,7 +4,7 @@ import type { MorphTarget } from "core/Morph/morphTarget"; import type { DataWriter } from "./dataWriter"; import { createAccessor, createBufferView } from "./glTFUtilities"; -import type { Mesh } from "core/Meshes"; +import type { Mesh } from "core/Meshes/mesh"; import { VertexBuffer } from "core/Buffers/buffer"; import { Vector3 } from "core/Maths/math.vector"; import type { Vector4 } from "core/Maths/math.vector"; @@ -36,7 +36,6 @@ function _NormalizeTangentFromRef(tangent: Vector4 | Vector3) { } } -// TO DO: Convert Babylon morph (absolute) to GLTF (deltas) export function buildMorphTargetBuffers( morphTarget: MorphTarget, mesh: Mesh, From 7b16084331370bdb3cfc57dc6ccbc4f3331256d2 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Fri, 22 Nov 2024 11:21:54 -0300 Subject: [PATCH 078/133] Changed interators to be complient with UMD --- .../serializers/src/glTF/2.0/glTFExporter.ts | 33 ++++++++++++++++--- 1 file changed, 29 insertions(+), 4 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 3295d94f79e..4aafc4f3f55 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -759,7 +759,8 @@ export class GLTFExporter { // Cleanup unused cameras and assign index to nodes. private _exportAndAssignCameras(): void { - for (const [, gltfCamera] of this._camerasMap) { + const gltfCameras = Array.from(this._camerasMap.values()); + for (const gltfCamera of gltfCameras) { const usedNodes = this._nodesCameraMap.get(gltfCamera); if (usedNodes !== undefined) { this._cameras.push(gltfCamera); @@ -1009,12 +1010,20 @@ export class GLTFExporter { this._collectBuffers(babylonNode, bufferToVertexBuffersMap, vertexBufferToMeshesMap, morphTagetsMeshesMap, state); } - for (const [buffer, vertexBuffers] of bufferToVertexBuffersMap) { + const buffers = Array.from(bufferToVertexBuffersMap.keys()); + + for (const buffer of buffers) { const data = buffer.getData(); if (!data) { throw new Error("Buffer data is not available"); } + const vertexBuffers = bufferToVertexBuffersMap.get(buffer); + + if (!vertexBuffers) { + continue; + } + const byteStride = vertexBuffers[0].byteStride; if (vertexBuffers.some((vertexBuffer) => vertexBuffer.byteStride !== byteStride)) { throw new Error("Vertex buffers pointing to the same buffer must have the same byte stride"); @@ -1133,7 +1142,15 @@ export class GLTFExporter { ); } - for (const [vertexBuffer, array] of floatMatricesIndices) { + const floatArrayVertexBuffers = Array.from(floatMatricesIndices.keys()); + + for (const vertexBuffer of floatArrayVertexBuffers) { + const array = floatMatricesIndices.get(vertexBuffer); + + if (!array) { + continue; + } + const byteOffset = this._dataWriter.byteOffset; if (state.userUint16SkinIndex) { const newArray = new Uint16Array(array.length); @@ -1155,7 +1172,15 @@ export class GLTFExporter { } } - for (const [morphTarget, meshes] of morphTagetsMeshesMap) { + const morphTargets = Array.from(morphTagetsMeshesMap.keys()); + + for (const morphTarget of morphTargets) { + const meshes = morphTagetsMeshesMap.get(morphTarget); + + if (!meshes) { + continue; + } + const glTFMorphTarget = buildMorphTargetBuffers(morphTarget, meshes[0], this._dataWriter, this._bufferViews, this._accessors, state.convertToRightHanded); for (const mesh of meshes) { From d2dc61d001e5bca9980cd87352ebcb661b28c22e Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:35:14 -0500 Subject: [PATCH 079/133] Remove IMaterialExtension and its references --- .../babylon.glTF2Interface.d.ts | 24 +++++++-------- .../devHost/src/babylon.glTF2Interface.ts | 29 ++++++++----------- 2 files changed, 24 insertions(+), 29 deletions(-) diff --git a/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts b/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts index 15d4fffb12d..e16dca1df1b 100644 --- a/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts +++ b/packages/public/glTF2Interface/babylon.glTF2Interface.d.ts @@ -1041,7 +1041,7 @@ declare module BABYLON.GLTF2 { } /** @internal */ - interface IKHRMaterialsClearcoat extends IMaterialExtension { + interface IKHRMaterialsClearcoat { clearcoatFactor?: number; clearcoatTexture?: ITextureInfo; clearcoatRoughnessFactor?: number; @@ -1050,7 +1050,7 @@ declare module BABYLON.GLTF2 { } /** @internal */ - interface IKHRMaterialsIridescence extends IMaterialExtension { + interface IKHRMaterialsIridescence { iridescenceFactor?: number; iridescenceIor?: number; iridescenceThicknessMinimum?: number; @@ -1060,7 +1060,7 @@ declare module BABYLON.GLTF2 { } /** @internal */ - interface IKHRMaterialsAnisotropy extends IMaterialExtension { + interface IKHRMaterialsAnisotropy { anisotropyStrength?: number; anisotropyRotation?: number; anisotropyTexture?: ITextureInfo; @@ -1071,7 +1071,7 @@ declare module BABYLON.GLTF2 { */ /** @internal */ - interface IKHRMaterialsIor extends IMaterialExtension { + interface IKHRMaterialsIor { ior?: number; } @@ -1080,7 +1080,7 @@ declare module BABYLON.GLTF2 { */ /** @internal */ - interface IKHRMaterialsVolume extends IMaterialExtension { + interface IKHRMaterialsVolume { thicknessFactor?: number; thicknessTexture?: ITextureInfo; attenuationDistance?: number; @@ -1092,7 +1092,7 @@ declare module BABYLON.GLTF2 { */ /** @internal */ - interface IKHRMaterialsDispersion extends IMaterialExtension { + interface IKHRMaterialsDispersion { dispersion?: number; } @@ -1101,7 +1101,7 @@ declare module BABYLON.GLTF2 { */ /** @internal */ - interface IKHRMaterialsSpecular extends IMaterialExtension { + interface IKHRMaterialsSpecular { specularFactor?: number; specularColorFactor?: number[]; specularTexture?: ITextureInfo; @@ -1113,7 +1113,7 @@ declare module BABYLON.GLTF2 { */ /** @internal */ - interface IKHRMaterialsTransmission extends IMaterialExtension { + interface IKHRMaterialsTransmission { transmissionFactor?: number; transmissionTexture?: ITextureInfo; } @@ -1123,7 +1123,7 @@ declare module BABYLON.GLTF2 { */ /** @internal */ - interface IKHRMaterialsEmissiveStrength extends IMaterialExtension { + interface IKHRMaterialsEmissiveStrength { emissiveStrength: number; } @@ -1132,7 +1132,7 @@ declare module BABYLON.GLTF2 { */ /** @internal */ - interface IKHRMaterialsPbrSpecularGlossiness extends IMaterialExtension { + interface IKHRMaterialsPbrSpecularGlossiness { diffuseFactor: number[]; diffuseTexture: ITextureInfo; specularFactor: number[]; @@ -1145,7 +1145,7 @@ declare module BABYLON.GLTF2 { */ /** @internal */ - interface IKHRMaterialsSheen extends IMaterialExtension { + interface IKHRMaterialsSheen { sheenColorFactor?: number[]; sheenColorTexture?: ITextureInfo; sheenRoughnessFactor?: number; @@ -1158,7 +1158,7 @@ declare module BABYLON.GLTF2 { */ /** @internal */ - interface IKHRMaterialsDiffuseTransmission extends IMaterialExtension { + interface IKHRMaterialsDiffuseTransmission { diffuseTransmissionFactor?: number; diffuseTransmissionTexture?: ITextureInfo; diffuseTransmissionColorFactor?: number[]; diff --git a/packages/tools/devHost/src/babylon.glTF2Interface.ts b/packages/tools/devHost/src/babylon.glTF2Interface.ts index 9df496583e1..16391d50bfa 100644 --- a/packages/tools/devHost/src/babylon.glTF2Interface.ts +++ b/packages/tools/devHost/src/babylon.glTF2Interface.ts @@ -1028,12 +1028,7 @@ interface IKHRLightsPunctual { } /** @internal */ -interface IMaterialExtension { - hasTextures?(): boolean; -} - -/** @internal */ -interface IKHRMaterialsClearcoat extends IMaterialExtension { +interface IKHRMaterialsClearcoat { clearcoatFactor?: number; clearcoatTexture?: ITextureInfo; clearcoatRoughnessFactor?: number; @@ -1042,7 +1037,7 @@ interface IKHRMaterialsClearcoat extends IMaterialExtension { } /** @internal */ -interface IKHRMaterialsIridescence extends IMaterialExtension { +interface IKHRMaterialsIridescence { iridescenceFactor?: number; iridescenceIor?: number; iridescenceThicknessMinimum?: number; @@ -1052,7 +1047,7 @@ interface IKHRMaterialsIridescence extends IMaterialExtension { } /** @internal */ -interface IKHRMaterialsAnisotropy extends IMaterialExtension { +interface IKHRMaterialsAnisotropy { anisotropyStrength?: number; anisotropyRotation?: number; anisotropyTexture?: ITextureInfo; @@ -1063,7 +1058,7 @@ interface IKHRMaterialsAnisotropy extends IMaterialExtension { */ /** @internal */ -interface IKHRMaterialsIor extends IMaterialExtension { +interface IKHRMaterialsIor { ior?: number; } @@ -1072,7 +1067,7 @@ interface IKHRMaterialsIor extends IMaterialExtension { */ /** @internal */ -interface IKHRMaterialsVolume extends IMaterialExtension { +interface IKHRMaterialsVolume { thicknessFactor?: number; thicknessTexture?: ITextureInfo; attenuationDistance?: number; @@ -1084,7 +1079,7 @@ interface IKHRMaterialsVolume extends IMaterialExtension { */ /** @internal */ -interface IKHRMaterialsDispersion extends IMaterialExtension { +interface IKHRMaterialsDispersion { dispersion?: number; } @@ -1093,7 +1088,7 @@ interface IKHRMaterialsDispersion extends IMaterialExtension { */ /** @internal */ -interface IKHRMaterialsSpecular extends IMaterialExtension { +interface IKHRMaterialsSpecular { specularFactor?: number; specularColorFactor?: number[]; specularTexture?: ITextureInfo; @@ -1105,7 +1100,7 @@ interface IKHRMaterialsSpecular extends IMaterialExtension { */ /** @internal */ -interface IKHRMaterialsTransmission extends IMaterialExtension { +interface IKHRMaterialsTransmission { transmissionFactor?: number; transmissionTexture?: ITextureInfo; } @@ -1115,7 +1110,7 @@ interface IKHRMaterialsTransmission extends IMaterialExtension { */ /** @internal */ -interface IKHRMaterialsEmissiveStrength extends IMaterialExtension { +interface IKHRMaterialsEmissiveStrength { emissiveStrength: number; } @@ -1124,7 +1119,7 @@ interface IKHRMaterialsEmissiveStrength extends IMaterialExtension { */ /** @internal */ -interface IKHRMaterialsPbrSpecularGlossiness extends IMaterialExtension { +interface IKHRMaterialsPbrSpecularGlossiness { diffuseFactor: number[]; diffuseTexture: ITextureInfo; specularFactor: number[]; @@ -1137,7 +1132,7 @@ interface IKHRMaterialsPbrSpecularGlossiness extends IMaterialExtension { */ /** @internal */ -interface IKHRMaterialsSheen extends IMaterialExtension { +interface IKHRMaterialsSheen { sheenColorFactor?: number[]; sheenColorTexture?: ITextureInfo; sheenRoughnessFactor?: number; @@ -1150,7 +1145,7 @@ interface IKHRMaterialsSheen extends IMaterialExtension { */ /** @internal */ -interface IKHRMaterialsDiffuseTransmission extends IMaterialExtension { +interface IKHRMaterialsDiffuseTransmission { diffuseTransmissionFactor?: number; diffuseTransmissionTexture?: ITextureInfo; diffuseTransmissionColorFactor?: number; From 12094f8b4d0c338bf8a61903410015c9797067b5 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Fri, 22 Nov 2024 12:44:38 -0500 Subject: [PATCH 080/133] Revert name change to copyFloatData --- packages/dev/core/src/Buffers/bufferUtils.ts | 2 +- packages/dev/core/src/Meshes/geometry.ts | 4 ++-- 2 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/dev/core/src/Buffers/bufferUtils.ts b/packages/dev/core/src/Buffers/bufferUtils.ts index 2b9476cf14a..462773ca38c 100644 --- a/packages/dev/core/src/Buffers/bufferUtils.ts +++ b/packages/dev/core/src/Buffers/bufferUtils.ts @@ -254,7 +254,7 @@ export function getFloatData( * @param totalVertices number of vertices in the buffer to take into account * @param output the output float array */ -export function copyFloatData( +export function CopyFloatData( input: DataArray, size: number, type: number, diff --git a/packages/dev/core/src/Meshes/geometry.ts b/packages/dev/core/src/Meshes/geometry.ts index 8af47d0701c..7d9e6204292 100644 --- a/packages/dev/core/src/Meshes/geometry.ts +++ b/packages/dev/core/src/Meshes/geometry.ts @@ -24,7 +24,7 @@ import type { Buffer } from "../Buffers/buffer"; import type { AbstractEngine } from "../Engines/abstractEngine"; import type { ThinEngine } from "../Engines/thinEngine"; import type { IAssetContainer } from "core/IAssetContainer"; -import { copyFloatData } from "../Buffers/bufferUtils"; +import { CopyFloatData } from "../Buffers/bufferUtils"; /** * Class used to store geometry data (vertex buffers + index buffer) @@ -472,7 +472,7 @@ export class Geometry implements IGetSetVerticesData { vertexData[kind] ||= new Float32Array(this._totalVertices * vertexBuffer.getSize()); const data = vertexBuffer.getData(); if (data) { - copyFloatData( + CopyFloatData( data, vertexBuffer.getSize(), vertexBuffer.type, From e2fa4539826962a84a549adef84c425a24c29017 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Fri, 22 Nov 2024 18:23:36 -0500 Subject: [PATCH 081/133] Revert move of CopyFloatData import --- packages/dev/core/src/Meshes/geometry.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/core/src/Meshes/geometry.ts b/packages/dev/core/src/Meshes/geometry.ts index 7d9e6204292..6ab8e09c2de 100644 --- a/packages/dev/core/src/Meshes/geometry.ts +++ b/packages/dev/core/src/Meshes/geometry.ts @@ -23,8 +23,8 @@ import type { Mesh } from "../Meshes/mesh"; import type { Buffer } from "../Buffers/buffer"; import type { AbstractEngine } from "../Engines/abstractEngine"; import type { ThinEngine } from "../Engines/thinEngine"; -import type { IAssetContainer } from "core/IAssetContainer"; import { CopyFloatData } from "../Buffers/bufferUtils"; +import type { IAssetContainer } from "core/IAssetContainer"; /** * Class used to store geometry data (vertex buffers + index buffer) From f20b84b1105b4e4ffc1ade239c67a6d5d37cd11f Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Fri, 22 Nov 2024 18:26:19 -0500 Subject: [PATCH 082/133] Modify iwt and iwr in-place --- .../src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts | 8 ++++---- 1 file changed, 4 insertions(+), 4 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts index a6ffabf6293..be608548d32 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts @@ -72,8 +72,8 @@ export class EXT_mesh_gpu_instancing implements IGLTFExporterExtensionV2 { // retrieve all the instance world matrix const matrix = babylonNode.thinInstanceGetWorldMatrices(); - let iwt = TmpVectors.Vector3[2]; - let iwr = TmpVectors.Quaternion[1]; + const iwt = TmpVectors.Vector3[2]; + const iwr = TmpVectors.Quaternion[1]; const iws = TmpVectors.Vector3[3]; let hasAnyInstanceWorldTranslation = false; @@ -90,8 +90,8 @@ export class EXT_mesh_gpu_instancing implements IGLTFExporterExtensionV2 { m.decompose(iws, iwr, iwt); if (convertToRightHanded) { - iwt = convertToRightHandedPosition(iwt); - iwr = convertToRightHandedRotation(iwr); + convertToRightHandedPosition(iwt); + convertToRightHandedRotation(iwr); } // fill the temp buffer From ba0167ba8b1c8dde98155544b5a3dbe36d153d8b Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Fri, 22 Nov 2024 19:12:46 -0500 Subject: [PATCH 083/133] Remove Nullable glTF object types from extension methods --- .../2.0/Extensions/KHR_lights_punctual.ts | 5 ++-- .../serializers/src/glTF/2.0/glTFExporter.ts | 23 ++++++------------- .../src/glTF/2.0/glTFExporterExtension.ts | 8 +++---- 3 files changed, 13 insertions(+), 23 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index e963e49c0ca..bb046aab944 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -73,10 +73,9 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { * @param convertToRightHanded Flag to convert the values to right-handed * @returns nullable INode promise */ - public postExportNodeAsync(context: string, node: Nullable, babylonNode: Node, nodeMap: Map, convertToRightHanded: boolean): Promise> { + public postExportNodeAsync(context: string, node: INode, babylonNode: Node, nodeMap: Map, convertToRightHanded: boolean): Promise> { return new Promise((resolve) => { - // If node was nullified (marked as skippable) earlier in the pipeline, or it's not a light, skip - if (!node || !(babylonNode instanceof ShadowLight)) { + if (!(babylonNode instanceof ShadowLight)) { resolve(node); return; } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 4aafc4f3f55..68d741d3dc5 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -274,10 +274,10 @@ export class GLTFExporter { private static readonly _ExtensionFactories: { [name: string]: (exporter: GLTFExporter) => IGLTFExporterExtensionV2 } = {}; private _applyExtension( - node: Nullable, + node: T, extensions: IGLTFExporterExtensionV2[], index: number, - actionAsync: (extension: IGLTFExporterExtensionV2, node: Nullable) => Promise> | undefined + actionAsync: (extension: IGLTFExporterExtensionV2, node: T) => Promise> | undefined ): Promise> { if (index >= extensions.length) { return Promise.resolve(node); @@ -289,13 +289,10 @@ export class GLTFExporter { return this._applyExtension(node, extensions, index + 1, actionAsync); } - return currentPromise.then((newNode) => this._applyExtension(newNode, extensions, index + 1, actionAsync)); + return currentPromise.then((newNode) => (newNode ? this._applyExtension(newNode, extensions, index + 1, actionAsync) : null)); } - private _applyExtensions( - node: Nullable, - actionAsync: (extension: IGLTFExporterExtensionV2, node: Nullable) => Promise> | undefined - ): Promise> { + private _applyExtensions(node: T, actionAsync: (extension: IGLTFExporterExtensionV2, node: T) => Promise> | undefined): Promise> { const extensions: IGLTFExporterExtensionV2[] = []; for (const name of GLTFExporter._ExtensionNames) { extensions.push(this._extensions[name]); @@ -304,7 +301,7 @@ export class GLTFExporter { return this._applyExtension(node, extensions, 0, actionAsync); } - public _extensionsPreExportTextureAsync(context: string, babylonTexture: Nullable, mimeType: ImageMimeType): Promise> { + public _extensionsPreExportTextureAsync(context: string, babylonTexture: Texture, mimeType: ImageMimeType): Promise> { return this._applyExtensions(babylonTexture, (extension, node) => extension.preExportTextureAsync && extension.preExportTextureAsync(context, node, mimeType)); } @@ -315,20 +312,14 @@ export class GLTFExporter { ); } - public _extensionsPostExportNodeAsync( - context: string, - node: Nullable, - babylonNode: Node, - nodeMap: Map, - convertToRightHanded: boolean - ): Promise> { + public _extensionsPostExportNodeAsync(context: string, node: INode, babylonNode: Node, nodeMap: Map, convertToRightHanded: boolean): Promise> { return this._applyExtensions( node, (extension, node) => extension.postExportNodeAsync && extension.postExportNodeAsync(context, node, babylonNode, nodeMap, convertToRightHanded, this._dataWriter) ); } - public _extensionsPostExportMaterialAsync(context: string, material: Nullable, babylonMaterial: Material): Promise> { + public _extensionsPostExportMaterialAsync(context: string, material: IMaterial, babylonMaterial: Material): Promise> { return this._applyExtensions(material, (extension, node) => extension.postExportMaterialAsync && extension.postExportMaterialAsync(context, node, babylonMaterial)); } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts index 1ba60c736bb..91a0b8e6513 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporterExtension.ts @@ -27,7 +27,7 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo * @param mimeType The mime-type of the generated image * @returns A promise that resolves with the exported texture */ - preExportTextureAsync?(context: string, babylonTexture: Nullable, mimeType: ImageMimeType): Promise>; + preExportTextureAsync?(context: string, babylonTexture: Texture, mimeType: ImageMimeType): Promise>; /** * Define this method to get notified when a texture info is created @@ -44,7 +44,7 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo * @param babylonSubMesh Babylon submesh * @returns nullable IMeshPrimitive promise */ - postExportMeshPrimitiveAsync?(context: string, meshPrimitive: Nullable, babylonSubMesh: SubMesh): Promise; + postExportMeshPrimitiveAsync?(context: string, meshPrimitive: IMeshPrimitive, babylonSubMesh: SubMesh): Promise; /** * Define this method to modify the default behavior when exporting a node @@ -57,7 +57,7 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo */ postExportNodeAsync?( context: string, - node: Nullable, + node: INode, babylonNode: Node, nodeMap: Map, convertToRightHanded: boolean, @@ -70,7 +70,7 @@ export interface IGLTFExporterExtensionV2 extends IGLTFExporterExtension, IDispo * @param babylonMaterial BabylonJS material * @returns nullable IMaterial promise */ - postExportMaterialAsync?(context: string, node: Nullable, babylonMaterial: Material): Promise; + postExportMaterialAsync?(context: string, node: IMaterial, babylonMaterial: Material): Promise; /** * Define this method to return additional textures to export from a material From 675312956f11e6b222221d093f5aaf6fb20fec6c Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Fri, 22 Nov 2024 21:48:37 -0500 Subject: [PATCH 084/133] Use PascalCase for exported module-level functions --- packages/dev/core/src/Buffers/buffer.ts | 20 ++-- packages/dev/core/src/Buffers/bufferUtils.ts | 16 +-- .../2.0/Extensions/EXT_mesh_gpu_instancing.ts | 6 +- .../2.0/Extensions/KHR_lights_punctual.ts | 14 +-- .../serializers/src/glTF/2.0/glTFAnimation.ts | 30 +++--- .../serializers/src/glTF/2.0/glTFExporter.ts | 98 +++++++++---------- .../src/glTF/2.0/glTFMaterialExporter.ts | 4 +- .../src/glTF/2.0/glTFMorphTargetsUtilities.ts | 16 +-- .../serializers/src/glTF/2.0/glTFUtilities.ts | 50 +++++----- .../test/integration/glTFSerializer.test.ts | 4 +- 10 files changed, 129 insertions(+), 129 deletions(-) diff --git a/packages/dev/core/src/Buffers/buffer.ts b/packages/dev/core/src/Buffers/buffer.ts index 18cf5d31054..95e3f901b06 100644 --- a/packages/dev/core/src/Buffers/buffer.ts +++ b/packages/dev/core/src/Buffers/buffer.ts @@ -4,7 +4,7 @@ import { DataBuffer } from "./dataBuffer"; import type { Mesh } from "../Meshes/mesh"; import { Logger } from "../Misc/logger"; import { Constants } from "../Engines/constants"; -import { enumerateFloatValues, getFloatData, getTypeByteLength } from "./bufferUtils"; +import { EnumerateFloatValues, GetFloatData, GetTypeByteLength } from "./bufferUtils"; /** * Class used to store data that will be store in GPU memory @@ -567,7 +567,7 @@ export class VertexBuffer { this.type = type; } - const typeByteLength = getTypeByteLength(this.type); + const typeByteLength = GetTypeByteLength(this.type); if (useBytes) { this._size = size || (stride ? stride / typeByteLength : VertexBuffer.DeduceStride(kind)); @@ -642,7 +642,7 @@ export class VertexBuffer { return null; } - return getFloatData(data, this._size, this.type, this.byteOffset, this.byteStride, this.normalized, totalVertices, forceCopy); + return GetFloatData(data, this._size, this.type, this.byteOffset, this.byteStride, this.normalized, totalVertices, forceCopy); } /** @@ -668,7 +668,7 @@ export class VertexBuffer { * @deprecated Please use byteStride instead. */ public getStrideSize(): number { - return this.byteStride / getTypeByteLength(this.type); + return this.byteStride / GetTypeByteLength(this.type); } /** @@ -677,7 +677,7 @@ export class VertexBuffer { * @deprecated Please use byteOffset instead. */ public getOffset(): number { - return this.byteOffset / getTypeByteLength(this.type); + return this.byteOffset / GetTypeByteLength(this.type); } /** @@ -686,7 +686,7 @@ export class VertexBuffer { * @returns the number of components */ public getSize(sizeInBytes = false): number { - return sizeInBytes ? this._size * getTypeByteLength(this.type) : this._size; + return sizeInBytes ? this._size * GetTypeByteLength(this.type) : this._size; } /** @@ -755,7 +755,7 @@ export class VertexBuffer { * @param callback the callback function called for each value */ public forEach(count: number, callback: (value: number, index: number) => void): void { - enumerateFloatValues(this._buffer.getData()!, this.byteOffset, this.byteStride, this._size, this.type, count, this.normalized, (values, index) => { + EnumerateFloatValues(this._buffer.getData()!, this.byteOffset, this.byteStride, this._size, this.type, count, this.normalized, (values, index) => { for (let i = 0; i < this._size; i++) { callback(values[i], index + i); } @@ -887,7 +887,7 @@ export class VertexBuffer { * @deprecated Use `getTypeByteLength` from `bufferUtils` instead */ public static GetTypeByteLength(type: number): number { - return getTypeByteLength(type); + return GetTypeByteLength(type); } /** @@ -912,7 +912,7 @@ export class VertexBuffer { normalized: boolean, callback: (value: number, index: number) => void ): void { - enumerateFloatValues(data, byteOffset, byteStride, componentCount, componentType, count, normalized, (values, index) => { + EnumerateFloatValues(data, byteOffset, byteStride, componentCount, componentType, count, normalized, (values, index) => { for (let componentIndex = 0; componentIndex < componentCount; componentIndex++) { callback(values[componentIndex], index + componentIndex); } @@ -942,6 +942,6 @@ export class VertexBuffer { totalVertices: number, forceCopy?: boolean ): FloatArray { - return getFloatData(data, size, type, byteOffset, byteStride, normalized, totalVertices, forceCopy); + return GetFloatData(data, size, type, byteOffset, byteStride, normalized, totalVertices, forceCopy); } } diff --git a/packages/dev/core/src/Buffers/bufferUtils.ts b/packages/dev/core/src/Buffers/bufferUtils.ts index 462773ca38c..364ef9684c0 100644 --- a/packages/dev/core/src/Buffers/bufferUtils.ts +++ b/packages/dev/core/src/Buffers/bufferUtils.ts @@ -100,7 +100,7 @@ function setFloatValue(dataView: DataView, type: number, byteOffset: number, nor * @param type the type * @returns the number of bytes */ -export function getTypeByteLength(type: number): number { +export function GetTypeByteLength(type: number): number { switch (type) { case Constants.BYTE: case Constants.UNSIGNED_BYTE: @@ -128,7 +128,7 @@ export function getTypeByteLength(type: number): number { * @param normalized whether the data is normalized * @param callback the callback function called for each group of component values */ -export function enumerateFloatValues( +export function EnumerateFloatValues( data: DataArray, byteOffset: number, byteStride: number, @@ -161,7 +161,7 @@ export function enumerateFloatValues( } } else { const dataView = data instanceof ArrayBuffer ? new DataView(data) : new DataView(data.buffer, data.byteOffset, data.byteLength); - const componentByteLength = getTypeByteLength(componentType); + const componentByteLength = GetTypeByteLength(componentType); for (let index = 0; index < count; index += componentCount) { for (let componentIndex = 0, componentByteOffset = byteOffset; componentIndex < componentCount; componentIndex++, componentByteOffset += componentByteLength) { oldValues[componentIndex] = newValues[componentIndex] = getFloatValue(dataView, componentType, componentByteOffset, normalized); @@ -192,7 +192,7 @@ export function enumerateFloatValues( * @param forceCopy defines a boolean indicating that the returned array must be cloned upon returning it * @returns a float array containing vertex data */ -export function getFloatData( +export function GetFloatData( data: DataArray, size: number, type: number, @@ -202,12 +202,12 @@ export function getFloatData( totalVertices: number, forceCopy?: boolean ): FloatArray { - const tightlyPackedByteStride = size * getTypeByteLength(type); + const tightlyPackedByteStride = size * GetTypeByteLength(type); const count = totalVertices * size; if (type !== Constants.FLOAT || byteStride !== tightlyPackedByteStride) { const copy = new Float32Array(count); - enumerateFloatValues(data, byteOffset, byteStride, size, type, count, normalized, (values, index) => { + EnumerateFloatValues(data, byteOffset, byteStride, size, type, count, normalized, (values, index) => { for (let i = 0; i < size; i++) { copy[index + i] = values[i]; } @@ -264,7 +264,7 @@ export function CopyFloatData( totalVertices: number, output: Float32Array ): void { - const tightlyPackedByteStride = size * getTypeByteLength(type); + const tightlyPackedByteStride = size * GetTypeByteLength(type); const count = totalVertices * size; if (output.length !== count) { @@ -272,7 +272,7 @@ export function CopyFloatData( } if (type !== Constants.FLOAT || byteStride !== tightlyPackedByteStride) { - enumerateFloatValues(input, byteOffset, byteStride, size, type, count, normalized, (values, index) => { + EnumerateFloatValues(input, byteOffset, byteStride, size, type, count, normalized, (values, index) => { for (let i = 0; i < size; i++) { output[index + i] = values[i]; } diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts index be608548d32..7a6ce39222c 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts @@ -9,7 +9,7 @@ import { Mesh } from "core/Meshes/mesh"; import "core/Meshes/thinInstanceMesh"; import { TmpVectors, Quaternion, Vector3 } from "core/Maths/math.vector"; import { VertexBuffer } from "core/Buffers/buffer"; -import { convertToRightHandedPosition, convertToRightHandedRotation } from "../glTFUtilities"; +import { ConvertToRightHandedPosition, ConvertToRightHandedRotation } from "../glTFUtilities"; const NAME = "EXT_mesh_gpu_instancing"; @@ -90,8 +90,8 @@ export class EXT_mesh_gpu_instancing implements IGLTFExporterExtensionV2 { m.decompose(iws, iwr, iwt); if (convertToRightHanded) { - convertToRightHandedPosition(iwt); - convertToRightHandedRotation(iwr); + ConvertToRightHandedPosition(iwt); + ConvertToRightHandedRotation(iwr); } // fill the temp buffer diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index bb046aab944..d98d23f818c 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -9,7 +9,7 @@ import { KHRLightsPunctual_LightType } from "babylonjs-gltf2interface"; import type { IGLTFExporterExtensionV2 } from "../glTFExporterExtension"; import { GLTFExporter } from "../glTFExporter"; import { Logger } from "core/Misc/logger"; -import { convertToRightHandedPosition, omitDefaultValues, collapseParentNode, isParentAddedByImporter } from "../glTFUtilities"; +import { ConvertToRightHandedPosition, OmitDefaultValues, CollapseParentNode, IsParentAddedByImporter } from "../glTFUtilities"; const NAME = "KHR_lights_punctual"; const DEFAULTS: Partial = { @@ -102,7 +102,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { if (!babylonNode.position.equalsToFloats(0, 0, 0)) { const translation = TmpVectors.Vector3[0].copyFrom(babylonNode.position); if (convertToRightHanded) { - convertToRightHandedPosition(translation); + ConvertToRightHandedPosition(translation); } node.translation = translation.asArray(); } @@ -113,7 +113,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { if (lightType !== KHRLightsPunctual_LightType.POINT) { const direction = babylonNode.direction.normalize(); if (convertToRightHanded) { - convertToRightHandedPosition(direction); + ConvertToRightHandedPosition(direction); } const angle = Math.acos(Vector3.Dot(LIGHTDIRECTION, direction)); const axis = Vector3.Cross(LIGHTDIRECTION, direction); @@ -130,7 +130,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { intensity: babylonNode.intensity, range: babylonNode.range, }; - light = omitDefaultValues(light, DEFAULTS); + light = OmitDefaultValues(light, DEFAULTS); // Separately handle the required 'spot' field for spot lights if (lightType === KHRLightsPunctual_LightType.SPOT) { @@ -139,7 +139,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { innerConeAngle: babylonSpotLight.innerAngle / 2.0, outerConeAngle: babylonSpotLight.angle / 2.0, }; - light.spot = omitDefaultValues(light.spot, SPOTDEFAULTS!); + light.spot = OmitDefaultValues(light.spot, SPOTDEFAULTS!); } this._lights ||= { @@ -155,12 +155,12 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { // Why and when: the glTF loader generates a new parent TransformNode for each light node, which we should undo on export const parentBabylonNode = babylonNode.parent; - if (parentBabylonNode && isParentAddedByImporter(babylonNode, parentBabylonNode)) { + if (parentBabylonNode && IsParentAddedByImporter(babylonNode, parentBabylonNode)) { const parentNodeIndex = nodeMap.get(parentBabylonNode); if (parentNodeIndex) { // Combine the light's transformation with the parent's const parentNode = this._exporter._nodes[parentNodeIndex]; - collapseParentNode(node, parentNode); + CollapseParentNode(node, parentNode); parentNode.extensions ||= {}; parentNode.extensions[NAME] = lightReference; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts index 193caa11b68..70fe2229deb 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts @@ -17,12 +17,12 @@ import { Camera } from "core/Cameras/camera"; import { Light } from "core/Lights/light"; import type { DataWriter } from "./dataWriter"; import { - createAccessor, - createBufferView, - getAccessorElementCount, - convertToRightHandedPosition, - convertCameraRotationToGLTF, - convertToRightHandedRotation, + CreateAccessor, + CreateBufferView, + GetAccessorElementCount, + ConvertToRightHandedPosition, + ConvertCameraRotationToGLTF, + ConvertToRightHandedRotation, } from "./glTFUtilities"; /** @@ -601,13 +601,13 @@ export class _GLTFAnimation { // Creates buffer view and accessor for key frames. let byteLength = animationData.inputs.length * 4; const offset = binaryWriter.byteOffset; - bufferView = createBufferView(0, offset, byteLength); + bufferView = CreateBufferView(0, offset, byteLength); bufferViews.push(bufferView); animationData.inputs.forEach(function (input) { binaryWriter.writeFloat32(input); }); - accessor = createAccessor(bufferViews.length - 1, AccessorType.SCALAR, AccessorComponentType.FLOAT, animationData.inputs.length, null, { + accessor = CreateAccessor(bufferViews.length - 1, AccessorType.SCALAR, AccessorComponentType.FLOAT, animationData.inputs.length, null, { min: [animationData.inputsMin], max: [animationData.inputsMax], }); @@ -617,10 +617,10 @@ export class _GLTFAnimation { // create bufferview and accessor for keyed values. outputLength = animationData.outputs.length; - byteLength = getAccessorElementCount(dataAccessorType) * 4 * animationData.outputs.length; + byteLength = GetAccessorElementCount(dataAccessorType) * 4 * animationData.outputs.length; // check for in and out tangents - bufferView = createBufferView(0, binaryWriter.byteOffset, byteLength); + bufferView = CreateBufferView(0, binaryWriter.byteOffset, byteLength); bufferViews.push(bufferView); const rotationQuaternion = new Quaternion(); @@ -634,7 +634,7 @@ export class _GLTFAnimation { switch (animationChannelTargetPath) { case AnimationChannelTargetPath.TRANSLATION: Vector3.FromArrayToRef(output, 0, position); - convertToRightHandedPosition(position); + ConvertToRightHandedPosition(position); binaryWriter.writeFloat32(position.x); binaryWriter.writeFloat32(position.y); binaryWriter.writeFloat32(position.z); @@ -649,10 +649,10 @@ export class _GLTFAnimation { } if (isCamera) { - convertCameraRotationToGLTF(rotationQuaternion); + ConvertCameraRotationToGLTF(rotationQuaternion); } else { if (!Quaternion.IsIdentity(rotationQuaternion)) { - convertToRightHandedRotation(rotationQuaternion); + ConvertToRightHandedRotation(rotationQuaternion); } } @@ -680,7 +680,7 @@ export class _GLTFAnimation { Quaternion.FromEulerVectorToRef(eulerVec3, rotationQuaternion); } if (isCamera) { - convertCameraRotationToGLTF(rotationQuaternion); + ConvertCameraRotationToGLTF(rotationQuaternion); } rotationQuaternion.toArray(tempQuaterionArray); binaryWriter.writeFloat32(tempQuaterionArray[0]); @@ -699,7 +699,7 @@ export class _GLTFAnimation { } }); - accessor = createAccessor(bufferViews.length - 1, dataAccessorType, AccessorComponentType.FLOAT, outputLength, null); + accessor = CreateAccessor(bufferViews.length - 1, dataAccessorType, AccessorComponentType.FLOAT, outputLength, null); accessors.push(accessor); dataAccessorIndex = accessors.length - 1; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 68d741d3dc5..baeb02db9a0 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -43,22 +43,22 @@ import { GLTFMaterialExporter } from "./glTFMaterialExporter"; import type { IExportOptions } from "./glTFSerializer"; import { GLTFData } from "./glTFData"; import { - areIndices32Bits, - convertToRightHandedPosition, - convertToRightHandedRotation, - createAccessor, - createBufferView, - dataArrayToUint8Array, - getAccessorType, - getAttributeType, - getMinMax, - getPrimitiveMode, - indicesArrayToUint8Array, - isNoopNode, - isTriangleFillMode, - isParentAddedByImporter, - convertToRightHandedNode, - rotateNode180Y, + AreIndices32Bits, + ConvertToRightHandedPosition, + ConvertToRightHandedRotation, + CreateAccessor, + CreateBufferView, + DataArrayToUint8Array, + GetAccessorType, + GetAttributeType, + GetMinMax, + GetPrimitiveMode, + IndicesArrayToUint8Array, + IsNoopNode, + IsTriangleFillMode, + IsParentAddedByImporter, + ConvertToRightHandedNode, + RotateNode180Y, } from "./glTFUtilities"; import { DataWriter } from "./dataWriter"; import { Camera } from "core/Cameras/camera"; @@ -66,11 +66,11 @@ import { MultiMaterial } from "core/Materials/multiMaterial"; import { PBRMaterial } from "core/Materials/PBR/pbrMaterial"; import { StandardMaterial } from "core/Materials/standardMaterial"; import { Logger } from "core/Misc/logger"; -import { enumerateFloatValues } from "core/Buffers/bufferUtils"; +import { EnumerateFloatValues } from "core/Buffers/bufferUtils"; import type { Bone, Skeleton } from "core/Bones"; import { _GLTFAnimation } from "./glTFAnimation"; import type { MorphTarget } from "core/Morph"; -import { buildMorphTargetBuffers } from "./glTFMorphTargetsUtilities"; +import { BuildMorphTargetBuffers } from "./glTFMorphTargetsUtilities"; import type { GlTFMorphTarget } from "./glTFMorphTargetsUtilities"; import { LinesMesh } from "core/Meshes/linesMesh"; import { Color3, Color4 } from "core/Maths/math.color"; @@ -497,7 +497,7 @@ export class GLTFExporter { if (image.uri) { imageData = this._imageData[image.uri]; this._orderedImageData.push(imageData); - bufferView = createBufferView(0, byteOffset, imageData.data.byteLength, undefined); + bufferView = CreateBufferView(0, byteOffset, imageData.data.byteLength, undefined); byteOffset += imageData.data.byteLength; this._bufferViews.push(bufferView); image.bufferView = this._bufferViews.length - 1; @@ -670,7 +670,7 @@ export class GLTFExporter { if (!babylonTransformNode.position.equalsToFloats(0, 0, 0)) { const translation = TmpVectors.Vector3[0].copyFrom(babylonTransformNode.position); if (convertToRightHanded) { - convertToRightHandedPosition(translation); + ConvertToRightHandedPosition(translation); } node.translation = translation.asArray(); @@ -686,7 +686,7 @@ export class GLTFExporter { } if (!Quaternion.IsIdentity(rotationQuaternion)) { if (convertToRightHanded) { - convertToRightHandedRotation(rotationQuaternion); + ConvertToRightHandedRotation(rotationQuaternion); } node.rotation = rotationQuaternion.normalize().asArray(); @@ -829,10 +829,10 @@ export class GLTFExporter { const byteStride = 64; // 4 x 4 matrix of 32 bit float const byteLength = inverseBindMatrices.length * byteStride; const bufferViewOffset = this._dataWriter.byteOffset; - const bufferView = createBufferView(0, bufferViewOffset, byteLength, undefined); + const bufferView = CreateBufferView(0, bufferViewOffset, byteLength, undefined); this._bufferViews.push(bufferView); const bufferViewIndex = this._bufferViews.length - 1; - const bindMatrixAccessor = createAccessor(bufferViewIndex, AccessorType.MAT4, AccessorComponentType.FLOAT, inverseBindMatrices.length, null, null); + const bindMatrixAccessor = CreateAccessor(bufferViewIndex, AccessorType.MAT4, AccessorComponentType.FLOAT, inverseBindMatrices.length, null, null); const inverseBindAccessorIndex = this._accessors.push(bindMatrixAccessor) - 1; skin.inverseBindMatrices = inverseBindAccessorIndex; inverseBindMatrices.forEach((mat) => { @@ -871,7 +871,7 @@ export class GLTFExporter { const rootNoopNodesRH = new Array(); for (const rootNode of this._babylonScene.rootNodes) { - if (this._options.removeNoopRootNodes && !this._options.includeCoordinateSystemConversionNodes && isNoopNode(rootNode, this._babylonScene.useRightHandedSystem)) { + if (this._options.removeNoopRootNodes && !this._options.includeCoordinateSystemConversionNodes && IsNoopNode(rootNode, this._babylonScene.useRightHandedSystem)) { rootNoopNodesRH.push(...rootNode.getChildren()); } else if (this._babylonScene.useRightHandedSystem) { rootNodesRH.push(rootNode); @@ -1020,7 +1020,7 @@ export class GLTFExporter { throw new Error("Vertex buffers pointing to the same buffer must have the same byte stride"); } - const bytes = dataArrayToUint8Array(data).slice(); + const bytes = DataArrayToUint8Array(data).slice(); // Apply conversions to buffer data in-place. for (const vertexBuffer of vertexBuffers) { @@ -1035,7 +1035,7 @@ export class GLTFExporter { // Normalize normals and tangents. case VertexBuffer.NormalKind: case VertexBuffer.TangentKind: { - enumerateFloatValues(bytes, byteOffset, byteStride, size, type, maxTotalVertices * size, normalized, (values) => { + EnumerateFloatValues(bytes, byteOffset, byteStride, size, type, maxTotalVertices * size, normalized, (values) => { const invLength = 1 / Math.sqrt(values[0] * values[0] + values[1] * values[1] + values[2] * values[2]); values[0] *= invLength; values[1] *= invLength; @@ -1065,7 +1065,7 @@ export class GLTFExporter { const vertexData4 = new Color4(); const useExactSrgbConversions = this._babylonScene.getEngine().useExactSrgbConversions; - enumerateFloatValues(bytes, byteOffset, byteStride, size, type, maxTotalVertices * size, normalized, (values) => { + EnumerateFloatValues(bytes, byteOffset, byteStride, size, type, maxTotalVertices * size, normalized, (values) => { // Using separate Color3 and Color4 objects to ensure the right functions are called. if (values.length === 3) { vertexData3.fromArray(values, 0); @@ -1091,7 +1091,7 @@ export class GLTFExporter { for (const mesh of vertexBufferToMeshesMap.get(vertexBuffer)!) { const { byteOffset, byteStride, type, normalized } = vertexBuffer; const size = vertexBuffer.getSize(); - enumerateFloatValues(bytes, byteOffset, byteStride, size, type, mesh.getTotalVertices() * size, normalized, (values) => { + EnumerateFloatValues(bytes, byteOffset, byteStride, size, type, mesh.getTotalVertices() * size, normalized, (values) => { values[0] = -values[0]; }); } @@ -1105,7 +1105,7 @@ export class GLTFExporter { const byteOffset = this._dataWriter.byteOffset; this._dataWriter.writeUint8Array(bytes); - this._bufferViews.push(createBufferView(0, byteOffset, bytes.length, byteStride)); + this._bufferViews.push(CreateBufferView(0, byteOffset, bytes.length, byteStride)); state.setVertexBufferView(buffer, this._bufferViews.length - 1); const floatMatricesIndices = new Map(); @@ -1149,14 +1149,14 @@ export class GLTFExporter { newArray[index] = array[index]; } this._dataWriter.writeUint16Array(newArray); - this._bufferViews.push(createBufferView(0, byteOffset, newArray.byteLength, 4 * 2)); + this._bufferViews.push(CreateBufferView(0, byteOffset, newArray.byteLength, 4 * 2)); } else { const newArray = new Uint8Array(array.length); for (let index = 0; index < array.length; index++) { newArray[index] = array[index]; } this._dataWriter.writeUint8Array(newArray); - this._bufferViews.push(createBufferView(0, byteOffset, newArray.byteLength, 4)); + this._bufferViews.push(CreateBufferView(0, byteOffset, newArray.byteLength, 4)); } state.setRemappedBufferView(buffer, vertexBuffer, this._bufferViews.length - 1); @@ -1172,7 +1172,7 @@ export class GLTFExporter { continue; } - const glTFMorphTarget = buildMorphTargetBuffers(morphTarget, meshes[0], this._dataWriter, this._bufferViews, this._accessors, state.convertToRightHanded); + const glTFMorphTarget = BuildMorphTargetBuffers(morphTarget, meshes[0], this._dataWriter, this._bufferViews, this._accessors, state.convertToRightHanded); for (const mesh of meshes) { state.bindMorphDataToMesh(mesh, glTFMorphTarget); @@ -1232,7 +1232,7 @@ export class GLTFExporter { this._setCameraTransformation(node, babylonNode, state.convertToRightHanded, parentBabylonNode); // If a camera has a node that was added by the GLTF importer, we can just use the parent node transform as the "camera" transform. - if (parentBabylonNode && isParentAddedByImporter(babylonNode, parentBabylonNode)) { + if (parentBabylonNode && IsParentAddedByImporter(babylonNode, parentBabylonNode)) { const parentNodeIndex = this._nodeMap.get(parentBabylonNode); if (parentNodeIndex) { const parentNode = this._nodes[parentNodeIndex]; @@ -1241,8 +1241,8 @@ export class GLTFExporter { } } else { if (state.convertToRightHanded) { - convertToRightHandedNode(node); - rotateNode180Y(node); + ConvertToRightHandedNode(node); + RotateNode180Y(node); } this._nodesCameraMap.get(gltfCamera)?.push(node); } @@ -1333,10 +1333,10 @@ export class GLTFExporter { state: ExporterState, primitive: IMeshPrimitive ): void { - const is32Bits = areIndices32Bits(indices, count); + const is32Bits = AreIndices32Bits(indices, count); let indicesToExport = indices; - primitive.mode = getPrimitiveMode(fillMode); + primitive.mode = GetPrimitiveMode(fillMode); // Flip if triangle winding order is not CCW as glTF is always CCW. const invertedMaterial = sideOrientation !== Material.CounterClockWiseSideOrientation; @@ -1345,14 +1345,14 @@ export class GLTFExporter { const result1 = !state.wasAddedByNoopNode && !state.convertToRightHanded; const result2 = !state.wasAddedByNoopNode && invertedMaterial; - const flip = isTriangleFillMode(fillMode) && (result1 || result2); + const flip = IsTriangleFillMode(fillMode) && (result1 || result2); if (flip) { if (fillMode === Material.TriangleStripDrawMode || fillMode === Material.TriangleFanDrawMode) { throw new Error("Triangle strip/fan fill mode is not implemented"); } - primitive.mode = getPrimitiveMode(fillMode); + primitive.mode = GetPrimitiveMode(fillMode); const newIndices = is32Bits ? new Uint32Array(count) : new Uint16Array(count); @@ -1384,13 +1384,13 @@ export class GLTFExporter { let accessorIndex = state.getIndicesAccessor(indices, start, count, offset, flip); if (accessorIndex === undefined) { const bufferViewByteOffset = this._dataWriter.byteOffset; - const bytes = indicesArrayToUint8Array(indicesToExport, start, count, is32Bits); + const bytes = IndicesArrayToUint8Array(indicesToExport, start, count, is32Bits); this._dataWriter.writeUint8Array(bytes); - this._bufferViews.push(createBufferView(0, bufferViewByteOffset, bytes.length)); + this._bufferViews.push(CreateBufferView(0, bufferViewByteOffset, bytes.length)); const bufferViewIndex = this._bufferViews.length - 1; const componentType = is32Bits ? AccessorComponentType.UNSIGNED_INT : AccessorComponentType.UNSIGNED_SHORT; - this._accessors.push(createAccessor(bufferViewIndex, AccessorType.SCALAR, componentType, count, 0)); + this._accessors.push(CreateAccessor(bufferViewIndex, AccessorType.SCALAR, componentType, count, 0)); accessorIndex = this._accessors.length - 1; state.setIndicesAccessor(indices, start, count, offset, flip, accessorIndex); } @@ -1417,26 +1417,26 @@ export class GLTFExporter { if (accessorIndex === undefined) { // Get min/max from converted or original data. const data = state.convertedToRightHandedBuffers.get(vertexBuffer._buffer) || vertexBuffer._buffer.getData()!; - const minMax = kind === VertexBuffer.PositionKind ? getMinMax(data, vertexBuffer, start, count) : null; + const minMax = kind === VertexBuffer.PositionKind ? GetMinMax(data, vertexBuffer, start, count) : null; if ((kind === VertexBuffer.MatricesIndicesKind || kind === VertexBuffer.MatricesIndicesExtraKind) && vertexBuffer.type === VertexBuffer.FLOAT) { const bufferViewIndex = state.getRemappedBufferView(vertexBuffer._buffer, vertexBuffer); if (bufferViewIndex !== undefined) { const byteOffset = vertexBuffer.byteOffset + start * vertexBuffer.byteStride; this._accessors.push( - createAccessor(bufferViewIndex, getAccessorType(kind, state.hasVertexColorAlpha(vertexBuffer)), VertexBuffer.UNSIGNED_BYTE, count, byteOffset, minMax) + CreateAccessor(bufferViewIndex, GetAccessorType(kind, state.hasVertexColorAlpha(vertexBuffer)), VertexBuffer.UNSIGNED_BYTE, count, byteOffset, minMax) ); accessorIndex = this._accessors.length - 1; state.setVertexAccessor(vertexBuffer, start, count, accessorIndex); - primitive.attributes[getAttributeType(kind)] = accessorIndex; + primitive.attributes[GetAttributeType(kind)] = accessorIndex; } } else { const bufferViewIndex = state.getVertexBufferView(vertexBuffer._buffer)!; const byteOffset = vertexBuffer.byteOffset + start * vertexBuffer.byteStride; this._accessors.push( - createAccessor( + CreateAccessor( bufferViewIndex, - getAccessorType(kind, state.hasVertexColorAlpha(vertexBuffer)), + GetAccessorType(kind, state.hasVertexColorAlpha(vertexBuffer)), vertexBuffer.type, count, byteOffset, @@ -1446,10 +1446,10 @@ export class GLTFExporter { ); accessorIndex = this._accessors.length - 1; state.setVertexAccessor(vertexBuffer, start, count, accessorIndex); - primitive.attributes[getAttributeType(kind)] = accessorIndex; + primitive.attributes[GetAttributeType(kind)] = accessorIndex; } } else { - primitive.attributes[getAttributeType(kind)] = accessorIndex; + primitive.attributes[GetAttributeType(kind)] = accessorIndex; } } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index 29031991d21..27f84bf66ed 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -72,7 +72,7 @@ function getFileExtensionFromMimeType(mimeType: ImageMimeType): string { * @param oneMinusSpecularStrength one minus the specular strength * @returns metallic value */ -export function solveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number { +export function SolveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number { if (specular < dielectricSpecular.r) { dielectricSpecular; return 0; @@ -528,7 +528,7 @@ export class GLTFMaterialExporter { const diffusePerceivedBrightness = this._getPerceivedBrightness(specularGlossiness.diffuseColor); const specularPerceivedBrightness = this._getPerceivedBrightness(specularGlossiness.specularColor); const oneMinusSpecularStrength = 1 - this._getMaxComponent(specularGlossiness.specularColor); - const metallic = solveMetallic(diffusePerceivedBrightness, specularPerceivedBrightness, oneMinusSpecularStrength); + const metallic = SolveMetallic(diffusePerceivedBrightness, specularPerceivedBrightness, oneMinusSpecularStrength); const baseColorFromDiffuse = specularGlossiness.diffuseColor.scale(oneMinusSpecularStrength / (1.0 - dielectricSpecular.r) / Math.max(1 - metallic)); const baseColorFromSpecular = specularGlossiness.specularColor.subtract(dielectricSpecular.scale(1 - metallic)).scale(1 / Math.max(metallic)); let baseColor = Color3.Lerp(baseColorFromDiffuse, baseColorFromSpecular, metallic * metallic); diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts index bb3a8eae40c..6c98682a516 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -3,7 +3,7 @@ import { AccessorComponentType, AccessorType } from "babylonjs-gltf2interface"; import type { MorphTarget } from "core/Morph/morphTarget"; import type { DataWriter } from "./dataWriter"; -import { createAccessor, createBufferView } from "./glTFUtilities"; +import { CreateAccessor, CreateBufferView } from "./glTFUtilities"; import type { Mesh } from "core/Meshes/mesh"; import { VertexBuffer } from "core/Buffers/buffer"; import { Vector3 } from "core/Maths/math.vector"; @@ -36,7 +36,7 @@ function _NormalizeTangentFromRef(tangent: Vector4 | Vector3) { } } -export function buildMorphTargetBuffers( +export function BuildMorphTargetBuffers( morphTarget: MorphTarget, mesh: Mesh, dataWriter: DataWriter, @@ -85,9 +85,9 @@ export function buildMorphTargetBuffers( dataWriter.writeFloat32(difference.z); } - bufferViews.push(createBufferView(0, byteOffset, morphPositions.length * floatSize, floatSize * 3)); + bufferViews.push(CreateBufferView(0, byteOffset, morphPositions.length * floatSize, floatSize * 3)); bufferViewIndex = bufferViews.length - 1; - accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphPositions.length / 3, 0, { min, max })); + accessors.push(CreateAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphPositions.length / 3, 0, { min, max })); result.attributes["POSITION"] = accessors.length - 1; } @@ -106,9 +106,9 @@ export function buildMorphTargetBuffers( dataWriter.writeFloat32(difference.z); } - bufferViews.push(createBufferView(0, byteOffset, morphNormals.length * floatSize, floatSize * 3)); + bufferViews.push(CreateBufferView(0, byteOffset, morphNormals.length * floatSize, floatSize * 3)); bufferViewIndex = bufferViews.length - 1; - accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphNormals.length / 3, 0)); + accessors.push(CreateAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphNormals.length / 3, 0)); result.attributes["NORMAL"] = accessors.length - 1; } @@ -133,9 +133,9 @@ export function buildMorphTargetBuffers( dataWriter.writeFloat32(difference.z); } - bufferViews.push(createBufferView(0, byteOffset, vertexCount * floatSize * 3, floatSize * 3)); + bufferViews.push(CreateBufferView(0, byteOffset, vertexCount * floatSize * 3, floatSize * 3)); bufferViewIndex = bufferViews.length - 1; - accessors.push(createAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, vertexCount, 0)); + accessors.push(CreateAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, vertexCount, 0)); result.attributes["TANGENT"] = accessors.length - 1; } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 05b335dec7b..f84c7872657 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -11,7 +11,7 @@ import { Material } from "core/Materials/material"; import { TransformNode } from "core/Meshes/transformNode"; import { Mesh } from "core/Meshes/mesh"; import { InstancedMesh } from "core/Meshes/instancedMesh"; -import { enumerateFloatValues } from "core/Buffers/bufferUtils"; +import { EnumerateFloatValues } from "core/Buffers/bufferUtils"; import type { Node } from "core/node"; // Matrix that converts handedness on the X-axis. @@ -28,7 +28,7 @@ const rotation180Y = new Quaternion(0, 1, 0, 0); * @param byteStride byte distance between conequential elements * @returns bufferView for glTF */ -export function createBufferView(bufferIndex: number, byteOffset: number, byteLength: number, byteStride?: number): IBufferView { +export function CreateBufferView(bufferIndex: number, byteOffset: number, byteLength: number, byteStride?: number): IBufferView { const bufferview: IBufferView = { buffer: bufferIndex, byteLength: byteLength }; if (byteOffset) { @@ -53,7 +53,7 @@ export function createBufferView(bufferIndex: number, byteOffset: number, byteLe * @param normalized Specifies whether integer data values are normalized before usage * @returns accessor for glTF */ -export function createAccessor( +export function CreateAccessor( bufferViewIndex: number, type: AccessorType, componentType: AccessorComponentType, @@ -80,7 +80,7 @@ export function createAccessor( return accessor; } -export function getAccessorElementCount(accessorType: AccessorType): number { +export function GetAccessorElementCount(accessorType: AccessorType): number { switch (accessorType) { case AccessorType.MAT2: return 4; @@ -99,7 +99,7 @@ export function getAccessorElementCount(accessorType: AccessorType): number { } } -export function getAccessorType(kind: string, hasVertexColorAlpha: boolean): AccessorType { +export function GetAccessorType(kind: string, hasVertexColorAlpha: boolean): AccessorType { if (kind == VertexBuffer.ColorKind) { return hasVertexColorAlpha ? AccessorType.VEC4 : AccessorType.VEC3; } @@ -126,7 +126,7 @@ export function getAccessorType(kind: string, hasVertexColorAlpha: boolean): Acc throw new Error(`Unknown kind ${kind}`); } -export function getAttributeType(kind: string): string { +export function GetAttributeType(kind: string): string { switch (kind) { case VertexBuffer.PositionKind: return "POSITION"; @@ -161,7 +161,7 @@ export function getAttributeType(kind: string): string { throw new Error(`Unknown kind: ${kind}`); } -export function getPrimitiveMode(fillMode: number): MeshPrimitiveMode { +export function GetPrimitiveMode(fillMode: number): MeshPrimitiveMode { switch (fillMode) { case Material.TriangleFillMode: return MeshPrimitiveMode.TRIANGLES; @@ -183,7 +183,7 @@ export function getPrimitiveMode(fillMode: number): MeshPrimitiveMode { throw new Error(`Unknown fill mode: ${fillMode}`); } -export function isTriangleFillMode(fillMode: number): boolean { +export function IsTriangleFillMode(fillMode: number): boolean { switch (fillMode) { case Material.TriangleFillMode: case Material.TriangleStripDrawMode: @@ -194,7 +194,7 @@ export function isTriangleFillMode(fillMode: number): boolean { return false; } -export function normalizeTangent(tangent: Vector4) { +export function NormalizeTangent(tangent: Vector4) { const length = Math.sqrt(tangent.x * tangent.x + tangent.y * tangent.y + tangent.z * tangent.z); if (length > 0) { tangent.x /= length; @@ -203,23 +203,23 @@ export function normalizeTangent(tangent: Vector4) { } } -export function convertToRightHandedPosition(value: Vector3): Vector3 { +export function ConvertToRightHandedPosition(value: Vector3): Vector3 { value.x *= -1; return value; } -export function convertToRightHandedRotation(value: Quaternion): Quaternion { +export function ConvertToRightHandedRotation(value: Quaternion): Quaternion { value.x *= -1; value.y *= -1; return value; } -export function convertToRightHandedNode(value: INode) { +export function ConvertToRightHandedNode(value: INode) { let translation = Vector3.FromArrayToRef(value.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); let rotation = Quaternion.FromArrayToRef(value.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); - translation = convertToRightHandedPosition(translation); - rotation = convertToRightHandedRotation(rotation); + translation = ConvertToRightHandedPosition(translation); + rotation = ConvertToRightHandedRotation(rotation); value.rotation = rotation.asArray(); value.translation = translation.asArray(); @@ -242,11 +242,11 @@ export function convertToRightHandedNode(value: INode) { * @param rotation Target camera rotation. * @returns Ref to camera rotation. */ -export function convertCameraRotationToGLTF(rotation: Quaternion): Quaternion { +export function ConvertCameraRotationToGLTF(rotation: Quaternion): Quaternion { return rotation.multiplyInPlace(rotation180Y); } -export function rotateNode180Y(node: INode) { +export function RotateNode180Y(node: INode) { if (node.rotation) { const rotation = Quaternion.FromArrayToRef(node.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[1]); rotation180Y.multiplyToRef(rotation, rotation); @@ -259,7 +259,7 @@ export function rotateNode180Y(node: INode) { * @param node Target parent node. * @param parentNode Original GLTF node (Light or Camera). */ -export function collapseParentNode(node: INode, parentNode: INode) { +export function CollapseParentNode(node: INode, parentNode: INode) { const parentTranslation = Vector3.FromArrayToRef(parentNode.translation || [0, 0, 0], 0, TmpVectors.Vector3[0]); const parentRotation = Quaternion.FromArrayToRef(parentNode.rotation || [0, 0, 0, 1], 0, TmpVectors.Quaternion[0]); const parentScale = Vector3.FromArrayToRef(parentNode.scale || [1, 1, 1], 0, TmpVectors.Vector3[1]); @@ -298,7 +298,7 @@ export function collapseParentNode(node: INode, parentNode: INode) { * @param parentBabylonNode Target parent node. * @returns True if the parent node was added by the GLTF importer. */ -export function isParentAddedByImporter(babylonNode: Node, parentBabylonNode: Node): boolean { +export function IsParentAddedByImporter(babylonNode: Node, parentBabylonNode: Node): boolean { return parentBabylonNode instanceof TransformNode && parentBabylonNode.getChildren().length == 1 && babylonNode.getChildren().length == 0; } @@ -363,7 +363,7 @@ export function isParentAddedByImporter(babylonNode: Node, parentBabylonNode: No // quaternion[1] *= -1; // } -export function isNoopNode(node: Node, useRightHandedSystem: boolean): boolean { +export function IsNoopNode(node: Node, useRightHandedSystem: boolean): boolean { if (!(node instanceof TransformNode)) { return false; } @@ -389,7 +389,7 @@ export function isNoopNode(node: Node, useRightHandedSystem: boolean): boolean { return true; } -export function areIndices32Bits(indices: Nullable, count: number): boolean { +export function AreIndices32Bits(indices: Nullable, count: number): boolean { if (indices) { if (indices instanceof Array) { return indices.some((value) => value >= 65536); @@ -401,7 +401,7 @@ export function areIndices32Bits(indices: Nullable, count: number) return count >= 65536; } -export function indicesArrayToUint8Array(indices: IndicesArray, start: number, count: number, is32Bits: boolean): Uint8Array { +export function IndicesArrayToUint8Array(indices: IndicesArray, start: number, count: number, is32Bits: boolean): Uint8Array { if (indices instanceof Array) { const subarray = indices.slice(start, start + count); indices = is32Bits ? new Uint32Array(subarray) : new Uint16Array(subarray); @@ -411,7 +411,7 @@ export function indicesArrayToUint8Array(indices: IndicesArray, start: number, c return ArrayBuffer.isView(indices) ? new Uint8Array(indices.buffer, indices.byteOffset, indices.byteLength) : new Uint8Array(indices); } -export function dataArrayToUint8Array(data: DataArray): Uint8Array { +export function DataArrayToUint8Array(data: DataArray): Uint8Array { if (data instanceof Array) { const floatData = new Float32Array(data); return new Uint8Array(floatData.buffer, floatData.byteOffset, floatData.byteLength); @@ -420,12 +420,12 @@ export function dataArrayToUint8Array(data: DataArray): Uint8Array { return ArrayBuffer.isView(data) ? new Uint8Array(data.buffer, data.byteOffset, data.byteLength) : new Uint8Array(data); } -export function getMinMax(data: DataArray, vertexBuffer: VertexBuffer, start: number, count: number): { min: number[]; max: number[] } { +export function GetMinMax(data: DataArray, vertexBuffer: VertexBuffer, start: number, count: number): { min: number[]; max: number[] } { const { byteOffset, byteStride, type, normalized } = vertexBuffer; const size = vertexBuffer.getSize(); const min = new Array(size).fill(Infinity); const max = new Array(size).fill(-Infinity); - enumerateFloatValues(data, byteOffset + start * byteStride, byteStride, size, type, count * size, normalized, (values) => { + EnumerateFloatValues(data, byteOffset + start * byteStride, byteStride, size, type, count * size, normalized, (values) => { for (let i = 0; i < size; i++) { min[i] = Math.min(min[i], values[i]); max[i] = Math.max(max[i], values[i]); @@ -442,7 +442,7 @@ export function getMinMax(data: DataArray, vertexBuffer: VertexBuffer, start: nu * @param defaultValues a partial object with default values * @returns new object with default values omitted */ -export function omitDefaultValues(object: T, defaultValues: Partial): T { +export function OmitDefaultValues(object: T, defaultValues: Partial): T { return Object.fromEntries( Object.entries(object).filter(([key, value]) => { const defaultValue = defaultValues[key as keyof T]; diff --git a/packages/dev/serializers/test/integration/glTFSerializer.test.ts b/packages/dev/serializers/test/integration/glTFSerializer.test.ts index 88694c57460..ec44944cd8a 100644 --- a/packages/dev/serializers/test/integration/glTFSerializer.test.ts +++ b/packages/dev/serializers/test/integration/glTFSerializer.test.ts @@ -62,8 +62,8 @@ describe("Babylon glTF Serializer", () => { }); it("should solve for metallic", async () => { const assertionData = await page.evaluate(() => { - const solveZero = BABYLON.GLTF2.Exporter.solveMetallic(1.0, 0.0, 1.0); - const solveAproxOne = BABYLON.GLTF2.Exporter.solveMetallic(0.0, 1.0, 1.0); + const solveZero = BABYLON.GLTF2.Exporter.SolveMetallic(1.0, 0.0, 1.0); + const solveAproxOne = BABYLON.GLTF2.Exporter.SolveMetallic(0.0, 1.0, 1.0); return { solveZero, solveAproxOne, From 41cef91e47cdb407af7b0dcc063bb4291b89fd39 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Fri, 22 Nov 2024 22:42:25 -0500 Subject: [PATCH 085/133] Remove unneeded normalize() --- .../serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index d98d23f818c..c119b373ce7 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -119,7 +119,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { const axis = Vector3.Cross(LIGHTDIRECTION, direction); const lightRotationQuaternion = Quaternion.RotationAxis(axis, angle); if (!Quaternion.IsIdentity(lightRotationQuaternion)) { - node.rotation = lightRotationQuaternion.normalize().asArray(); + node.rotation = lightRotationQuaternion.asArray(); } } From 166c88ae8da296692edcf77174db4f775cd15d3e Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Fri, 22 Nov 2024 23:18:42 -0500 Subject: [PATCH 086/133] Remove properties in-place in OmitDefaultValues --- .../2.0/Extensions/KHR_lights_punctual.ts | 6 ++--- .../serializers/src/glTF/2.0/glTFUtilities.ts | 24 ++++++++++--------- 2 files changed, 16 insertions(+), 14 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index c119b373ce7..e9e97899d0f 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -123,14 +123,14 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { } } - let light: IKHRLightsPunctual_Light = { + const light: IKHRLightsPunctual_Light = { type: lightType, name: babylonNode.name, color: babylonNode.diffuse.asArray(), intensity: babylonNode.intensity, range: babylonNode.range, }; - light = OmitDefaultValues(light, DEFAULTS); + OmitDefaultValues(light, DEFAULTS); // Separately handle the required 'spot' field for spot lights if (lightType === KHRLightsPunctual_LightType.SPOT) { @@ -139,7 +139,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { innerConeAngle: babylonSpotLight.innerAngle / 2.0, outerConeAngle: babylonSpotLight.angle / 2.0, }; - light.spot = OmitDefaultValues(light.spot, SPOTDEFAULTS!); + OmitDefaultValues(light.spot, SPOTDEFAULTS!); } this._lights ||= { diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index f84c7872657..1a7fac24ed2 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -436,20 +436,22 @@ export function GetMinMax(data: DataArray, vertexBuffer: VertexBuffer, start: nu } /** - * Removes keys from an object that have the same value as the default values. + * Removes, in-place, object properties which have the same value as the default value. * Useful for avoiding unnecessary properties in the glTF JSON. * @param object the object to omit default values from * @param defaultValues a partial object with default values - * @returns new object with default values omitted + * @returns object with default values omitted */ export function OmitDefaultValues(object: T, defaultValues: Partial): T { - return Object.fromEntries( - Object.entries(object).filter(([key, value]) => { - const defaultValue = defaultValues[key as keyof T]; - if (Array.isArray(value) && Array.isArray(defaultValue) && value.length === defaultValue.length) { - return !value.every((val, i) => val === defaultValue[i]); - } - return value !== defaultValue; - }) - ) as T; + for (const [key, value] of Object.entries(object)) { + const defaultValue = defaultValues[key as keyof T]; + if ((Array.isArray(value) && Array.isArray(defaultValue) && areArraysEqual(value, defaultValue)) || value === defaultValue) { + delete object[key as keyof T]; + } + } + return object; +} + +function areArraysEqual(array1: unknown[], array2: unknown[]): boolean { + return array1.length === array2.length && array1.every((val, i) => val === array2[i]); } From 84ac1cf28ad907c3dfaa4b6fc8d01aaf80bdc292 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Fri, 22 Nov 2024 23:27:28 -0500 Subject: [PATCH 087/133] Narrow types for DEFAULTS --- .../src/glTF/2.0/Extensions/KHR_lights_punctual.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index e9e97899d0f..8e345737f7b 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -12,13 +12,13 @@ import { Logger } from "core/Misc/logger"; import { ConvertToRightHandedPosition, OmitDefaultValues, CollapseParentNode, IsParentAddedByImporter } from "../glTFUtilities"; const NAME = "KHR_lights_punctual"; -const DEFAULTS: Partial = { +const DEFAULTS: Omit = { name: "", color: [1, 1, 1], intensity: 1, range: Number.MAX_VALUE, }; -const SPOTDEFAULTS: IKHRLightsPunctual_Light["spot"] = { +const SPOTDEFAULTS: NonNullable = { innerConeAngle: 0, outerConeAngle: Math.PI / 4.0, }; @@ -139,7 +139,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { innerConeAngle: babylonSpotLight.innerAngle / 2.0, outerConeAngle: babylonSpotLight.angle / 2.0, }; - OmitDefaultValues(light.spot, SPOTDEFAULTS!); + OmitDefaultValues(light.spot, SPOTDEFAULTS); } this._lights ||= { From 7ae5b78dab014cc8b152d58d66d2993bb8e2e4d9 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 25 Nov 2024 07:18:31 -0500 Subject: [PATCH 088/133] Remove commented-out functions --- .../serializers/src/glTF/2.0/glTFUtilities.ts | 61 ------------------- 1 file changed, 61 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 1a7fac24ed2..21b727ea0c0 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -302,67 +302,6 @@ export function IsParentAddedByImporter(babylonNode: Node, parentBabylonNode: No return parentBabylonNode instanceof TransformNode && parentBabylonNode.getChildren().length == 1 && babylonNode.getChildren().length == 0; } -// /** -// * Converts a new right-handed Vector3 -// * @param vector vector3 array -// * @returns right-handed Vector3 -// */ -// public static _GetRightHandedNormalVector3(vector: Vector3): Vector3 { -// return new Vector3(vector.x, vector.y, -vector.z); -// } - -// /** -// * Converts a Vector3 to right-handed -// * @param vector Vector3 to convert to right-handed -// */ -// public static _GetRightHandedNormalVector3FromRef(vector: Vector3) { -// vector.z *= -1; -// } - -// /** -// * Converts a three element number array to right-handed -// * @param vector number array to convert to right-handed -// */ -// public static _GetRightHandedNormalArray3FromRef(vector: number[]) { -// vector[2] *= -1; -// } - -// /** -// * Converts a Vector4 to right-handed -// * @param vector Vector4 to convert to right-handed -// */ -// public static _GetRightHandedVector4FromRef(vector: Vector4) { -// vector.z *= -1; -// vector.w *= -1; -// } - -// /** -// * Converts a Vector4 to right-handed -// * @param vector Vector4 to convert to right-handed -// */ -// public static _GetRightHandedArray4FromRef(vector: number[]) { -// vector[2] *= -1; -// vector[3] *= -1; -// } - -// /** -// * Converts a Quaternion to right-handed -// * @param quaternion Source quaternion to convert to right-handed -// */ -// public static _GetRightHandedQuaternionFromRef(quaternion: Quaternion) { -// quaternion.x *= -1; -// quaternion.y *= -1; -// } - -// /** -// * Converts a Quaternion to right-handed -// * @param quaternion Source quaternion to convert to right-handed -// */ -// public static _GetRightHandedQuaternionArrayFromRef(quaternion: number[]) { -// quaternion[0] *= -1; -// quaternion[1] *= -1; -// } - export function IsNoopNode(node: Node, useRightHandedSystem: boolean): boolean { if (!(node instanceof TransformNode)) { return false; From 88c86ea7cefefe990b1e28ad9224388e549f445e Mon Sep 17 00:00:00 2001 From: "Sergio R. Z. Masson" <97050577+SergioRZMasson@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:38:54 -0300 Subject: [PATCH 089/133] Update packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts Co-authored-by: Gary Hsu --- packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index 27f84bf66ed..59b5c3148de 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -914,7 +914,7 @@ export class GLTFMaterialExporter { } private async _exportTextureInfoAsync(babylonTexture: BaseTexture, mimeType: ImageMimeType): Promise> { - let textureInfo = this._textureMap.get(babylonTexture); + const textureInfo = this._textureMap.get(babylonTexture); if (!textureInfo) { const pixels = await this._getPixelsFromTexture(babylonTexture); if (!pixels) { From ab14b32203ed4f3ea68528762c9832d7f0b59368 Mon Sep 17 00:00:00 2001 From: "Sergio R. Z. Masson" <97050577+SergioRZMasson@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:40:00 -0300 Subject: [PATCH 090/133] Update packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts Co-authored-by: Gary Hsu --- packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts | 9 ++++----- 1 file changed, 4 insertions(+), 5 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts index 70fe2229deb..648cae282b2 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts @@ -656,11 +656,10 @@ export class _GLTFAnimation { } } - rotationQuaternion.toArray(tempQuaterionArray); - binaryWriter.writeFloat32(tempQuaterionArray[0]); - binaryWriter.writeFloat32(tempQuaterionArray[1]); - binaryWriter.writeFloat32(tempQuaterionArray[2]); - binaryWriter.writeFloat32(tempQuaterionArray[3]); + binaryWriter.writeFloat32(rotationQuaternion.x); + binaryWriter.writeFloat32(rotationQuaternion.y); + binaryWriter.writeFloat32(rotationQuaternion.z); + binaryWriter.writeFloat32(rotationQuaternion.w); break; From d37023eda3a8d4f5db40991243a267fea423f2b7 Mon Sep 17 00:00:00 2001 From: "Sergio R. Z. Masson" <97050577+SergioRZMasson@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:40:16 -0300 Subject: [PATCH 091/133] Update packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts Co-authored-by: Gary Hsu --- .../dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts index 6c98682a516..3e429a7878a 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -27,7 +27,7 @@ export class GlTFMorphTarget { public name: string; } -function _NormalizeTangentFromRef(tangent: Vector4 | Vector3) { +function NormalizeTangentFromRef(tangent: Vector4 | Vector3) { const length = Math.sqrt(tangent.x * tangent.x + tangent.y * tangent.y + tangent.z * tangent.z); if (length > 0) { tangent.x /= length; From cd4da13bba096c8015a38eb8e66b5e4c622957d8 Mon Sep 17 00:00:00 2001 From: "Sergio R. Z. Masson" <97050577+SergioRZMasson@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:47:47 -0300 Subject: [PATCH 092/133] Update packages/dev/serializers/src/glTF/2.0/glTFExporter.ts Co-authored-by: Gary Hsu --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index baeb02db9a0..4b6072de04f 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -106,7 +106,7 @@ class ExporterState { public readonly wasAddedByNoopNode: boolean; - public readonly userUint16SkinIndex: boolean; + public readonly useUint16SkinIndex: boolean; // Only used when convertToRightHanded is true. public readonly convertedToRightHandedBuffers = new Map(); From a63f7d9cf223e144a0116e236530645fc5dc82fd Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 25 Nov 2024 12:48:28 -0300 Subject: [PATCH 093/133] Changed GlTFMorphTarget to interface --- .../serializers/src/glTF/2.0/glTFAnimation.ts | 10 +++---- .../serializers/src/glTF/2.0/glTFExporter.ts | 8 ++--- .../src/glTF/2.0/glTFMorphTargetsUtilities.ts | 29 ++++++++----------- 3 files changed, 20 insertions(+), 27 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts index 648cae282b2..84e6afe8714 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFAnimation.ts @@ -626,7 +626,6 @@ export class _GLTFAnimation { const rotationQuaternion = new Quaternion(); const eulerVec3 = new Vector3(); const position = new Vector3(); - const tempQuaterionArray = [0, 0, 0, 0]; const isCamera = babylonTransformNode instanceof Camera; animationData.outputs.forEach(function (output) { @@ -681,11 +680,10 @@ export class _GLTFAnimation { if (isCamera) { ConvertCameraRotationToGLTF(rotationQuaternion); } - rotationQuaternion.toArray(tempQuaterionArray); - binaryWriter.writeFloat32(tempQuaterionArray[0]); - binaryWriter.writeFloat32(tempQuaterionArray[1]); - binaryWriter.writeFloat32(tempQuaterionArray[2]); - binaryWriter.writeFloat32(tempQuaterionArray[3]); + binaryWriter.writeFloat32(rotationQuaternion.x); + binaryWriter.writeFloat32(rotationQuaternion.y); + binaryWriter.writeFloat32(rotationQuaternion.z); + binaryWriter.writeFloat32(rotationQuaternion.w); break; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index baeb02db9a0..56ecdf286b8 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -71,7 +71,7 @@ import type { Bone, Skeleton } from "core/Bones"; import { _GLTFAnimation } from "./glTFAnimation"; import type { MorphTarget } from "core/Morph"; import { BuildMorphTargetBuffers } from "./glTFMorphTargetsUtilities"; -import type { GlTFMorphTarget } from "./glTFMorphTargetsUtilities"; +import type { IGlTFMorphTarget } from "./glTFMorphTargetsUtilities"; import { LinesMesh } from "core/Meshes/linesMesh"; import { Color3, Color4 } from "core/Maths/math.color"; @@ -87,7 +87,7 @@ class ExporterState { private _remappedBufferView = new Map>(); - private _meshMorphTargetMap = new Map(); + private _meshMorphTargetMap = new Map(); private _vertexMapColorAlpha = new Map(); @@ -206,7 +206,7 @@ class ExporterState { this._meshMap.set(mesh, meshIndex); } - public bindMorphDataToMesh(mesh: Mesh, morphData: GlTFMorphTarget) { + public bindMorphDataToMesh(mesh: Mesh, morphData: IGlTFMorphTarget) { const morphTargets = this._meshMorphTargetMap.get(mesh) || []; this._meshMorphTargetMap.set(mesh, morphTargets); if (morphTargets.indexOf(morphData) === -1) { @@ -214,7 +214,7 @@ class ExporterState { } } - public getMorphTargetsFromMesh(mesh: Mesh): GlTFMorphTarget[] | undefined { + public getMorphTargetsFromMesh(mesh: Mesh): IGlTFMorphTarget[] | undefined { return this._meshMorphTargetMap.get(mesh); } } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts index 3e429a7878a..f0f4e690e48 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -12,19 +12,10 @@ import type { Vector4 } from "core/Maths/math.vector"; /** * Temporary structure to store morph target information. */ -export class GlTFMorphTarget { - /** - * - */ - public attributes: { [name: string]: number }; - /** - * - */ - public influence: number; - /** - * - */ - public name: string; +export interface IGlTFMorphTarget { + attributes: { [name: string]: number }; + influence: number; + name: string; } function NormalizeTangentFromRef(tangent: Vector4 | Vector3) { @@ -43,8 +34,12 @@ export function BuildMorphTargetBuffers( bufferViews: IBufferView[], accessors: IAccessor[], convertToRightHanded: boolean -): GlTFMorphTarget { - const result = new GlTFMorphTarget(); +): IGlTFMorphTarget { + const result: IGlTFMorphTarget = { + attributes: {}, + influence: 0, + name: "", + }; result.influence = morphTarget.influence; result.name = morphTarget.name; result.attributes = {}; @@ -121,11 +116,11 @@ export function BuildMorphTargetBuffers( for (let i = vertexStart; i < vertexCount; ++i) { // Only read the x, y, z components and ignore w const originalTangent = Vector3.FromArray(originalTangents, i * 4); - _NormalizeTangentFromRef(originalTangent); + NormalizeTangentFromRef(originalTangent); // Morph target tangents omit the w component so it won't be present in the data const morphTangent = Vector3.FromArray(morphTangents, i * 3); - _NormalizeTangentFromRef(morphTangent); + NormalizeTangentFromRef(morphTangent); morphTangent.subtractToRef(originalTangent, difference); dataWriter.writeFloat32(difference.x * flipX); From fda7cf7bc09ec7ec7b2c1c56d43ae16ac14ba29f Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 25 Nov 2024 12:49:35 -0300 Subject: [PATCH 094/133] Fixed tipo --- .../dev/serializers/src/glTF/2.0/glTFExporter.ts | 14 +++++++------- .../dev/serializers/src/glTF/2.0/glTFSerializer.ts | 2 +- 2 files changed, 8 insertions(+), 8 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index ecd209a8660..4e46e5cdec3 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -96,9 +96,9 @@ class ExporterState { // Babylon mesh -> glTF mesh index private _meshMap = new Map(); - public constructor(convertToRightHanded: boolean, userUint16SkinIndex: boolean, wasAddedByNoopNode: boolean) { + public constructor(convertToRightHanded: boolean, useUint16SkinIndex: boolean, wasAddedByNoopNode: boolean) { this.convertToRightHanded = convertToRightHanded; - this.userUint16SkinIndex = userUint16SkinIndex; + this.useUint16SkinIndex = useUint16SkinIndex; this.wasAddedByNoopNode = wasAddedByNoopNode; } @@ -402,7 +402,7 @@ export class GLTFExporter { exportUnusedUVs: false, removeNoopRootNodes: true, includeCoordinateSystemConversionNodes: false, - userUint16SkinIndex: false, + useUint16SkinIndex: false, ...options, }; @@ -884,11 +884,11 @@ export class GLTFExporter { this._listAvailableSkeletons(); // await this._materialExporter.convertMaterialsToGLTFAsync(this._getMaterials(nodes)); - const stateLH = new ExporterState(true, this._options.userUint16SkinIndex, false); + const stateLH = new ExporterState(true, this._options.useUint16SkinIndex, false); scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, stateLH))); - const stateRH = new ExporterState(false, this._options.userUint16SkinIndex, false); + const stateRH = new ExporterState(false, this._options.useUint16SkinIndex, false); scene.nodes.push(...(await this._exportNodesAsync(rootNodesRH, stateRH))); - const noopRH = new ExporterState(false, this._options.userUint16SkinIndex, true); + const noopRH = new ExporterState(false, this._options.useUint16SkinIndex, true); scene.nodes.push(...(await this._exportNodesAsync(rootNoopNodesRH, noopRH))); if (scene.nodes.length) { @@ -1143,7 +1143,7 @@ export class GLTFExporter { } const byteOffset = this._dataWriter.byteOffset; - if (state.userUint16SkinIndex) { + if (state.useUint16SkinIndex) { const newArray = new Uint16Array(array.length); for (let index = 0; index < array.length; index++) { newArray[index] = array[index]; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts b/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts index c5662352928..0c194919224 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts @@ -58,7 +58,7 @@ export interface IExportOptions { /** * If set to true it will export skin matrix index as Uint16. */ - userUint16SkinIndex?: boolean; + useUint16SkinIndex?: boolean; } /** From 5be8cf247b6362c0e3f2d512ee0923413ba46169 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 25 Nov 2024 12:51:35 -0300 Subject: [PATCH 095/133] Fixed comments on camera coordinates --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 4e46e5cdec3..14eeb0b5b1d 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -698,7 +698,7 @@ export class GLTFExporter { const rotation = TmpVectors.Quaternion[0]; if (parent !== null) { - // We need local coordinates. If camera has parent we need to que local translation/rotation. + // Camera.getWorldMatrix returs global coordinates. GLTF node must use local coordinates. If camera has parent we need to use local translation/rotation. const parentWorldMatrix = Matrix.Invert(parent.getWorldMatrix()); const cameraWorldMatrix = babylonCamera.getWorldMatrix(); const cameraLocal = cameraWorldMatrix.multiply(parentWorldMatrix); From f637d92381fed8009c7306cdbf7ef7823755046f Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 25 Nov 2024 13:00:16 -0300 Subject: [PATCH 096/133] Changed exporter to automatically detect need for 16bit integers --- .../dev/serializers/src/glTF/2.0/glTFExporter.ts | 15 ++++++--------- .../serializers/src/glTF/2.0/glTFSerializer.ts | 5 ----- .../dev/serializers/src/glTF/2.0/glTFUtilities.ts | 6 +++++- 3 files changed, 11 insertions(+), 15 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 14eeb0b5b1d..e1f1bde556a 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -59,6 +59,7 @@ import { IsParentAddedByImporter, ConvertToRightHandedNode, RotateNode180Y, + FloatsNeed16BitInteger, } from "./glTFUtilities"; import { DataWriter } from "./dataWriter"; import { Camera } from "core/Cameras/camera"; @@ -96,9 +97,8 @@ class ExporterState { // Babylon mesh -> glTF mesh index private _meshMap = new Map(); - public constructor(convertToRightHanded: boolean, useUint16SkinIndex: boolean, wasAddedByNoopNode: boolean) { + public constructor(convertToRightHanded: boolean, wasAddedByNoopNode: boolean) { this.convertToRightHanded = convertToRightHanded; - this.useUint16SkinIndex = useUint16SkinIndex; this.wasAddedByNoopNode = wasAddedByNoopNode; } @@ -106,8 +106,6 @@ class ExporterState { public readonly wasAddedByNoopNode: boolean; - public readonly useUint16SkinIndex: boolean; - // Only used when convertToRightHanded is true. public readonly convertedToRightHandedBuffers = new Map(); @@ -402,7 +400,6 @@ export class GLTFExporter { exportUnusedUVs: false, removeNoopRootNodes: true, includeCoordinateSystemConversionNodes: false, - useUint16SkinIndex: false, ...options, }; @@ -884,11 +881,11 @@ export class GLTFExporter { this._listAvailableSkeletons(); // await this._materialExporter.convertMaterialsToGLTFAsync(this._getMaterials(nodes)); - const stateLH = new ExporterState(true, this._options.useUint16SkinIndex, false); + const stateLH = new ExporterState(true, false); scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, stateLH))); - const stateRH = new ExporterState(false, this._options.useUint16SkinIndex, false); + const stateRH = new ExporterState(false, false); scene.nodes.push(...(await this._exportNodesAsync(rootNodesRH, stateRH))); - const noopRH = new ExporterState(false, this._options.useUint16SkinIndex, true); + const noopRH = new ExporterState(false, true); scene.nodes.push(...(await this._exportNodesAsync(rootNoopNodesRH, noopRH))); if (scene.nodes.length) { @@ -1143,7 +1140,7 @@ export class GLTFExporter { } const byteOffset = this._dataWriter.byteOffset; - if (state.useUint16SkinIndex) { + if (FloatsNeed16BitInteger(array)) { const newArray = new Uint16Array(array.length); for (let index = 0; index < array.length; index++) { newArray[index] = array[index]; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts b/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts index 0c194919224..1787b779fa3 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFSerializer.ts @@ -54,11 +54,6 @@ export interface IExportOptions { * @deprecated Please use removeNoopRootNodes instead */ includeCoordinateSystemConversionNodes?: boolean; - - /** - * If set to true it will export skin matrix index as Uint16. - */ - useUint16SkinIndex?: boolean; } /** diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 21b727ea0c0..4f1e585e3ed 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -3,7 +3,7 @@ import type { IBufferView, AccessorComponentType, IAccessor, INode } from "babylonjs-gltf2interface"; import { AccessorType, MeshPrimitiveMode } from "babylonjs-gltf2interface"; -import type { DataArray, IndicesArray, Nullable } from "core/types"; +import type { FloatArray, DataArray, IndicesArray, Nullable } from "core/types"; import type { Vector4 } from "core/Maths/math.vector"; import { Quaternion, TmpVectors, Matrix, Vector3 } from "core/Maths/math.vector"; import { VertexBuffer } from "core/Buffers/buffer"; @@ -99,6 +99,10 @@ export function GetAccessorElementCount(accessorType: AccessorType): number { } } +export function FloatsNeed16BitInteger(floatArray: FloatArray): boolean { + return floatArray.some((value) => value >= 256); +} + export function GetAccessorType(kind: string, hasVertexColorAlpha: boolean): AccessorType { if (kind == VertexBuffer.ColorKind) { return hasVertexColorAlpha ? AccessorType.VEC4 : AccessorType.VEC3; From a81af10d21b7c3bf9e1c411cee798549adb9e5f9 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 25 Nov 2024 13:10:45 -0300 Subject: [PATCH 097/133] Fixed error when using const --- packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index 59b5c3148de..27f84bf66ed 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -914,7 +914,7 @@ export class GLTFMaterialExporter { } private async _exportTextureInfoAsync(babylonTexture: BaseTexture, mimeType: ImageMimeType): Promise> { - const textureInfo = this._textureMap.get(babylonTexture); + let textureInfo = this._textureMap.get(babylonTexture); if (!textureInfo) { const pixels = await this._getPixelsFromTexture(babylonTexture); if (!pixels) { From a9ae8b21ffc3ab6fb2244d9b614a5eb9a3365962 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 25 Nov 2024 13:16:02 -0300 Subject: [PATCH 098/133] Removed legacy comment --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index e1f1bde556a..bd007f5a977 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1338,11 +1338,10 @@ export class GLTFExporter { // Flip if triangle winding order is not CCW as glTF is always CCW. const invertedMaterial = sideOrientation !== Material.CounterClockWiseSideOrientation; - // Temporary logic to handle indices flipping. Not sure how this is working yet. - const result1 = !state.wasAddedByNoopNode && !state.convertToRightHanded; - const result2 = !state.wasAddedByNoopNode && invertedMaterial; + const flipWhenLeftHanded = !state.wasAddedByNoopNode && !state.convertToRightHanded; + const flipWhenInvertedMaterial = !state.wasAddedByNoopNode && invertedMaterial; - const flip = IsTriangleFillMode(fillMode) && (result1 || result2); + const flip = IsTriangleFillMode(fillMode) && (flipWhenLeftHanded || flipWhenInvertedMaterial); if (flip) { if (fillMode === Material.TriangleStripDrawMode || fillMode === Material.TriangleFanDrawMode) { From 77e1369e5858203bbcb252d10520b41ea93e2670 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Mon, 25 Nov 2024 14:07:13 -0300 Subject: [PATCH 099/133] Added test for morph target vertex buffers on WebGL1 --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 5 +++++ packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts | 10 ++++++++++ 2 files changed, 15 insertions(+) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index bd007f5a977..ff80952f717 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -60,6 +60,7 @@ import { ConvertToRightHandedNode, RotateNode180Y, FloatsNeed16BitInteger, + IsMorphTargetsVertexBuffer, } from "./glTFUtilities"; import { DataWriter } from "./dataWriter"; import { Camera } from "core/Cameras/camera"; @@ -1398,6 +1399,10 @@ export class GLTFExporter { private _exportVertexBuffer(vertexBuffer: VertexBuffer, babylonMaterial: Material, start: number, count: number, state: ExporterState, primitive: IMeshPrimitive): void { const kind = vertexBuffer.getKind(); + if (IsMorphTargetsVertexBuffer(kind)) { + return; + } + if (kind.startsWith("world")) { return; } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 4f1e585e3ed..42fe8cea92b 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -20,6 +20,12 @@ const convertHandednessMatrix = Matrix.Compose(new Vector3(-1, 1, 1), Quaternion // 180 degrees rotation in Y. const rotation180Y = new Quaternion(0, 1, 0, 0); +// Regex for testing if vertexBuffer type comes from morph targets. +const positionRegex = /^position\d+$/; +const normalRegex = /^normal\d+$/; +const tangentRegex = /^tangent\d+$/; +const uvRegex = /^uv_\d+$/; + /** * Creates a buffer view based on the supplied arguments * @param bufferIndex index value of the specified buffer @@ -103,6 +109,10 @@ export function FloatsNeed16BitInteger(floatArray: FloatArray): boolean { return floatArray.some((value) => value >= 256); } +export function IsMorphTargetsVertexBuffer(type: string): boolean { + return positionRegex.test(type) || normalRegex.test(type) || tangentRegex.test(type) || uvRegex.test(type); +} + export function GetAccessorType(kind: string, hasVertexColorAlpha: boolean): AccessorType { if (kind == VertexBuffer.ColorKind) { return hasVertexColorAlpha ? AccessorType.VEC4 : AccessorType.VEC3; From a2edb8b861fb3286cae389e4544e4cea2aa1a903 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 25 Nov 2024 12:14:44 -0500 Subject: [PATCH 100/133] Use PascalCase for non-exported module-level functions --- packages/dev/core/src/Buffers/bufferUtils.ts | 8 +++---- .../dev/serializers/src/glTF/2.0/glTFData.ts | 4 ++-- .../src/glTF/2.0/glTFMaterialExporter.ts | 22 +++++++++---------- .../serializers/src/glTF/2.0/glTFUtilities.ts | 4 ++-- 4 files changed, 19 insertions(+), 19 deletions(-) diff --git a/packages/dev/core/src/Buffers/bufferUtils.ts b/packages/dev/core/src/Buffers/bufferUtils.ts index 364ef9684c0..db01aa7a345 100644 --- a/packages/dev/core/src/Buffers/bufferUtils.ts +++ b/packages/dev/core/src/Buffers/bufferUtils.ts @@ -2,7 +2,7 @@ import { Constants } from "../Engines/constants"; import { Logger } from "../Misc/logger"; import type { DataArray, FloatArray } from "../types"; -function getFloatValue(dataView: DataView, type: number, byteOffset: number, normalized: boolean): number { +function GetFloatValue(dataView: DataView, type: number, byteOffset: number, normalized: boolean): number { switch (type) { case Constants.BYTE: { let value = dataView.getInt8(byteOffset); @@ -47,7 +47,7 @@ function getFloatValue(dataView: DataView, type: number, byteOffset: number, nor } } -function setFloatValue(dataView: DataView, type: number, byteOffset: number, normalized: boolean, value: number): void { +function SetFloatValue(dataView: DataView, type: number, byteOffset: number, normalized: boolean, value: number): void { switch (type) { case Constants.BYTE: { if (normalized) { @@ -164,14 +164,14 @@ export function EnumerateFloatValues( const componentByteLength = GetTypeByteLength(componentType); for (let index = 0; index < count; index += componentCount) { for (let componentIndex = 0, componentByteOffset = byteOffset; componentIndex < componentCount; componentIndex++, componentByteOffset += componentByteLength) { - oldValues[componentIndex] = newValues[componentIndex] = getFloatValue(dataView, componentType, componentByteOffset, normalized); + oldValues[componentIndex] = newValues[componentIndex] = GetFloatValue(dataView, componentType, componentByteOffset, normalized); } callback(newValues, index); for (let componentIndex = 0, componentByteOffset = byteOffset; componentIndex < componentCount; componentIndex++, componentByteOffset += componentByteLength) { if (oldValues[componentIndex] !== newValues[componentIndex]) { - setFloatValue(dataView, componentType, componentByteOffset, normalized, newValues[componentIndex]); + SetFloatValue(dataView, componentType, componentByteOffset, normalized, newValues[componentIndex]); } } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFData.ts b/packages/dev/serializers/src/glTF/2.0/glTFData.ts index 0b0cf6a5d0e..91a26bd163f 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFData.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFData.ts @@ -1,7 +1,7 @@ import { ImageMimeType } from "babylonjs-gltf2interface"; import { Tools } from "core/Misc/tools"; -function getMimeType(fileName: string): string | undefined { +function GetMimeType(fileName: string): string | undefined { if (fileName.endsWith(".glb")) { return "model/gltf-binary"; } else if (fileName.endsWith(".bin")) { @@ -41,7 +41,7 @@ export class GLTFData { public downloadFiles(): void { for (const key in this.files) { const value = this.files[key]; - const blob = new Blob([value], { type: getMimeType(key) }); + const blob = new Blob([value], { type: GetMimeType(key) }); Tools.Download(blob, key); } } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index 27f84bf66ed..4d3c6fd8aa5 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -51,7 +51,7 @@ interface IPBRMetallicRoughness { baseColorTextureData?: Nullable; } -function getFileExtensionFromMimeType(mimeType: ImageMimeType): string { +function GetFileExtensionFromMimeType(mimeType: ImageMimeType): string { switch (mimeType) { case ImageMimeType.JPEG: return ".jpg"; @@ -90,7 +90,7 @@ export function SolveMetallic(diffuse: number, specular: number, oneMinusSpecula * @param glTFMaterial glTF material * @param babylonMaterial Babylon material */ -function setAlphaMode(glTFMaterial: IMaterial, babylonMaterial: Material & { alphaCutOff?: number }): void { +function SetAlphaMode(glTFMaterial: IMaterial, babylonMaterial: Material & { alphaCutOff?: number }): void { if (babylonMaterial.needAlphaBlending()) { glTFMaterial.alphaMode = MaterialAlphaMode.BLEND; } else if (babylonMaterial.needAlphaTesting()) { @@ -99,7 +99,7 @@ function setAlphaMode(glTFMaterial: IMaterial, babylonMaterial: Material & { alp } } -function createWhiteTexture(width: number, height: number, scene: Scene): Texture { +function CreateWhiteTexture(width: number, height: number, scene: Scene): Texture { const data = new Uint8Array(width * height * 4); for (let i = 0; i < data.length; i = i + 4) { @@ -111,7 +111,7 @@ function createWhiteTexture(width: number, height: number, scene: Scene): Textur return rawTexture; } -function convertPixelArrayToFloat32(pixels: ArrayBufferView): Float32Array { +function ConvertPixelArrayToFloat32(pixels: ArrayBufferView): Float32Array { if (pixels instanceof Uint8Array) { const length = pixels.length; const buffer = new Float32Array(pixels.length); @@ -232,7 +232,7 @@ export class GLTFMaterialExporter { } material.pbrMetallicRoughness = pbrMetallicRoughness; - setAlphaMode(material, babylonStandardMaterial); + SetAlphaMode(material, babylonStandardMaterial); await this._finishMaterialAsync(material, babylonStandardMaterial, mimeType); @@ -338,14 +338,14 @@ export class GLTFMaterialExporter { if (texture1 && texture1 instanceof Texture) { resizedTexture1 = TextureTools.CreateResizedCopy(texture1, texture2Size.width, texture2Size.height, true); } else { - resizedTexture1 = createWhiteTexture(texture2Size.width, texture2Size.height, scene); + resizedTexture1 = CreateWhiteTexture(texture2Size.width, texture2Size.height, scene); } resizedTexture2 = texture2!; } else if (texture1Size.width > texture2Size.width) { if (texture2 && texture2 instanceof Texture) { resizedTexture2 = TextureTools.CreateResizedCopy(texture2, texture1Size.width, texture1Size.height, true); } else { - resizedTexture2 = createWhiteTexture(texture1Size.width, texture1Size.height, scene); + resizedTexture2 = CreateWhiteTexture(texture1Size.width, texture1Size.height, scene); } resizedTexture1 = texture1!; } else { @@ -396,12 +396,12 @@ export class GLTFMaterialExporter { const specularPixels = await resizedTextures.texture2.readPixels(); if (diffusePixels) { - diffuseBuffer = convertPixelArrayToFloat32(diffusePixels); + diffuseBuffer = ConvertPixelArrayToFloat32(diffusePixels); } else { return Promise.reject("Failed to retrieve pixels from diffuse texture!"); } if (specularPixels) { - specularGlossinessBuffer = convertPixelArrayToFloat32(specularPixels); + specularGlossinessBuffer = ConvertPixelArrayToFloat32(specularPixels); } else { return Promise.reject("Failed to retrieve pixels from specular glossiness texture!"); } @@ -808,7 +808,7 @@ export class GLTFMaterialExporter { mimeType: ImageMimeType, hasUVs: boolean ): Promise { - setAlphaMode(glTFMaterial, babylonPBRMaterial); + SetAlphaMode(glTFMaterial, babylonPBRMaterial); if (!metallicRoughness.baseColor.equalsWithEpsilon(white, epsilon) || !Scalar.WithinEpsilon(babylonPBRMaterial.alpha, 1, epsilon)) { glTFPbrMetallicRoughness.baseColorFactor = [metallicRoughness.baseColor.r, metallicRoughness.baseColor.g, metallicRoughness.baseColor.b, babylonPBRMaterial.alpha]; @@ -963,7 +963,7 @@ export class GLTFMaterialExporter { const imageData = this._exporter._imageData; const baseName = name.replace(/\.\/|\/|\.\\|\\/g, "_"); - const extension = getFileExtensionFromMimeType(mimeType); + const extension = GetFileExtensionFromMimeType(mimeType); let fileName = baseName + extension; if (fileName in imageData) { fileName = `${baseName}_${Tools.RandomId()}${extension}`; diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 42fe8cea92b..072d477f0bb 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -398,13 +398,13 @@ export function GetMinMax(data: DataArray, vertexBuffer: VertexBuffer, start: nu export function OmitDefaultValues(object: T, defaultValues: Partial): T { for (const [key, value] of Object.entries(object)) { const defaultValue = defaultValues[key as keyof T]; - if ((Array.isArray(value) && Array.isArray(defaultValue) && areArraysEqual(value, defaultValue)) || value === defaultValue) { + if ((Array.isArray(value) && Array.isArray(defaultValue) && AreArraysEqual(value, defaultValue)) || value === defaultValue) { delete object[key as keyof T]; } } return object; } -function areArraysEqual(array1: unknown[], array2: unknown[]): boolean { +function AreArraysEqual(array1: unknown[], array2: unknown[]): boolean { return array1.length === array2.length && array1.every((val, i) => val === array2[i]); } From 20e52dc213b6337dd9f40a1b97a89225afe842a9 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 25 Nov 2024 15:24:21 -0500 Subject: [PATCH 101/133] Mark SolveMetallic as internal --- .../dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts | 6 +++--- .../dev/serializers/test/integration/glTFSerializer.test.ts | 4 ++-- 2 files changed, 5 insertions(+), 5 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index 4d3c6fd8aa5..757f0ffd36e 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -65,14 +65,14 @@ function GetFileExtensionFromMimeType(mimeType: ImageMimeType): string { } /** + * @internal * Computes the metallic factor. - * Exported to be testable in unit tests. * @param diffuse diffused value * @param specular specular value * @param oneMinusSpecularStrength one minus the specular strength * @returns metallic value */ -export function SolveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number { +export function _SolveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number { if (specular < dielectricSpecular.r) { dielectricSpecular; return 0; @@ -528,7 +528,7 @@ export class GLTFMaterialExporter { const diffusePerceivedBrightness = this._getPerceivedBrightness(specularGlossiness.diffuseColor); const specularPerceivedBrightness = this._getPerceivedBrightness(specularGlossiness.specularColor); const oneMinusSpecularStrength = 1 - this._getMaxComponent(specularGlossiness.specularColor); - const metallic = SolveMetallic(diffusePerceivedBrightness, specularPerceivedBrightness, oneMinusSpecularStrength); + const metallic = _SolveMetallic(diffusePerceivedBrightness, specularPerceivedBrightness, oneMinusSpecularStrength); const baseColorFromDiffuse = specularGlossiness.diffuseColor.scale(oneMinusSpecularStrength / (1.0 - dielectricSpecular.r) / Math.max(1 - metallic)); const baseColorFromSpecular = specularGlossiness.specularColor.subtract(dielectricSpecular.scale(1 - metallic)).scale(1 / Math.max(metallic)); let baseColor = Color3.Lerp(baseColorFromDiffuse, baseColorFromSpecular, metallic * metallic); diff --git a/packages/dev/serializers/test/integration/glTFSerializer.test.ts b/packages/dev/serializers/test/integration/glTFSerializer.test.ts index ec44944cd8a..723bac90ed9 100644 --- a/packages/dev/serializers/test/integration/glTFSerializer.test.ts +++ b/packages/dev/serializers/test/integration/glTFSerializer.test.ts @@ -62,8 +62,8 @@ describe("Babylon glTF Serializer", () => { }); it("should solve for metallic", async () => { const assertionData = await page.evaluate(() => { - const solveZero = BABYLON.GLTF2.Exporter.SolveMetallic(1.0, 0.0, 1.0); - const solveAproxOne = BABYLON.GLTF2.Exporter.SolveMetallic(0.0, 1.0, 1.0); + const solveZero = BABYLON.GLTF2.Exporter._SolveMetallic(1.0, 0.0, 1.0); + const solveAproxOne = BABYLON.GLTF2.Exporter._SolveMetallic(0.0, 1.0, 1.0); return { solveZero, solveAproxOne, From e8a571ab306eed424b67914d1e52ba242007e96e Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:27:06 -0500 Subject: [PATCH 102/133] Update packages/dev/serializers/src/glTF/2.0/glTFExporter.ts Co-authored-by: Gary Hsu --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index ff80952f717..66be0ac17bb 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -696,7 +696,7 @@ export class GLTFExporter { const rotation = TmpVectors.Quaternion[0]; if (parent !== null) { - // Camera.getWorldMatrix returs global coordinates. GLTF node must use local coordinates. If camera has parent we need to use local translation/rotation. + // Camera.getWorldMatrix returns global coordinates. GLTF node must use local coordinates. If camera has parent we need to use local translation/rotation. const parentWorldMatrix = Matrix.Invert(parent.getWorldMatrix()); const cameraWorldMatrix = babylonCamera.getWorldMatrix(); const cameraLocal = cameraWorldMatrix.multiply(parentWorldMatrix); From d85014b43eff6961fb0fcfed8121ad45d5965a30 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 25 Nov 2024 17:57:49 -0500 Subject: [PATCH 103/133] Capitalize L in IGlTFMorphTarget --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 8 ++++---- .../serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts | 6 +++--- 2 files changed, 7 insertions(+), 7 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 66be0ac17bb..93dbe09205e 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -73,7 +73,7 @@ import type { Bone, Skeleton } from "core/Bones"; import { _GLTFAnimation } from "./glTFAnimation"; import type { MorphTarget } from "core/Morph"; import { BuildMorphTargetBuffers } from "./glTFMorphTargetsUtilities"; -import type { IGlTFMorphTarget } from "./glTFMorphTargetsUtilities"; +import type { IGLTFMorphTarget } from "./glTFMorphTargetsUtilities"; import { LinesMesh } from "core/Meshes/linesMesh"; import { Color3, Color4 } from "core/Maths/math.color"; @@ -89,7 +89,7 @@ class ExporterState { private _remappedBufferView = new Map>(); - private _meshMorphTargetMap = new Map(); + private _meshMorphTargetMap = new Map(); private _vertexMapColorAlpha = new Map(); @@ -205,7 +205,7 @@ class ExporterState { this._meshMap.set(mesh, meshIndex); } - public bindMorphDataToMesh(mesh: Mesh, morphData: IGlTFMorphTarget) { + public bindMorphDataToMesh(mesh: Mesh, morphData: IGLTFMorphTarget) { const morphTargets = this._meshMorphTargetMap.get(mesh) || []; this._meshMorphTargetMap.set(mesh, morphTargets); if (morphTargets.indexOf(morphData) === -1) { @@ -213,7 +213,7 @@ class ExporterState { } } - public getMorphTargetsFromMesh(mesh: Mesh): IGlTFMorphTarget[] | undefined { + public getMorphTargetsFromMesh(mesh: Mesh): IGLTFMorphTarget[] | undefined { return this._meshMorphTargetMap.get(mesh); } } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts index f0f4e690e48..39733f64744 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -12,7 +12,7 @@ import type { Vector4 } from "core/Maths/math.vector"; /** * Temporary structure to store morph target information. */ -export interface IGlTFMorphTarget { +export interface IGLTFMorphTarget { attributes: { [name: string]: number }; influence: number; name: string; @@ -34,8 +34,8 @@ export function BuildMorphTargetBuffers( bufferViews: IBufferView[], accessors: IAccessor[], convertToRightHanded: boolean -): IGlTFMorphTarget { - const result: IGlTFMorphTarget = { +): IGLTFMorphTarget { + const result: IGLTFMorphTarget = { attributes: {}, influence: 0, name: "", From d6b73fa613ca66c99129283b64ab21c5b6d9cd42 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 25 Nov 2024 18:12:08 -0500 Subject: [PATCH 104/133] Typo in solveApproxOne --- .../dev/serializers/test/integration/glTFSerializer.test.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/dev/serializers/test/integration/glTFSerializer.test.ts b/packages/dev/serializers/test/integration/glTFSerializer.test.ts index 723bac90ed9..13226c62ac3 100644 --- a/packages/dev/serializers/test/integration/glTFSerializer.test.ts +++ b/packages/dev/serializers/test/integration/glTFSerializer.test.ts @@ -63,14 +63,14 @@ describe("Babylon glTF Serializer", () => { it("should solve for metallic", async () => { const assertionData = await page.evaluate(() => { const solveZero = BABYLON.GLTF2.Exporter._SolveMetallic(1.0, 0.0, 1.0); - const solveAproxOne = BABYLON.GLTF2.Exporter._SolveMetallic(0.0, 1.0, 1.0); + const solveApproxOne = BABYLON.GLTF2.Exporter._SolveMetallic(0.0, 1.0, 1.0); return { solveZero, - solveAproxOne, + solveApproxOne: solveApproxOne, }; }); expect(assertionData.solveZero).toBe(0.0); - expect(assertionData.solveAproxOne).toBeCloseTo(1.0, 1e-6); + expect(assertionData.solveApproxOne).toBeCloseTo(1.0, 1e-6); }); it("should serialize empty Babylon window.scene to glTF with only asset property", async () => { const assertionData = await page.evaluate(() => { From 698a56a397b995a96de46daef8a9de3473402c72 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 25 Nov 2024 18:12:42 -0500 Subject: [PATCH 105/133] Update packages/dev/core/src/Buffers/buffer.ts Co-authored-by: Gary Hsu --- packages/dev/core/src/Buffers/buffer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/core/src/Buffers/buffer.ts b/packages/dev/core/src/Buffers/buffer.ts index 95e3f901b06..8c6e99ef4ed 100644 --- a/packages/dev/core/src/Buffers/buffer.ts +++ b/packages/dev/core/src/Buffers/buffer.ts @@ -900,7 +900,7 @@ export class VertexBuffer { * @param count the number of values to enumerate * @param normalized whether the data is normalized * @param callback the callback function called for each value - * @deprecated Use `enumerateFloatValues` from `bufferUtils` instead + * @deprecated Use `EnumerateFloatValues` from `bufferUtils` instead */ public static ForEach( data: DataArray, From d95787f4cc92b4b2621b9010ca0d73f8697b85ba Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 25 Nov 2024 18:12:56 -0500 Subject: [PATCH 106/133] Update packages/dev/core/src/Buffers/buffer.ts Co-authored-by: Gary Hsu --- packages/dev/core/src/Buffers/buffer.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/core/src/Buffers/buffer.ts b/packages/dev/core/src/Buffers/buffer.ts index 8c6e99ef4ed..1824f997d0f 100644 --- a/packages/dev/core/src/Buffers/buffer.ts +++ b/packages/dev/core/src/Buffers/buffer.ts @@ -930,7 +930,7 @@ export class VertexBuffer { * @param totalVertices number of vertices in the buffer to take into account * @param forceCopy defines a boolean indicating that the returned array must be cloned upon returning it * @returns a float array containing vertex data - * @deprecated Use `getFloatData` from `bufferUtils` instead + * @deprecated Use `GetFloatData` from `bufferUtils` instead */ public static GetFloatData( data: DataArray, From cea0b7a50130cacda1e7e579758b2264b66973cd Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 25 Nov 2024 18:32:02 -0500 Subject: [PATCH 107/133] Remove linter disables --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 93dbe09205e..d8e837b337b 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1,6 +1,3 @@ -/* eslint-disable jsdoc/require-jsdoc */ -/* eslint-disable babylonjs/available */ - import type { IBufferView, IAccessor, From f73f79cbaeff3692286c673852aa215656c51e11 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 25 Nov 2024 18:34:34 -0500 Subject: [PATCH 108/133] Remove double comment --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index d8e837b337b..653b98270dc 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -238,9 +238,9 @@ export class GLTFExporter { public readonly _imageData: { [fileName: string]: { data: ArrayBuffer; mimeType: ImageMimeType } } = {}; private readonly _orderedImageData: Array<{ data: ArrayBuffer; mimeType: ImageMimeType }> = []; - // /** - // * Baked animation sample rate - // */ + /** + * Baked animation sample rate + */ private _animationSampleRate: number; private readonly _options: Required; From 3244640d01b37bf007ff2c7d317bf14ac82c8207 Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 25 Nov 2024 18:42:44 -0500 Subject: [PATCH 109/133] nits --- packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index 757f0ffd36e..ad57b7a0556 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -33,7 +33,6 @@ const black = Color3.Black(); * Interface for storing specular glossiness factors * @internal */ -// eslint-disable-next-line @typescript-eslint/naming-convention interface IPBRSpecularGlossiness { /** * Represents the linear diffuse factors of the material @@ -65,12 +64,12 @@ function GetFileExtensionFromMimeType(mimeType: ImageMimeType): string { } /** - * @internal * Computes the metallic factor. * @param diffuse diffused value * @param specular specular value * @param oneMinusSpecularStrength one minus the specular strength * @returns metallic value + * @internal */ export function _SolveMetallic(diffuse: number, specular: number, oneMinusSpecularStrength: number): number { if (specular < dielectricSpecular.r) { From 9a3e312ad352b6d954e098afc13c3f1e56001ead Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Mon, 25 Nov 2024 18:48:50 -0500 Subject: [PATCH 110/133] Rename IGLTFMorphTargets to IMorphTargetData --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 8 ++++---- .../src/glTF/2.0/glTFMorphTargetsUtilities.ts | 9 +++++---- 2 files changed, 9 insertions(+), 8 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 653b98270dc..c3ee355b96c 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -70,7 +70,7 @@ import type { Bone, Skeleton } from "core/Bones"; import { _GLTFAnimation } from "./glTFAnimation"; import type { MorphTarget } from "core/Morph"; import { BuildMorphTargetBuffers } from "./glTFMorphTargetsUtilities"; -import type { IGLTFMorphTarget } from "./glTFMorphTargetsUtilities"; +import type { IMorphTargetData } from "./glTFMorphTargetsUtilities"; import { LinesMesh } from "core/Meshes/linesMesh"; import { Color3, Color4 } from "core/Maths/math.color"; @@ -86,7 +86,7 @@ class ExporterState { private _remappedBufferView = new Map>(); - private _meshMorphTargetMap = new Map(); + private _meshMorphTargetMap = new Map(); private _vertexMapColorAlpha = new Map(); @@ -202,7 +202,7 @@ class ExporterState { this._meshMap.set(mesh, meshIndex); } - public bindMorphDataToMesh(mesh: Mesh, morphData: IGLTFMorphTarget) { + public bindMorphDataToMesh(mesh: Mesh, morphData: IMorphTargetData) { const morphTargets = this._meshMorphTargetMap.get(mesh) || []; this._meshMorphTargetMap.set(mesh, morphTargets); if (morphTargets.indexOf(morphData) === -1) { @@ -210,7 +210,7 @@ class ExporterState { } } - public getMorphTargetsFromMesh(mesh: Mesh): IGLTFMorphTarget[] | undefined { + public getMorphTargetsFromMesh(mesh: Mesh): IMorphTargetData[] | undefined { return this._meshMorphTargetMap.get(mesh); } } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts index 39733f64744..a620a98af27 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -10,9 +10,10 @@ import { Vector3 } from "core/Maths/math.vector"; import type { Vector4 } from "core/Maths/math.vector"; /** - * Temporary structure to store morph target information. + * Interface to store morph target information. + * @internal */ -export interface IGLTFMorphTarget { +export interface IMorphTargetData { attributes: { [name: string]: number }; influence: number; name: string; @@ -34,8 +35,8 @@ export function BuildMorphTargetBuffers( bufferViews: IBufferView[], accessors: IAccessor[], convertToRightHanded: boolean -): IGLTFMorphTarget { - const result: IGLTFMorphTarget = { +): IMorphTargetData { + const result: IMorphTargetData = { attributes: {}, influence: 0, name: "", From b31b7441f8d060068dc67ff93258683e7d57450c Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Tue, 26 Nov 2024 13:58:31 -0300 Subject: [PATCH 111/133] Simplified vertex attribute check --- .../serializers/src/glTF/2.0/glTFExporter.ts | 8 ++---- .../serializers/src/glTF/2.0/glTFUtilities.ts | 27 +++++++++++++------ 2 files changed, 21 insertions(+), 14 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index ff80952f717..58287989b40 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -60,7 +60,7 @@ import { ConvertToRightHandedNode, RotateNode180Y, FloatsNeed16BitInteger, - IsMorphTargetsVertexBuffer, + IsStandardVertexAttribute, } from "./glTFUtilities"; import { DataWriter } from "./dataWriter"; import { Camera } from "core/Cameras/camera"; @@ -1399,11 +1399,7 @@ export class GLTFExporter { private _exportVertexBuffer(vertexBuffer: VertexBuffer, babylonMaterial: Material, start: number, count: number, state: ExporterState, primitive: IMeshPrimitive): void { const kind = vertexBuffer.getKind(); - if (IsMorphTargetsVertexBuffer(kind)) { - return; - } - - if (kind.startsWith("world")) { + if (!IsStandardVertexAttribute(kind)) { return; } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 072d477f0bb..bf5871f5acf 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -20,12 +20,6 @@ const convertHandednessMatrix = Matrix.Compose(new Vector3(-1, 1, 1), Quaternion // 180 degrees rotation in Y. const rotation180Y = new Quaternion(0, 1, 0, 0); -// Regex for testing if vertexBuffer type comes from morph targets. -const positionRegex = /^position\d+$/; -const normalRegex = /^normal\d+$/; -const tangentRegex = /^tangent\d+$/; -const uvRegex = /^uv_\d+$/; - /** * Creates a buffer view based on the supplied arguments * @param bufferIndex index value of the specified buffer @@ -109,8 +103,25 @@ export function FloatsNeed16BitInteger(floatArray: FloatArray): boolean { return floatArray.some((value) => value >= 256); } -export function IsMorphTargetsVertexBuffer(type: string): boolean { - return positionRegex.test(type) || normalRegex.test(type) || tangentRegex.test(type) || uvRegex.test(type); +export function IsStandardVertexAttribute(type: string): boolean { + switch (type) { + case VertexBuffer.PositionKind: + case VertexBuffer.NormalKind: + case VertexBuffer.TangentKind: + case VertexBuffer.ColorKind: + case VertexBuffer.MatricesIndicesKind: + case VertexBuffer.MatricesIndicesExtraKind: + case VertexBuffer.MatricesWeightsKind: + case VertexBuffer.MatricesWeightsExtraKind: + case VertexBuffer.UVKind: + case VertexBuffer.UV2Kind: + case VertexBuffer.UV3Kind: + case VertexBuffer.UV4Kind: + case VertexBuffer.UV5Kind: + case VertexBuffer.UV6Kind: + return true; + } + return false; } export function GetAccessorType(kind: string, hasVertexColorAlpha: boolean): AccessorType { From 185c9391a0ce123b6b5e998734003d38f094e3cc Mon Sep 17 00:00:00 2001 From: "Alex C. Huber" <91097647+alexchuber@users.noreply.github.com> Date: Tue, 3 Dec 2024 12:25:58 -0500 Subject: [PATCH 112/133] Update packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts Co-authored-by: Popov72 --- packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index bf5871f5acf..3e29691695d 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -280,7 +280,7 @@ export function RotateNode180Y(node: INode) { } /** - * Colapses GLTF parent and node into a single node. This is useful for removing nodes that were added by the GLTF importer. + * Collapses GLTF parent and node into a single node. This is useful for removing nodes that were added by the GLTF importer. * @param node Target parent node. * @param parentNode Original GLTF node (Light or Camera). */ From d4f016e119b7884aec99df34ae4f9045197ef616 Mon Sep 17 00:00:00 2001 From: "Sergio R. Z. Masson" <97050577+SergioRZMasson@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:34:40 -0300 Subject: [PATCH 113/133] Update packages/dev/serializers/src/glTF/2.0/glTFExporter.ts Co-authored-by: Popov72 --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 993a142f509..ed98b597657 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -159,7 +159,7 @@ class ExporterState { public setRemappedBufferView(buffer: Buffer, vertexBuffer: VertexBuffer, bufferViewIndex: number) { this._remappedBufferView.set(buffer, new Map()); - this._remappedBufferView.get(buffer)?.set(vertexBuffer, bufferViewIndex); + this._remappedBufferView.get(buffer)!.set(vertexBuffer, bufferViewIndex); } public getRemappedBufferView(buffer: Buffer, vertexBuffer: VertexBuffer): number | undefined { From a42ad4446d11cdb96f274b6e9c26ad52b57256f9 Mon Sep 17 00:00:00 2001 From: "Sergio R. Z. Masson" <97050577+SergioRZMasson@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:58:25 -0300 Subject: [PATCH 114/133] Update packages/dev/serializers/src/glTF/2.0/glTFExporter.ts Co-authored-by: Popov72 --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index ed98b597657..bafbde38d73 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -765,7 +765,6 @@ export class GLTFExporter { } const skin: ISkin = { joints: [] }; - //this._skins.push(skin); this._skinMap.set(skeleton, skin); } } From 9620f372dfb3910d06e76e39326d2fdfe7ba59cf Mon Sep 17 00:00:00 2001 From: "Sergio R. Z. Masson" <97050577+SergioRZMasson@users.noreply.github.com> Date: Tue, 3 Dec 2024 14:58:58 -0300 Subject: [PATCH 115/133] Update packages/dev/serializers/src/glTF/2.0/glTFExporter.ts Co-authored-by: Popov72 --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 1 - 1 file changed, 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index bafbde38d73..6bd789aa449 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -877,7 +877,6 @@ export class GLTFExporter { this._listAvailableCameras(); this._listAvailableSkeletons(); - // await this._materialExporter.convertMaterialsToGLTFAsync(this._getMaterials(nodes)); const stateLH = new ExporterState(true, false); scene.nodes.push(...(await this._exportNodesAsync(rootNodesLH, stateLH))); const stateRH = new ExporterState(false, false); From 493e1931b426883e37c7f3a479f6989953c06e52 Mon Sep 17 00:00:00 2001 From: "Sergio R. Z. Masson" <97050577+SergioRZMasson@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:41:10 -0300 Subject: [PATCH 116/133] Update packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts Co-authored-by: Ryan Tremblay --- .../serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts index a620a98af27..2d3d3fad495 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -38,12 +38,9 @@ export function BuildMorphTargetBuffers( ): IMorphTargetData { const result: IMorphTargetData = { attributes: {}, - influence: 0, - name: "", + influence: morphTarget.influence, + name: morphTarget.name, }; - result.influence = morphTarget.influence; - result.name = morphTarget.name; - result.attributes = {}; const flipX = convertToRightHanded ? -1 : 1; const floatSize = 4; From 45ad7b6f464c2734ddf5d32025fae4ea72361743 Mon Sep 17 00:00:00 2001 From: "Sergio R. Z. Masson" <97050577+SergioRZMasson@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:41:20 -0300 Subject: [PATCH 117/133] Update packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts Co-authored-by: Ryan Tremblay --- .../dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts index 2d3d3fad495..e72f035a20c 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -14,7 +14,7 @@ import type { Vector4 } from "core/Maths/math.vector"; * @internal */ export interface IMorphTargetData { - attributes: { [name: string]: number }; + attributes: Record; influence: number; name: string; } From 14238d33323b5b258a7ec7b375475c3b4f7f684d Mon Sep 17 00:00:00 2001 From: "Sergio R. Z. Masson" <97050577+SergioRZMasson@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:43:19 -0300 Subject: [PATCH 118/133] Update packages/dev/serializers/src/glTF/2.0/dataWriter.ts Co-authored-by: Ryan Tremblay --- packages/dev/serializers/src/glTF/2.0/dataWriter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/dataWriter.ts b/packages/dev/serializers/src/glTF/2.0/dataWriter.ts index fa19241b894..5d442d7fd36 100644 --- a/packages/dev/serializers/src/glTF/2.0/dataWriter.ts +++ b/packages/dev/serializers/src/glTF/2.0/dataWriter.ts @@ -4,7 +4,7 @@ export class DataWriter { private _data: Uint8Array; private _dataView: DataView; - private _byteOffset: number; + private readonly _byteOffset: number; public constructor(byteLength: number) { this._data = new Uint8Array(byteLength); From e13dce7d1c3ef7f62c9c7c101bb91a288f772fe8 Mon Sep 17 00:00:00 2001 From: "Sergio R. Z. Masson" <97050577+SergioRZMasson@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:43:32 -0300 Subject: [PATCH 119/133] Update packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts Co-authored-by: Ryan Tremblay --- packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index ad57b7a0556..3d80f7b969a 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -143,7 +143,7 @@ export class GLTFMaterialExporter { } public getTextureInfo(babylonTexture: Nullable): Nullable { - return babylonTexture ? this._textureMap.get(babylonTexture) || null : null; + return babylonTexture ? this._textureMap.get(babylonTexture) ?? null : null; } public async exportStandardMaterialAsync(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, hasUVs: boolean): Promise { From fd0a0475777ec5a8c9fc1406ffb72b0dbd4607f4 Mon Sep 17 00:00:00 2001 From: "Sergio R. Z. Masson" <97050577+SergioRZMasson@users.noreply.github.com> Date: Wed, 4 Dec 2024 10:44:09 -0300 Subject: [PATCH 120/133] Update packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts Co-authored-by: Ryan Tremblay --- packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index 3d80f7b969a..367bc8b7565 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -130,7 +130,7 @@ function ConvertPixelArrayToFloat32(pixels: ArrayBufferView): Float32Array { * @internal */ export class GLTFMaterialExporter { - private _exporter: GLTFExporter; + private readonly _exporter: GLTFExporter; // Mapping to store textures private _textureMap = new Map(); From cc390ca259616993d21127b4f8667f5cb54c23d4 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 4 Dec 2024 10:49:58 -0300 Subject: [PATCH 121/133] Moved explicit property to constructor --- .../dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts | 8 ++------ 1 file changed, 2 insertions(+), 6 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index 367bc8b7565..2a1b6418ec7 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -130,20 +130,16 @@ function ConvertPixelArrayToFloat32(pixels: ArrayBufferView): Float32Array { * @internal */ export class GLTFMaterialExporter { - private readonly _exporter: GLTFExporter; - // Mapping to store textures private _textureMap = new Map(); // Mapping of internal textures to images to avoid exporting duplicate images private _internalTextureToImage: { [uniqueId: number]: { [mimeType: string]: Promise } } = {}; - constructor(exporter: GLTFExporter) { - this._exporter = exporter; - } + constructor(private readonly _exporter: GLTFExporter) {} public getTextureInfo(babylonTexture: Nullable): Nullable { - return babylonTexture ? this._textureMap.get(babylonTexture) ?? null : null; + return babylonTexture ? (this._textureMap.get(babylonTexture) ?? null) : null; } public async exportStandardMaterialAsync(babylonStandardMaterial: StandardMaterial, mimeType: ImageMimeType, hasUVs: boolean): Promise { From 317ec1827b3ad090f6aadc321cd8d689f2c7c16f Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 4 Dec 2024 10:50:50 -0300 Subject: [PATCH 122/133] Fixed wrong capitalization --- packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index 2a1b6418ec7..c37bd6e9b8b 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -80,8 +80,8 @@ export function _SolveMetallic(diffuse: number, specular: number, oneMinusSpecul const a = dielectricSpecular.r; const b = (diffuse * oneMinusSpecularStrength) / (1.0 - dielectricSpecular.r) + specular - 2.0 * dielectricSpecular.r; const c = dielectricSpecular.r - specular; - const D = b * b - 4.0 * a * c; - return Scalar.Clamp((-b + Math.sqrt(D)) / (2.0 * a), 0, 1); + const d = b * b - 4.0 * a * c; + return Scalar.Clamp((-b + Math.sqrt(d)) / (2.0 * a), 0, 1); } /** From 473e5ebba47afe2936c2711c26587638307876d7 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 4 Dec 2024 10:52:12 -0300 Subject: [PATCH 123/133] Simplified morph targets min max ranges --- .../dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts index e72f035a20c..5e03bd6a0ea 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -53,8 +53,8 @@ export function BuildMorphTargetBuffers( if (morphTarget.hasPositions) { const morphPositions = morphTarget.getPositions()!; const originalPositions = mesh.getVerticesData(VertexBuffer.PositionKind, undefined, undefined, true)!; - const min = new Array(3).fill(Infinity); - const max = new Array(3).fill(-Infinity); + const min = [Infinity, Infinity, Infinity]; + const max = [-Infinity, -Infinity, -Infinity]; vertexCount = originalPositions.length / 3; byteOffset = dataWriter.byteOffset; vertexStart = 0; From 818b9adfb1c5e1efe4cd63656e2e8d5edb1bce1a Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 4 Dec 2024 10:58:13 -0300 Subject: [PATCH 124/133] Added proper error handling when extracting morph target data --- .../src/glTF/2.0/glTFMorphTargetsUtilities.ts | 150 ++++++++++-------- 1 file changed, 83 insertions(+), 67 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts index 5e03bd6a0ea..276d9bb98ef 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -8,6 +8,7 @@ import type { Mesh } from "core/Meshes/mesh"; import { VertexBuffer } from "core/Buffers/buffer"; import { Vector3 } from "core/Maths/math.vector"; import type { Vector4 } from "core/Maths/math.vector"; +import { Tools } from "core/Misc/tools"; /** * Interface to store morph target information. @@ -52,84 +53,99 @@ export function BuildMorphTargetBuffers( if (morphTarget.hasPositions) { const morphPositions = morphTarget.getPositions()!; - const originalPositions = mesh.getVerticesData(VertexBuffer.PositionKind, undefined, undefined, true)!; - const min = [Infinity, Infinity, Infinity]; - const max = [-Infinity, -Infinity, -Infinity]; - vertexCount = originalPositions.length / 3; - byteOffset = dataWriter.byteOffset; - vertexStart = 0; - for (let i = vertexStart; i < vertexCount; ++i) { - const originalPosition = Vector3.FromArray(originalPositions, i * 3); - const morphPosition = Vector3.FromArray(morphPositions, i * 3); - morphPosition.subtractToRef(originalPosition, difference); - difference.x *= flipX; - - min[0] = Math.min(min[0], difference.x); - max[0] = Math.max(max[0], difference.x); - - min[1] = Math.min(min[1], difference.y); - max[1] = Math.max(max[1], difference.y); - - min[2] = Math.min(min[2], difference.z); - max[2] = Math.max(max[2], difference.z); - - dataWriter.writeFloat32(difference.x); - dataWriter.writeFloat32(difference.y); - dataWriter.writeFloat32(difference.z); + const originalPositions = mesh.getVerticesData(VertexBuffer.PositionKind, undefined, undefined, true); + + if (originalPositions) { + const min = [Infinity, Infinity, Infinity]; + const max = [-Infinity, -Infinity, -Infinity]; + vertexCount = originalPositions.length / 3; + byteOffset = dataWriter.byteOffset; + vertexStart = 0; + for (let i = vertexStart; i < vertexCount; ++i) { + const originalPosition = Vector3.FromArray(originalPositions, i * 3); + const morphPosition = Vector3.FromArray(morphPositions, i * 3); + morphPosition.subtractToRef(originalPosition, difference); + difference.x *= flipX; + + min[0] = Math.min(min[0], difference.x); + max[0] = Math.max(max[0], difference.x); + + min[1] = Math.min(min[1], difference.y); + max[1] = Math.max(max[1], difference.y); + + min[2] = Math.min(min[2], difference.z); + max[2] = Math.max(max[2], difference.z); + + dataWriter.writeFloat32(difference.x); + dataWriter.writeFloat32(difference.y); + dataWriter.writeFloat32(difference.z); + } + + bufferViews.push(CreateBufferView(0, byteOffset, morphPositions.length * floatSize, floatSize * 3)); + bufferViewIndex = bufferViews.length - 1; + accessors.push(CreateAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphPositions.length / 3, 0, { min, max })); + result.attributes["POSITION"] = accessors.length - 1; + } else { + Tools.Warn(`Morph target positions for mesh ${mesh.name} were not exported. Mesh does not have position vertex data`); } - - bufferViews.push(CreateBufferView(0, byteOffset, morphPositions.length * floatSize, floatSize * 3)); - bufferViewIndex = bufferViews.length - 1; - accessors.push(CreateAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphPositions.length / 3, 0, { min, max })); - result.attributes["POSITION"] = accessors.length - 1; } if (morphTarget.hasNormals) { const morphNormals = morphTarget.getNormals()!; - const originalNormals = mesh.getVerticesData(VertexBuffer.NormalKind, undefined, undefined, true)!; - vertexCount = originalNormals.length / 3; - byteOffset = dataWriter.byteOffset; - vertexStart = 0; - for (let i = vertexStart; i < vertexCount; ++i) { - const originalNormal = Vector3.FromArray(originalNormals, i * 3).normalize(); - const morphNormal = Vector3.FromArray(morphNormals, i * 3).normalize(); - morphNormal.subtractToRef(originalNormal, difference); - dataWriter.writeFloat32(difference.x * flipX); - dataWriter.writeFloat32(difference.y); - dataWriter.writeFloat32(difference.z); + const originalNormals = mesh.getVerticesData(VertexBuffer.NormalKind, undefined, undefined, true); + + if (originalNormals) { + vertexCount = originalNormals.length / 3; + byteOffset = dataWriter.byteOffset; + vertexStart = 0; + for (let i = vertexStart; i < vertexCount; ++i) { + const originalNormal = Vector3.FromArray(originalNormals, i * 3).normalize(); + const morphNormal = Vector3.FromArray(morphNormals, i * 3).normalize(); + morphNormal.subtractToRef(originalNormal, difference); + dataWriter.writeFloat32(difference.x * flipX); + dataWriter.writeFloat32(difference.y); + dataWriter.writeFloat32(difference.z); + } + + bufferViews.push(CreateBufferView(0, byteOffset, morphNormals.length * floatSize, floatSize * 3)); + bufferViewIndex = bufferViews.length - 1; + accessors.push(CreateAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphNormals.length / 3, 0)); + result.attributes["NORMAL"] = accessors.length - 1; + } else { + Tools.Warn(`Morph target normals for mesh ${mesh.name} were not exported. Mesh does not have normals vertex data`); } - - bufferViews.push(CreateBufferView(0, byteOffset, morphNormals.length * floatSize, floatSize * 3)); - bufferViewIndex = bufferViews.length - 1; - accessors.push(CreateAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, morphNormals.length / 3, 0)); - result.attributes["NORMAL"] = accessors.length - 1; } if (morphTarget.hasTangents) { const morphTangents = morphTarget.getTangents()!; - const originalTangents = mesh.getVerticesData(VertexBuffer.TangentKind, undefined, undefined, true)!; - vertexCount = originalTangents.length / 4; - vertexStart = 0; - byteOffset = dataWriter.byteOffset; - for (let i = vertexStart; i < vertexCount; ++i) { - // Only read the x, y, z components and ignore w - const originalTangent = Vector3.FromArray(originalTangents, i * 4); - NormalizeTangentFromRef(originalTangent); - - // Morph target tangents omit the w component so it won't be present in the data - const morphTangent = Vector3.FromArray(morphTangents, i * 3); - NormalizeTangentFromRef(morphTangent); - - morphTangent.subtractToRef(originalTangent, difference); - dataWriter.writeFloat32(difference.x * flipX); - dataWriter.writeFloat32(difference.y); - dataWriter.writeFloat32(difference.z); + const originalTangents = mesh.getVerticesData(VertexBuffer.TangentKind, undefined, undefined, true); + + if (originalTangents) { + vertexCount = originalTangents.length / 4; + vertexStart = 0; + byteOffset = dataWriter.byteOffset; + for (let i = vertexStart; i < vertexCount; ++i) { + // Only read the x, y, z components and ignore w + const originalTangent = Vector3.FromArray(originalTangents, i * 4); + NormalizeTangentFromRef(originalTangent); + + // Morph target tangents omit the w component so it won't be present in the data + const morphTangent = Vector3.FromArray(morphTangents, i * 3); + NormalizeTangentFromRef(morphTangent); + + morphTangent.subtractToRef(originalTangent, difference); + dataWriter.writeFloat32(difference.x * flipX); + dataWriter.writeFloat32(difference.y); + dataWriter.writeFloat32(difference.z); + } + + bufferViews.push(CreateBufferView(0, byteOffset, vertexCount * floatSize * 3, floatSize * 3)); + bufferViewIndex = bufferViews.length - 1; + accessors.push(CreateAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, vertexCount, 0)); + result.attributes["TANGENT"] = accessors.length - 1; + } else { + Tools.Warn(`Morph target tangents for mesh ${mesh.name} were not exported. Mesh does not have tangents vertex data`); } - - bufferViews.push(CreateBufferView(0, byteOffset, vertexCount * floatSize * 3, floatSize * 3)); - bufferViewIndex = bufferViews.length - 1; - accessors.push(CreateAccessor(bufferViewIndex, AccessorType.VEC3, AccessorComponentType.FLOAT, vertexCount, 0)); - result.attributes["TANGENT"] = accessors.length - 1; } return result; From bb073db3957bed2ccb47e3fc8214f4f723444d5c Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 4 Dec 2024 11:07:16 -0300 Subject: [PATCH 125/133] Changed light normalization to not modify original babylon object --- .../serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index 8e345737f7b..803c8f3c7d5 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -111,7 +111,7 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { // glTF lights have variable rotation and constant direction. Therefore, // compute a quaternion that aligns the Babylon light's direction with glTF's constant one. if (lightType !== KHRLightsPunctual_LightType.POINT) { - const direction = babylonNode.direction.normalize(); + const direction = babylonNode.direction.normalizeToNew(); if (convertToRightHanded) { ConvertToRightHandedPosition(direction); } From 763558fac7445773761e98d85b4686d629f8854e Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 4 Dec 2024 11:08:02 -0300 Subject: [PATCH 126/133] Reverted change made to _byteOffset --- packages/dev/serializers/src/glTF/2.0/dataWriter.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/dataWriter.ts b/packages/dev/serializers/src/glTF/2.0/dataWriter.ts index 5d442d7fd36..fa19241b894 100644 --- a/packages/dev/serializers/src/glTF/2.0/dataWriter.ts +++ b/packages/dev/serializers/src/glTF/2.0/dataWriter.ts @@ -4,7 +4,7 @@ export class DataWriter { private _data: Uint8Array; private _dataView: DataView; - private readonly _byteOffset: number; + private _byteOffset: number; public constructor(byteLength: number) { this._data = new Uint8Array(byteLength); From 9d18d31e9616eda5d9e1988e40e543e80b068256 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 4 Dec 2024 11:13:26 -0300 Subject: [PATCH 127/133] Fixed type mismatch in GPU instancing accessor creation --- .../src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts | 2 +- packages/dev/serializers/src/glTF/2.0/dataWriter.ts | 6 ++++++ 2 files changed, 7 insertions(+), 1 deletion(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts index 7a6ce39222c..31a23974752 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/EXT_mesh_gpu_instancing.ts @@ -152,7 +152,7 @@ export class EXT_mesh_gpu_instancing implements IGLTFExporterExtensionV2 { } case AccessorComponentType.BYTE: { for (let i = 0; i != buffer.length; i++) { - binaryWriter.writeUInt8(buffer[i] * 127); + binaryWriter.writeInt8(buffer[i] * 127); } break; } diff --git a/packages/dev/serializers/src/glTF/2.0/dataWriter.ts b/packages/dev/serializers/src/glTF/2.0/dataWriter.ts index fa19241b894..a37b9b81dc5 100644 --- a/packages/dev/serializers/src/glTF/2.0/dataWriter.ts +++ b/packages/dev/serializers/src/glTF/2.0/dataWriter.ts @@ -26,6 +26,12 @@ export class DataWriter { this._byteOffset++; } + public writeInt8(value: number): void { + this._checkGrowBuffer(1); + this._dataView.setInt8(this._byteOffset, value); + this._byteOffset++; + } + public writeInt16(entry: number): void { this._checkGrowBuffer(2); this._dataView.setInt16(this._byteOffset, entry, true); From 7d82ce4596c2e418f64d1b6a4de9b9c93bf4f17d Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 4 Dec 2024 11:17:13 -0300 Subject: [PATCH 128/133] Removed duplicated functions --- .../src/glTF/2.0/glTFMorphTargetsUtilities.ts | 16 +++------------- .../serializers/src/glTF/2.0/glTFUtilities.ts | 2 +- 2 files changed, 4 insertions(+), 14 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts index 276d9bb98ef..69257f1cb31 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMorphTargetsUtilities.ts @@ -3,11 +3,10 @@ import { AccessorComponentType, AccessorType } from "babylonjs-gltf2interface"; import type { MorphTarget } from "core/Morph/morphTarget"; import type { DataWriter } from "./dataWriter"; -import { CreateAccessor, CreateBufferView } from "./glTFUtilities"; +import { CreateAccessor, CreateBufferView, NormalizeTangent } from "./glTFUtilities"; import type { Mesh } from "core/Meshes/mesh"; import { VertexBuffer } from "core/Buffers/buffer"; import { Vector3 } from "core/Maths/math.vector"; -import type { Vector4 } from "core/Maths/math.vector"; import { Tools } from "core/Misc/tools"; /** @@ -20,15 +19,6 @@ export interface IMorphTargetData { name: string; } -function NormalizeTangentFromRef(tangent: Vector4 | Vector3) { - const length = Math.sqrt(tangent.x * tangent.x + tangent.y * tangent.y + tangent.z * tangent.z); - if (length > 0) { - tangent.x /= length; - tangent.y /= length; - tangent.z /= length; - } -} - export function BuildMorphTargetBuffers( morphTarget: MorphTarget, mesh: Mesh, @@ -127,11 +117,11 @@ export function BuildMorphTargetBuffers( for (let i = vertexStart; i < vertexCount; ++i) { // Only read the x, y, z components and ignore w const originalTangent = Vector3.FromArray(originalTangents, i * 4); - NormalizeTangentFromRef(originalTangent); + NormalizeTangent(originalTangent); // Morph target tangents omit the w component so it won't be present in the data const morphTangent = Vector3.FromArray(morphTangents, i * 3); - NormalizeTangentFromRef(morphTangent); + NormalizeTangent(morphTangent); morphTangent.subtractToRef(originalTangent, difference); dataWriter.writeFloat32(difference.x * flipX); diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 3e29691695d..a09701e065a 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -219,7 +219,7 @@ export function IsTriangleFillMode(fillMode: number): boolean { return false; } -export function NormalizeTangent(tangent: Vector4) { +export function NormalizeTangent(tangent: Vector4 | Vector3) { const length = Math.sqrt(tangent.x * tangent.x + tangent.y * tangent.y + tangent.z * tangent.z); if (length > 0) { tangent.x /= length; From ef466803cba971d12e689184f64c4874ea9a0e48 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 4 Dec 2024 11:18:25 -0300 Subject: [PATCH 129/133] Removed double assignment --- packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts | 3 --- 1 file changed, 3 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index a09701e065a..0d1df20a593 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -246,9 +246,6 @@ export function ConvertToRightHandedNode(value: INode) { translation = ConvertToRightHandedPosition(translation); rotation = ConvertToRightHandedRotation(rotation); - value.rotation = rotation.asArray(); - value.translation = translation.asArray(); - if (translation.equalsToFloats(0, 0, 0)) { delete value.translation; } else { From 758e20cf90bc8af189e1b2ac41bc97d9c35d8a76 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 4 Dec 2024 11:46:25 -0300 Subject: [PATCH 130/133] Changed default value compare to be done using equalsWithEpsilon --- .../dev/serializers/src/glTF/2.0/glTFUtilities.ts | 11 ++++++++--- 1 file changed, 8 insertions(+), 3 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts index 0d1df20a593..66eee24ccc6 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFUtilities.ts @@ -20,6 +20,11 @@ const convertHandednessMatrix = Matrix.Compose(new Vector3(-1, 1, 1), Quaternion // 180 degrees rotation in Y. const rotation180Y = new Quaternion(0, 1, 0, 0); +// Default values for comparison. +const epsilon = 1e-6; +const defaultTranslation = Vector3.Zero(); +const defaultScale = Vector3.One(); + /** * Creates a buffer view based on the supplied arguments * @param bufferIndex index value of the specified buffer @@ -246,7 +251,7 @@ export function ConvertToRightHandedNode(value: INode) { translation = ConvertToRightHandedPosition(translation); rotation = ConvertToRightHandedRotation(rotation); - if (translation.equalsToFloats(0, 0, 0)) { + if (translation.equalsWithEpsilon(defaultTranslation, epsilon)) { delete value.translation; } else { value.translation = translation.asArray(); @@ -295,7 +300,7 @@ export function CollapseParentNode(node: INode, parentNode: INode) { parentMatrix.multiplyToRef(matrix, matrix); matrix.decompose(parentScale, parentRotation, parentTranslation); - if (parentTranslation.equalsToFloats(0, 0, 0)) { + if (parentTranslation.equalsWithEpsilon(defaultTranslation, epsilon)) { delete parentNode.translation; } else { parentNode.translation = parentTranslation.asArray(); @@ -307,7 +312,7 @@ export function CollapseParentNode(node: INode, parentNode: INode) { parentNode.rotation = parentRotation.asArray(); } - if (parentScale.equalsToFloats(1, 1, 1)) { + if (parentScale.equalsWithEpsilon(defaultScale, epsilon)) { delete parentNode.scale; } else { parentNode.scale = parentScale.asArray(); From f333f54b11ad63c1f94582b4d69d01aa27352245 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 4 Dec 2024 11:52:49 -0300 Subject: [PATCH 131/133] Updating naming convention for public function --- .../Extensions/KHR_materials_transmission.ts | 2 +- .../src/glTF/2.0/glTFMaterialExporter.ts | 22 +++++++++---------- 2 files changed, 12 insertions(+), 12 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_transmission.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_transmission.ts index f35cb919e46..fbea34a86d1 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_transmission.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_materials_transmission.ts @@ -98,7 +98,7 @@ export class KHR_materials_transmission implements IGLTFExporterExtensionV2 { if (subSurface.refractionIntensityTexture) { if (subSurface.useGltfStyleTextures) { - const transmissionTexture = await this._exporter._materialExporter._exportTextureAsync(subSurface.refractionIntensityTexture, ImageMimeType.PNG); + const transmissionTexture = await this._exporter._materialExporter.exportTextureAsync(subSurface.refractionIntensityTexture, ImageMimeType.PNG); if (transmissionTexture) { volumeInfo.transmissionTexture = transmissionTexture; } diff --git a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts index c37bd6e9b8b..ff32e35db70 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFMaterialExporter.ts @@ -159,7 +159,7 @@ export class GLTFMaterialExporter { const diffuseTexture = babylonStandardMaterial.diffuseTexture; if (diffuseTexture) { promises.push( - this._exportTextureAsync(diffuseTexture, mimeType).then((textureInfo) => { + this.exportTextureAsync(diffuseTexture, mimeType).then((textureInfo) => { if (textureInfo) { pbrMetallicRoughness.baseColorTexture = textureInfo; } @@ -170,7 +170,7 @@ export class GLTFMaterialExporter { const bumpTexture = babylonStandardMaterial.bumpTexture; if (bumpTexture) { promises.push( - this._exportTextureAsync(bumpTexture, mimeType).then((textureInfo) => { + this.exportTextureAsync(bumpTexture, mimeType).then((textureInfo) => { if (textureInfo) { material.normalTexture = textureInfo; if (bumpTexture.level !== 1) { @@ -186,7 +186,7 @@ export class GLTFMaterialExporter { material.emissiveFactor = [1.0, 1.0, 1.0]; promises.push( - this._exportTextureAsync(emissiveTexture, mimeType).then((textureInfo) => { + this.exportTextureAsync(emissiveTexture, mimeType).then((textureInfo) => { if (textureInfo) { material.emissiveTexture = textureInfo; } @@ -197,7 +197,7 @@ export class GLTFMaterialExporter { const ambientTexture = babylonStandardMaterial.ambientTexture; if (ambientTexture) { promises.push( - this._exportTextureAsync(ambientTexture, mimeType).then((textureInfo) => { + this.exportTextureAsync(ambientTexture, mimeType).then((textureInfo) => { if (textureInfo) { const occlusionTexture: IMaterialOcclusionTextureInfo = { index: textureInfo.index, @@ -292,7 +292,7 @@ export class GLTFMaterialExporter { const promises: Array>> = []; for (const texture of textures) { - promises.push(this._exportTextureAsync(texture, mimeType)); + promises.push(this.exportTextureAsync(texture, mimeType)); } await Promise.all(promises); @@ -588,7 +588,7 @@ export class GLTFMaterialExporter { const albedoTexture = babylonPBRMaterial._albedoTexture; if (albedoTexture) { promises.push( - this._exportTextureAsync(babylonPBRMaterial._albedoTexture!, mimeType).then((glTFTexture) => { + this.exportTextureAsync(babylonPBRMaterial._albedoTexture!, mimeType).then((glTFTexture) => { if (glTFTexture) { glTFPbrMetallicRoughness.baseColorTexture = glTFTexture; } @@ -598,7 +598,7 @@ export class GLTFMaterialExporter { const metallicTexture = babylonPBRMaterial._metallicTexture; if (metallicTexture) { promises.push( - this._exportTextureAsync(metallicTexture, mimeType).then((glTFTexture) => { + this.exportTextureAsync(metallicTexture, mimeType).then((glTFTexture) => { if (glTFTexture) { glTFPbrMetallicRoughness.metallicRoughnessTexture = glTFTexture; } @@ -829,7 +829,7 @@ export class GLTFMaterialExporter { const bumpTexture = babylonPBRMaterial._bumpTexture; if (bumpTexture) { promises.push( - this._exportTextureAsync(bumpTexture, mimeType).then((glTFTexture) => { + this.exportTextureAsync(bumpTexture, mimeType).then((glTFTexture) => { if (glTFTexture) { glTFMaterial.normalTexture = glTFTexture; if (bumpTexture.level !== 1) { @@ -843,7 +843,7 @@ export class GLTFMaterialExporter { const ambientTexture = babylonPBRMaterial._ambientTexture; if (ambientTexture) { promises.push( - this._exportTextureAsync(ambientTexture, mimeType).then((glTFTexture) => { + this.exportTextureAsync(ambientTexture, mimeType).then((glTFTexture) => { if (glTFTexture) { const occlusionTexture: IMaterialOcclusionTextureInfo = { index: glTFTexture.index, @@ -864,7 +864,7 @@ export class GLTFMaterialExporter { const emissiveTexture = babylonPBRMaterial._emissiveTexture; if (emissiveTexture) { promises.push( - this._exportTextureAsync(emissiveTexture, mimeType).then((glTFTexture) => { + this.exportTextureAsync(emissiveTexture, mimeType).then((glTFTexture) => { if (glTFTexture) { glTFMaterial.emissiveTexture = glTFTexture; } @@ -894,7 +894,7 @@ export class GLTFMaterialExporter { return pixels; } - public async _exportTextureAsync(babylonTexture: BaseTexture, mimeType: ImageMimeType): Promise> { + public async exportTextureAsync(babylonTexture: BaseTexture, mimeType: ImageMimeType): Promise> { const extensionPromise = this._exporter._extensionsPreExportTextureAsync("exporter", babylonTexture as Texture, mimeType); if (!extensionPromise) { return this._exportTextureInfoAsync(babylonTexture, mimeType); From f7652261ca5f6c804e4f4403d87f4fcae95d60b1 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 4 Dec 2024 13:43:10 -0300 Subject: [PATCH 132/133] Fixed index buffer flipping logic --- packages/dev/serializers/src/glTF/2.0/glTFExporter.ts | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts index 6bd789aa449..b0b4db5490a 100644 --- a/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts +++ b/packages/dev/serializers/src/glTF/2.0/glTFExporter.ts @@ -1334,10 +1334,9 @@ export class GLTFExporter { // Flip if triangle winding order is not CCW as glTF is always CCW. const invertedMaterial = sideOrientation !== Material.CounterClockWiseSideOrientation; - const flipWhenLeftHanded = !state.wasAddedByNoopNode && !state.convertToRightHanded; const flipWhenInvertedMaterial = !state.wasAddedByNoopNode && invertedMaterial; - const flip = IsTriangleFillMode(fillMode) && (flipWhenLeftHanded || flipWhenInvertedMaterial); + const flip = IsTriangleFillMode(fillMode) && flipWhenInvertedMaterial; if (flip) { if (fillMode === Material.TriangleStripDrawMode || fillMode === Material.TriangleFanDrawMode) { From a1f989eedd826c9867bc0124faf674ee26a18f73 Mon Sep 17 00:00:00 2001 From: Sergio Ricardo Zerbetto Masson Date: Wed, 4 Dec 2024 15:26:44 -0300 Subject: [PATCH 133/133] Added code to avoid allocations during processing --- .../src/glTF/2.0/Extensions/KHR_lights_punctual.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts index 803c8f3c7d5..4af4988b8db 100644 --- a/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts +++ b/packages/dev/serializers/src/glTF/2.0/Extensions/KHR_lights_punctual.ts @@ -111,13 +111,13 @@ export class KHR_lights_punctual implements IGLTFExporterExtensionV2 { // glTF lights have variable rotation and constant direction. Therefore, // compute a quaternion that aligns the Babylon light's direction with glTF's constant one. if (lightType !== KHRLightsPunctual_LightType.POINT) { - const direction = babylonNode.direction.normalizeToNew(); + const direction = babylonNode.direction.normalizeToRef(TmpVectors.Vector3[0]); if (convertToRightHanded) { ConvertToRightHandedPosition(direction); } const angle = Math.acos(Vector3.Dot(LIGHTDIRECTION, direction)); const axis = Vector3.Cross(LIGHTDIRECTION, direction); - const lightRotationQuaternion = Quaternion.RotationAxis(axis, angle); + const lightRotationQuaternion = Quaternion.RotationAxisToRef(axis, angle, TmpVectors.Quaternion[0]); if (!Quaternion.IsIdentity(lightRotationQuaternion)) { node.rotation = lightRotationQuaternion.asArray(); }