Skip to content
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
98 changes: 48 additions & 50 deletions Sources/IO/Geometry/GLTFImporter/Reader.js
Original file line number Diff line number Diff line change
Expand Up @@ -186,6 +186,22 @@ async function createPropertyFromGLTFMaterial(model, material, actor) {
const emissiveFactor = material.emissiveFactor;

const property = actor.getProperty();
const texturePromises = {
diffuse: null,
rm: null,
ao: null,
emissive: null,
normal: null,
};

const loadTextureRef = async (texRef) => {
if (!texRef?.texture) return null;
const tex = texRef.texture;
const sampler = tex.sampler;
const img = await loadImage(tex.source);
return createVTKTextureFromGLTFTexture(img, sampler, texRef.extensions);
};

const pbr = material.pbrMetallicRoughness;

if (pbr != null) {
Expand Down Expand Up @@ -220,7 +236,7 @@ async function createPropertyFromGLTFMaterial(model, material, actor) {
property.setEmission(emissiveFactor);

if (pbr.baseColorTexture) {
const extensions = pbr.baseColorTexture.extensions;
// const extensions = pbr.baseColorTexture.extensions;
const tex = pbr.baseColorTexture.texture;

if (tex.extensions != null) {
Expand All @@ -235,57 +251,23 @@ async function createPropertyFromGLTFMaterial(model, material, actor) {
});
}

const sampler = tex.sampler;
const image = await loadImage(tex.source);
const diffuseTex = createVTKTextureFromGLTFTexture(
image,
sampler,
extensions
);

property.setDiffuseTexture(diffuseTex);
texturePromises.diffuse = loadTextureRef(pbr.baseColorTexture);
}

// Handle metallic-roughness texture (metallicRoughnessTexture)
if (pbr.metallicRoughnessTexture) {
const extensions = pbr.metallicRoughnessTexture.extensions;
const tex = pbr.metallicRoughnessTexture.texture;
const sampler = tex.sampler;
const rmImage = await loadImage(tex.source);
const rmTex = createVTKTextureFromGLTFTexture(
rmImage,
sampler,
extensions
);
property.setRMTexture(rmTex);
texturePromises.rm = loadTextureRef(pbr.metallicRoughnessTexture);
}

// Handle ambient occlusion texture (occlusionTexture)
if (material.occlusionTexture) {
const extensions = material.occlusionTexture.extensions;
const tex = material.occlusionTexture.texture;
const sampler = tex.sampler;
const aoImage = await loadImage(tex.source);
const aoTex = createVTKTextureFromGLTFTexture(
aoImage,
sampler,
extensions
);
property.setAmbientOcclusionTexture(aoTex);
texturePromises.ao = loadTextureRef(material.occlusionTexture);
// TODO: Handle occlusionTexture.strength
}

// Handle emissive texture (emissiveTexture)
if (material.emissiveTexture) {
const extensions = material.emissiveTexture.extensions;
const tex = material.emissiveTexture.texture;
const sampler = tex.sampler;
const emissiveImage = await loadImage(tex.source);
const emissiveTex = createVTKTextureFromGLTFTexture(
emissiveImage,
sampler,
extensions
);
property.setEmissionTexture(emissiveTex);
texturePromises.emissive = loadTextureRef(material.emissiveTexture);

// Handle mutiple Uvs
if (material.emissiveTexture.texCoord != null) {
Expand All @@ -296,21 +278,37 @@ async function createPropertyFromGLTFMaterial(model, material, actor) {

// Handle normal texture (normalTexture)
if (material.normalTexture) {
const extensions = material.normalTexture.extensions;
const tex = material.normalTexture.texture;
const sampler = tex.sampler;
const normalImage = await loadImage(tex.source);
const normalTex = createVTKTextureFromGLTFTexture(
normalImage,
sampler,
extensions
);
property.setNormalTexture(normalTex);
texturePromises.normal = loadTextureRef(material.normalTexture);

if (material.normalTexture.scale != null) {
property.setNormalStrength(material.normalTexture.scale);
}
}

const [diffuseTex, rmTex, aoTex, emissiveTex, normalTex] =
await Promise.all([
texturePromises.diffuse,
texturePromises.rm,
texturePromises.ao,
texturePromises.emissive,
texturePromises.normal,
]);

if (diffuseTex) {
property.setDiffuseTexture(diffuseTex);
}
if (rmTex) {
property.setRMTexture(rmTex);
}
if (aoTex) {
property.setAmbientOcclusionTexture(aoTex);
}
if (emissiveTex) {
property.setEmissionTexture(emissiveTex);
}
if (normalTex) {
property.setNormalTexture(normalTex);
}
}

// Material extensions
Expand Down
92 changes: 75 additions & 17 deletions Sources/IO/Geometry/GLTFImporter/Utils.js
Original file line number Diff line number Diff line change
Expand Up @@ -8,6 +8,8 @@ import {
} from 'vtk.js/Sources/IO/Geometry/GLTFImporter/Constants';

const { vtkWarningMacro, vtkErrorMacro } = macro;
const imageBufferViewCache = new WeakMap();
const imageUriCache = new Map();

/**
* Get GL enum from sampler parameter
Expand Down Expand Up @@ -90,29 +92,85 @@ export function resolveUrl(url, originalPath) {
* @returns
*/
export async function loadImage(image) {
if (image.bufferView) {
const blob = new Blob([image.bufferView.data], { type: image.mimeType });
const bitmap = await createImageBitmap(blob, {
colorSpaceConversion: 'none',
imageOrientation: 'flipY',
});
return bitmap;
if (!image) return null;

const cacheKey = image.bufferView || image.uri;
const cache = image.bufferView ? imageBufferViewCache : imageUriCache;

if (cacheKey) {
const cached = cache.get(cacheKey);

if (cached) {
// In flight promise
if (typeof cached?.then === 'function') {
return cached;
}

// WeakRef
const value = cached?.deref?.();
if (value) return value;

// Stale WeakRef
cache.delete(cacheKey);
}
}

if (image.uri) {
vtkWarningMacro('Falling back to image uri', image.uri);
return new Promise((resolve, reject) => {
const loadPromise = (async () => {
if (image.bufferView) {
const blob = new Blob([image.bufferView.data], {
type: image.mimeType,
});
return createImageBitmap(blob, {
premultiplyAlpha: 'none',
colorSpaceConversion: 'none',
imageOrientation: 'flipY',
});
}

if (image.uri) {
const img = new Image();
img.crossOrigin = 'Anonymous';
img.onload = () => {
resolve(img);
};
img.onerror = reject;
img.src = image.uri;
});

await new Promise((resolve, reject) => {
img.onload = resolve;
img.onerror = reject;
img.src = image.uri;
});

return createImageBitmap(img, {
premultiplyAlpha: 'none',
colorSpaceConversion: 'none',
imageOrientation: 'flipY',
});
}

return null;
})();

if (cacheKey) {
cache.set(cacheKey, loadPromise);
}

try {
const result = await loadPromise;

if (cacheKey && result) {
// eslint-disable-next-line no-undef
cache.set(cacheKey, new WeakRef(result));
}

return result;
} catch (err) {
if (cacheKey) {
cache.delete(cacheKey);
}
throw err;
}
}

return null;
export function clearImageCaches() {
imageUriCache.clear();
imageBufferViewCache.clear();
}

/**
Expand Down
5 changes: 5 additions & 0 deletions Sources/IO/Geometry/GLTFImporter/index.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -204,6 +204,11 @@ export interface vtkGLTFImporter extends vtkGLTFImporterBase {
* @param variantIndex The index of the variant to switch to.
*/
switchToVariant(variantIndex: number): void;

/**
* Clear the importer to initial state, clearing all internal data structures.
*/
clear(): void;
}

/**
Expand Down
25 changes: 25 additions & 0 deletions Sources/IO/Geometry/GLTFImporter/index.js
Original file line number Diff line number Diff line change
Expand Up @@ -13,6 +13,7 @@ import {
import parseGLB from 'vtk.js/Sources/IO/Geometry/GLTFImporter/Decoder';
import { createAnimationMixer } from 'vtk.js/Sources/IO/Geometry/GLTFImporter/Animations';
import { BINARY_HEADER_MAGIC } from 'vtk.js/Sources/IO/Geometry/GLTFImporter/Constants';
import { clearImageCaches } from 'vtk.js/Sources/IO/Geometry/GLTFImporter/Utils';

const { vtkDebugMacro, vtkErrorMacro } = macro;

Expand Down Expand Up @@ -221,6 +222,30 @@ function vtkGLTFImporter(publicAPI, model) {

await Promise.all(promises);
};

publicAPI.clear = () => {
model.actors?.clear?.();
model.cameras?.clear?.();
model.lights?.clear?.();
model.nodeLights?.clear?.();
model.variantMappings?.clear?.();
model.nodeTransforms?.clear?.();
model.nodeChildren?.clear?.();
model.skins?.clear?.();
model.morphTargets?.clear?.();
model.materialProperties?.clear?.();
model.pointerAnimations = [];
model.nodeAnimations = [];
model.animationClips = [];
model.skeletons = [];
model.animations = [];
model.scenes = [];
model.glTFTree = null;
model.parseData = null;
clearImageCaches();
};

publicAPI.delete = macro.chain(() => publicAPI.clear(), publicAPI.delete);
}

// ----------------------------------------------------------------------------
Expand Down
Loading