Refactor GLTF model import and project asset storage

This commit is contained in:
2026-03-31 17:30:26 +02:00
parent bcb6f5686e
commit 082f1ffc3a
2 changed files with 10 additions and 21 deletions

View File

@@ -1,9 +1,6 @@
import { Box3, BoxGeometry, Group, Mesh, MeshStandardMaterial, Object3D, type BufferGeometry, type Material } from "three"; import { Box3, Group, Mesh, Object3D } from "three";
import { GLTFLoader, type GLTF } from "three/examples/jsm/loaders/GLTFLoader.js"; import { GLTFLoader, type GLTF } from "three/examples/jsm/loaders/GLTFLoader.js";
import { createOpaqueId } from "../core/ids";
import type { Vec3 } from "../core/vector";
import { createModelInstance, type ModelInstance } from "./model-instances"; import { createModelInstance, type ModelInstance } from "./model-instances";
import { import {
createProjectAssetStorageKey, createProjectAssetStorageKey,
@@ -11,6 +8,7 @@ import {
type ModelAssetRecord, type ModelAssetRecord,
type ProjectAssetStorage type ProjectAssetStorage
} from "./project-assets"; } from "./project-assets";
import { createOpaqueId } from "../core/ids";
import type { ProjectAssetStorageRecord } from "./project-asset-storage"; import type { ProjectAssetStorageRecord } from "./project-asset-storage";
export interface LoadedModelAsset { export interface LoadedModelAsset {
@@ -26,14 +24,6 @@ export interface ImportedModelAssetResult {
loadedAsset: LoadedModelAsset; loadedAsset: LoadedModelAsset;
} }
function cloneVec3(vector: Vec3): Vec3 {
return {
x: vector.x,
y: vector.y,
z: vector.z
};
}
function getErrorDetail(error: unknown): string { function getErrorDetail(error: unknown): string {
if (error instanceof Error && error.message.trim().length > 0) { if (error instanceof Error && error.message.trim().length > 0) {
return error.message.trim(); return error.message.trim();
@@ -98,7 +88,7 @@ function collectMaterialNames(scene: Group): string[] {
const names = new Set<string>(); const names = new Set<string>();
scene.traverse((object) => { scene.traverse((object) => {
const maybeMesh = object as Mesh | null; const maybeMesh = object as Mesh & { isMesh?: boolean };
if (maybeMesh.isMesh !== true) { if (maybeMesh.isMesh !== true) {
return; return;
@@ -131,7 +121,7 @@ function collectTextureNames(parserJson: { textures?: Array<{ name?: string }> }
function collectAnimationNames(gltf: GLTF): string[] { function collectAnimationNames(gltf: GLTF): string[] {
return gltf.animations return gltf.animations
.map((animation, index) => animation.name.trim().length > 0 ? animation.name : `Animation ${index + 1}`) .map((animation, index) => (animation.name.trim().length > 0 ? animation.name : `Animation ${index + 1}`))
.sort((left, right) => left.localeCompare(right)); .sort((left, right) => left.localeCompare(right));
} }
@@ -148,7 +138,6 @@ function countNodes(scene: Group): number {
export function extractModelAssetMetadata(gltf: GLTF, format: "glb" | "gltf"): ModelAssetMetadata { export function extractModelAssetMetadata(gltf: GLTF, format: "glb" | "gltf"): ModelAssetMetadata {
gltf.scene.updateMatrixWorld(true); gltf.scene.updateMatrixWorld(true);
const boundingBox = createBoundingBoxFromObject(gltf.scene); const boundingBox = createBoundingBoxFromObject(gltf.scene);
const meshCount = gltf.scene.children.length === 0 ? 0 : gltf.scene.getObjectByProperty("isMesh", true) !== undefined ? 1 : 0;
let actualMeshCount = 0; let actualMeshCount = 0;
@@ -185,7 +174,7 @@ export function extractModelAssetMetadata(gltf: GLTF, format: "glb" | "gltf"): M
format, format,
sceneName: gltf.scene.name.trim().length > 0 ? gltf.scene.name : null, sceneName: gltf.scene.name.trim().length > 0 ? gltf.scene.name : null,
nodeCount: countNodes(gltf.scene), nodeCount: countNodes(gltf.scene),
meshCount: actualMeshCount ?? meshCount, meshCount: actualMeshCount,
materialNames: [...new Set(materialNames)].sort((left, right) => left.localeCompare(right)), materialNames: [...new Set(materialNames)].sort((left, right) => left.localeCompare(right)),
textureNames, textureNames,
animationNames, animationNames,
@@ -286,4 +275,3 @@ export async function loadModelAssetFromStorage(
return createLoadedModelAsset(asset, cloneTemplateScene(gltf.scene)); return createLoadedModelAsset(asset, cloneTemplateScene(gltf.scene));
} }

View File

@@ -70,8 +70,8 @@ function openIndexedDb(): Promise<IDBDatabase> {
class IndexedDbProjectAssetStorage implements ProjectAssetStorage { class IndexedDbProjectAssetStorage implements ProjectAssetStorage {
private readonly databasePromise: Promise<IDBDatabase>; private readonly databasePromise: Promise<IDBDatabase>;
constructor() { constructor(databasePromise: Promise<IDBDatabase>) {
this.databasePromise = openIndexedDb(); this.databasePromise = databasePromise;
} }
private async withStore<T>(mode: IDBTransactionMode, callback: (store: IDBObjectStore) => IDBRequest<T>): Promise<T> { private async withStore<T>(mode: IDBTransactionMode, callback: (store: IDBObjectStore) => IDBRequest<T>): Promise<T> {
@@ -172,8 +172,10 @@ export async function getBrowserProjectAssetStorageAccess(): Promise<ProjectAsse
} }
try { try {
const databasePromise = openIndexedDb();
await databasePromise;
return { return {
storage: new IndexedDbProjectAssetStorage(), storage: new IndexedDbProjectAssetStorage(databasePromise),
diagnostic: null diagnostic: null
}; };
} catch (error) { } catch (error) {
@@ -183,4 +185,3 @@ export async function getBrowserProjectAssetStorageAccess(): Promise<ProjectAsse
}; };
} }
} }