Files
webeditor3d/src/assets/model-instance-rendering.js

158 lines
6.0 KiB
JavaScript
Raw Normal View History

auto-git: [add] playwright.config.js [add] src/app/App.js [add] src/app/editor-store.js [add] src/app/use-editor-store.js [add] src/assets/audio-assets.js [add] src/assets/gltf-model-import.js [add] src/assets/image-assets.js [add] src/assets/model-instance-labels.js [add] src/assets/model-instance-rendering.js [add] src/assets/model-instances.js [add] src/assets/project-asset-storage.js [add] src/assets/project-assets.js [add] src/commands/brush-command-helpers.js [add] src/commands/command-history.js [add] src/commands/command.js [add] src/commands/commit-transform-session-command.js [add] src/commands/create-box-brush-command.js [add] src/commands/delete-box-brush-command.js [add] src/commands/delete-entity-command.js [add] src/commands/delete-interaction-link-command.js [add] src/commands/delete-model-instance-command.js [add] src/commands/import-audio-asset-command.js [add] src/commands/import-background-image-asset-command.js [add] src/commands/import-model-asset-command.js [add] src/commands/move-box-brush-command.js [add] src/commands/resize-box-brush-command.js [add] src/commands/rotate-box-brush-command.js [add] src/commands/set-box-brush-face-material-command.js [add] src/commands/set-box-brush-face-uv-state-command.js [add] src/commands/set-box-brush-name-command.js [add] src/commands/set-box-brush-transform-command.js [add] src/commands/set-entity-name-command.js [add] src/commands/set-model-instance-name-command.js [add] src/commands/set-player-start-command.js [add] src/commands/set-scene-name-command.js [add] src/commands/set-world-settings-command.js [add] src/commands/upsert-entity-command.js [add] src/commands/upsert-interaction-link-command.js [add] src/commands/upsert-model-instance-command.js [add] src/core/ids.js [add] src/core/selection.js [add] src/core/tool-mode.js [add] src/core/transform-session.js [add] src/core/vector.js [add] src/core/whitebox-selection-feedback.js [add] src/core/whitebox-selection-mode.js [add] src/document/brushes.js [add] src/document/migrate-scene-document.js [add] src/document/scene-document-validation.js [add] src/document/scene-document.js [add] src/document/world-settings.js [add] src/entities/entity-instances.js [add] src/entities/entity-labels.js [add] src/geometry/box-brush-components.js [add] src/geometry/box-brush-mesh.js [add] src/geometry/box-brush.js [add] src/geometry/box-face-uvs.js [add] src/geometry/grid-snapping.js [add] src/geometry/model-instance-collider-debug-mesh.js [add] src/geometry/model-instance-collider-generation.js [add] src/interactions/interaction-links.js [add] src/main.js [add] src/materials/starter-material-library.js [add] src/materials/starter-material-textures.js [add] src/rendering/advanced-rendering.js [add] src/runner-web/RunnerCanvas.js [add] src/runtime-three/first-person-navigation-controller.js [add] src/runtime-three/navigation-controller.js [add] src/runtime-three/orbit-visitor-navigation-controller.js [add] src/runtime-three/player-collision.js [add] src/runtime-three/rapier-collision-world.js [add] src/runtime-three/runtime-audio-system.js [add] src/runtime-three/runtime-host.js [add] src/runtime-three/runtime-interaction-system.js [add] src/runtime-three/runtime-scene-build.js [add] src/runtime-three/runtime-scene-validation.js [add] src/serialization/local-draft-storage.js [add] src/serialization/scene-document-json.js [add] src/shared-ui/HierarchicalMenu.js [add] src/shared-ui/Panel.js [add] src/shared-ui/world-background-style.js [add] src/viewport-three/ViewportCanvas.js [add] src/viewport-three/ViewportPanel.js [add] src/viewport-three/viewport-entity-markers.js [add] src/viewport-three/viewport-focus.js [add] src/viewport-three/viewport-host.js [add] src/viewport-three/viewport-layout.js [add] src/viewport-three/viewport-transient-state.js [add] src/viewport-three/viewport-view-modes.js [add] tests/domain/box-brush-face-editing.command.test.js [add] tests/domain/build-runtime-scene.test.js [add] tests/domain/create-box-brush.command.test.js [add] tests/domain/create-empty-scene-document.test.js [add] tests/domain/editor-store.test.js [add] tests/domain/entity.command.test.js [add] tests/domain/interaction-links.validation.test.js [add] tests/domain/model-import.test.js [add] tests/domain/model-instance.command.test.js [add] tests/domain/player-start.command.test.js [add] tests/domain/rapier-collision-world.test.js [add] tests/domain/runtime-audio-system.test.js [add] tests/domain/runtime-interaction-system.test.js [add] tests/domain/runtime-scene-validation.test.js [add] tests/domain/scene-document-validation.test.js [add] tests/domain/transform-session.command.test.js [add] tests/domain/world-settings.command.test.js [add] tests/domain/world-settings.test.js [add] tests/e2e/app-smoke.e2e.js [add] tests/e2e/box-brush-authoring.e2e.js [add] tests/e2e/entities-foundation.e2e.js [add] tests/e2e/face-material-authoring.e2e.js [add] tests/e2e/first-room-workflow.e2e.js [add] tests/e2e/import-draco-model-asset.e2e.js [add] tests/e2e/import-external-model-asset.e2e.js [add] tests/e2e/import-model-asset.e2e.js [add] tests/e2e/local-lights-and-background.e2e.js [add] tests/e2e/orthographic-views.e2e.js [add] tests/e2e/runner-v1.e2e.js [add] tests/e2e/runtime-click-interaction.e2e.js [add] tests/e2e/runtime-trigger-teleport.e2e.js [add] tests/e2e/viewport-quad-layout.e2e.js [add] tests/e2e/viewport-test-helpers.js [add] tests/e2e/whitebox-component-selection.e2e.js [add] tests/e2e/world-environment.e2e.js [add] tests/geometry/box-brush-geometry.test.js [add] tests/geometry/box-face-uvs.test.js [add] tests/geometry/model-instance-collider-generation.test.js [add] tests/helpers/model-collider-fixtures.js [add] tests/serialization/local-draft-storage.test.js [add] tests/serialization/project-asset-storage.test.js [add] tests/serialization/scene-document-json.test.js [add] tests/setup/vitest.setup.js [add] tests/unit/audio-assets.test.js [add] tests/unit/entity-instances.test.js [add] tests/unit/package-scripts.test.js [add] tests/unit/transform-foundation.integration.test.js [add] tests/unit/viewport-canvas.test.js [add] tests/unit/viewport-entity-markers.test.js [add] tests/unit/viewport-focus.test.js [add] tests/unit/viewport-layout.test.js [add] tests/unit/viewport-view-modes.test.js [add] vite.config.js [add] vitest.config.js
2026-04-05 02:25:08 +02:00
import { BoxGeometry, Group, Mesh, MeshBasicMaterial } from "three";
import { instantiateModelTemplate } from "./gltf-model-import";
const MODEL_PLACEHOLDER_COLOR = 0x89b6ff;
const MODEL_SELECTION_COLOR = 0xf7d2aa;
const MODEL_PREVIEW_SHELL_OPACITY = 0.5;
function getLocalModelBounds(asset) {
if (asset?.kind === "model" && asset.metadata.boundingBox !== null) {
const boundingBox = asset.metadata.boundingBox;
return {
center: {
x: (boundingBox.min.x + boundingBox.max.x) * 0.5,
y: (boundingBox.min.y + boundingBox.max.y) * 0.5,
z: (boundingBox.min.z + boundingBox.max.z) * 0.5
},
size: {
x: Math.max(0.1, Math.abs(boundingBox.max.x - boundingBox.min.x)),
y: Math.max(0.1, Math.abs(boundingBox.max.y - boundingBox.min.y)),
z: Math.max(0.1, Math.abs(boundingBox.max.z - boundingBox.min.z))
}
};
}
return {
center: {
x: 0,
y: 0,
z: 0
},
size: {
x: 1,
y: 1,
z: 1
}
};
}
function createWireframeBox(size, color, opacity) {
return new Mesh(new BoxGeometry(size.x, size.y, size.z), new MeshBasicMaterial({
color,
wireframe: true,
transparent: true,
opacity,
depthWrite: false
}));
}
function createWireframeMaterial(material) {
const source = material;
const opacity = typeof source.opacity === "number" ? source.opacity : 1;
return new MeshBasicMaterial({
color: source.color?.getHex() ?? MODEL_PLACEHOLDER_COLOR,
wireframe: true,
transparent: source.transparent === true || opacity < 1,
opacity,
depthWrite: false
});
}
function applyWireframeMaterialPresentation(group) {
group.traverse((object) => {
const maybeMesh = object;
if (maybeMesh.isMesh !== true) {
return;
}
if (Array.isArray(maybeMesh.material)) {
const originalMaterials = maybeMesh.material;
maybeMesh.material = originalMaterials.map((material) => createWireframeMaterial(material));
for (const material of originalMaterials) {
material.dispose();
}
return;
}
const originalMaterial = maybeMesh.material;
maybeMesh.material = createWireframeMaterial(originalMaterial);
originalMaterial.dispose();
});
}
function disposeTexture(texture, seenTextures) {
if (seenTextures.has(texture)) {
return;
}
seenTextures.add(texture);
texture.dispose();
}
function disposeMaterialResources(material, disposeTextures, seenTextures) {
if (disposeTextures) {
for (const value of Object.values(material)) {
if (value === null || value === undefined) {
continue;
}
if (Array.isArray(value)) {
for (const entry of value) {
if (entry !== null && typeof entry === "object" && "isTexture" in entry) {
disposeTexture(entry, seenTextures);
}
}
continue;
}
if (typeof value === "object" && "isTexture" in value) {
disposeTexture(value, seenTextures);
}
}
}
material.dispose();
}
function disposeMeshResources(object, disposeTextures, seenTextures) {
const maybeMesh = object;
if (maybeMesh.isMesh !== true) {
return;
}
maybeMesh.geometry.dispose();
if (Array.isArray(maybeMesh.material)) {
for (const material of maybeMesh.material) {
disposeMaterialResources(material, disposeTextures, seenTextures);
}
}
else {
disposeMaterialResources(maybeMesh.material, disposeTextures, seenTextures);
}
}
export function createModelInstanceRenderGroup(modelInstance, asset, loadedAsset, selected = false, previewShellColor, renderMode = "normal") {
const bounds = getLocalModelBounds(asset);
const group = new Group();
group.position.set(modelInstance.position.x, modelInstance.position.y, modelInstance.position.z);
group.rotation.set((modelInstance.rotationDegrees.x * Math.PI) / 180, (modelInstance.rotationDegrees.y * Math.PI) / 180, (modelInstance.rotationDegrees.z * Math.PI) / 180);
group.scale.set(modelInstance.scale.x, modelInstance.scale.y, modelInstance.scale.z);
group.userData.modelInstanceId = modelInstance.id;
group.userData.assetId = modelInstance.assetId;
if (loadedAsset !== undefined) {
const instantiatedModel = instantiateModelTemplate(loadedAsset.template);
if (renderMode === "wireframe") {
applyWireframeMaterialPresentation(instantiatedModel);
}
group.add(instantiatedModel);
}
else {
const placeholder = createWireframeBox(bounds.size, previewShellColor ?? MODEL_PLACEHOLDER_COLOR, previewShellColor === undefined ? 0.28 : MODEL_PREVIEW_SHELL_OPACITY);
placeholder.position.set(bounds.center.x, bounds.center.y, bounds.center.z);
placeholder.userData.shadowIgnored = true;
group.add(placeholder);
}
if (loadedAsset !== undefined && previewShellColor !== undefined) {
const previewShell = createWireframeBox(bounds.size, previewShellColor, MODEL_PREVIEW_SHELL_OPACITY);
previewShell.position.set(bounds.center.x, bounds.center.y, bounds.center.z);
previewShell.userData.shadowIgnored = true;
group.add(previewShell);
}
if (selected) {
const selectionShell = createWireframeBox(bounds.size, MODEL_SELECTION_COLOR, 0.8);
selectionShell.position.set(bounds.center.x, bounds.center.y, bounds.center.z);
selectionShell.userData.shadowIgnored = true;
group.add(selectionShell);
}
return group;
}
export function disposeModelInstance(instance) {
const seenTextures = new Set();
instance.traverse((object) => {
disposeMeshResources(object, false, seenTextures);
});
}