feat(All):Initial
This commit is contained in:
@@ -0,0 +1,137 @@
|
||||
import {Group, BufferGeometryLoader, Mesh} from "three";
|
||||
import {Viewport} from "@/core/preview/Viewport.ts";
|
||||
import GenerateColliderEnvironmentWorkerUrl from './generateColliderEnvironment.worker.js?worker&url';
|
||||
|
||||
export class GenerateColliderEnvironmentWorker{
|
||||
constructor() {
|
||||
this.worker = new Worker(GenerateColliderEnvironmentWorkerUrl, { type: 'module' });
|
||||
this.worker.onerror = e => {
|
||||
if ( e.message ) {
|
||||
throw new Error( `GenerateColliderEnvironmentWorker: Could not create Web Worker with error "${ e.message }"` );
|
||||
} else {
|
||||
throw new Error( `GenerateColliderEnvironmentWorker: Could not create Web Worker.`);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
parseAttr(attr,gArray){
|
||||
// 遍历attr的key,还原
|
||||
for (const key in attr) {
|
||||
const item = attr[key];
|
||||
if(item.array){
|
||||
item.array = gArray[key].array;
|
||||
item.count = gArray[key].count;
|
||||
item.itemSize = gArray[key].itemSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
generate(scene){
|
||||
if (this.worker === null) {
|
||||
throw new Error( 'GenerateColliderEnvironmentWorker: Worker has been disposed.' );
|
||||
}
|
||||
|
||||
const { worker } = this;
|
||||
const dbTable = "environment";
|
||||
|
||||
return new Promise(async (resolve,reject)=>{
|
||||
worker.onerror = e => {
|
||||
reject(new Error(`GenerateColliderEnvironmentWorker: ${ e.message }`));
|
||||
};
|
||||
|
||||
// 按照材质分组mesh
|
||||
let toMerge = {},keys = [];
|
||||
|
||||
const loader = new BufferGeometryLoader();
|
||||
|
||||
const db = window.VIEWPORT.modules["db"];
|
||||
const has = await db.hasStore(dbTable);
|
||||
if(has){
|
||||
// TODO 此处应该直接读取返回
|
||||
//db.clear(dbTable);
|
||||
}else{
|
||||
await db.addStore(dbTable);
|
||||
}
|
||||
|
||||
let environment = new Group();
|
||||
environment.name = "ROAMING-ColliderEnvironment";
|
||||
|
||||
worker.onmessage = ({data}) => {
|
||||
switch (data.type) {
|
||||
case "env":
|
||||
const geoJson = data.geometryJson;
|
||||
const geometry = loader.parse(geoJson);
|
||||
|
||||
const newMesh = new Mesh(geometry, window.BOM3D.materials[data.materialUuid]);
|
||||
newMesh.castShadow = true;
|
||||
newMesh.receiveShadow = true;
|
||||
newMesh.material.shadowSide = 2;
|
||||
|
||||
environment.add(newMesh);
|
||||
break;
|
||||
case "collider":
|
||||
const mergedGeometryJson = data.mergedGeometryJson;
|
||||
const mergedGeometry = loader.parse(mergedGeometryJson);
|
||||
|
||||
this.parseAttr(mergedGeometry.attributes,data.gArray);
|
||||
mergedGeometry.index.array = data.gArray.index.array;
|
||||
mergedGeometry.index.itemSize = data.gArray.index.itemSize;
|
||||
|
||||
resolve({mergedGeometry, environment});
|
||||
|
||||
worker.onmessage = null;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
scene.traverse(c => {
|
||||
// @ts-ignore 只合并网格
|
||||
if (c.isMesh) {
|
||||
// @ts-ignore
|
||||
let uuid;
|
||||
// @ts-ignore
|
||||
if (c.material.uuid === Viewport.RegularMat.uuid) {
|
||||
uuid = c.userData.old.materialUuid;
|
||||
} else {
|
||||
// @ts-ignore
|
||||
uuid = c.material.uuid;
|
||||
}
|
||||
|
||||
toMerge[uuid] = toMerge[uuid] || [];
|
||||
let mesh = c.clone();
|
||||
mesh.material = undefined;
|
||||
|
||||
// if (mesh.material?.map === null || !mesh.material?.map?.isTexture) {
|
||||
// // 材质不存在贴图则删除uv属性
|
||||
// mesh.geometry.deleteAttribute("uv");
|
||||
// }
|
||||
|
||||
const json = mesh.toJSON();
|
||||
json.matrixWorld = c.matrixWorld.toArray();
|
||||
toMerge[uuid].push(json);
|
||||
}
|
||||
});
|
||||
keys = Object.keys(toMerge);
|
||||
|
||||
const num = new Proxy({value:keys.length},{
|
||||
set(target, p, newValue, receiver) {
|
||||
target[p] = newValue;
|
||||
|
||||
if(newValue === 0){
|
||||
toMerge = undefined;
|
||||
|
||||
worker.postMessage({indexedDBName:"BOM",table:dbTable,uuids:[...keys]})
|
||||
}
|
||||
|
||||
return true;
|
||||
}
|
||||
})
|
||||
|
||||
keys.forEach((uuid)=>{
|
||||
db.setItem(uuid,toMerge[uuid],dbTable).then(()=>{
|
||||
num.value--;
|
||||
});
|
||||
})
|
||||
})
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,104 @@
|
||||
import { Box3, BufferAttribute } from 'three';
|
||||
import { MeshBVH } from 'three-mesh-bvh';
|
||||
import GenerateAsyncWorkerUrl from './generateAsync.worker.js?worker&url';
|
||||
|
||||
export class GenerateMeshBVHWorker {
|
||||
constructor() {
|
||||
this.running = false;
|
||||
this.worker = new Worker(GenerateAsyncWorkerUrl, { type: 'module' });
|
||||
this.worker.onerror = e => {
|
||||
if ( e.message ) {
|
||||
throw new Error( `GenerateMeshBVHWorker: Could not create Web Worker with error "${ e.message }"` );
|
||||
} else {
|
||||
throw new Error( 'GenerateMeshBVHWorker: Could not create Web Worker.' );
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
generate( geometry, options = {} ) {
|
||||
if (this.running) {
|
||||
throw new Error( 'GenerateMeshBVHWorker: Already running job.' );
|
||||
}
|
||||
|
||||
if (this.worker === null) {
|
||||
throw new Error( 'GenerateMeshBVHWorker: Worker has been disposed.' );
|
||||
}
|
||||
|
||||
const { worker } = this;
|
||||
this.running = true;
|
||||
|
||||
return new Promise((resolve, reject) => {
|
||||
worker.onerror = e => {
|
||||
reject( new Error( `GenerateMeshBVHWorker: ${ e.message }` ) );
|
||||
this.running = false;
|
||||
};
|
||||
|
||||
worker.onmessage = e => {
|
||||
this.running = false;
|
||||
const { data } = e;
|
||||
|
||||
if (data.error) {
|
||||
reject( new Error( data.error ) );
|
||||
worker.onmessage = null;
|
||||
} else if ( data.serialized ) {
|
||||
const { serialized, position } = data;
|
||||
const bvh = MeshBVH.deserialize( serialized, geometry, { setIndex: false } );
|
||||
const boundsOptions = Object.assign( {
|
||||
setBoundingBox: true,
|
||||
}, options );
|
||||
|
||||
// we need to replace the arrays because they're neutered entirely by the
|
||||
// webworker transfer.
|
||||
geometry.attributes.position.array = position;
|
||||
if ( geometry.index ) {
|
||||
geometry.index.array = serialized.index;
|
||||
} else {
|
||||
const newIndex = new BufferAttribute( serialized.index, 1, false );
|
||||
geometry.setIndex( newIndex );
|
||||
}
|
||||
if ( boundsOptions.setBoundingBox ) {
|
||||
geometry.boundingBox = bvh.getBoundingBox( new Box3() );
|
||||
}
|
||||
resolve( bvh );
|
||||
worker.onmessage = null;
|
||||
|
||||
} else if ( options.onProgress ) {
|
||||
options.onProgress( data.progress );
|
||||
}
|
||||
};
|
||||
|
||||
const index = geometry.index ? geometry.index.array : null;
|
||||
const position = geometry.attributes.position.array;
|
||||
|
||||
if (position.isInterleavedBufferAttribute || index && index.isInterleavedBufferAttribute) {
|
||||
throw new Error( 'GenerateMeshBVHWorker: InterleavedBufferAttribute are not supported for the geometry attributes.' );
|
||||
}
|
||||
|
||||
const transferable = [ position ];
|
||||
if (index) {
|
||||
transferable.push( index );
|
||||
}
|
||||
|
||||
worker.postMessage({
|
||||
index,
|
||||
position,
|
||||
options: {
|
||||
...options,
|
||||
onProgress: null,
|
||||
includedProgressCallback: Boolean( options.onProgress ),
|
||||
groups: [ ... geometry.groups ],
|
||||
},
|
||||
}, transferable.map(arr => arr.buffer));
|
||||
});
|
||||
}
|
||||
|
||||
dispose() {
|
||||
this.worker.terminate();
|
||||
this.worker = null;
|
||||
}
|
||||
|
||||
terminate() {
|
||||
console.warn( 'GenerateMeshBVHWorker: "terminate" is deprecated. Use "dispose" instead.' );
|
||||
this.dispose();
|
||||
}
|
||||
}
|
||||
@@ -0,0 +1,60 @@
|
||||
import {
|
||||
BufferGeometry,
|
||||
BufferAttribute,
|
||||
} from 'three';
|
||||
import { MeshBVH } from 'three-mesh-bvh';
|
||||
|
||||
onmessage = function ( { data } ) {
|
||||
let prevTime = performance.now();
|
||||
function onProgressCallback( progress ) {
|
||||
const currTime = performance.now();
|
||||
if ( currTime - prevTime >= 10 || progress === 1.0 ) {
|
||||
postMessage({
|
||||
error: null,
|
||||
serialized: null,
|
||||
position: null,
|
||||
progress,
|
||||
});
|
||||
prevTime = currTime;
|
||||
}
|
||||
}
|
||||
|
||||
const { index, position, options } = data;
|
||||
try {
|
||||
const geometry = new BufferGeometry();
|
||||
geometry.setAttribute( 'position', new BufferAttribute( position, 3, false ) );
|
||||
if ( index ) {
|
||||
geometry.setIndex( new BufferAttribute( index, 1, false ) );
|
||||
}
|
||||
|
||||
if ( options.includedProgressCallback ) {
|
||||
options.onProgress = onProgressCallback;
|
||||
}
|
||||
|
||||
if ( options.groups ) {
|
||||
const groups = options.groups;
|
||||
for ( const i in groups ) {
|
||||
const group = groups[ i ];
|
||||
geometry.addGroup( group.start, group.count, group.materialIndex );
|
||||
}
|
||||
}
|
||||
|
||||
const bvh = new MeshBVH( geometry, options );
|
||||
const serialized = MeshBVH.serialize( bvh, { copyIndexBuffer: false } );
|
||||
|
||||
postMessage( {
|
||||
error: null,
|
||||
serialized,
|
||||
position,
|
||||
progress: 1,
|
||||
}, [ serialized.index.buffer, position.buffer, ...serialized.roots ] );
|
||||
|
||||
} catch ( error ) {
|
||||
postMessage( {
|
||||
error,
|
||||
serialized: null,
|
||||
position: null,
|
||||
progress: 1,
|
||||
} );
|
||||
}
|
||||
};
|
||||
@@ -0,0 +1,140 @@
|
||||
import {Group, Mesh, MeshBasicMaterial, Matrix4, InstancedMesh} from "three";
|
||||
import {ObjectLoader} from "@/core/loader/ObjectLoader";
|
||||
import * as BufferGeometryUtils from "three/examples/jsm/utils/BufferGeometryUtils.js";
|
||||
import {StaticGeometryGenerator} from "three-mesh-bvh";
|
||||
import {TYPED_ARRAYS} from "../../../config/global";
|
||||
import DBStorage from "../../../core/utils/DBStorage";
|
||||
|
||||
/**
|
||||
* 解析attr
|
||||
* @param attr Geometry Attributes
|
||||
*/
|
||||
function parseAttr(attr) {
|
||||
// 遍历attr的key,组织为新对象返回
|
||||
const newAttr = {};
|
||||
for (const key in attr) {
|
||||
const item = attr[key];
|
||||
if (item.array) {
|
||||
newAttr[key] = {
|
||||
itemSize: item.itemSize,
|
||||
array: item.array,
|
||||
count: item.count
|
||||
}
|
||||
item.array = new TYPED_ARRAYS[item.array.constructor.name]([]);
|
||||
}
|
||||
}
|
||||
|
||||
return newAttr;
|
||||
}
|
||||
|
||||
let basicMaterial = new MeshBasicMaterial();
|
||||
|
||||
function getMeshByInstancedMesh(instancedMesh){
|
||||
const meshes = [];
|
||||
|
||||
const matrixWorld = instancedMesh.matrixWorld;
|
||||
const count = instancedMesh.count;
|
||||
|
||||
for (let instanceId = 0; instanceId < count; instanceId++) {
|
||||
const _mesh = new Mesh();
|
||||
const _instanceLocalMatrix = new Matrix4();
|
||||
const _instanceWorldMatrix = new Matrix4();
|
||||
|
||||
_mesh.geometry = instancedMesh.geometry;
|
||||
_mesh.material = instancedMesh.material;
|
||||
|
||||
// 计算每个实例的世界矩阵
|
||||
instancedMesh.getMatrixAt(instanceId, _instanceLocalMatrix);
|
||||
|
||||
_instanceWorldMatrix.multiplyMatrices(matrixWorld, _instanceLocalMatrix);
|
||||
|
||||
// 网格表示这个单一实例
|
||||
_mesh.matrixWorld = _instanceWorldMatrix;
|
||||
|
||||
meshes.push(_mesh);
|
||||
}
|
||||
|
||||
return meshes;
|
||||
}
|
||||
|
||||
onmessage = async function ({data}) {
|
||||
const {indexedDBName,table,uuids} = data;
|
||||
let loader = new ObjectLoader();
|
||||
|
||||
let environment = new Group();
|
||||
|
||||
const db = new DBStorage(indexedDBName,table);
|
||||
|
||||
for (const uuid of uuids) {
|
||||
const arr = await db.getItem(uuid);
|
||||
|
||||
const visualGeometries = [];
|
||||
arr.forEach((meshJson) => {
|
||||
const m = new Matrix4();
|
||||
const matrixWorld = m.fromArray(meshJson.matrixWorld);
|
||||
|
||||
meshJson.matrixWorld = undefined;
|
||||
const mesh = loader.parse(meshJson);
|
||||
|
||||
const cloneGeom = (me) => {
|
||||
const geom = me.geometry.clone();
|
||||
geom.applyMatrix4(me.matrixWorld);
|
||||
visualGeometries.push(geom);
|
||||
}
|
||||
|
||||
// @ts-ignore
|
||||
if (!mesh.isInstancedMesh) {
|
||||
cloneGeom(mesh);
|
||||
} else {
|
||||
const meshes = getMeshByInstancedMesh(mesh);
|
||||
meshes.forEach((m) => {
|
||||
cloneGeom(m);
|
||||
});
|
||||
}
|
||||
});
|
||||
|
||||
if (visualGeometries.length) {
|
||||
const newGeom = BufferGeometryUtils.mergeGeometries(visualGeometries);
|
||||
let n_geo = newGeom.clone().toJSON();
|
||||
|
||||
const newMesh = new Mesh(newGeom, basicMaterial);
|
||||
environment.add(newMesh);
|
||||
|
||||
postMessage({
|
||||
type: "env",
|
||||
materialUuid:uuid,
|
||||
geometryJson: n_geo
|
||||
})
|
||||
|
||||
n_geo = undefined;
|
||||
}
|
||||
|
||||
await db.removeItem(uuid);
|
||||
}
|
||||
|
||||
const staticGenerator = new StaticGeometryGenerator(environment);
|
||||
staticGenerator.attributes = ['position'];
|
||||
|
||||
const mergedGeometry = staticGenerator.generate();
|
||||
|
||||
const gArray = {
|
||||
...parseAttr(mergedGeometry.attributes),
|
||||
index: {
|
||||
array:mergedGeometry.index.array,
|
||||
itemSize:mergedGeometry.index.itemSize
|
||||
}
|
||||
}
|
||||
mergedGeometry.index.array = new TYPED_ARRAYS[gArray.index.array.constructor.name]([]);
|
||||
|
||||
const mergedGeometryJson = mergedGeometry.toJSON();
|
||||
|
||||
const transfer = Object.values(gArray).map(arrayBuffer => arrayBuffer.array.buffer);
|
||||
postMessage({
|
||||
type: "collider",
|
||||
mergedGeometryJson: mergedGeometryJson,
|
||||
gArray: gArray
|
||||
}, transfer);
|
||||
|
||||
basicMaterial = undefined;
|
||||
environment = undefined;
|
||||
}
|
||||
@@ -0,0 +1,40 @@
|
||||
import {BufferGeometry, BufferAttribute} from "three";
|
||||
import * as BufferGeometryUtils from "three/examples/jsm/utils/BufferGeometryUtils.js";
|
||||
|
||||
const visualGeometries: BufferGeometry[] = [];
|
||||
|
||||
self.onmessage = function ({data}) {
|
||||
switch (data.type) {
|
||||
case "push":
|
||||
pushGeometry(data);
|
||||
break;
|
||||
case "merge":
|
||||
if(visualGeometries.length === 0) {
|
||||
self.postMessage({
|
||||
type: "error",
|
||||
message:"No geometries found."
|
||||
})
|
||||
break;
|
||||
}
|
||||
|
||||
const geo = BufferGeometryUtils.mergeGeometries(visualGeometries);
|
||||
self.postMessage({
|
||||
type: "success",
|
||||
geometry: geo
|
||||
})
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
function pushGeometry(data) {
|
||||
const {geometry} = data;
|
||||
|
||||
if(!geometry) return;
|
||||
|
||||
geometry.__proto__ = BufferGeometry.prototype;
|
||||
geometry.index && (geometry.index.__proto__ = BufferAttribute.prototype);
|
||||
geometry.attributes.position && (geometry.attributes.position.__proto__ = BufferAttribute.prototype);
|
||||
geometry.attributes.normal && (geometry.attributes.normal.__proto__ = BufferAttribute.prototype);
|
||||
|
||||
visualGeometries.push(geometry);
|
||||
}
|
||||
Reference in New Issue
Block a user