Merge pull request #166 from ryanhlewis/main

GLTF Multiple Materials Fix
This commit is contained in:
Lucas Dower 2025-04-28 21:19:08 +01:00 committed by GitHub
commit 0813e8ddae
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194

View File

@ -1,204 +1,170 @@
import { parse } from '@loaders.gl/core'; import { parse } from '@loaders.gl/core';
import { GLTFLoader } from '@loaders.gl/gltf'; import { GLTFLoader } from '@loaders.gl/gltf';
import { RGBAColours, RGBAUtil } from '../colour'; import { RGBAColours, RGBAUtil } from '../colour';
import { LOC } from '../localiser'; import { LOC } from '../localiser';
import { MaterialMap, MaterialType, Mesh, Tri } from '../mesh'; import { MaterialMap, MaterialType, Mesh, Tri } from '../mesh';
import { StatusHandler } from '../status'; import { StatusHandler } from '../status';
import { UV } from '../util'; import { UV } from '../util';
import { AppError } from '../util/error_util'; import { AppError } from '../util/error_util';
import { Vector3 } from '../vector'; import { Vector3 } from '../vector';
import { IImporter } from './base_importer'; import { IImporter } from './base_importer';
export class GltfLoader extends IImporter { export class GltfLoader extends IImporter {
public override import(file: File): Promise<Mesh> { public override import(file: File): Promise<Mesh> {
StatusHandler.warning(LOC('import.gltf_experimental')); StatusHandler.warning(LOC('import.gltf_experimental'));
return new Promise<Mesh>((resolve, reject) => { return new Promise<Mesh>((resolve, reject) => {
parse(file, GLTFLoader, { loadImages: true }) parse(file, GLTFLoader, { loadImages: true })
.then((gltf: any) => { .then((gltf: any) => {
resolve(this._handleGLTF(gltf)); resolve(this._handleGLTF(gltf));
}) })
.catch((err: any) => { .catch((err: any) => {
reject(err); reject(err);
}); });
}); });
} }
private _handleGLTF(gltf: any): Mesh { private _handleGLTF(gltf: any): Mesh {
const meshVertices: Vector3[] = []; const meshVertices: Vector3[] = [];
const meshNormals: Vector3[] = []; const meshNormals: Vector3[] = [];
const meshTexcoords: UV[] = []; const meshTexcoords: UV[] = [];
const meshTriangles: Tri[] = []; const meshTriangles: Tri[] = [];
const meshMaterials: MaterialMap = new Map(); const meshMaterials: MaterialMap = new Map();
meshMaterials.set('NONE', {
type: MaterialType.solid, meshMaterials.set('NONE', {
colour: RGBAUtil.copy(RGBAColours.WHITE), type: MaterialType.solid,
needsAttention: false, colour: RGBAUtil.copy(RGBAColours.WHITE),
canBeTextured: false, needsAttention: false,
}); canBeTextured: false,
let maxIndex = 0; });
Object.values(gltf.meshes).forEach((mesh: any) => { let maxIndex = 0;
Object.values(mesh.primitives).forEach((primitive: any) => { let materialIndex = 0; // New variable to create unique material identifiers
const attributes = primitive.attributes;
if (attributes.POSITION !== undefined) { Object.values(gltf.meshes).forEach((mesh: any) => {
const positions = attributes.POSITION.value as Float32Array; Object.values(mesh.primitives).forEach((primitive: any) => {
for (let i = 0; i < positions.length; i += 3) { const attributes = primitive.attributes;
meshVertices.push(new Vector3(
positions[i + 0], // Handling vertices
positions[i + 1], if (attributes.POSITION !== undefined) {
positions[i + 2], const positions = attributes.POSITION.value as Float32Array;
)); for (let i = 0; i < positions.length; i += 3) {
} meshVertices.push(new Vector3(
} positions[i + 0],
if (attributes.NORMAL !== undefined) { positions[i + 1],
const normals = attributes.NORMAL.value as Float32Array; positions[i + 2],
for (let i = 0; i < normals.length; i += 3) { ));
meshNormals.push(new Vector3( }
normals[i + 0], }
normals[i + 1],
normals[i + 2], // Handling normals
)); if (attributes.NORMAL !== undefined) {
} const normals = attributes.NORMAL.value as Float32Array;
} for (let i = 0; i < normals.length; i += 3) {
if (attributes.TEXCOORD_0 !== undefined) { meshNormals.push(new Vector3(
const texcoords = attributes.TEXCOORD_0.value as Float32Array; normals[i + 0],
for (let i = 0; i < texcoords.length; i += 2) { normals[i + 1],
meshTexcoords.push(new UV( normals[i + 2],
texcoords[i + 0], ));
1.0 - texcoords[i + 1], }
)); }
}
} // Handling texture coordinates
// Material if (attributes.TEXCOORD_0 !== undefined) {
let materialNameToUse = 'NONE'; const texcoords = attributes.TEXCOORD_0.value as Float32Array;
{ for (let i = 0; i < texcoords.length; i += 2) {
if (primitive.material) { meshTexcoords.push(new UV(
const materialName = primitive.material.name; texcoords[i + 0],
1.0 - texcoords[i + 1],
let materialMade = false; ));
}
const pbr = primitive.material.pbrMetallicRoughness; }
if (pbr !== undefined) {
const diffuseTexture = pbr.baseColorTexture; // Material
if (diffuseTexture !== undefined) { let materialBaseName = 'NONE';
const imageData: Uint8Array = diffuseTexture.texture.source.bufferView.data; if (primitive.material) {
const mimeType: string = diffuseTexture.texture.source.mimeType; materialBaseName = primitive.material.name || 'Material';
}
try {
if (mimeType !== 'image/png' && mimeType !== 'image/jpeg') { const materialNameToUse = materialBaseName + '_' + materialIndex; // Unique material identifier
StatusHandler.warning(LOC('import.unsupported_image_type', { file_name: diffuseTexture.texture.source.id, file_type: mimeType })); materialIndex++; // Increment material index
throw new Error('Unsupported image type');
} // Handling materials
if (primitive.material) {
const base64 = btoa( const pbr = primitive.material.pbrMetallicRoughness;
imageData.reduce((data, byte) => data + String.fromCharCode(byte), ''), if (pbr !== undefined) {
); const diffuseTexture = pbr.baseColorTexture;
if (diffuseTexture !== undefined) {
meshMaterials.set(materialName, { const imageData: Uint8Array = diffuseTexture.texture.source.bufferView.data;
type: MaterialType.textured, const mimeType: string = diffuseTexture.texture.source.mimeType;
diffuse: {
filetype: mimeType === 'image/jpeg' ? 'jpg' : 'png', try {
raw: (mimeType === 'image/jpeg' ? 'data:image/jpeg;base64,' : 'data:image/png;base64,') + base64, if (mimeType !== 'image/png' && mimeType !== 'image/jpeg') {
}, StatusHandler.warning(LOC('import.unsupported_image_type', { file_name: diffuseTexture.texture.source.id, file_type: mimeType }));
extension: 'clamp', throw new Error('Unsupported image type');
interpolation: 'linear', }
needsAttention: false,
transparency: { type: 'None' }, const base64 = btoa(
}); imageData.reduce((data, byte) => data + String.fromCharCode(byte), ''),
} catch { );
meshMaterials.set(materialName, {
type: MaterialType.solid, meshMaterials.set(materialNameToUse, {
colour: RGBAUtil.copy(RGBAColours.WHITE), type: MaterialType.textured,
needsAttention: false, diffuse: {
canBeTextured: true, filetype: mimeType === 'image/jpeg' ? 'jpg' : 'png',
}); raw: (mimeType === 'image/jpeg' ? 'data:image/jpeg;base64,' : 'data:image/png;base64,') + base64,
} },
extension: 'clamp',
interpolation: 'linear',
/* needsAttention: false,
transparency: { type: 'None' },
*/ });
} catch {
materialNameToUse = materialName; meshMaterials.set(materialNameToUse, {
materialMade = true; type: MaterialType.solid,
} else { colour: RGBAUtil.copy(RGBAColours.WHITE),
const diffuseColour: (number[] | undefined) = pbr.baseColorFactor; needsAttention: false,
canBeTextured: true,
if (diffuseColour !== undefined) { });
meshMaterials.set(materialName, { }
type: MaterialType.solid, }
colour: { }
r: diffuseColour[0], }
g: diffuseColour[1],
b: diffuseColour[2], // Indices
a: diffuseColour[3], const indices = primitive.indices.value as Uint16Array;
}, for (let i = 0; i < indices.length / 3; ++i) {
needsAttention: false, meshTriangles.push({
canBeTextured: false, material: materialNameToUse,
}); positionIndices: {
} x: maxIndex + indices[i * 3 + 0],
y: maxIndex + indices[i * 3 + 1],
materialNameToUse = materialName; z: maxIndex + indices[i * 3 + 2],
materialMade = true; },
} texcoordIndices: {
} x: maxIndex + indices[i * 3 + 0],
y: maxIndex + indices[i * 3 + 1],
const emissiveColour: (number[] | undefined) = primitive.material.pbr; z: maxIndex + indices[i * 3 + 2],
if (!materialMade && emissiveColour !== undefined) { },
meshMaterials.set(materialName, { });
type: MaterialType.solid, }
colour: {
r: emissiveColour[0], let localMax = 0;
g: emissiveColour[1], for (let i = 0; i < indices.length; ++i) {
b: emissiveColour[2], localMax = Math.max(localMax, indices[i]);
a: 1.0, }
},
needsAttention: false, maxIndex += localMax + 1;
canBeTextured: false, });
}); });
materialNameToUse = materialName; return new Mesh(
materialMade = true; meshVertices,
} meshNormals,
} meshTexcoords,
} meshTriangles,
// Indices meshMaterials,
{ );
const indices = primitive.indices.value as Uint16Array; }
for (let i = 0; i < indices.length / 3; ++i) { }
meshTriangles.push({
material: materialNameToUse,
positionIndices: {
x: maxIndex + indices[i * 3 + 0],
y: maxIndex + indices[i * 3 + 1],
z: maxIndex + indices[i * 3 + 2],
},
texcoordIndices: {
x: maxIndex + indices[i * 3 + 0],
y: maxIndex + indices[i * 3 + 1],
z: maxIndex + indices[i * 3 + 2],
},
});
}
let localMax = 0;
for (let i = 0; i < indices.length; ++i) {
localMax = Math.max(localMax, indices[i]);
}
maxIndex += localMax + 1;
}
});
});
return new Mesh(
meshVertices,
meshNormals,
meshTexcoords,
meshTriangles,
meshMaterials,
);
}
}