mson-three/src/three/builder.ts

163 lines
3.8 KiB
TypeScript

import {
BoxGeometry,
Euler,
Material,
MathUtils,
Mesh,
Object3D,
Vector3,
} from 'three';
import {
MsonBaseComponent,
MsonBox,
MsonComponent,
MsonComponentType,
MsonCompound,
MsonCompoundComponent,
MsonEvaluatedModel,
Vec3,
} from '../mson';
export class ThreeBuilder {
constructor(private readonly material: Material) {}
/**
* Create a THREE.js object from MSON evaluated model data.
* @param model MSON Evaluated model
* @returns THREE.js object
*/
buildGeometry(model: MsonEvaluatedModel) {
if (!model._dataEvaluated) {
throw new Error(
'Please evaluate the MSON model before building a geometry.',
);
}
const wrapper = new Object3D();
wrapper.name = model.name;
for (const [name, component] of Object.entries(model._dataEvaluated)) {
this.makeGeometry(name, component, wrapper);
}
return wrapper;
}
/**
* Generate geometry from a MSON component
* @param component Component type
* @param parent Parent object
* @returns Geometry object
*/
protected makeGeometry(
name: string,
component: MsonComponent,
parent: Object3D,
parentComponent?: MsonComponent,
) {
// Compound objects
if (!component.type || component.type === 'mson:compound') {
return this.makeMsonCompound(
name,
component as MsonCompound,
parent,
parentComponent,
);
}
}
protected makeMsonCompound(
name: string,
component: MsonCompound,
parent: Object3D,
parentComponent?: MsonComponent,
) {
const wrapper = this.createWrapper(name, component);
parent.add(wrapper);
component.cubes?.forEach((part: MsonCompoundComponent) => {
if (!part.type || part.type === 'mson:box') {
this.makeMsonBox(name, part as MsonBox, wrapper, component);
return;
}
});
}
protected makeMsonBox(
name: string,
component: MsonBox,
parent: Object3D,
parentComponent?: MsonComponent,
) {
const size = new Vector3().fromArray(component.size as Vec3);
const dilate = new Vector3();
if (component.dilate) {
if (Array.isArray(component.dilate)) dilate.fromArray(component.dilate);
else dilate.set(component.dilate, component.dilate, component.dilate);
}
const rotate = new Vector3();
if (parentComponent?.rotate) {
rotate.fromArray(
(parentComponent.rotate as Vec3).map((entry) =>
MathUtils.degToRad(entry),
),
);
}
const pos = new Vector3();
if (parentComponent?.pivot) {
pos.fromArray(parentComponent.pivot as Vec3);
}
pos.setY(pos.y * -1);
const offset = new Vector3().fromArray(component.from as Vec3);
const halfOffset = offset.clone().divideScalar(2);
const halfSize = size.clone().divideScalar(2);
offset.setY(offset.y * -1);
const adjustedTranslate = halfSize.clone().add(offset);
const geometry = new BoxGeometry(
size.x + dilate.x,
size.y + dilate.y,
size.z + dilate.z,
1,
1,
1,
);
geometry.translate(
adjustedTranslate.x,
adjustedTranslate.y - size.y,
adjustedTranslate.z,
);
geometry.rotateX(rotate.x);
geometry.rotateY(rotate.y);
geometry.rotateZ(rotate.z);
// FIXME: hack toJSON
(geometry as any).type = 'BufferGeometry';
delete (geometry as any).parameters;
// TODO: apply UVs
// pos.add(halfOffset).add(halfSize);
const mesh = new Mesh(geometry, this.material);
mesh.name = `${name}__mesh`;
mesh.position.copy(pos);
mesh.updateMatrix();
parent.add(mesh);
}
protected createWrapper(name: string, component: MsonBaseComponent) {
let wrapper = new Object3D();
wrapper.name = name;
wrapper.userData.type = 'mson:compound';
wrapper.visible = component.visible ?? true;
return wrapper;
}
}