1
0
mirror of https://github.com/misskey-dev/misskey.git synced 2026-05-13 14:05:35 +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 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[];
for (const mesh of childMeshes) {
@@ -301,7 +306,12 @@ class ModelManager {
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.material.freeze();
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> = {
id: string;
name: string;
@@ -344,6 +392,7 @@ export type ObjectDef<OpSc extends OptionsSchema = OptionsSchema> = {
placement: 'top' | 'side' | 'bottom' | 'wall' | 'ceiling' | 'floor';
hasCollisions?: boolean;
hasTexture?: boolean;
canPreMeshesMerging?: boolean;
//groupingMeshes: string[]; // multi-materialなメッシュは複数のメッシュに分割されるが、それだと不便な場合に追加の親メッシュでグルーピングするための指定
isChair?: boolean;
treatLoaderResult?: (loaderResult: BABYLON.AssetContainer) => void;
@@ -833,13 +882,13 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
// TODO: __PICK__考慮
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) {
const oid = pickingInfo.pickedMesh.metadata.objectId;
if (oid != null && this.objectEntities.has(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);
{ // 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) {
subRoot.scaling = new BABYLON.Vector3(1, 1, 1);
subRoot.rotationQuaternion = null;
@@ -1330,7 +1392,7 @@ export class RoomEngine extends EventEmitter<RoomEngineEvents> {
root.rotation = args.rotation.clone();
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) {
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 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によって自動で追加される右手系変換用ード
const subRoot = loaderResult.meshes[0];
subRoot.scaling = subRoot.scaling.scale(WORLD_SCALE);// cmをmに
@@ -2090,7 +2166,7 @@ export class RoomObjectPreviewEngine {
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) {
const mesh = m;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -48,6 +48,7 @@ export const desktopPc = defineObject({
},
placement: 'top',
hasCollisions: true,
canPreMeshesMerging: true,
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);
light1.parent = root;

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

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

View File

@@ -15,6 +15,7 @@ export const tabletopCalendar = defineObject({
placement: 'top',
hasCollisions: false,
hasTexture: true,
canPreMeshesMerging: true,
createInstance: () => {
return {
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()) {
if (m.material == null) continue;
if (m.material.name.includes(keyword)) {
return m.material as BABYLON.PBRMaterial;
} else if ((m.material as BABYLON.MultiMaterial).subMaterials != null) {
if (m.material instanceof BABYLON.MultiMaterial) {
if (allowMultiMaterial && m.material.name.includes(keyword)) {
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) {
if (sm == null) continue;
if (sm.name.includes(keyword)) {
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`);