Skip to content

Commit

Permalink
Support TGA and flip bitmaps (#26)
Browse files Browse the repository at this point in the history
  • Loading branch information
MarcusLongmuir authored Dec 7, 2023
1 parent bd30712 commit c97262c
Show file tree
Hide file tree
Showing 6 changed files with 295 additions and 173 deletions.
14 changes: 10 additions & 4 deletions tools/gltf-avatar-exporter/src/scene/ImportView.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { Group, LoadingManager, SkinnedMesh, Vector3 } from "three";
import { TGALoader } from "three/examples/jsm/loaders/TGALoader.js";

import { LoggerView } from "../logger/LoggerView";

Expand Down Expand Up @@ -102,12 +103,16 @@ export class ImportView extends QuadrantScene {
}

private async loadModelFromBuffer(buffer: ArrayBuffer, name: string): Promise<void> {
const { group } = await this.modelLoader.loadFromBuffer(buffer, "");
const importViewLoadingManager = new LoadingManager();
importViewLoadingManager.addHandler(/\.tga$/i, new TGALoader(importViewLoadingManager));
const { group } = await this.modelLoader.loadFromBuffer(buffer, "", importViewLoadingManager);

if (group) {
group.traverse((child) => {
if (child.type === "SkinnedMesh") {
(child as SkinnedMesh).receiveShadow = true;
(child as SkinnedMesh).castShadow = true;
const asSkinnedMesh = child as SkinnedMesh;
if (asSkinnedMesh.isSkinnedMesh) {
asSkinnedMesh.receiveShadow = true;
asSkinnedMesh.castShadow = true;
}
});

Expand All @@ -126,6 +131,7 @@ export class ImportView extends QuadrantScene {
}

const loadingManager = new LoadingManager();
loadingManager.addHandler(/\.tga$/i, new TGALoader(loadingManager));
let hasAssetsToLoad = false;
loadingManager.onStart = () => {
hasAssetsToLoad = true;
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,107 @@
import * as THREE from "three";
import { Group, Texture } from "three";

import { forEachMapKey } from "./materials/forEachMapKey";
import { Step } from "./types";

function fixTexture(texture: Texture): Texture | null {
if (
texture.image &&
typeof texture.image === "object" &&
texture.flipY &&
texture.image.data &&
texture.image.data instanceof Uint8Array
) {
const image = texture.image;
const data = image.data;
const newData = [];
// Copy the data into a new array, but with rows reversed
for (let i = 0; i < data.length; i += image.width * 4) {
const row = data.slice(i, i + image.width * 4);
newData.unshift(...row);
}

const clamped = new Uint8ClampedArray(newData);
const imageSrc = new ImageData(clamped, image.width, image.height);
const canvas = document.createElement("canvas");
canvas.width = image.width;
canvas.height = image.height;
const context = canvas.getContext("2d");
if (!context) {
return null;
}
context.putImageData(imageSrc, 0, 0);
const imageData = context.getImageData(0, 0, image.width, image.height);
const sanitizedTexture = new THREE.Texture();
sanitizedTexture.image = imageData;
sanitizedTexture.needsUpdate = true;
return sanitizedTexture;
}
return null;
}

function fixMaterial(material: THREE.Material): Array<string> {
const flippedTextures: Array<string> = [];
forEachMapKey(material, (key, texture) => {
const fixedTexture = fixTexture(texture);
if (fixedTexture) {
flippedTextures.push(`${texture.name} (${key})`);
(material as any)[key] = fixedTexture;
}
});
return flippedTextures;
}

export const fixFlippedBitmapTexturesCorrectionStep: Step = {
name: "fixFlippedBitmapTextures",
action: (group: Group) => {
const flippedTextureNames: Array<string> = [];

group.traverse((child) => {
const asSkinnedMesh = child as THREE.SkinnedMesh;
if (asSkinnedMesh.isSkinnedMesh) {
const originalMaterial = asSkinnedMesh.material;
if (Array.isArray(originalMaterial)) {
originalMaterial.forEach((innerMaterial) => {
const flippedTextures = fixMaterial(innerMaterial);
for (const flippedTextureName of flippedTextures) {
flippedTextureNames.push(
`Flipped texture for ${asSkinnedMesh.name} - ${innerMaterial.name} - ${flippedTextureName}`,
);
}
});
} else {
const flippedTextures = fixMaterial(originalMaterial);
for (const flippedTextureName of flippedTextures) {
flippedTextureNames.push(
`Flipped texture for ${asSkinnedMesh.name} - ${originalMaterial.name} - ${flippedTextureName}`,
);
}
}
}
});

if (flippedTextureNames.length === 0) {
return {
didApply: false,
topLevelMessage: {
level: "info",
message: "No materials with missing textures found.",
},
};
}

return {
didApply: true,
topLevelMessage: {
level: "info",
message:
"Detected at least one material with a missing texture. Replaced with placeholder(s).",
},
logs: flippedTextureNames.map((name) => ({
level: "error",
message: name,
})),
};
},
};
Original file line number Diff line number Diff line change
@@ -0,0 +1,44 @@
import { Material, Texture } from "three";
import * as THREE from "three";

import {
lambertMaterialTextureKeys,
phongMaterialTextureKeys,
physicalMaterialTextureKeys,
standardMaterialTextureKeys,
} from "./mapKeys";

export function forEachMapKey(
material: Material,
callback: (key: string, texture: Texture) => void,
) {
if (material instanceof THREE.MeshLambertMaterial) {
for (const key of lambertMaterialTextureKeys) {
const texture = material[key];
if (texture) {
callback(key, texture);
}
}
} else if (material instanceof THREE.MeshStandardMaterial) {
for (const key of standardMaterialTextureKeys) {
const texture = material[key];
if (texture) {
callback(key, texture);
}
}
} else if (material instanceof THREE.MeshPhysicalMaterial) {
for (const key of physicalMaterialTextureKeys) {
const texture = material[key];
if (texture) {
callback(key, texture);
}
}
} else if (material instanceof THREE.MeshPhongMaterial) {
for (const key of phongMaterialTextureKeys) {
const texture = material[key];
if (texture) {
callback(key, texture);
}
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,123 @@
export const lambertMaterialTextureKeys: Array<
| "bumpMap"
| "displacementMap"
| "emissiveMap"
| "map"
| "lightMap"
| "normalMap"
| "aoMap"
| "specularMap"
| "alphaMap"
| "envMap"
> = [
"bumpMap",
"displacementMap",
"emissiveMap",
"map",
"lightMap",
"normalMap",
"aoMap",
"specularMap",
"alphaMap",
"envMap",
];

export const standardMaterialTextureKeys: Array<
| "map"
| "lightMap"
| "aoMap"
| "emissiveMap"
| "bumpMap"
| "normalMap"
| "displacementMap"
| "roughnessMap"
| "metalnessMap"
| "alphaMap"
| "envMap"
> = [
"map",
"lightMap",
"aoMap",
"emissiveMap",
"bumpMap",
"normalMap",
"displacementMap",
"roughnessMap",
"metalnessMap",
"alphaMap",
"envMap",
];

export const physicalMaterialTextureKeys: Array<
| "clearcoatMap"
| "clearcoatRoughnessMap"
| "clearcoatNormalMap"
| "sheenColorMap"
| "sheenRoughnessMap"
| "transmissionMap"
| "thicknessMap"
| "specularIntensityMap"
| "specularColorMap"
| "iridescenceMap"
| "iridescenceThicknessMap"
| "anisotropyMap"
| "map"
| "lightMap"
| "aoMap"
| "emissiveMap"
| "bumpMap"
| "normalMap"
| "displacementMap"
| "roughnessMap"
| "metalnessMap"
| "alphaMap"
| "envMap"
> = [
"clearcoatMap",
"clearcoatRoughnessMap",
"clearcoatNormalMap",
"sheenColorMap",
"sheenRoughnessMap",
"transmissionMap",
"thicknessMap",
"specularIntensityMap",
"specularColorMap",
"iridescenceMap",
"iridescenceThicknessMap",
"anisotropyMap",
"map",
"lightMap",
"aoMap",
"emissiveMap",
"bumpMap",
"normalMap",
"displacementMap",
"roughnessMap",
"metalnessMap",
"alphaMap",
"envMap",
];

export const phongMaterialTextureKeys: Array<
| "map"
| "lightMap"
| "aoMap"
| "emissiveMap"
| "bumpMap"
| "normalMap"
| "displacementMap"
| "specularMap"
| "alphaMap"
| "envMap"
> = [
"map",
"lightMap",
"aoMap",
"emissiveMap",
"bumpMap",
"normalMap",
"displacementMap",
"specularMap",
"alphaMap",
"envMap",
];
Loading

0 comments on commit c97262c

Please sign in to comment.