163 lines
3.8 KiB
TypeScript
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;
|
|
}
|
|
}
|