158 lines
6.0 KiB
JavaScript
158 lines
6.0 KiB
JavaScript
|
|
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);
|
||
|
|
});
|
||
|
|
}
|