mostly working
This commit is contained in:
parent
b959cea254
commit
c290a0e977
@ -206,6 +206,7 @@ export class MsonEvaluate {
|
|||||||
|
|
||||||
return {
|
return {
|
||||||
type: 'mson:slot',
|
type: 'mson:slot',
|
||||||
|
texture: evaluateChild.texture,
|
||||||
implementation: asSlot.implementation,
|
implementation: asSlot.implementation,
|
||||||
data: evaluateChild._dataEvaluated,
|
data: evaluateChild._dataEvaluated,
|
||||||
};
|
};
|
||||||
|
@ -47,6 +47,7 @@ export type MsonComponentType =
|
|||||||
|
|
||||||
export interface MsonBaseComponent {
|
export interface MsonBaseComponent {
|
||||||
type?: MsonComponentType;
|
type?: MsonComponentType;
|
||||||
|
implementation: string;
|
||||||
/**
|
/**
|
||||||
* Whether or not this part is visible. You shouldn't have to use this in most circumstances.
|
* Whether or not this part is visible. You shouldn't have to use this in most circumstances.
|
||||||
* @default true
|
* @default true
|
||||||
@ -91,7 +92,7 @@ export interface MsonCompound extends MsonBaseComponent {
|
|||||||
* Whether to flip this part's textures.
|
* Whether to flip this part's textures.
|
||||||
* @default false
|
* @default false
|
||||||
*/
|
*/
|
||||||
mirror?: boolean | MsonVec3;
|
mirror?: boolean | [boolean, boolean, boolean];
|
||||||
/**
|
/**
|
||||||
* default type for components (if omitted): mson:box
|
* default type for components (if omitted): mson:box
|
||||||
* allowed types: <mson:box|mson:cone|mson:link|mson:quads|mson:plane|mson:slot>
|
* allowed types: <mson:box|mson:cone|mson:link|mson:quads|mson:plane|mson:slot>
|
||||||
@ -174,7 +175,6 @@ export interface MsonPlane extends MsonBaseComponent {
|
|||||||
|
|
||||||
export interface MsonSlot extends MsonBaseComponent {
|
export interface MsonSlot extends MsonBaseComponent {
|
||||||
type: 'mson:slot';
|
type: 'mson:slot';
|
||||||
implementation: string;
|
|
||||||
name: string;
|
name: string;
|
||||||
data: MsonData | string;
|
data: MsonData | string;
|
||||||
locals?: MsonLocals;
|
locals?: MsonLocals;
|
||||||
|
@ -1,8 +1,8 @@
|
|||||||
import { resolve } from 'path';
|
import { resolve } from 'path';
|
||||||
import { ModelStore, MsonEvaluate } from '.';
|
import { ModelStore, MsonEvaluate } from '.';
|
||||||
import { fillStoreFromFilesystem, saveGeometry } from './util/node';
|
import { fillStoreFromFilesystem, saveGeometry, saveModel } from './util/node';
|
||||||
import { ThreeBuilder } from './three';
|
import { ThreeBuilder } from './three';
|
||||||
import { MeshBasicMaterial, MeshLambertMaterial } from 'three';
|
import { MeshLambertMaterial } from 'three';
|
||||||
|
|
||||||
async function init() {
|
async function init() {
|
||||||
const store = new ModelStore();
|
const store = new ModelStore();
|
||||||
@ -16,12 +16,11 @@ async function init() {
|
|||||||
// minelittlepony:steve_pony
|
// minelittlepony:steve_pony
|
||||||
const final = evaluate.evaluateModel('minelittlepony:races/steve/alicorn');
|
const final = evaluate.evaluateModel('minelittlepony:races/steve/alicorn');
|
||||||
// console.log(final.texture);
|
// console.log(final.texture);
|
||||||
console.dir(final._dataEvaluated, {
|
|
||||||
depth: 20,
|
|
||||||
});
|
|
||||||
|
|
||||||
const geometry = builder.buildGeometry(final);
|
const geometry = builder.buildGeometry(final);
|
||||||
await saveGeometry(resolve(process.cwd(), 'outputs'), 'steve', geometry);
|
const outputName = 'alicorn';
|
||||||
|
const outputs = resolve(process.cwd(), 'outputs');
|
||||||
|
await saveGeometry(outputs, outputName, geometry);
|
||||||
|
await saveModel(outputs, outputName, final);
|
||||||
}
|
}
|
||||||
|
|
||||||
init().catch(console.error);
|
init().catch(console.error);
|
||||||
|
@ -1,6 +1,6 @@
|
|||||||
import {
|
import {
|
||||||
BoxGeometry,
|
BoxGeometry,
|
||||||
Euler,
|
CylinderGeometry,
|
||||||
Material,
|
Material,
|
||||||
MathUtils,
|
MathUtils,
|
||||||
Mesh,
|
Mesh,
|
||||||
@ -13,26 +13,26 @@ import {
|
|||||||
MsonBaseComponent,
|
MsonBaseComponent,
|
||||||
MsonBox,
|
MsonBox,
|
||||||
MsonComponent,
|
MsonComponent,
|
||||||
MsonComponentType,
|
|
||||||
MsonCompound,
|
MsonCompound,
|
||||||
MsonCompoundComponent,
|
MsonCompoundComponent,
|
||||||
MsonData,
|
MsonCone,
|
||||||
MsonEvaluatedModel,
|
MsonEvaluatedModel,
|
||||||
MsonFace,
|
MsonFace,
|
||||||
MsonFaces,
|
MsonFaces,
|
||||||
MsonPlanar,
|
MsonPlanar,
|
||||||
MsonPlanarPlane,
|
|
||||||
MsonPlanarXYZWHUVXY,
|
MsonPlanarXYZWHUVXY,
|
||||||
MsonPlane,
|
MsonPlane,
|
||||||
MsonSlot,
|
MsonSlot,
|
||||||
|
MsonTexture,
|
||||||
Vec3,
|
Vec3,
|
||||||
} from '../mson';
|
} from '../mson';
|
||||||
import { isArrayOfArrays } from '../util/array-of-array';
|
import { isArrayOfArrays } from '../util/array-of-array';
|
||||||
|
import { UVMapper } from './uv';
|
||||||
|
|
||||||
export class ThreeBuilder {
|
export class ThreeBuilder {
|
||||||
static normals: Record<MsonFace, Vector3> = {
|
static normals: Record<MsonFace, Vector3> = {
|
||||||
up: new Vector3(0, -1, 0),
|
up: new Vector3(0, 1, 0),
|
||||||
down: new Vector3(0, 1, 0),
|
down: new Vector3(0, -1, 0),
|
||||||
east: new Vector3(1, 0, 0),
|
east: new Vector3(1, 0, 0),
|
||||||
west: new Vector3(-1, 0, 0),
|
west: new Vector3(-1, 0, 0),
|
||||||
south: new Vector3(0, 0, 1),
|
south: new Vector3(0, 0, 1),
|
||||||
@ -50,16 +50,16 @@ export class ThreeBuilder {
|
|||||||
return new Vector3(pos.x + size.x / 2, pos.y, pos.z + size.y / 2);
|
return new Vector3(pos.x + size.x / 2, pos.y, pos.z + size.y / 2);
|
||||||
},
|
},
|
||||||
east: function (pos: Vector3, size: Vector2): Vector3 {
|
east: function (pos: Vector3, size: Vector2): Vector3 {
|
||||||
return new Vector3(pos.x, pos.y + size.y / 2, pos.z + size.x / 2);
|
return new Vector3(pos.x, pos.y - size.y / 2, pos.z + size.x / 2);
|
||||||
},
|
},
|
||||||
west: function (pos: Vector3, size: Vector2): Vector3 {
|
west: function (pos: Vector3, size: Vector2): Vector3 {
|
||||||
return new Vector3(pos.x, pos.y + size.y / 2, pos.z + size.x / 2);
|
return new Vector3(pos.x, pos.y - size.y / 2, pos.z + size.x / 2);
|
||||||
},
|
},
|
||||||
south: function (pos: Vector3, size: Vector2): Vector3 {
|
south: function (pos: Vector3, size: Vector2): Vector3 {
|
||||||
return new Vector3(pos.x + size.x / 2, pos.y + size.y / 2, pos.z);
|
return new Vector3(pos.x + size.x / 2, pos.y - size.y / 2, pos.z);
|
||||||
},
|
},
|
||||||
north: function (pos: Vector3, size: Vector2): Vector3 {
|
north: function (pos: Vector3, size: Vector2): Vector3 {
|
||||||
return new Vector3(pos.x + size.x / 2, pos.y + size.y / 2, pos.z);
|
return new Vector3(pos.x + size.x / 2, pos.y - size.y / 2, pos.z);
|
||||||
},
|
},
|
||||||
};
|
};
|
||||||
|
|
||||||
@ -81,7 +81,7 @@ export class ThreeBuilder {
|
|||||||
wrapper.name = model.name;
|
wrapper.name = model.name;
|
||||||
|
|
||||||
for (const [name, component] of Object.entries(model._dataEvaluated)) {
|
for (const [name, component] of Object.entries(model._dataEvaluated)) {
|
||||||
this.makeGeometry(name, component, wrapper);
|
this.makeGeometry(name, component, wrapper, undefined, model.texture);
|
||||||
}
|
}
|
||||||
|
|
||||||
return wrapper;
|
return wrapper;
|
||||||
@ -98,47 +98,78 @@ export class ThreeBuilder {
|
|||||||
component: MsonComponent,
|
component: MsonComponent,
|
||||||
parent: Object3D,
|
parent: Object3D,
|
||||||
parentComponent?: MsonComponent,
|
parentComponent?: MsonComponent,
|
||||||
|
texture?: MsonTexture,
|
||||||
) {
|
) {
|
||||||
const wrapper = this.createWrapper(
|
const wrapper = this.createWrapper(name, component);
|
||||||
name,
|
|
||||||
component.type || 'mson:compound',
|
|
||||||
component,
|
|
||||||
);
|
|
||||||
parent.add(wrapper);
|
parent.add(wrapper);
|
||||||
|
|
||||||
// Compound objects
|
// Compound objects
|
||||||
if (!component.type || component.type === 'mson:compound') {
|
if (!component.type || component.type === 'mson:compound') {
|
||||||
this.makeMsonCompound(
|
// mhm, go on ahead
|
||||||
name,
|
|
||||||
component as MsonCompound,
|
|
||||||
wrapper,
|
|
||||||
parentComponent,
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.type === 'mson:slot') {
|
if (
|
||||||
for (const [childName, child] of Object.entries(component.data)) {
|
component.type === 'mson:slot' ||
|
||||||
this.makeGeometry(childName, child, wrapper, component);
|
(!component.type && (component as unknown as MsonSlot).data)
|
||||||
|
) {
|
||||||
|
for (const [childName, child] of Object.entries(
|
||||||
|
(component as MsonSlot).data,
|
||||||
|
)) {
|
||||||
|
this.makeGeometry(childName, child, wrapper, component, {
|
||||||
|
...texture,
|
||||||
|
...component.texture,
|
||||||
|
...child.texture,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.type === 'mson:planar') {
|
if (component.type === 'mson:planar') {
|
||||||
this.makeMsonPlanar(name, component, wrapper, parentComponent);
|
this.makeMsonPlanar(name, component, wrapper, parentComponent, {
|
||||||
|
...texture,
|
||||||
|
...component.texture,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
if (component.type === 'mson:plane') {
|
if (component.type === 'mson:plane') {
|
||||||
this.makeMsonPlane(name, component, wrapper, parentComponent);
|
this.makeMsonPlane(name, component, wrapper, parentComponent, {
|
||||||
|
...texture,
|
||||||
|
...component.texture,
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
if (component.type === 'mson:cone') {
|
||||||
|
this.makeMsonCone(name, component, wrapper, parentComponent, {
|
||||||
|
...texture,
|
||||||
|
...component.texture,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
(component as MsonCompound).cubes?.forEach(
|
(component as MsonCompound).cubes?.forEach(
|
||||||
(part: MsonCompoundComponent) => {
|
(part: MsonCompoundComponent) => {
|
||||||
if (!part.type || part.type === 'mson:box') {
|
if (!part.type || part.type === 'mson:box') {
|
||||||
this.makeMsonBox(name, part as MsonBox, wrapper, component);
|
this.makeMsonBox(name, part as MsonBox, wrapper, component, {
|
||||||
|
...texture,
|
||||||
|
...component.texture,
|
||||||
|
...part.texture,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (part.type === 'mson:plane') {
|
if (part.type === 'mson:plane') {
|
||||||
this.makeMsonPlane(name, part as MsonPlane, wrapper, component);
|
this.makeMsonPlane(name, part as MsonPlane, wrapper, component, {
|
||||||
|
...texture,
|
||||||
|
...component.texture,
|
||||||
|
...part.texture,
|
||||||
|
});
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (part.type === 'mson:cone') {
|
||||||
|
this.makeMsonCone(name, part as MsonCone, wrapper, component, {
|
||||||
|
...texture,
|
||||||
|
...component.texture,
|
||||||
|
...part.texture,
|
||||||
|
});
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
@ -146,23 +177,21 @@ export class ThreeBuilder {
|
|||||||
|
|
||||||
if (component.children) {
|
if (component.children) {
|
||||||
for (const [childName, child] of Object.entries(component.children)) {
|
for (const [childName, child] of Object.entries(component.children)) {
|
||||||
this.makeGeometry(childName, child, wrapper, component);
|
this.makeGeometry(childName, child, wrapper, component, {
|
||||||
|
...texture,
|
||||||
|
...component.texture,
|
||||||
|
...child.texture,
|
||||||
|
});
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
protected makeMsonCompound(
|
|
||||||
name: string,
|
|
||||||
component: MsonCompound,
|
|
||||||
parent: Object3D,
|
|
||||||
parentComponent?: MsonComponent,
|
|
||||||
) {}
|
|
||||||
|
|
||||||
protected makeMsonBox(
|
protected makeMsonBox(
|
||||||
name: string,
|
name: string,
|
||||||
component: MsonBox,
|
component: MsonBox,
|
||||||
parent: Object3D,
|
parent: Object3D,
|
||||||
parentComponent?: MsonComponent,
|
parentComponent?: MsonComponent,
|
||||||
|
texture?: MsonTexture,
|
||||||
) {
|
) {
|
||||||
const size = new Vector3().fromArray(component.size as Vec3);
|
const size = new Vector3().fromArray(component.size as Vec3);
|
||||||
|
|
||||||
@ -188,7 +217,7 @@ export class ThreeBuilder {
|
|||||||
);
|
);
|
||||||
|
|
||||||
geometry.translate(
|
geometry.translate(
|
||||||
adjustedTranslate.x,
|
-adjustedTranslate.x,
|
||||||
adjustedTranslate.y - size.y,
|
adjustedTranslate.y - size.y,
|
||||||
adjustedTranslate.z,
|
adjustedTranslate.z,
|
||||||
);
|
);
|
||||||
@ -197,7 +226,12 @@ export class ThreeBuilder {
|
|||||||
(geometry as any).type = 'BufferGeometry';
|
(geometry as any).type = 'BufferGeometry';
|
||||||
delete (geometry as any).parameters;
|
delete (geometry as any).parameters;
|
||||||
|
|
||||||
// TODO: apply UVs
|
UVMapper.mapBoxUVs(
|
||||||
|
size,
|
||||||
|
geometry,
|
||||||
|
texture,
|
||||||
|
(parentComponent as MsonCompound)?.mirror,
|
||||||
|
);
|
||||||
|
|
||||||
const mesh = new Mesh(geometry, this.material);
|
const mesh = new Mesh(geometry, this.material);
|
||||||
mesh.name = `${name}__mesh`;
|
mesh.name = `${name}__mesh`;
|
||||||
@ -206,23 +240,72 @@ export class ThreeBuilder {
|
|||||||
parent.add(mesh);
|
parent.add(mesh);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected makeMsonCone(
|
||||||
|
name: string,
|
||||||
|
component: MsonCone,
|
||||||
|
parent: Object3D,
|
||||||
|
parentComponent?: MsonComponent,
|
||||||
|
texture?: MsonTexture,
|
||||||
|
) {
|
||||||
|
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 as number,
|
||||||
|
component.dilate as number,
|
||||||
|
component.dilate as number,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
const offset = new Vector3().fromArray(component.from as Vec3);
|
||||||
|
const halfSize = size.clone().divideScalar(2);
|
||||||
|
|
||||||
|
offset.setY(offset.y * -1);
|
||||||
|
const adjustedTranslate = halfSize.clone().add(offset);
|
||||||
|
const adjustedSize = size.x * (component.taper as number) ?? 1;
|
||||||
|
|
||||||
|
let geometry = new CylinderGeometry(
|
||||||
|
(adjustedSize + dilate.x) / Math.sqrt(2),
|
||||||
|
(size.x + dilate.x) / Math.sqrt(2),
|
||||||
|
size.y + dilate.y,
|
||||||
|
4,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
geometry.rotateY(Math.PI / 4);
|
||||||
|
geometry.translate(
|
||||||
|
-adjustedTranslate.x,
|
||||||
|
adjustedTranslate.y - size.y,
|
||||||
|
adjustedTranslate.z,
|
||||||
|
);
|
||||||
|
geometry = geometry.toNonIndexed() as CylinderGeometry;
|
||||||
|
geometry.computeVertexNormals();
|
||||||
|
|
||||||
|
// FIXME: hack toJSON
|
||||||
|
(geometry as any).type = 'BufferGeometry';
|
||||||
|
delete (geometry as any).parameters;
|
||||||
|
|
||||||
|
const mesh = new Mesh(geometry, this.material);
|
||||||
|
mesh.name = `${name}__cone`;
|
||||||
|
mesh.updateMatrix();
|
||||||
|
|
||||||
|
parent.add(mesh);
|
||||||
|
}
|
||||||
|
|
||||||
protected makeMsonPlaneFace(
|
protected makeMsonPlaneFace(
|
||||||
face: MsonFace,
|
face: MsonFace,
|
||||||
parent: Object3D,
|
parent: Object3D,
|
||||||
pos: Vector3,
|
pos: Vector3,
|
||||||
size: Vector2,
|
size: Vector2,
|
||||||
uv: Vector2,
|
texture?: MsonTexture,
|
||||||
mirror?: boolean | [boolean, boolean],
|
mirror?: boolean | [boolean, boolean],
|
||||||
invertY = false,
|
|
||||||
) {
|
) {
|
||||||
const planeGeom = new PlaneGeometry(size.x, size.y);
|
const planeGeom = new PlaneGeometry(size.x, size.y);
|
||||||
let axisNormal = ThreeBuilder.normals[face];
|
let axisNormal = ThreeBuilder.normals[face];
|
||||||
if (invertY) {
|
pos.setY(pos.y * -1);
|
||||||
axisNormal = axisNormal.clone().setY(axisNormal.y * -1);
|
|
||||||
pos.setY(pos.y * -1);
|
|
||||||
if (['east', 'west', 'south', 'north'].includes(face))
|
|
||||||
size.setY(size.y * -1);
|
|
||||||
}
|
|
||||||
const adjustedTranslate = ThreeBuilder.pocConvert[face](pos, size);
|
const adjustedTranslate = ThreeBuilder.pocConvert[face](pos, size);
|
||||||
planeGeom.lookAt(axisNormal);
|
planeGeom.lookAt(axisNormal);
|
||||||
planeGeom.translate(
|
planeGeom.translate(
|
||||||
@ -231,12 +314,12 @@ export class ThreeBuilder {
|
|||||||
adjustedTranslate.z,
|
adjustedTranslate.z,
|
||||||
);
|
);
|
||||||
|
|
||||||
// TODO: apply UVs
|
|
||||||
|
|
||||||
// FIXME: hack toJSON
|
// FIXME: hack toJSON
|
||||||
(planeGeom as any).type = 'BufferGeometry';
|
(planeGeom as any).type = 'BufferGeometry';
|
||||||
delete (planeGeom as any).parameters;
|
delete (planeGeom as any).parameters;
|
||||||
|
|
||||||
|
UVMapper.mapPlanarUvs(face, size, planeGeom, texture, mirror);
|
||||||
|
|
||||||
const mesh = new Mesh(planeGeom, this.material);
|
const mesh = new Mesh(planeGeom, this.material);
|
||||||
// mesh.position.copy(pos);
|
// mesh.position.copy(pos);
|
||||||
mesh.name = face;
|
mesh.name = face;
|
||||||
@ -248,10 +331,12 @@ export class ThreeBuilder {
|
|||||||
component: MsonPlanar,
|
component: MsonPlanar,
|
||||||
parent: Object3D,
|
parent: Object3D,
|
||||||
parentComponent?: MsonComponent,
|
parentComponent?: MsonComponent,
|
||||||
|
texture?: MsonTexture,
|
||||||
) {
|
) {
|
||||||
const wrapper = new Object3D();
|
const wrapper = new Object3D();
|
||||||
wrapper.name = name;
|
wrapper.name = name;
|
||||||
wrapper.userData.type = component.type;
|
wrapper.userData.type = component.type;
|
||||||
|
wrapper.userData.implementation = component.implementation;
|
||||||
parent.add(wrapper);
|
parent.add(wrapper);
|
||||||
|
|
||||||
for (const face of MsonFaces) {
|
for (const face of MsonFaces) {
|
||||||
@ -266,7 +351,6 @@ export class ThreeBuilder {
|
|||||||
let mirror: [boolean, boolean] = [false, false];
|
let mirror: [boolean, boolean] = [false, false];
|
||||||
|
|
||||||
planePos.fromArray(faceinfo as number[]);
|
planePos.fromArray(faceinfo as number[]);
|
||||||
// planePos.setY(planePos.y * -1);
|
|
||||||
size.fromArray(faceinfo as number[], 3);
|
size.fromArray(faceinfo as number[], 3);
|
||||||
|
|
||||||
if (faceinfo.length > 5) {
|
if (faceinfo.length > 5) {
|
||||||
@ -276,7 +360,20 @@ export class ThreeBuilder {
|
|||||||
if (faceinfo.length > 7) {
|
if (faceinfo.length > 7) {
|
||||||
mirror = [faceinfo[7] as boolean, faceinfo[8] as boolean];
|
mirror = [faceinfo[7] as boolean, faceinfo[8] as boolean];
|
||||||
}
|
}
|
||||||
this.makeMsonPlaneFace(face, wrapper, planePos, size, uv, mirror, true);
|
this.makeMsonPlaneFace(
|
||||||
|
face,
|
||||||
|
wrapper,
|
||||||
|
planePos,
|
||||||
|
size,
|
||||||
|
faceinfo.length > 5
|
||||||
|
? {
|
||||||
|
...texture,
|
||||||
|
u: uv.x,
|
||||||
|
v: uv.y,
|
||||||
|
}
|
||||||
|
: texture,
|
||||||
|
mirror,
|
||||||
|
);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
wrapper.updateMatrix();
|
wrapper.updateMatrix();
|
||||||
@ -287,44 +384,39 @@ export class ThreeBuilder {
|
|||||||
component: MsonPlane,
|
component: MsonPlane,
|
||||||
parent: Object3D,
|
parent: Object3D,
|
||||||
parentComponent?: MsonComponent,
|
parentComponent?: MsonComponent,
|
||||||
|
texture?: MsonTexture,
|
||||||
) {
|
) {
|
||||||
const wrapper = new Object3D();
|
const wrapper = new Object3D();
|
||||||
wrapper.name = name;
|
wrapper.name = name;
|
||||||
wrapper.userData.type = component.type;
|
wrapper.userData.type = component.type;
|
||||||
|
wrapper.userData.implementation = component.implementation;
|
||||||
parent.add(wrapper);
|
parent.add(wrapper);
|
||||||
|
|
||||||
const planePos = new Vector3();
|
const planePos = new Vector3();
|
||||||
const size = new Vector2();
|
const size = new Vector2();
|
||||||
const uv = new Vector2();
|
|
||||||
|
|
||||||
let mirror: [boolean, boolean] = [false, false];
|
let mirror: [boolean, boolean] = [false, false];
|
||||||
|
|
||||||
if (component.position) planePos.fromArray(component.position as number[]);
|
if (component.position) planePos.fromArray(component.position as number[]);
|
||||||
if (component.size) size.fromArray(component.size as number[]);
|
if (component.size) size.fromArray(component.size as number[]);
|
||||||
|
|
||||||
uv.set(component.texture?.u ?? 0, component.texture?.v ?? 0);
|
|
||||||
|
|
||||||
if (component.mirror) mirror = component.mirror;
|
if (component.mirror) mirror = component.mirror;
|
||||||
this.makeMsonPlaneFace(
|
this.makeMsonPlaneFace(
|
||||||
component.face as MsonFace,
|
component.face as MsonFace,
|
||||||
wrapper,
|
wrapper,
|
||||||
planePos,
|
planePos,
|
||||||
size,
|
size,
|
||||||
uv,
|
texture,
|
||||||
mirror,
|
mirror,
|
||||||
true,
|
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
protected createWrapper(
|
protected createWrapper(name: string, component: MsonBaseComponent) {
|
||||||
name: string,
|
|
||||||
typeName: string,
|
|
||||||
component: MsonBaseComponent,
|
|
||||||
) {
|
|
||||||
let wrapper = new Object3D();
|
let wrapper = new Object3D();
|
||||||
wrapper.name = name;
|
wrapper.name = name;
|
||||||
wrapper.userData.type = typeName;
|
wrapper.userData.type = component.type || 'mson:component';
|
||||||
wrapper.visible = component.visible ?? true;
|
wrapper.visible = component.visible ?? true;
|
||||||
|
wrapper.userData.implementation = component.implementation;
|
||||||
|
|
||||||
const rotate = new Vector3();
|
const rotate = new Vector3();
|
||||||
if (component?.rotate) {
|
if (component?.rotate) {
|
||||||
|
276
src/three/uv.ts
Normal file
276
src/three/uv.ts
Normal file
@ -0,0 +1,276 @@
|
|||||||
|
import {
|
||||||
|
BoxGeometry,
|
||||||
|
BufferAttribute,
|
||||||
|
PlaneGeometry,
|
||||||
|
Vector2,
|
||||||
|
Vector3,
|
||||||
|
Vector4,
|
||||||
|
} from 'three';
|
||||||
|
import { MsonFace, MsonTexture } from '../mson';
|
||||||
|
import { convertRange } from '../util/num-range';
|
||||||
|
|
||||||
|
export class UVMapper {
|
||||||
|
// px nx py ny pz nz
|
||||||
|
static boxFaceOrder = ['west', 'east', 'up', 'down', 'north', 'south'];
|
||||||
|
static boxWidthSide: Record<string, 'x' | 'y' | 'z'> = {
|
||||||
|
east: 'z',
|
||||||
|
west: 'z',
|
||||||
|
up: 'x',
|
||||||
|
down: 'x',
|
||||||
|
south: 'x',
|
||||||
|
north: 'x',
|
||||||
|
};
|
||||||
|
|
||||||
|
static boxHeightSide: Record<string, 'x' | 'y' | 'z'> = {
|
||||||
|
east: 'y',
|
||||||
|
west: 'y',
|
||||||
|
up: 'z',
|
||||||
|
down: 'z',
|
||||||
|
south: 'y',
|
||||||
|
north: 'y',
|
||||||
|
};
|
||||||
|
|
||||||
|
static planeWidthSide: Record<string, 'x' | 'y'> = {
|
||||||
|
east: 'x',
|
||||||
|
west: 'x',
|
||||||
|
up: 'x',
|
||||||
|
down: 'x',
|
||||||
|
south: 'x',
|
||||||
|
north: 'x',
|
||||||
|
};
|
||||||
|
|
||||||
|
static planeHeightSide: Record<string, 'x' | 'y'> = {
|
||||||
|
east: 'y',
|
||||||
|
west: 'y',
|
||||||
|
up: 'y',
|
||||||
|
down: 'y',
|
||||||
|
south: 'y',
|
||||||
|
north: 'y',
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param sizeU Size of quad U
|
||||||
|
* @param sizeV Size of quad V
|
||||||
|
* @param txU Texture width
|
||||||
|
* @param txV Texture height
|
||||||
|
* @param setU Specified U offset
|
||||||
|
* @param setV Specified V offset
|
||||||
|
* @param invertU Invert U axis
|
||||||
|
* @param invertV Invert V axis
|
||||||
|
*/
|
||||||
|
static uvOffsetConvert(
|
||||||
|
sizeU: number,
|
||||||
|
sizeV: number,
|
||||||
|
txU: number,
|
||||||
|
txV: number,
|
||||||
|
setU: number = 0,
|
||||||
|
setV: number = 0,
|
||||||
|
invertU = false,
|
||||||
|
invertV = false,
|
||||||
|
) {
|
||||||
|
/*
|
||||||
|
* UV coordinate system
|
||||||
|
* 0,1 1,1
|
||||||
|
* V
|
||||||
|
* |
|
||||||
|
* |
|
||||||
|
* |
|
||||||
|
* |
|
||||||
|
* +-----------U
|
||||||
|
* 0,0 1,0
|
||||||
|
*/
|
||||||
|
|
||||||
|
/*
|
||||||
|
* txU equivalent to 1
|
||||||
|
* txV equivalent to 1
|
||||||
|
*
|
||||||
|
* 0 txU
|
||||||
|
* 0 -------------------- 1
|
||||||
|
* | |
|
||||||
|
* setU +sizeU
|
||||||
|
* u1 u2
|
||||||
|
*/
|
||||||
|
|
||||||
|
const uStart = convertRange(setU, [0, txU], [0, 1]);
|
||||||
|
const uEnd = convertRange(setU + sizeU, [0, txU], [0, 1]);
|
||||||
|
const vStart = 1 - convertRange(setV, [0, txV], [0, 1]);
|
||||||
|
const vEnd = 1 - convertRange(setV + sizeV, [0, txV], [0, 1]);
|
||||||
|
|
||||||
|
return new Vector4(
|
||||||
|
invertU ? uEnd : uStart,
|
||||||
|
invertU ? uStart : uEnd,
|
||||||
|
invertV ? vEnd : vStart,
|
||||||
|
invertV ? vStart : vEnd,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
static unwrapBox(size: Vector3) {
|
||||||
|
const widths = UVMapper.boxFaceOrder.map(
|
||||||
|
(item) => size[UVMapper.boxWidthSide[item]],
|
||||||
|
);
|
||||||
|
const heights = UVMapper.boxFaceOrder.map(
|
||||||
|
(item) => size[UVMapper.boxHeightSide[item]],
|
||||||
|
);
|
||||||
|
const layout: any = {
|
||||||
|
up: { x: 'west', y: 0 },
|
||||||
|
down: { x: 'up', y: 0 },
|
||||||
|
|
||||||
|
west: { x: 0, y: 'up' },
|
||||||
|
south: { x: 'west', y: 'up' },
|
||||||
|
east: { x: 'south', y: 'down' },
|
||||||
|
north: { x: 'east', y: 'down' },
|
||||||
|
};
|
||||||
|
Object.keys(layout).forEach((key) => {
|
||||||
|
const entry = layout[key];
|
||||||
|
if (typeof entry.x === 'string') {
|
||||||
|
const prev = layout[entry.x];
|
||||||
|
entry.x = prev.x + widths[UVMapper.boxFaceOrder.indexOf(entry.x)];
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof entry.y === 'string') {
|
||||||
|
const prev = layout[entry.y];
|
||||||
|
entry.y = prev.y + heights[UVMapper.boxFaceOrder.indexOf(entry.y)];
|
||||||
|
}
|
||||||
|
});
|
||||||
|
return layout;
|
||||||
|
}
|
||||||
|
|
||||||
|
static mapBoxUVs(
|
||||||
|
size: Vector3,
|
||||||
|
geometry: BoxGeometry,
|
||||||
|
texture?: MsonTexture,
|
||||||
|
mirror?: boolean | [boolean, boolean, boolean],
|
||||||
|
) {
|
||||||
|
const uvAttribute = geometry.getAttribute('uv') as BufferAttribute;
|
||||||
|
const index = geometry.getIndex() as BufferAttribute;
|
||||||
|
const uv = new Vector2();
|
||||||
|
|
||||||
|
const layout = UVMapper.unwrapBox(size);
|
||||||
|
|
||||||
|
let textureWidth = texture?.w ?? 64;
|
||||||
|
let textureHeight = texture?.h ?? 64;
|
||||||
|
let textureOffsetU = texture?.u ?? 0;
|
||||||
|
let textureOffsetV = texture?.v ?? 0;
|
||||||
|
|
||||||
|
const invU: string[] = ['up'];
|
||||||
|
const invV: string[] = ['up'];
|
||||||
|
|
||||||
|
const mirroring =
|
||||||
|
mirror !== undefined
|
||||||
|
? Array.isArray(mirror)
|
||||||
|
? mirror
|
||||||
|
: [mirror, mirror, mirror]
|
||||||
|
: [false, false, false];
|
||||||
|
|
||||||
|
// TODO: figure out what rest of the components mirror
|
||||||
|
const [flipX, flipY, flipZ] = mirroring;
|
||||||
|
if (flipX) {
|
||||||
|
invU.push('south', 'north');
|
||||||
|
}
|
||||||
|
|
||||||
|
// px nx py ny pz nz
|
||||||
|
// clockwise count
|
||||||
|
for (let i = 0; i < index!.count; i += 3) {
|
||||||
|
const faceName = UVMapper.boxFaceOrder[Math.floor(i / 6)];
|
||||||
|
const triIndex = Math.ceil((i / 6) % 1);
|
||||||
|
const sizeU = size[UVMapper.boxWidthSide[faceName]];
|
||||||
|
const sizeV = size[UVMapper.boxHeightSide[faceName]];
|
||||||
|
const addVOffset = layout[faceName].y;
|
||||||
|
const addUOffset = layout[faceName].x;
|
||||||
|
let finalU = textureOffsetU + addUOffset;
|
||||||
|
let finalV = textureOffsetV + addVOffset;
|
||||||
|
const faceCoords = UVMapper.uvOffsetConvert(
|
||||||
|
sizeU,
|
||||||
|
sizeV,
|
||||||
|
textureWidth,
|
||||||
|
textureHeight,
|
||||||
|
finalU,
|
||||||
|
finalV,
|
||||||
|
invU.includes(faceName),
|
||||||
|
invV.includes(faceName),
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let attrib = 0; attrib < 3; attrib++) {
|
||||||
|
const a = index?.getX(i + attrib);
|
||||||
|
uv.fromBufferAttribute(uvAttribute, a);
|
||||||
|
|
||||||
|
// -> bottom left, top left, bottom right
|
||||||
|
if (triIndex === 0) {
|
||||||
|
if (attrib === 0) uv.set(faceCoords.x, faceCoords.z);
|
||||||
|
if (attrib === 1) uv.set(faceCoords.x, faceCoords.w);
|
||||||
|
if (attrib === 2) uv.set(faceCoords.y, faceCoords.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -> top left, top right, bottom right
|
||||||
|
if (triIndex === 1) {
|
||||||
|
if (attrib === 0) uv.set(faceCoords.x, faceCoords.w);
|
||||||
|
if (attrib === 1) uv.set(faceCoords.y, faceCoords.w);
|
||||||
|
if (attrib === 2) uv.set(faceCoords.y, faceCoords.z);
|
||||||
|
}
|
||||||
|
uvAttribute.setXY(a, uv.x, uv.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
static mapPlanarUvs(
|
||||||
|
face: MsonFace,
|
||||||
|
size: Vector2,
|
||||||
|
geometry: PlaneGeometry,
|
||||||
|
texture?: MsonTexture,
|
||||||
|
mirror?: boolean | [boolean, boolean],
|
||||||
|
) {
|
||||||
|
const uvAttribute = geometry.getAttribute('uv') as BufferAttribute;
|
||||||
|
const index = geometry.getIndex() as BufferAttribute;
|
||||||
|
const uv = new Vector2();
|
||||||
|
|
||||||
|
let textureWidth = texture?.w ?? 64;
|
||||||
|
let textureHeight = texture?.h ?? 64;
|
||||||
|
let textureOffsetU = texture?.u ?? 0;
|
||||||
|
let textureOffsetV = texture?.v ?? 0;
|
||||||
|
|
||||||
|
const mirroring =
|
||||||
|
mirror !== undefined
|
||||||
|
? Array.isArray(mirror)
|
||||||
|
? mirror
|
||||||
|
: [mirror, mirror]
|
||||||
|
: [false, false];
|
||||||
|
|
||||||
|
for (let i = 0; i < index!.count; i += 3) {
|
||||||
|
const triIndex = Math.ceil((i / 6) % 1);
|
||||||
|
const sizeU = size[UVMapper.planeWidthSide[face]];
|
||||||
|
const sizeV = size[UVMapper.planeHeightSide[face]];
|
||||||
|
let finalU = textureOffsetU;
|
||||||
|
let finalV = textureOffsetV;
|
||||||
|
const faceCoords = UVMapper.uvOffsetConvert(
|
||||||
|
sizeU,
|
||||||
|
sizeV,
|
||||||
|
textureWidth,
|
||||||
|
textureHeight,
|
||||||
|
finalU,
|
||||||
|
finalV,
|
||||||
|
!mirroring[0],
|
||||||
|
mirroring[1],
|
||||||
|
);
|
||||||
|
|
||||||
|
for (let attrib = 0; attrib < 3; attrib++) {
|
||||||
|
const a = index?.getX(i + attrib);
|
||||||
|
uv.fromBufferAttribute(uvAttribute, a);
|
||||||
|
|
||||||
|
// -> bottom left, top left, bottom right
|
||||||
|
if (triIndex === 0) {
|
||||||
|
if (attrib === 0) uv.set(faceCoords.x, faceCoords.z);
|
||||||
|
if (attrib === 1) uv.set(faceCoords.x, faceCoords.w);
|
||||||
|
if (attrib === 2) uv.set(faceCoords.y, faceCoords.z);
|
||||||
|
}
|
||||||
|
|
||||||
|
// -> top left, top right, bottom right
|
||||||
|
if (triIndex === 1) {
|
||||||
|
if (attrib === 0) uv.set(faceCoords.x, faceCoords.w);
|
||||||
|
if (attrib === 1) uv.set(faceCoords.y, faceCoords.w);
|
||||||
|
if (attrib === 2) uv.set(faceCoords.y, faceCoords.z);
|
||||||
|
}
|
||||||
|
uvAttribute.setXY(a, uv.x, uv.y);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
import { ModelStore } from '../mson';
|
import { ModelStore, MsonEvaluatedModel } from '../mson';
|
||||||
import { promises as fs } from 'node:fs';
|
import { promises as fs } from 'node:fs';
|
||||||
import { join } from 'node:path';
|
import { join } from 'node:path';
|
||||||
import { Object3D } from 'three';
|
import { Object3D } from 'three';
|
||||||
@ -54,3 +54,21 @@ export const saveGeometry = async (
|
|||||||
join(root, `${name}.json`),
|
join(root, `${name}.json`),
|
||||||
JSON.stringify(object.toJSON()),
|
JSON.stringify(object.toJSON()),
|
||||||
);
|
);
|
||||||
|
|
||||||
|
export const saveModel = async (
|
||||||
|
root: string,
|
||||||
|
name: string,
|
||||||
|
data: MsonEvaluatedModel,
|
||||||
|
) =>
|
||||||
|
await fs.writeFile(
|
||||||
|
join(root, `${name}.mson.json`),
|
||||||
|
JSON.stringify(
|
||||||
|
{
|
||||||
|
texture: data.texture,
|
||||||
|
locals: data._localsEvaluated,
|
||||||
|
data: data._dataEvaluated,
|
||||||
|
},
|
||||||
|
undefined,
|
||||||
|
2,
|
||||||
|
),
|
||||||
|
);
|
||||||
|
7
src/util/num-range.ts
Normal file
7
src/util/num-range.ts
Normal file
@ -0,0 +1,7 @@
|
|||||||
|
export function convertRange(
|
||||||
|
value: number,
|
||||||
|
r1: [number, number],
|
||||||
|
r2: [number, number],
|
||||||
|
) {
|
||||||
|
return ((value - r1[0]) * (r2[1] - r2[0])) / (r1[1] - r1[0]) + r2[0];
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user