code stash
This commit is contained in:
commit
b5c0adca22
2
.gitignore
vendored
Normal file
2
.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
node_modules
|
||||||
|
lib
|
5
.prettierrc
Normal file
5
.prettierrc
Normal file
@ -0,0 +1,5 @@
|
|||||||
|
{
|
||||||
|
"semi": true,
|
||||||
|
"singleQuote": true,
|
||||||
|
"trailingComma": "all"
|
||||||
|
}
|
3
.vscode/settings.json
vendored
Normal file
3
.vscode/settings.json
vendored
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
{
|
||||||
|
"editor.formatOnSave": true
|
||||||
|
}
|
13
README.md
Normal file
13
README.md
Normal file
@ -0,0 +1,13 @@
|
|||||||
|
# Mson -> THREE
|
||||||
|
|
||||||
|
WIP do not use
|
||||||
|
|
||||||
|
Parse [Mson](https://github.com/MineLittlePony/Mson).
|
||||||
|
|
||||||
|
## Setup
|
||||||
|
|
||||||
|
1. copy the folder at https://github.com/MineLittlePony/Mson/tree/1.20.2/src/main/resources/assets/mson/models/entity as `inputs/mson`
|
||||||
|
2. build `npm run build`
|
||||||
|
3. export `node lib/test-node.js`
|
||||||
|
|
||||||
|
THREE object will be placed in `outputs`
|
2
inputs/.gitignore
vendored
Normal file
2
inputs/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
2
outputs/.gitignore
vendored
Normal file
2
outputs/.gitignore
vendored
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
*
|
||||||
|
!.gitignore
|
106
package-lock.json
generated
Normal file
106
package-lock.json
generated
Normal file
@ -0,0 +1,106 @@
|
|||||||
|
{
|
||||||
|
"name": "mson-three",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"lockfileVersion": 3,
|
||||||
|
"requires": true,
|
||||||
|
"packages": {
|
||||||
|
"": {
|
||||||
|
"name": "mson-three",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"license": "ISC",
|
||||||
|
"dependencies": {
|
||||||
|
"three": "^0.161.0"
|
||||||
|
},
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.19",
|
||||||
|
"@types/three": "^0.161.2",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/node": {
|
||||||
|
"version": "20.11.19",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/node/-/node-20.11.19.tgz",
|
||||||
|
"integrity": "sha512-7xMnVEcZFu0DikYjWOlRq7NTPETrm7teqUT2WkQjrTIkEgUyyGdWsj/Zg8bEJt5TNklzbPD1X3fqfsHw3SpapQ==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"undici-types": "~5.26.4"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/stats.js": {
|
||||||
|
"version": "0.17.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/stats.js/-/stats.js-0.17.3.tgz",
|
||||||
|
"integrity": "sha512-pXNfAD3KHOdif9EQXZ9deK82HVNaXP5ZIF5RP2QG6OQFNTaY2YIetfrE9t528vEreGQvEPRDDc8muaoYeK0SxQ==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/@types/three": {
|
||||||
|
"version": "0.161.2",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/three/-/three-0.161.2.tgz",
|
||||||
|
"integrity": "sha512-DazpZ+cIfBzbW/p0zm6G8CS03HBMd748A3R1ZOXHpqaXZLv2I5zNgQUrRG//UfJ6zYFp2cUoCQaOLaz8ubH07w==",
|
||||||
|
"dev": true,
|
||||||
|
"dependencies": {
|
||||||
|
"@types/stats.js": "*",
|
||||||
|
"@types/webxr": "*",
|
||||||
|
"fflate": "~0.6.10",
|
||||||
|
"meshoptimizer": "~0.18.1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/@types/webxr": {
|
||||||
|
"version": "0.5.14",
|
||||||
|
"resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.14.tgz",
|
||||||
|
"integrity": "sha512-UEMMm/Xn3DtEa+gpzUrOcDj+SJS1tk5YodjwOxcqStNhCfPcwgyC5Srg2ToVKyg2Fhq16Ffpb0UWUQHqoT9AMA==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/fflate": {
|
||||||
|
"version": "0.6.10",
|
||||||
|
"resolved": "https://registry.npmjs.org/fflate/-/fflate-0.6.10.tgz",
|
||||||
|
"integrity": "sha512-IQrh3lEPM93wVCEczc9SaAOvkmcoQn/G8Bo1e8ZPlY3X3bnAxWaBdvTdvM1hP62iZp0BXWDy4vTAy4fF0+Dlpg==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/meshoptimizer": {
|
||||||
|
"version": "0.18.1",
|
||||||
|
"resolved": "https://registry.npmjs.org/meshoptimizer/-/meshoptimizer-0.18.1.tgz",
|
||||||
|
"integrity": "sha512-ZhoIoL7TNV4s5B6+rx5mC//fw8/POGyNxS/DZyCJeiZ12ScLfVwRE/GfsxwiTkMYYD5DmK2/JXnEVXqL4rF+Sw==",
|
||||||
|
"dev": true
|
||||||
|
},
|
||||||
|
"node_modules/prettier": {
|
||||||
|
"version": "3.2.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
|
||||||
|
"integrity": "sha512-3/GWa9aOC0YeD7LUfvOG2NiDyhOWRvt1k+rcKhOuYnMY24iiCphgneUfJDyFXd6rZCAnuLBv6UeAULtrhT/F4A==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"prettier": "bin/prettier.cjs"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14"
|
||||||
|
},
|
||||||
|
"funding": {
|
||||||
|
"url": "https://github.com/prettier/prettier?sponsor=1"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/three": {
|
||||||
|
"version": "0.161.0",
|
||||||
|
"resolved": "https://registry.npmjs.org/three/-/three-0.161.0.tgz",
|
||||||
|
"integrity": "sha512-LC28VFtjbOyEu5b93K0bNRLw1rQlMJ85lilKsYj6dgTu+7i17W+JCCEbvrpmNHF1F3NAUqDSWq50UD7w9H2xQw=="
|
||||||
|
},
|
||||||
|
"node_modules/typescript": {
|
||||||
|
"version": "5.3.3",
|
||||||
|
"resolved": "https://registry.npmjs.org/typescript/-/typescript-5.3.3.tgz",
|
||||||
|
"integrity": "sha512-pXWcraxM0uxAS+tN0AG/BF2TyqmHO014Z070UsJ+pFvYuRSq8KH8DmWpnbXe0pEPDHXZV3FcAbJkijJ5oNEnWw==",
|
||||||
|
"dev": true,
|
||||||
|
"bin": {
|
||||||
|
"tsc": "bin/tsc",
|
||||||
|
"tsserver": "bin/tsserver"
|
||||||
|
},
|
||||||
|
"engines": {
|
||||||
|
"node": ">=14.17"
|
||||||
|
}
|
||||||
|
},
|
||||||
|
"node_modules/undici-types": {
|
||||||
|
"version": "5.26.5",
|
||||||
|
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
|
||||||
|
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
|
||||||
|
"dev": true
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
23
package.json
Normal file
23
package.json
Normal file
@ -0,0 +1,23 @@
|
|||||||
|
{
|
||||||
|
"name": "mson-three",
|
||||||
|
"version": "1.0.0",
|
||||||
|
"description": "",
|
||||||
|
"main": "index.js",
|
||||||
|
"scripts": {
|
||||||
|
"test": "echo \"Error: no test specified\" && exit 1",
|
||||||
|
"build": "tsc",
|
||||||
|
"watch": "tsc -w"
|
||||||
|
},
|
||||||
|
"keywords": [],
|
||||||
|
"author": "",
|
||||||
|
"license": "ISC",
|
||||||
|
"devDependencies": {
|
||||||
|
"@types/node": "^20.11.19",
|
||||||
|
"@types/three": "^0.161.2",
|
||||||
|
"prettier": "^3.2.5",
|
||||||
|
"typescript": "^5.3.3"
|
||||||
|
},
|
||||||
|
"dependencies": {
|
||||||
|
"three": "^0.161.0"
|
||||||
|
}
|
||||||
|
}
|
1
src/index.ts
Normal file
1
src/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './mson';
|
304
src/mson/eval.ts
Normal file
304
src/mson/eval.ts
Normal file
@ -0,0 +1,304 @@
|
|||||||
|
import { deepClone } from '../util/deep-clone';
|
||||||
|
import { isObject } from '../util/merge-deep';
|
||||||
|
import {
|
||||||
|
MsonComponent,
|
||||||
|
MsonData,
|
||||||
|
MsonEvaluatedLocals,
|
||||||
|
MsonEvaluatedModel,
|
||||||
|
MsonExpression,
|
||||||
|
MsonLocals,
|
||||||
|
MsonSlot,
|
||||||
|
MsonVariable,
|
||||||
|
} from './mson.type';
|
||||||
|
import { ModelStore } from './store';
|
||||||
|
|
||||||
|
type MsonVariables = Record<string, number>;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This class is used to evaluate all variables within a MSON model definition.
|
||||||
|
*/
|
||||||
|
export class MsonEvaluate {
|
||||||
|
constructor(protected readonly store: ModelStore) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate a model, resolving all of its references and parameters.
|
||||||
|
* @param model MSON data to evaluate
|
||||||
|
* @param useLocals Override locals
|
||||||
|
* @returns Evaluated model, with (hopefully) all variables replaced.
|
||||||
|
*/
|
||||||
|
evaluateModel(
|
||||||
|
model: string,
|
||||||
|
useLocals?: MsonEvaluatedLocals,
|
||||||
|
): MsonEvaluatedModel {
|
||||||
|
const modelData = this.store.getModelByName(model);
|
||||||
|
if (!modelData) {
|
||||||
|
throw new Error(`Model ${model} not found for evaluation`);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Deep copy the model
|
||||||
|
const localCopy = deepClone(modelData) as MsonEvaluatedModel;
|
||||||
|
localCopy.name = model;
|
||||||
|
localCopy._locals = { ...(localCopy.locals || {}) };
|
||||||
|
localCopy._data = { ...localCopy.data };
|
||||||
|
|
||||||
|
// Evaluate parents
|
||||||
|
if (modelData.parent) {
|
||||||
|
localCopy._parent = this.evaluateModel(modelData.parent);
|
||||||
|
|
||||||
|
// Overwrite / combine parent locals with current locals
|
||||||
|
localCopy._locals = {
|
||||||
|
...(localCopy._parent._locals || {}),
|
||||||
|
...localCopy._locals,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Overwrite / combine parent data with current data
|
||||||
|
localCopy._data = {
|
||||||
|
...(localCopy._parent._data || {}),
|
||||||
|
...localCopy._data,
|
||||||
|
};
|
||||||
|
|
||||||
|
// Inherit texture parameters
|
||||||
|
localCopy.texture = {
|
||||||
|
...(localCopy._parent?.texture || {}),
|
||||||
|
...(localCopy.texture || {}),
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Overwrite / merge locals
|
||||||
|
if (useLocals) {
|
||||||
|
localCopy._locals = {
|
||||||
|
...localCopy._locals,
|
||||||
|
...useLocals,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate locals with globally combined parameters
|
||||||
|
this.evaluateLocals(localCopy);
|
||||||
|
|
||||||
|
// Recursively substitute all locals within the data.
|
||||||
|
// Also, fill all slots.
|
||||||
|
localCopy._dataEvaluated = this.recursiveSubstitute(
|
||||||
|
localCopy._data,
|
||||||
|
localCopy._localsEvaluated,
|
||||||
|
);
|
||||||
|
|
||||||
|
return localCopy;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate a MSON expression (arithmetic)
|
||||||
|
* @param expression Mson Expression definition
|
||||||
|
* @param variables Scope variables
|
||||||
|
*/
|
||||||
|
protected evaluateExpression(
|
||||||
|
expression: MsonExpression,
|
||||||
|
variables: MsonVariables = {},
|
||||||
|
) {
|
||||||
|
if (!this.isExpression(expression)) {
|
||||||
|
throw new Error('Invalid expression');
|
||||||
|
}
|
||||||
|
|
||||||
|
let [src, oper, trg] = expression;
|
||||||
|
|
||||||
|
if (this.isExpression(src)) {
|
||||||
|
// TODO: resolve as unknown, probably with a tuple of some kind
|
||||||
|
src = this.evaluateExpression(
|
||||||
|
src as unknown as MsonExpression,
|
||||||
|
variables,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isExpression(trg)) {
|
||||||
|
// TODO: resolve as unknown, probably with a tuple of some kind
|
||||||
|
trg = this.evaluateExpression(
|
||||||
|
trg as unknown as MsonExpression,
|
||||||
|
variables,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
src = this.substitute(src, variables);
|
||||||
|
trg = this.substitute(trg, variables);
|
||||||
|
|
||||||
|
// Assertions
|
||||||
|
if (isNaN(src)) {
|
||||||
|
throw new Error(
|
||||||
|
`Expression evaluation [${expression}] failed: ${src} is NaN`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (isNaN(trg)) {
|
||||||
|
throw new Error(
|
||||||
|
`Expression evaluation [${expression}] failed: ${trg} is NaN`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!['+', '-', '*', '/', '%', '^'].includes(oper)) {
|
||||||
|
throw new Error(
|
||||||
|
`Expression evaluation [${expression}] failed: ${oper} is not supported`,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Operators
|
||||||
|
switch (oper) {
|
||||||
|
case '+':
|
||||||
|
return src + trg;
|
||||||
|
case '-':
|
||||||
|
return src - trg;
|
||||||
|
case '*':
|
||||||
|
return src * trg;
|
||||||
|
case '/':
|
||||||
|
return src / trg;
|
||||||
|
case '%':
|
||||||
|
return src % trg;
|
||||||
|
case '^':
|
||||||
|
return Math.pow(src, trg);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Determine if input is an expression or not.
|
||||||
|
* @param input Questionable input
|
||||||
|
* @returns Is expression or not
|
||||||
|
*/
|
||||||
|
protected isExpression(input: MsonVariable | MsonExpression) {
|
||||||
|
return (
|
||||||
|
Array.isArray(input) &&
|
||||||
|
input.length === 3 &&
|
||||||
|
typeof input[1] === 'string' &&
|
||||||
|
!input[1].startsWith('#')
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Substitute input with variables or return input.
|
||||||
|
* @param input Input to substitute
|
||||||
|
* @param variables Pool of variables
|
||||||
|
* @returns Substituted value
|
||||||
|
*/
|
||||||
|
protected substitute(input: MsonVariable, variables: MsonVariables = {}) {
|
||||||
|
if (typeof input !== 'string') return input;
|
||||||
|
const key = input.replace('#', '');
|
||||||
|
return variables[key] ?? input;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Substitute ALL variables recursively.
|
||||||
|
* Also fills slots. (TODO: separate)
|
||||||
|
* @param input Model data, a component or component metadata
|
||||||
|
* @param variables Variables to use
|
||||||
|
* @returns Substituted object
|
||||||
|
*/
|
||||||
|
protected recursiveSubstitute(
|
||||||
|
input: MsonData | MsonComponent,
|
||||||
|
variables: MsonVariables = {},
|
||||||
|
) {
|
||||||
|
const keyList = Object.keys(input);
|
||||||
|
|
||||||
|
// Handle slots, include the child model with current locals already applied.
|
||||||
|
const asSlot = input as MsonSlot;
|
||||||
|
if (keyList.includes('data') && typeof asSlot.data === 'string') {
|
||||||
|
const evaluateChild = this.evaluateModel(
|
||||||
|
asSlot.data as string,
|
||||||
|
asSlot.locals
|
||||||
|
? this.evaluateLocalsObject(asSlot.locals, variables)
|
||||||
|
: variables,
|
||||||
|
);
|
||||||
|
|
||||||
|
return {
|
||||||
|
type: 'mson:slot',
|
||||||
|
implementation: asSlot.implementation,
|
||||||
|
data: evaluateChild._dataEvaluated,
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
return keyList.reduce<any>((item, key) => {
|
||||||
|
const value = item[key];
|
||||||
|
|
||||||
|
// Do not substitute locals or generated values here.
|
||||||
|
if (key === 'locals' || key.startsWith('_')) return item;
|
||||||
|
|
||||||
|
if (isObject(value)) {
|
||||||
|
item[key] = this.recursiveSubstitute(value, variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (typeof value === 'string' && value.startsWith('#')) {
|
||||||
|
item[key] = this.substitute(value, variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (this.isExpression(value)) {
|
||||||
|
item[key] = this.evaluateExpression(value, variables);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Array.isArray(value)) {
|
||||||
|
item[key] = value.map((entry) =>
|
||||||
|
isObject(entry)
|
||||||
|
? this.recursiveSubstitute(entry, variables)
|
||||||
|
: this.substitute(entry, variables),
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return item;
|
||||||
|
}, input);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluate locals on model
|
||||||
|
* @param localCopy Local evaluated model
|
||||||
|
*/
|
||||||
|
protected evaluateLocals(localCopy: MsonEvaluatedModel) {
|
||||||
|
if (!localCopy._locals) {
|
||||||
|
if (localCopy._parent?._localsEvaluated) {
|
||||||
|
// Copy parent's globals
|
||||||
|
localCopy._localsEvaluated = deepClone(
|
||||||
|
localCopy._parent._localsEvaluated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
// Nothing more to evaluate
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
localCopy._localsEvaluated = this.evaluateLocalsObject(
|
||||||
|
localCopy._locals,
|
||||||
|
localCopy._parent?._localsEvaluated,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Substitute locals' variables and evaluate expressions.
|
||||||
|
* @param locals Locals to evaluate
|
||||||
|
* @param parentEvals Parents evaluated locals, falls back to these.
|
||||||
|
* @returns Locals with variables substituted.
|
||||||
|
*/
|
||||||
|
protected evaluateLocalsObject(
|
||||||
|
locals: MsonLocals,
|
||||||
|
parentEvals: MsonEvaluatedLocals = {},
|
||||||
|
) {
|
||||||
|
return Object.keys(locals).reduce<MsonEvaluatedLocals>(
|
||||||
|
(pool, local) => {
|
||||||
|
const currentValue = locals?.[local];
|
||||||
|
if (currentValue === undefined) return pool;
|
||||||
|
|
||||||
|
// Number type just pass on
|
||||||
|
if (typeof currentValue === 'number') {
|
||||||
|
pool[local] = currentValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Substitute strings, those are variables
|
||||||
|
if (typeof currentValue === 'string') {
|
||||||
|
pool[local] = this.substitute(currentValue, pool);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Evaluate expressions
|
||||||
|
if (this.isExpression(currentValue)) {
|
||||||
|
pool[local] = this.evaluateExpression(
|
||||||
|
currentValue as MsonExpression,
|
||||||
|
pool,
|
||||||
|
);
|
||||||
|
}
|
||||||
|
|
||||||
|
return pool;
|
||||||
|
},
|
||||||
|
{ ...parentEvals },
|
||||||
|
);
|
||||||
|
}
|
||||||
|
}
|
3
src/mson/index.ts
Normal file
3
src/mson/index.ts
Normal file
@ -0,0 +1,3 @@
|
|||||||
|
export * from './mson.type';
|
||||||
|
export * from './store';
|
||||||
|
export * from './eval';
|
269
src/mson/mson.type.ts
Normal file
269
src/mson/mson.type.ts
Normal file
@ -0,0 +1,269 @@
|
|||||||
|
import { PartialBy } from '../util/deep-partial.type';
|
||||||
|
|
||||||
|
export interface MsonTexture {
|
||||||
|
w?: number;
|
||||||
|
h?: number;
|
||||||
|
u?: number;
|
||||||
|
v?: number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MsonOperand =
|
||||||
|
| '+' /* ADD */
|
||||||
|
| '-' /* SUBTRACT */
|
||||||
|
| '*' /* MULTIPLY */
|
||||||
|
| '/' /* DIVIDE */
|
||||||
|
| '%' /* MODULUS */
|
||||||
|
| '^' /* EXPONENT */;
|
||||||
|
|
||||||
|
export type MsonVariable = string | number;
|
||||||
|
|
||||||
|
export type Vec3 = [number, number, number];
|
||||||
|
export type Vec2 = [number, number];
|
||||||
|
export type MsonVec3 = Vec3 | [MsonVariable, MsonVariable, MsonVariable];
|
||||||
|
export type MsonVec2 = Vec2 | [MsonVariable, MsonVariable];
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Mson allows you to use variables and do basic calculations within the model file.
|
||||||
|
* Unless otherwise stated, it can be assumed that every place where a numerical value
|
||||||
|
* is expected can also accept a reference to a variable (see below).
|
||||||
|
*
|
||||||
|
* Expressions with calculations can only be done in the `local` blocks where veriables are defined.
|
||||||
|
*/
|
||||||
|
export type MsonExpression = [MsonVariable, MsonOperand, MsonVariable];
|
||||||
|
|
||||||
|
export type MsonLocals = Record<string, MsonVariable | MsonExpression>;
|
||||||
|
|
||||||
|
export type MsonEvaluatedLocals = Record<string, number>;
|
||||||
|
|
||||||
|
export type MsonComponentType =
|
||||||
|
| 'mson:compound'
|
||||||
|
| 'mson:box'
|
||||||
|
| 'mson:plane'
|
||||||
|
| 'mson:planar'
|
||||||
|
| 'mson:slot'
|
||||||
|
| 'mson:cone'
|
||||||
|
| 'mson:quads'
|
||||||
|
| string;
|
||||||
|
|
||||||
|
export interface MsonBaseComponent {
|
||||||
|
type?: MsonComponentType;
|
||||||
|
/**
|
||||||
|
* Whether or not this part is visible. You shouldn't have to use this in most circumstances.
|
||||||
|
* @default true
|
||||||
|
*/
|
||||||
|
visible?: boolean;
|
||||||
|
/**
|
||||||
|
* The XYZ center of rotation of this part.
|
||||||
|
*/
|
||||||
|
pivot?: MsonVec3;
|
||||||
|
/**
|
||||||
|
* The XYZ rotation angle of this part, given in degrees.
|
||||||
|
*/
|
||||||
|
rotate?: MsonVec3;
|
||||||
|
texture?: MsonTexture;
|
||||||
|
/**
|
||||||
|
* A map of child components to load as part of this one.
|
||||||
|
*/
|
||||||
|
children?: MsonData;
|
||||||
|
__comment?: any;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MsonBox extends MsonBaseComponent {
|
||||||
|
type: 'mson:box';
|
||||||
|
/**
|
||||||
|
* The relative XYZ position of the box
|
||||||
|
*/
|
||||||
|
from: MsonVec3;
|
||||||
|
/**
|
||||||
|
* The width, height, and depth of the box
|
||||||
|
*/
|
||||||
|
size: MsonVec3;
|
||||||
|
/**
|
||||||
|
* Part-specific dilation applied to any cubes created by components defined by this part.
|
||||||
|
*/
|
||||||
|
dilate?: Vec3 | number;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MsonCompound extends MsonBaseComponent {
|
||||||
|
type: 'mson:compound';
|
||||||
|
dilate?: MsonVec3;
|
||||||
|
/**
|
||||||
|
* Whether to flip this part's textures.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
mirror?: boolean | MsonVec3;
|
||||||
|
/**
|
||||||
|
* default type for components (if omitted): mson:box
|
||||||
|
* allowed types: <mson:box|mson:cone|mson:link|mson:quads|mson:plane|mson:slot>
|
||||||
|
*/
|
||||||
|
cubes?: MsonCompoundComponent[];
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MsonFace = 'up' | 'down' | 'east' | 'west' | 'south' | 'north';
|
||||||
|
|
||||||
|
export type MsonPlanarXYZWH = [
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
];
|
||||||
|
export type MsonPlanarXYZWHUV = [
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
];
|
||||||
|
export type MsonPlanarXYZWHUVXY = [
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
MsonVariable,
|
||||||
|
boolean,
|
||||||
|
boolean,
|
||||||
|
];
|
||||||
|
/**
|
||||||
|
* First 3 are the position,
|
||||||
|
* Next two are the size,
|
||||||
|
* Last two are an optional texture coordinate. If not given, the parent's texture will be used.
|
||||||
|
*
|
||||||
|
* Two booleans indicate whether to flip the texture vertically and horizontally.
|
||||||
|
*/
|
||||||
|
export type MsonPlanarPlane =
|
||||||
|
| MsonPlanarXYZWH
|
||||||
|
| MsonPlanarXYZWHUV
|
||||||
|
| MsonPlanarXYZWHUVXY;
|
||||||
|
export interface MsonPlanar extends MsonBaseComponent {
|
||||||
|
type: 'mson:planar';
|
||||||
|
dilate?: MsonVec3;
|
||||||
|
/**
|
||||||
|
* Whether to flip this part's textures.
|
||||||
|
* @default false
|
||||||
|
*/
|
||||||
|
mirror?: boolean | MsonVec3;
|
||||||
|
up?: MsonPlanarPlane;
|
||||||
|
down?: MsonPlanarPlane;
|
||||||
|
east?: MsonPlanarPlane;
|
||||||
|
west?: MsonPlanarPlane;
|
||||||
|
south?: MsonPlanarPlane;
|
||||||
|
north?: MsonPlanarPlane;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MsonPlane extends MsonBaseComponent {
|
||||||
|
type: 'mson:plane';
|
||||||
|
face?: MsonFace;
|
||||||
|
position?: MsonVec3;
|
||||||
|
size?: MsonVec2;
|
||||||
|
mirror?: [boolean, boolean];
|
||||||
|
dilate?: MsonVec3;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MsonSlot extends MsonBaseComponent {
|
||||||
|
type: 'mson:slot';
|
||||||
|
implementation: string;
|
||||||
|
name: string;
|
||||||
|
data: MsonData | string;
|
||||||
|
locals?: MsonLocals;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MsonCone extends MsonBaseComponent {
|
||||||
|
type: 'mson:cone';
|
||||||
|
size?: MsonVec3;
|
||||||
|
from?: MsonVec3;
|
||||||
|
dilate?: MsonVariable;
|
||||||
|
taper?: MsonVariable;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MsonQuads extends MsonBaseComponent {
|
||||||
|
type: 'mson:quads';
|
||||||
|
// TODO: dunno
|
||||||
|
}
|
||||||
|
|
||||||
|
export type MsonComponent =
|
||||||
|
| MsonBox
|
||||||
|
| PartialBy<MsonCompound, 'type'>
|
||||||
|
| MsonPlanar
|
||||||
|
| MsonPlane
|
||||||
|
| MsonSlot
|
||||||
|
| MsonCone
|
||||||
|
| MsonQuads;
|
||||||
|
|
||||||
|
export type MsonCompoundComponent =
|
||||||
|
| PartialBy<MsonBox, 'type'>
|
||||||
|
| MsonCone
|
||||||
|
| MsonQuads
|
||||||
|
| MsonPlane
|
||||||
|
| MsonSlot;
|
||||||
|
|
||||||
|
export type MsonData = Record<MsonComponentType, MsonComponent>;
|
||||||
|
|
||||||
|
export interface MsonModel {
|
||||||
|
/**
|
||||||
|
* The parent file to extend from.
|
||||||
|
*/
|
||||||
|
parent?: MsonComponentType;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Texture definition specifying the default width and height for the file.
|
||||||
|
* This property is inherited by all components defined in this file
|
||||||
|
* and defining it will function similarly to defining variables in the locals block.
|
||||||
|
*/
|
||||||
|
texture?: MsonTexture;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The default growth (dilation) applied to all boxes defined by components in this file.
|
||||||
|
* Note that components may have their own dilation parameter, in which instance that value is *added*
|
||||||
|
* to this one
|
||||||
|
*
|
||||||
|
* ** DOES NOT SUPPORT VARIABLES **
|
||||||
|
*/
|
||||||
|
dilate?: Vec3;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A block of local variables that may be references by components within the data block.
|
||||||
|
* Values defined in here are typically applied over what is inherited from the parent file (if specified)
|
||||||
|
* and become available for use by the parent's components as well.
|
||||||
|
*/
|
||||||
|
locals?: MsonLocals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* default type for components (if omitted): mson:compound
|
||||||
|
* allowed types: <mson:compound|mson:slot|mson:link|mson:planar>
|
||||||
|
*/
|
||||||
|
data: MsonData;
|
||||||
|
}
|
||||||
|
|
||||||
|
export interface MsonEvaluatedModel extends MsonModel {
|
||||||
|
name: string;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combined locals from whole dependency tree
|
||||||
|
*/
|
||||||
|
_locals?: MsonLocals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluated locals
|
||||||
|
*/
|
||||||
|
_localsEvaluated?: MsonEvaluatedLocals;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Combined data from whole dependency tree
|
||||||
|
*/
|
||||||
|
_data?: MsonData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluated data with evaluated locals
|
||||||
|
*/
|
||||||
|
_dataEvaluated?: MsonData;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Evaluated parent
|
||||||
|
*/
|
||||||
|
_parent?: MsonEvaluatedModel;
|
||||||
|
}
|
24
src/mson/store.ts
Normal file
24
src/mson/store.ts
Normal file
@ -0,0 +1,24 @@
|
|||||||
|
import { DeepPartial } from '../util/deep-partial.type';
|
||||||
|
import { mergeDeep } from '../util/merge-deep';
|
||||||
|
import { MsonModel } from './mson.type';
|
||||||
|
|
||||||
|
export class ModelStore {
|
||||||
|
public componentStore = new Map<string, MsonModel>();
|
||||||
|
|
||||||
|
getModelByName(name: string) {
|
||||||
|
return this.componentStore.get(name);
|
||||||
|
}
|
||||||
|
|
||||||
|
updateModel(name: string, upsert: DeepPartial<MsonModel>) {
|
||||||
|
const existing = this.getModelByName(name);
|
||||||
|
this.componentStore.set(name, mergeDeep(existing || {}, upsert));
|
||||||
|
}
|
||||||
|
|
||||||
|
insertModel(name: string, model: MsonModel) {
|
||||||
|
this.componentStore.set(name, model);
|
||||||
|
}
|
||||||
|
|
||||||
|
listModels() {
|
||||||
|
return Array.from(this.componentStore.keys());
|
||||||
|
}
|
||||||
|
}
|
27
src/test-node.ts
Normal file
27
src/test-node.ts
Normal file
@ -0,0 +1,27 @@
|
|||||||
|
import { resolve } from 'path';
|
||||||
|
import { ModelStore, MsonEvaluate } from '.';
|
||||||
|
import { fillStoreFromFilesystem, saveGeometry } from './util/node';
|
||||||
|
import { ThreeBuilder } from './three';
|
||||||
|
import { MeshBasicMaterial } from 'three';
|
||||||
|
|
||||||
|
async function init() {
|
||||||
|
const store = new ModelStore();
|
||||||
|
const evaluate = new MsonEvaluate(store);
|
||||||
|
const mat = new MeshBasicMaterial();
|
||||||
|
const builder = new ThreeBuilder(mat);
|
||||||
|
|
||||||
|
await fillStoreFromFilesystem(store, resolve(process.cwd(), 'inputs'));
|
||||||
|
|
||||||
|
// mson:steve
|
||||||
|
// minelittlepony:steve_pony
|
||||||
|
const final = evaluate.evaluateModel('mson:biped');
|
||||||
|
// console.log(final.texture);
|
||||||
|
console.dir(final._dataEvaluated, {
|
||||||
|
depth: 20,
|
||||||
|
});
|
||||||
|
|
||||||
|
const geometry = builder.buildGeometry(final);
|
||||||
|
await saveGeometry(resolve(process.cwd(), 'outputs'), 'steve', geometry);
|
||||||
|
}
|
||||||
|
|
||||||
|
init().catch(console.error);
|
155
src/three/builder.ts
Normal file
155
src/three/builder.ts
Normal file
@ -0,0 +1,155 @@
|
|||||||
|
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 offset = new Vector3();
|
||||||
|
if (parentComponent?.pivot) {
|
||||||
|
offset.sub(new Vector3().fromArray(parentComponent.pivot as Vec3));
|
||||||
|
}
|
||||||
|
|
||||||
|
const size = new Vector3().fromArray(component.size as Vec3);
|
||||||
|
const pos = new Vector3().fromArray(component.from 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 halfOffset = offset.clone().divideScalar(2);
|
||||||
|
const halfSize = size.clone().divideScalar(2);
|
||||||
|
const halfDilation = dilate.clone().divideScalar(2);
|
||||||
|
|
||||||
|
const geometry = new BoxGeometry(
|
||||||
|
size.x + dilate.x,
|
||||||
|
size.y + dilate.y,
|
||||||
|
size.z + dilate.z,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
1,
|
||||||
|
);
|
||||||
|
|
||||||
|
geometry.translate(halfOffset.x, halfOffset.y, halfOffset.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;
|
||||||
|
}
|
||||||
|
}
|
1
src/three/index.ts
Normal file
1
src/three/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './builder';
|
2
src/util/deep-clone.ts
Normal file
2
src/util/deep-clone.ts
Normal file
@ -0,0 +1,2 @@
|
|||||||
|
export const deepClone = <T = any>(input: T): T =>
|
||||||
|
JSON.parse(JSON.stringify(input));
|
6
src/util/deep-partial.type.ts
Normal file
6
src/util/deep-partial.type.ts
Normal file
@ -0,0 +1,6 @@
|
|||||||
|
export type PartialBy<T, K extends keyof T> = Omit<T, K> & Partial<Pick<T, K>>;
|
||||||
|
export type DeepPartial<T> = T extends object
|
||||||
|
? {
|
||||||
|
[P in keyof T]?: DeepPartial<T[P]>;
|
||||||
|
}
|
||||||
|
: T;
|
31
src/util/merge-deep.ts
Normal file
31
src/util/merge-deep.ts
Normal file
@ -0,0 +1,31 @@
|
|||||||
|
/**
|
||||||
|
* Simple object check.
|
||||||
|
* @param item
|
||||||
|
* @returns {boolean}
|
||||||
|
*/
|
||||||
|
export function isObject(item: any) {
|
||||||
|
return item && typeof item === 'object' && !Array.isArray(item);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deep merge two objects.
|
||||||
|
* @param target
|
||||||
|
* @param ...sources
|
||||||
|
*/
|
||||||
|
export function mergeDeep(target: any, ...sources: any[]) {
|
||||||
|
if (!sources.length) return target;
|
||||||
|
const source = sources.shift();
|
||||||
|
|
||||||
|
if (isObject(target) && isObject(source)) {
|
||||||
|
for (const key in source) {
|
||||||
|
if (isObject(source[key])) {
|
||||||
|
if (!target[key]) Object.assign(target, { [key]: {} });
|
||||||
|
mergeDeep(target[key], source[key]);
|
||||||
|
} else {
|
||||||
|
Object.assign(target, { [key]: source[key] });
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return mergeDeep(target, ...sources);
|
||||||
|
}
|
56
src/util/node.ts
Normal file
56
src/util/node.ts
Normal file
@ -0,0 +1,56 @@
|
|||||||
|
import { ModelStore } from '../mson';
|
||||||
|
import { promises as fs } from 'node:fs';
|
||||||
|
import { join } from 'node:path';
|
||||||
|
import { Object3D } from 'three';
|
||||||
|
|
||||||
|
export const fillStoreFromFilesystem = async (
|
||||||
|
store: ModelStore,
|
||||||
|
root: string,
|
||||||
|
) => {
|
||||||
|
// Get all mod prefixes from the input directory.
|
||||||
|
const prefixes = (await fs.readdir(root, { withFileTypes: true }))
|
||||||
|
.filter((item) => item.isDirectory())
|
||||||
|
.map((item) => item.name);
|
||||||
|
|
||||||
|
// Loop through all of the prefixes
|
||||||
|
for (const prefix of prefixes) {
|
||||||
|
// Prefix absolute path
|
||||||
|
const prefixPath = join(root, prefix);
|
||||||
|
|
||||||
|
// Recurse all of the files in the mod directory
|
||||||
|
const allFiles = await fs.readdir(prefixPath, {
|
||||||
|
recursive: true,
|
||||||
|
withFileTypes: true,
|
||||||
|
});
|
||||||
|
|
||||||
|
// Loop through all JSON files
|
||||||
|
for (const dirent of allFiles) {
|
||||||
|
if (!dirent.isFile()) continue;
|
||||||
|
if (!dirent.name.endsWith('.json')) continue;
|
||||||
|
const absPath = join(dirent.path, dirent.name);
|
||||||
|
|
||||||
|
// Relative path of model
|
||||||
|
const relPath = absPath.replace(prefixPath, '');
|
||||||
|
|
||||||
|
// Model file contents
|
||||||
|
const readFile = await fs.readFile(absPath, { encoding: 'utf-8' });
|
||||||
|
const parsed = JSON.parse(readFile);
|
||||||
|
|
||||||
|
// Component name referenced from other models
|
||||||
|
const componentName = `${prefix}:${relPath.replace('.json', '').substring(1)}`;
|
||||||
|
|
||||||
|
// Insert component into the store
|
||||||
|
store.insertModel(componentName, parsed);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
export const saveGeometry = async (
|
||||||
|
root: string,
|
||||||
|
name: string,
|
||||||
|
object: Object3D,
|
||||||
|
) =>
|
||||||
|
await fs.writeFile(
|
||||||
|
join(root, `${name}.json`),
|
||||||
|
JSON.stringify(object.toJSON()),
|
||||||
|
);
|
101
tsconfig.json
Normal file
101
tsconfig.json
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
{
|
||||||
|
"compilerOptions": {
|
||||||
|
/* Visit https://aka.ms/tsconfig to read more about this file */
|
||||||
|
/* Projects */
|
||||||
|
// "incremental": true, /* Save .tsbuildinfo files to allow for incremental compilation of projects. */
|
||||||
|
// "composite": true, /* Enable constraints that allow a TypeScript project to be used with project references. */
|
||||||
|
// "tsBuildInfoFile": "./.tsbuildinfo", /* Specify the path to .tsbuildinfo incremental compilation file. */
|
||||||
|
// "disableSourceOfProjectReferenceRedirect": true, /* Disable preferring source files instead of declaration files when referencing composite projects. */
|
||||||
|
// "disableSolutionSearching": true, /* Opt a project out of multi-project reference checking when editing. */
|
||||||
|
// "disableReferencedProjectLoad": true, /* Reduce the number of projects loaded automatically by TypeScript. */
|
||||||
|
/* Language and Environment */
|
||||||
|
"target": "es2016", /* Set the JavaScript language version for emitted JavaScript and include compatible library declarations. */
|
||||||
|
// "lib": [], /* Specify a set of bundled library declaration files that describe the target runtime environment. */
|
||||||
|
// "jsx": "preserve", /* Specify what JSX code is generated. */
|
||||||
|
// "experimentalDecorators": true, /* Enable experimental support for legacy experimental decorators. */
|
||||||
|
// "emitDecoratorMetadata": true, /* Emit design-type metadata for decorated declarations in source files. */
|
||||||
|
// "jsxFactory": "", /* Specify the JSX factory function used when targeting React JSX emit, e.g. 'React.createElement' or 'h'. */
|
||||||
|
// "jsxFragmentFactory": "", /* Specify the JSX Fragment reference used for fragments when targeting React JSX emit e.g. 'React.Fragment' or 'Fragment'. */
|
||||||
|
// "jsxImportSource": "", /* Specify module specifier used to import the JSX factory functions when using 'jsx: react-jsx*'. */
|
||||||
|
// "reactNamespace": "", /* Specify the object invoked for 'createElement'. This only applies when targeting 'react' JSX emit. */
|
||||||
|
// "noLib": true, /* Disable including any library files, including the default lib.d.ts. */
|
||||||
|
// "useDefineForClassFields": true, /* Emit ECMAScript-standard-compliant class fields. */
|
||||||
|
// "moduleDetection": "auto", /* Control what method is used to detect module-format JS files. */
|
||||||
|
/* Modules */
|
||||||
|
"module": "commonjs", /* Specify what module code is generated. */
|
||||||
|
// "rootDir": "./", /* Specify the root folder within your source files. */
|
||||||
|
// "moduleResolution": "node10", /* Specify how TypeScript looks up a file from a given module specifier. */
|
||||||
|
// "baseUrl": "./", /* Specify the base directory to resolve non-relative module names. */
|
||||||
|
// "paths": {}, /* Specify a set of entries that re-map imports to additional lookup locations. */
|
||||||
|
// "rootDirs": [], /* Allow multiple folders to be treated as one when resolving modules. */
|
||||||
|
// "typeRoots": [], /* Specify multiple folders that act like './node_modules/@types'. */
|
||||||
|
// "types": [], /* Specify type package names to be included without being referenced in a source file. */
|
||||||
|
// "allowUmdGlobalAccess": true, /* Allow accessing UMD globals from modules. */
|
||||||
|
// "moduleSuffixes": [], /* List of file name suffixes to search when resolving a module. */
|
||||||
|
// "allowImportingTsExtensions": true, /* Allow imports to include TypeScript file extensions. Requires '--moduleResolution bundler' and either '--noEmit' or '--emitDeclarationOnly' to be set. */
|
||||||
|
// "resolvePackageJsonExports": true, /* Use the package.json 'exports' field when resolving package imports. */
|
||||||
|
// "resolvePackageJsonImports": true, /* Use the package.json 'imports' field when resolving imports. */
|
||||||
|
// "customConditions": [], /* Conditions to set in addition to the resolver-specific defaults when resolving imports. */
|
||||||
|
// "resolveJsonModule": true, /* Enable importing .json files. */
|
||||||
|
// "allowArbitraryExtensions": true, /* Enable importing files with any extension, provided a declaration file is present. */
|
||||||
|
// "noResolve": true, /* Disallow 'import's, 'require's or '<reference>'s from expanding the number of files TypeScript should add to a project. */
|
||||||
|
/* JavaScript Support */
|
||||||
|
// "allowJs": true, /* Allow JavaScript files to be a part of your program. Use the 'checkJS' option to get errors from these files. */
|
||||||
|
// "checkJs": true, /* Enable error reporting in type-checked JavaScript files. */
|
||||||
|
// "maxNodeModuleJsDepth": 1, /* Specify the maximum folder depth used for checking JavaScript files from 'node_modules'. Only applicable with 'allowJs'. */
|
||||||
|
/* Emit */
|
||||||
|
// "declaration": true, /* Generate .d.ts files from TypeScript and JavaScript files in your project. */
|
||||||
|
// "declarationMap": true, /* Create sourcemaps for d.ts files. */
|
||||||
|
// "emitDeclarationOnly": true, /* Only output d.ts files and not JavaScript files. */
|
||||||
|
// "sourceMap": true, /* Create source map files for emitted JavaScript files. */
|
||||||
|
// "inlineSourceMap": true, /* Include sourcemap files inside the emitted JavaScript. */
|
||||||
|
// "outFile": "./", /* Specify a file that bundles all outputs into one JavaScript file. If 'declaration' is true, also designates a file that bundles all .d.ts output. */
|
||||||
|
"outDir": "./lib", /* Specify an output folder for all emitted files. */
|
||||||
|
// "removeComments": true, /* Disable emitting comments. */
|
||||||
|
// "noEmit": true, /* Disable emitting files from a compilation. */
|
||||||
|
// "importHelpers": true, /* Allow importing helper functions from tslib once per project, instead of including them per-file. */
|
||||||
|
// "importsNotUsedAsValues": "remove", /* Specify emit/checking behavior for imports that are only used for types. */
|
||||||
|
// "downlevelIteration": true, /* Emit more compliant, but verbose and less performant JavaScript for iteration. */
|
||||||
|
// "sourceRoot": "", /* Specify the root path for debuggers to find the reference source code. */
|
||||||
|
// "mapRoot": "", /* Specify the location where debugger should locate map files instead of generated locations. */
|
||||||
|
// "inlineSources": true, /* Include source code in the sourcemaps inside the emitted JavaScript. */
|
||||||
|
// "emitBOM": true, /* Emit a UTF-8 Byte Order Mark (BOM) in the beginning of output files. */
|
||||||
|
// "newLine": "crlf", /* Set the newline character for emitting files. */
|
||||||
|
// "stripInternal": true, /* Disable emitting declarations that have '@internal' in their JSDoc comments. */
|
||||||
|
// "noEmitHelpers": true, /* Disable generating custom helper functions like '__extends' in compiled output. */
|
||||||
|
// "noEmitOnError": true, /* Disable emitting files if any type checking errors are reported. */
|
||||||
|
// "preserveConstEnums": true, /* Disable erasing 'const enum' declarations in generated code. */
|
||||||
|
// "declarationDir": "./", /* Specify the output directory for generated declaration files. */
|
||||||
|
// "preserveValueImports": true, /* Preserve unused imported values in the JavaScript output that would otherwise be removed. */
|
||||||
|
/* Interop Constraints */
|
||||||
|
// "isolatedModules": true, /* Ensure that each file can be safely transpiled without relying on other imports. */
|
||||||
|
// "verbatimModuleSyntax": true, /* Do not transform or elide any imports or exports not marked as type-only, ensuring they are written in the output file's format based on the 'module' setting. */
|
||||||
|
// "allowSyntheticDefaultImports": true, /* Allow 'import x from y' when a module doesn't have a default export. */
|
||||||
|
"esModuleInterop": true, /* Emit additional JavaScript to ease support for importing CommonJS modules. This enables 'allowSyntheticDefaultImports' for type compatibility. */
|
||||||
|
// "preserveSymlinks": true, /* Disable resolving symlinks to their realpath. This correlates to the same flag in node. */
|
||||||
|
"forceConsistentCasingInFileNames": true, /* Ensure that casing is correct in imports. */
|
||||||
|
/* Type Checking */
|
||||||
|
"strict": true, /* Enable all strict type-checking options. */
|
||||||
|
// "noImplicitAny": true, /* Enable error reporting for expressions and declarations with an implied 'any' type. */
|
||||||
|
// "strictNullChecks": true, /* When type checking, take into account 'null' and 'undefined'. */
|
||||||
|
// "strictFunctionTypes": true, /* When assigning functions, check to ensure parameters and the return values are subtype-compatible. */
|
||||||
|
// "strictBindCallApply": true, /* Check that the arguments for 'bind', 'call', and 'apply' methods match the original function. */
|
||||||
|
// "strictPropertyInitialization": true, /* Check for class properties that are declared but not set in the constructor. */
|
||||||
|
// "noImplicitThis": true, /* Enable error reporting when 'this' is given the type 'any'. */
|
||||||
|
// "useUnknownInCatchVariables": true, /* Default catch clause variables as 'unknown' instead of 'any'. */
|
||||||
|
// "alwaysStrict": true, /* Ensure 'use strict' is always emitted. */
|
||||||
|
// "noUnusedLocals": true, /* Enable error reporting when local variables aren't read. */
|
||||||
|
// "noUnusedParameters": true, /* Raise an error when a function parameter isn't read. */
|
||||||
|
// "exactOptionalPropertyTypes": true, /* Interpret optional property types as written, rather than adding 'undefined'. */
|
||||||
|
// "noImplicitReturns": true, /* Enable error reporting for codepaths that do not explicitly return in a function. */
|
||||||
|
// "noFallthroughCasesInSwitch": true, /* Enable error reporting for fallthrough cases in switch statements. */
|
||||||
|
// "noUncheckedIndexedAccess": true, /* Add 'undefined' to a type when accessed using an index. */
|
||||||
|
// "noImplicitOverride": true, /* Ensure overriding members in derived classes are marked with an override modifier. */
|
||||||
|
// "noPropertyAccessFromIndexSignature": true, /* Enforces using indexed accessors for keys declared using an indexed type. */
|
||||||
|
// "allowUnusedLabels": true, /* Disable error reporting for unused labels. */
|
||||||
|
// "allowUnreachableCode": true, /* Disable error reporting for unreachable code. */
|
||||||
|
/* Completeness */
|
||||||
|
// "skipDefaultLibCheck": true, /* Skip type checking .d.ts files that are included with TypeScript. */
|
||||||
|
"skipLibCheck": true /* Skip type checking all .d.ts files. */
|
||||||
|
}
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user