1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-24 13:54:12 +02:00
This commit is contained in:
syuilo
2026-04-15 21:44:26 +09:00
parent ac2c6b93ce
commit 124079f80a
30 changed files with 121 additions and 22 deletions

View File

@@ -243,7 +243,12 @@ class ModelManager {
const excludeMeshes = [...this.bakeExcludeMeshes, ...this.root.getChildMeshes().filter(m => SYSTEM_MESH_NAMES.some(s => m.name.includes(s)))]; const excludeMeshes = [...this.bakeExcludeMeshes, ...this.root.getChildMeshes().filter(m => SYSTEM_MESH_NAMES.some(s => m.name.includes(s)))];
const childMeshes = this.root.getChildMeshes().filter(m => !excludeMeshes.some(x => x === m) && m.isVisible); const childMeshes = this.root.getChildMeshes().filter(m => !excludeMeshes.some(x => x === m) && m.isVisible && !m.isDisposed());
if (childMeshes.length <= 1) {
this.bakedCallback?.([...childMeshes, ...excludeMeshes]);
return;
}
const _toMerge = [] as BABYLON.Mesh[]; const _toMerge = [] as BABYLON.Mesh[];
for (const mesh of childMeshes) { for (const mesh of childMeshes) {
@@ -301,7 +306,12 @@ class ModelManager {
toMerge.push(newMesh); toMerge.push(newMesh);
} }
const merged = BABYLON.Mesh.MergeMeshes(toMerge, true, true, undefined, false, true); if (toMerge.length === 0) {
this.bakedCallback?.([...childMeshes, ...excludeMeshes]);
return;
}
const merged = BABYLON.Mesh.MergeMeshes(toMerge, true, false, undefined, false, true);
merged.parent = this.root; merged.parent = this.root;
merged.material.freeze(); merged.material.freeze();
if (merged.material instanceof BABYLON.MultiMaterial) { if (merged.material instanceof BABYLON.MultiMaterial) {
@@ -333,6 +343,44 @@ class ModelManager {
} }
} }
function mergeMeshes(meshes: BABYLON.Mesh[], root: BABYLON.Mesh, hasTexture: boolean) {
const excludeMeshes = root.getChildMeshes().filter(m => SYSTEM_MESH_NAMES.some(s => m.name.includes(s)));
const childMeshes = root.getChildMeshes().filter(m => !excludeMeshes.some(x => x === m) && m.isVisible && !m.isDisposed());
const toMerge = [] as BABYLON.Mesh[];
for (const mesh of childMeshes) {
if (mesh instanceof BABYLON.InstancedMesh) {
continue;
}
if (mesh.hasInstances) continue;
if (mesh instanceof BABYLON.Mesh) {
toMerge.push(mesh);
}
}
for (const mesh of toMerge) {
if (hasTexture) {
if (mesh.getVerticesData(BABYLON.VertexBuffer.UVKind) == null) {
const vertexCount = mesh.getTotalVertices();
const uvs = new Array(vertexCount * 2).fill(0);
mesh.setVerticesData(BABYLON.VertexBuffer.UVKind, uvs, false, 2);
}
if (mesh.getVerticesData(BABYLON.VertexBuffer.UV2Kind) == null) {
const vertexCount = mesh.getTotalVertices();
const uvs = new Array(vertexCount * 2).fill(0);
mesh.setVerticesData(BABYLON.VertexBuffer.UV2Kind, uvs, false, 2);
}
}
}
const merged = BABYLON.Mesh.MergeMeshes(toMerge, true, false, undefined, false, true);
return merged;
}
export type ObjectDef<OpSc extends OptionsSchema = OptionsSchema> = { export type ObjectDef<OpSc extends OptionsSchema = OptionsSchema> = {
id: string; id: string;
name: string; name: string;
@@ -344,6 +392,7 @@ export type ObjectDef<OpSc extends OptionsSchema = OptionsSchema> = {
placement: 'top' | 'side' | 'bottom' | 'wall' | 'ceiling' | 'floor'; placement: 'top' | 'side' | 'bottom' | 'wall' | 'ceiling' | 'floor';
hasCollisions?: boolean; hasCollisions?: boolean;
hasTexture?: boolean; hasTexture?: boolean;
canPreMeshesMerging?: boolean;
//groupingMeshes: string[]; // multi-materialなメッシュは複数のメッシュに分割されるが、それだと不便な場合に追加の親メッシュでグルーピングするための指定 //groupingMeshes: string[]; // multi-materialなメッシュは複数のメッシュに分割されるが、それだと不便な場合に追加の親メッシュでグルーピングするための指定
isChair?: boolean; isChair?: boolean;
treatLoaderResult?: (loaderResult: BABYLON.AssetContainer) => void; treatLoaderResult?: (loaderResult: BABYLON.AssetContainer) => void;
@@ -833,13 +882,13 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
// TODO: __PICK__考慮 // TODO: __PICK__考慮
const pickingInfo = this.scene.pick(this.scene.pointerX, this.scene.pointerY, const pickingInfo = this.scene.pick(this.scene.pointerX, this.scene.pointerY,
(m) => m.metadata?.objectId != null && this.objectEntities.has(m.metadata.objectId)); (m) => m.name.includes('__PICK__') || (m.isVisible && m.isEnabled() && m.metadata?.objectId != null && this.objectEntities.has(m.metadata.objectId)));
if (pickingInfo.pickedMesh != null) { if (pickingInfo.pickedMesh != null) {
const oid = pickingInfo.pickedMesh.metadata.objectId; const oid = pickingInfo.pickedMesh.metadata.objectId;
if (oid != null && this.objectEntities.has(oid)) { if (oid != null && this.objectEntities.has(oid)) {
const o = this.objectEntities.get(oid)!; const o = this.objectEntities.get(oid)!;
const boundingInfo = getMeshesBoundingBox(o.rootMesh.getChildMeshes().filter(m => m.isEnabled() && m.isVisible)); const boundingInfo = getMeshesBoundingBox(o.rootMesh.getChildMeshes().filter(m => m.isEnabled() && m.isVisible && !m.isDisposed()));
this.selectObject(oid); this.selectObject(oid);
{ // camera animation { // camera animation
@@ -1169,6 +1218,19 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
} }
} }
if (def.canPreMeshesMerging) {
const merged = mergeMeshes(loaderResult.meshes, subRoot, def.hasTexture);
merged.setParent(subRoot);
merged.name = 'preMerged';
// TODO: 再帰的にする
for (const m of loaderResult.transformNodes) {
if (m.getChildren().length === 0) {
m.dispose();
}
}
}
if (BAKE_TRANSFORM) { if (BAKE_TRANSFORM) {
subRoot.scaling = new BABYLON.Vector3(1, 1, 1); subRoot.scaling = new BABYLON.Vector3(1, 1, 1);
subRoot.rotationQuaternion = null; subRoot.rotationQuaternion = null;
@@ -1330,7 +1392,7 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
root.rotation = args.rotation.clone(); root.rotation = args.rotation.clone();
root.metadata = metadata; root.metadata = metadata;
const model = new ModelManager(BAKE_TRANSFORM ? root : subRoot, loaderResult.meshes.filter(m => m.name !== '__root__'), def.hasTexture, (meshes) => { const model = new ModelManager(BAKE_TRANSFORM ? root : subRoot, loaderResult.meshes.filter(m => !m.isDisposed() && m.name !== '__root__'), def.hasTexture, (meshes) => {
if (this.selected?.objectId === args.id) { if (this.selected?.objectId === args.id) {
this.highlightMeshes(meshes); this.highlightMeshes(meshes);
} }
@@ -2082,6 +2144,20 @@ export class RoomObjectPreviewEngine {
const filePath = def.path != null ? `/client-assets/room/objects/${def.path}.glb` : `/client-assets/room/objects/${camelToKebab(args.type)}/${camelToKebab(args.type)}.glb`; const filePath = def.path != null ? `/client-assets/room/objects/${def.path}.glb` : `/client-assets/room/objects/${camelToKebab(args.type)}/${camelToKebab(args.type)}.glb`;
const loaderResult = await BABYLON.LoadAssetContainerAsync(filePath, this.scene); const loaderResult = await BABYLON.LoadAssetContainerAsync(filePath, this.scene);
// 不要なUVを掃除
if (!def.hasTexture) {
for (const m of loaderResult.meshes) {
if (m.geometry != null) {
m.geometry.removeVerticesData(BABYLON.VertexBuffer.UVKind);
m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV2Kind);
m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV3Kind);
m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV4Kind);
m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV5Kind);
m.geometry.removeVerticesData(BABYLON.VertexBuffer.UV6Kind);
}
}
}
// babylonによって自動で追加される右手系変換用ード // babylonによって自動で追加される右手系変換用ード
const subRoot = loaderResult.meshes[0]; const subRoot = loaderResult.meshes[0];
subRoot.scaling = subRoot.scaling.scale(WORLD_SCALE);// cmをmに subRoot.scaling = subRoot.scaling.scale(WORLD_SCALE);// cmをmに
@@ -2090,7 +2166,7 @@ export class RoomObjectPreviewEngine {
root.addChild(subRoot); root.addChild(subRoot);
const model = new ModelManager(subRoot, loaderResult.meshes.filter(m => m !== subRoot), def.hasTexture, (meshes) => { const model = new ModelManager(subRoot, loaderResult.meshes.filter(m => !m.isDisposed() && m !== subRoot), def.hasTexture, (meshes) => {
for (const m of meshes) { for (const m of meshes) {
const mesh = m; const mesh = m;

View File

@@ -14,6 +14,7 @@ export const aircon = defineObject({
}, },
placement: 'wall', placement: 'wall',
hasCollisions: false, hasCollisions: false,
canPreMeshesMerging: true,
createInstance: () => { createInstance: () => {
return { return {
interactions: {}, interactions: {},

View File

@@ -28,12 +28,10 @@ export const aromaReedDiffuser = defineObject({
placement: 'top', placement: 'top',
hasCollisions: false, hasCollisions: false,
hasTexture: true, hasTexture: true,
canPreMeshesMerging: true,
createInstance: ({ options, model }) => { createInstance: ({ options, model }) => {
const bottleMesh = model.findMesh('__X_BOTTLE__'); const bottleMaterial = model.findMaterial('__X_BOTTLE__');
const bottleMaterial = bottleMesh.material as BABYLON.PBRMaterial; const oilMaterial = model.findMaterial('__X_OIL__');
const oilMesh = model.findMesh('__X_OIL__');
const oilMaterial = oilMesh.material as BABYLON.PBRMaterial;
const applyBottleColor = () => { const applyBottleColor = () => {
const [r, g, b] = options.bottleColor; const [r, g, b] = options.bottleColor;

View File

@@ -15,6 +15,7 @@ export const banknote = defineObject({
placement: 'top', placement: 'top',
hasCollisions: false, hasCollisions: false,
hasTexture: true, hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => { createInstance: () => {
return { return {
interactions: {}, interactions: {},

View File

@@ -16,6 +16,7 @@ export const beamLamp = defineObject({
}, },
placement: 'top', placement: 'top',
hasCollisions: false, hasCollisions: false,
canPreMeshesMerging: true,
createInstance: ({ room, root, scene }) => { createInstance: ({ room, root, scene }) => {
return { return {
onInited: () => { onInited: () => {

View File

@@ -23,9 +23,9 @@ export const cactusS = defineObject({
placement: 'top', placement: 'top',
hasCollisions: false, hasCollisions: false,
hasTexture: true, hasTexture: true,
canPreMeshesMerging: true,
createInstance: ({ options, model }) => { createInstance: ({ options, model }) => {
const potMesh = model.findMesh('__X_POT__'); const potMaterial = model.findMaterial('__X_POT__');
const potMaterial = potMesh.material as BABYLON.PBRMaterial;
const applyPotColor = () => { const applyPotColor = () => {
const [r, g, b] = options.potColor; const [r, g, b] = options.potColor;

View File

@@ -28,12 +28,10 @@ export const chair = defineObject({
placement: 'floor', placement: 'floor',
hasCollisions: true, hasCollisions: true,
isChair: true, isChair: true,
canPreMeshesMerging: true,
createInstance: ({ model, options }) => { createInstance: ({ model, options }) => {
const primaryMesh = model.findMesh('__X_PRIMARY__'); const primaryMaterial = model.findMaterial('__X_PRIMARY__');
const primaryMaterial = primaryMesh.material as BABYLON.PBRMaterial; const secondaryMaterial = model.findMaterial('__X_SECONDARY__');
const secondaryMesh = model.findMesh('__X_SECONDARY__');
const secondaryMaterial = secondaryMesh.material as BABYLON.PBRMaterial;
const applyPrimaryColor = () => { const applyPrimaryColor = () => {
const [r, g, b] = options.primaryColor; const [r, g, b] = options.primaryColor;

View File

@@ -15,6 +15,7 @@ export const coffeeCup = defineObject({
placement: 'top', placement: 'top',
hasCollisions: false, hasCollisions: false,
hasTexture: true, hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => { createInstance: () => {
return { return {
interactions: {}, interactions: {},

View File

@@ -17,6 +17,7 @@ export const cupNoodle = defineObject({
placement: 'top', placement: 'top',
hasCollisions: false, hasCollisions: false,
hasTexture: true, hasTexture: true,
canPreMeshesMerging: true,
createInstance: ({ scene, root }) => { createInstance: ({ scene, root }) => {
let yugeDispose: (() => void) | null = null; let yugeDispose: (() => void) | null = null;

View File

@@ -14,6 +14,7 @@ export const custardPudding = defineObject({
}, },
placement: 'top', placement: 'top',
hasCollisions: false, hasCollisions: false,
canPreMeshesMerging: true,
createInstance: () => { createInstance: () => {
return { return {
interactions: {}, interactions: {},

View File

@@ -48,6 +48,7 @@ export const desktopPc = defineObject({
}, },
placement: 'top', placement: 'top',
hasCollisions: true, hasCollisions: true,
canPreMeshesMerging: true,
createInstance: ({ options, model, root, scene, room }) => { createInstance: ({ options, model, root, scene, room }) => {
const light1 = new BABYLON.SpotLight('', new BABYLON.Vector3(0, cm(10), cm(22)), new BABYLON.Vector3(0, 0, 1), Math.PI / 1, 2, scene, room?.lightContainer != null); const light1 = new BABYLON.SpotLight('', new BABYLON.Vector3(0, cm(10), cm(22)), new BABYLON.Vector3(0, 0, 1), Math.PI / 1, 2, scene, room?.lightContainer != null);
light1.parent = root; light1.parent = root;

View File

@@ -15,6 +15,7 @@ export const djMixer = defineObject({
placement: 'top', placement: 'top',
hasCollisions: false, hasCollisions: false,
hasTexture: true, hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => { createInstance: () => {
return { return {
interactions: {}, interactions: {},

View File

@@ -15,6 +15,7 @@ export const facialTissue = defineObject({
placement: 'top', placement: 'top',
hasCollisions: false, hasCollisions: false,
hasTexture: true, hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => { createInstance: () => {
return { return {
interactions: {}, interactions: {},

View File

@@ -15,6 +15,7 @@ export const hangingTShirt = defineObject({
placement: 'side', placement: 'side',
hasCollisions: false, hasCollisions: false,
hasTexture: true, hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => { createInstance: () => {
return { return {
interactions: {}, interactions: {},

View File

@@ -14,6 +14,7 @@ export const keyboard = defineObject({
}, },
placement: 'top', placement: 'top',
hasCollisions: false, hasCollisions: false,
canPreMeshesMerging: true,
createInstance: () => { createInstance: () => {
return { return {
interactions: {}, interactions: {},

View File

@@ -13,6 +13,7 @@ export const letterCase = defineObject({
default: {}, default: {},
}, },
placement: 'top', placement: 'top',
canPreMeshesMerging: true,
createInstance: () => { createInstance: () => {
return { return {
interactions: {}, interactions: {},

View File

@@ -14,6 +14,7 @@ export const miPlate = defineObject({
}, },
placement: 'top', placement: 'top',
hasCollisions: false, hasCollisions: false,
canPreMeshesMerging: true,
createInstance: () => { createInstance: () => {
return { return {
interactions: {}, interactions: {},

View File

@@ -14,6 +14,7 @@ export const miPlateDisplayed = defineObject({
}, },
placement: 'top', placement: 'top',
hasCollisions: false, hasCollisions: false,
canPreMeshesMerging: true,
createInstance: () => { createInstance: () => {
return { return {
interactions: {}, interactions: {},

View File

@@ -15,6 +15,7 @@ export const pachira = defineObject({
placement: 'top', placement: 'top',
hasCollisions: true, hasCollisions: true,
hasTexture: true, hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => { createInstance: () => {
return { return {
interactions: {}, interactions: {},

View File

@@ -22,6 +22,7 @@ export const piano = defineObject({
}, },
placement: 'floor', placement: 'floor',
hasCollisions: true, hasCollisions: true,
canPreMeshesMerging: true,
createInstance: ({ options, model }) => { createInstance: ({ options, model }) => {
const bodyMaterial = model.findMaterial('__X_BODY__'); const bodyMaterial = model.findMaterial('__X_BODY__');

View File

@@ -14,6 +14,7 @@ export const pizza = defineObject({
}, },
placement: 'top', placement: 'top',
hasCollisions: false, hasCollisions: false,
canPreMeshesMerging: true,
createInstance: () => { createInstance: () => {
return { return {
interactions: {}, interactions: {},

View File

@@ -15,6 +15,7 @@ export const plant = defineObject({
placement: 'top', placement: 'top',
hasCollisions: false, hasCollisions: false,
hasTexture: true, hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => { createInstance: () => {
return { return {
interactions: {}, interactions: {},

View File

@@ -15,6 +15,7 @@ export const tabletopCalendar = defineObject({
placement: 'top', placement: 'top',
hasCollisions: false, hasCollisions: false,
hasTexture: true, hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => { createInstance: () => {
return { return {
interactions: {}, interactions: {},

View File

@@ -417,18 +417,26 @@ export function createPlaneUvMapper(mesh: BABYLON.Mesh) {
}; };
} }
export function findMaterial(rootMesh: BABYLON.AbstractMesh, keyword: string): BABYLON.PBRMaterial { export function findMaterial(rootMesh: BABYLON.AbstractMesh, keyword: string, allowMultiMaterial = false): BABYLON.PBRMaterial {
for (const m of rootMesh.getChildMeshes()) { for (const m of rootMesh.getChildMeshes()) {
if (m.material == null) continue; if (m.material == null) continue;
if (m.material.name.includes(keyword)) { if (m.material instanceof BABYLON.MultiMaterial) {
return m.material as BABYLON.PBRMaterial; if (allowMultiMaterial && m.material.name.includes(keyword)) {
} else if ((m.material as BABYLON.MultiMaterial).subMaterials != null) { return m.material as BABYLON.MultiMaterial;
}
if ((m.material as BABYLON.MultiMaterial).subMaterials == null) continue;
for (const sm of (m.material as BABYLON.MultiMaterial).subMaterials) { for (const sm of (m.material as BABYLON.MultiMaterial).subMaterials) {
if (sm == null) continue; if (sm == null) continue;
if (sm.name.includes(keyword)) { if (sm.name.includes(keyword)) {
return sm as BABYLON.PBRMaterial; return sm as BABYLON.PBRMaterial;
} }
} }
} else {
if (m.material.name.includes(keyword)) {
return m.material as BABYLON.PBRMaterial;
}
} }
} }
throw new Error(`Material with keyword "${keyword}" not found`); throw new Error(`Material with keyword "${keyword}" not found`);