cleanup, gltf output, readme

This commit is contained in:
Evert Prants 2024-04-25 17:18:44 +03:00
parent 573c3e8f67
commit a7c1a6199f
Signed by: evert
GPG Key ID: 1688DA83D222D0B5
16 changed files with 1518 additions and 61 deletions

17
LICENSE.txt Normal file
View File

@ -0,0 +1,17 @@
ISC License
Copyright 2024 Evert "Diamond" Prants <evert@lunasqu.ee>
Permission to use, copy, modify, and/or distribute this software
for any purpose with or without fee is hereby granted, provided
that the above copyright notice and this permission notice appear
in all copies.
THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL
WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED
WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE
AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR
CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS
OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT,
NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN
CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.

View File

@ -1,13 +1,21 @@
# Mson -> THREE
WIP do not use
Parse [Mson](https://github.com/MineLittlePony/Mson).
Convert [Mson](https://github.com/MineLittlePony/Mson) models to THREE.js object JSON or GLTF.
## 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`
1. Clone and `npm install`. Optional dependencies are required when using this on Node.js (see implementation notes below).
2. copy the folder at https://github.com/MineLittlePony/Mson/tree/1.20.2/src/main/resources/assets/mson/models/entity as `inputs/mson`
3. build `npm run build`
4. run `node lib/cli.js export <model name>`
THREE object will be placed in `outputs`
Output will be placed in `outputs`. For more options run `node lib/cli.js export -h`.
## Implementation notes
1. Mson models seem to have the X and Y coordinates inverse of what THREE.js uses (and Minecraft for that matter, lol).
2. Many Mson example models do not work. They seem to be all either outdated or just outright broken. Mine Little Pony works fine, though.
3. Most of the code here makes a lot of assumptions. This project is a reverse engineering of the format, not a Java to JavaScript port of Mson's rendering engine. It should not be used in any serious capacity, as it was only made as an experiment.
4. THREE.js GLTF exporter does not support Node.js by default, so some extra junk was included to support it. You can omit pretty much the entire `optionalDependencies` array of items (and `src/cli.ts` + `src/node`) to use this library on the browser.
5. The parser and object generator by themselves have no dependencies except for THREE.js, of course.
6. Output could be further optimized by merging composite objects into a single geometry, but this is not yet done.

776
package-lock.json generated
View File

@ -8,14 +8,37 @@
"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"
},
"optionalDependencies": {
"buffer": "^6.0.3",
"canvas": "^2.11.2",
"commander": "^12.0.0",
"three": "^0.161.0"
}
},
"node_modules/@mapbox/node-pre-gyp": {
"version": "1.0.11",
"resolved": "https://registry.npmjs.org/@mapbox/node-pre-gyp/-/node-pre-gyp-1.0.11.tgz",
"integrity": "sha512-Yhlar6v9WQgUp/He7BdgzOz8lqMQ8sU+jkCq7Wx8Myc5YFJLbEe7lgui/V7G1qB1DJykHSGwreceSaD60Y0PUQ==",
"optional": true,
"dependencies": {
"detect-libc": "^2.0.0",
"https-proxy-agent": "^5.0.0",
"make-dir": "^3.1.0",
"node-fetch": "^2.6.7",
"nopt": "^5.0.0",
"npmlog": "^5.0.1",
"rimraf": "^3.0.2",
"semver": "^7.3.5",
"tar": "^6.1.11"
},
"bin": {
"node-pre-gyp": "bin/node-pre-gyp"
}
},
"node_modules/@types/node": {
@ -51,18 +74,554 @@
"integrity": "sha512-UEMMm/Xn3DtEa+gpzUrOcDj+SJS1tk5YodjwOxcqStNhCfPcwgyC5Srg2ToVKyg2Fhq16Ffpb0UWUQHqoT9AMA==",
"dev": true
},
"node_modules/abbrev": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/abbrev/-/abbrev-1.1.1.tgz",
"integrity": "sha512-nne9/IiQ/hzIhY6pdDnbBtz7DjPTKrY00P/zvPSm5pOFkl6xuGrGnXn/VtTNNfNtAfZ9/1RtehkszU9qcTii0Q==",
"optional": true
},
"node_modules/agent-base": {
"version": "6.0.2",
"resolved": "https://registry.npmjs.org/agent-base/-/agent-base-6.0.2.tgz",
"integrity": "sha512-RZNwNclF7+MS/8bDg70amg32dyeZGZxiDuQmZxKLAlQjr3jGyLx+4Kkk58UO7D2QdgFIQCovuSuZESne6RG6XQ==",
"optional": true,
"dependencies": {
"debug": "4"
},
"engines": {
"node": ">= 6.0.0"
}
},
"node_modules/ansi-regex": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
"integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
"optional": true,
"engines": {
"node": ">=8"
}
},
"node_modules/aproba": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/aproba/-/aproba-2.0.0.tgz",
"integrity": "sha512-lYe4Gx7QT+MKGbDsA+Z+he/Wtef0BiwDOlK/XkBrdfsh9J/jPPXbX0tE9x9cl27Tmu5gg3QUbUrQYa/y+KOHPQ==",
"optional": true
},
"node_modules/are-we-there-yet": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/are-we-there-yet/-/are-we-there-yet-2.0.0.tgz",
"integrity": "sha512-Ci/qENmwHnsYo9xKIcUJN5LeDKdJ6R1Z1j9V/J5wyq8nh/mYPEpIKJbBZXtZjG04HiK7zV/p6Vs9952MrMeUIw==",
"optional": true,
"dependencies": {
"delegates": "^1.0.0",
"readable-stream": "^3.6.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/balanced-match": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
"integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw==",
"optional": true
},
"node_modules/base64-js": {
"version": "1.5.1",
"resolved": "https://registry.npmjs.org/base64-js/-/base64-js-1.5.1.tgz",
"integrity": "sha512-AKpaYlHn8t4SVbOHCy+b5+KKgvR4vrsD8vbvrbiQJps7fKDTkjkDry6ji0rUJjC0kzbNePLwzxq8iypo41qeWA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"optional": true
},
"node_modules/brace-expansion": {
"version": "1.1.11",
"resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
"integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
"optional": true,
"dependencies": {
"balanced-match": "^1.0.0",
"concat-map": "0.0.1"
}
},
"node_modules/buffer": {
"version": "6.0.3",
"resolved": "https://registry.npmjs.org/buffer/-/buffer-6.0.3.tgz",
"integrity": "sha512-FTiCpNxtwiZZHEZbcbTIcZjERVICn9yq/pDFkTl95/AxzD1naBctN7YO68riM/gLSDY7sdrMby8hofADYuuqOA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"optional": true,
"dependencies": {
"base64-js": "^1.3.1",
"ieee754": "^1.2.1"
}
},
"node_modules/canvas": {
"version": "2.11.2",
"resolved": "https://registry.npmjs.org/canvas/-/canvas-2.11.2.tgz",
"integrity": "sha512-ItanGBMrmRV7Py2Z+Xhs7cT+FNt5K0vPL4p9EZ/UX/Mu7hFbkxSjKF2KVtPwX7UYWp7dRKnrTvReflgrItJbdw==",
"hasInstallScript": true,
"optional": true,
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.0",
"nan": "^2.17.0",
"simple-get": "^3.0.3"
},
"engines": {
"node": ">=6"
}
},
"node_modules/chownr": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/chownr/-/chownr-2.0.0.tgz",
"integrity": "sha512-bIomtDF5KGpdogkLd9VspvFzk9KfpyyGlS8YFVZl7TGPBHL5snIOnxeshwVgPteQ9b4Eydl+pVbIyE1DcvCWgQ==",
"optional": true,
"engines": {
"node": ">=10"
}
},
"node_modules/color-support": {
"version": "1.1.3",
"resolved": "https://registry.npmjs.org/color-support/-/color-support-1.1.3.tgz",
"integrity": "sha512-qiBjkpbMLO/HL68y+lh4q0/O1MZFj2RX6X/KmMa3+gJD3z+WwI1ZzDHysvqHGS3mP6mznPckpXmw1nI9cJjyRg==",
"optional": true,
"bin": {
"color-support": "bin.js"
}
},
"node_modules/commander": {
"version": "12.0.0",
"resolved": "https://registry.npmjs.org/commander/-/commander-12.0.0.tgz",
"integrity": "sha512-MwVNWlYjDTtOjX5PiD7o5pK0UrFU/OYgcJfjjK4RaHZETNtjJqrZa9Y9ds88+A+f+d5lv+561eZ+yCKoS3gbAA==",
"optional": true,
"engines": {
"node": ">=18"
}
},
"node_modules/concat-map": {
"version": "0.0.1",
"resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
"integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg==",
"optional": true
},
"node_modules/console-control-strings": {
"version": "1.1.0",
"resolved": "https://registry.npmjs.org/console-control-strings/-/console-control-strings-1.1.0.tgz",
"integrity": "sha512-ty/fTekppD2fIwRvnZAVdeOiGd1c7YXEixbgJTNzqcxJWKQnjJ/V1bNEEE6hygpM3WjwHFUVK6HTjWSzV4a8sQ==",
"optional": true
},
"node_modules/debug": {
"version": "4.3.4",
"resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
"integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
"optional": true,
"dependencies": {
"ms": "2.1.2"
},
"engines": {
"node": ">=6.0"
},
"peerDependenciesMeta": {
"supports-color": {
"optional": true
}
}
},
"node_modules/decompress-response": {
"version": "4.2.1",
"resolved": "https://registry.npmjs.org/decompress-response/-/decompress-response-4.2.1.tgz",
"integrity": "sha512-jOSne2qbyE+/r8G1VU+G/82LBs2Fs4LAsTiLSHOCOMZQl2OKZ6i8i4IyHemTe+/yIXOtTcRQMzPcgyhoFlqPkw==",
"optional": true,
"dependencies": {
"mimic-response": "^2.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/delegates": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/delegates/-/delegates-1.0.0.tgz",
"integrity": "sha512-bd2L678uiWATM6m5Z1VzNCErI3jiGzt6HGY8OVICs40JQq/HALfbyNJmp0UDakEY4pMMaN0Ly5om/B1VI/+xfQ==",
"optional": true
},
"node_modules/detect-libc": {
"version": "2.0.3",
"resolved": "https://registry.npmjs.org/detect-libc/-/detect-libc-2.0.3.tgz",
"integrity": "sha512-bwy0MGW55bG41VqxxypOsdSdGqLwXPI/focwgTYCFMbdUiBAxLg9CFzG08sz2aqzknwiX7Hkl0bQENjg8iLByw==",
"optional": true,
"engines": {
"node": ">=8"
}
},
"node_modules/emoji-regex": {
"version": "8.0.0",
"resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
"integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A==",
"optional": 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/fs-minipass": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/fs-minipass/-/fs-minipass-2.1.0.tgz",
"integrity": "sha512-V/JgOLFCS+R6Vcq0slCuaeWEdNC3ouDlJMNIsacH2VtALiu9mV4LPrHc5cDl8k5aw6J8jwgWWpiTo5RYhmIzvg==",
"optional": true,
"dependencies": {
"minipass": "^3.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/fs-minipass/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"optional": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/fs.realpath": {
"version": "1.0.0",
"resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
"integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw==",
"optional": true
},
"node_modules/gauge": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/gauge/-/gauge-3.0.2.tgz",
"integrity": "sha512-+5J6MS/5XksCuXq++uFRsnUd7Ovu1XenbeuIuNRJxYWjgQbPuFhT14lAvsWfqfAmnwluf1OwMjz39HjfLPci0Q==",
"optional": true,
"dependencies": {
"aproba": "^1.0.3 || ^2.0.0",
"color-support": "^1.1.2",
"console-control-strings": "^1.0.0",
"has-unicode": "^2.0.1",
"object-assign": "^4.1.1",
"signal-exit": "^3.0.0",
"string-width": "^4.2.3",
"strip-ansi": "^6.0.1",
"wide-align": "^1.1.2"
},
"engines": {
"node": ">=10"
}
},
"node_modules/glob": {
"version": "7.2.3",
"resolved": "https://registry.npmjs.org/glob/-/glob-7.2.3.tgz",
"integrity": "sha512-nFR0zLpU2YCaRxwoCJvL6UvCH2JFyFVIvwTLsIf21AuHlMskA1hhTdk+LlYJtOlYt9v6dvszD2BGRqBL+iQK9Q==",
"optional": true,
"dependencies": {
"fs.realpath": "^1.0.0",
"inflight": "^1.0.4",
"inherits": "2",
"minimatch": "^3.1.1",
"once": "^1.3.0",
"path-is-absolute": "^1.0.0"
},
"engines": {
"node": "*"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/has-unicode": {
"version": "2.0.1",
"resolved": "https://registry.npmjs.org/has-unicode/-/has-unicode-2.0.1.tgz",
"integrity": "sha512-8Rf9Y83NBReMnx0gFzA8JImQACstCYWUplepDa9xprwwtmgEZUF0h/i5xSA625zB/I37EtrswSST6OXxwaaIJQ==",
"optional": true
},
"node_modules/https-proxy-agent": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/https-proxy-agent/-/https-proxy-agent-5.0.1.tgz",
"integrity": "sha512-dFcAjpTQFgoLMzC2VwU+C/CbS7uRL0lWmxDITmqm7C+7F0Odmj6s9l6alZc6AELXhrnggM2CeWSXHGOdX2YtwA==",
"optional": true,
"dependencies": {
"agent-base": "6",
"debug": "4"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/ieee754": {
"version": "1.2.1",
"resolved": "https://registry.npmjs.org/ieee754/-/ieee754-1.2.1.tgz",
"integrity": "sha512-dcyqhDvX1C46lXZcVqCpK+FtMRQVdIMN6/Df5js2zouUsqG7I6sFxitIC+7KYK29KdXOLHdu9zL4sFnoVQnqaA==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"optional": true
},
"node_modules/inflight": {
"version": "1.0.6",
"resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
"integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
"optional": true,
"dependencies": {
"once": "^1.3.0",
"wrappy": "1"
}
},
"node_modules/inherits": {
"version": "2.0.4",
"resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
"integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ==",
"optional": true
},
"node_modules/is-fullwidth-code-point": {
"version": "3.0.0",
"resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
"integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
"optional": true,
"engines": {
"node": ">=8"
}
},
"node_modules/lru-cache": {
"version": "6.0.0",
"resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
"integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
"optional": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/make-dir": {
"version": "3.1.0",
"resolved": "https://registry.npmjs.org/make-dir/-/make-dir-3.1.0.tgz",
"integrity": "sha512-g3FeP20LNwhALb/6Cz6Dd4F2ngze0jz7tbzrD2wAV+o9FeNHe4rL+yK2md0J/fiSf1sa1ADhXqi5+oVwOM/eGw==",
"optional": true,
"dependencies": {
"semver": "^6.0.0"
},
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/make-dir/node_modules/semver": {
"version": "6.3.1",
"resolved": "https://registry.npmjs.org/semver/-/semver-6.3.1.tgz",
"integrity": "sha512-BR7VvDCVHO+q2xBEWskxS6DJE1qRnb7DxzUrogb71CWoSficBxYsiAGd+Kl0mmq/MprG9yArRkyrQxTO6XjMzA==",
"optional": true,
"bin": {
"semver": "bin/semver.js"
}
},
"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/mimic-response": {
"version": "2.1.0",
"resolved": "https://registry.npmjs.org/mimic-response/-/mimic-response-2.1.0.tgz",
"integrity": "sha512-wXqjST+SLt7R009ySCglWBCFpjUygmCIfD790/kVbiGmUgfYGuB14PiTd5DwVxSV4NcYHjzMkoj5LjQZwTQLEA==",
"optional": true,
"engines": {
"node": ">=8"
},
"funding": {
"url": "https://github.com/sponsors/sindresorhus"
}
},
"node_modules/minimatch": {
"version": "3.1.2",
"resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
"integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
"optional": true,
"dependencies": {
"brace-expansion": "^1.1.7"
},
"engines": {
"node": "*"
}
},
"node_modules/minipass": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-5.0.0.tgz",
"integrity": "sha512-3FnjYuehv9k6ovOEbyOswadCDPX1piCfhV8ncmYtHOjuPwylVWsghTLo7rabjC3Rx5xD4HDx8Wm1xnMF7S5qFQ==",
"optional": true,
"engines": {
"node": ">=8"
}
},
"node_modules/minizlib": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/minizlib/-/minizlib-2.1.2.tgz",
"integrity": "sha512-bAxsR8BVfj60DWXHE3u30oHzfl4G7khkSuPW+qvpd7jFRHm7dLxOjUk1EHACJ/hxLY8phGJ0YhYHZo7jil7Qdg==",
"optional": true,
"dependencies": {
"minipass": "^3.0.0",
"yallist": "^4.0.0"
},
"engines": {
"node": ">= 8"
}
},
"node_modules/minizlib/node_modules/minipass": {
"version": "3.3.6",
"resolved": "https://registry.npmjs.org/minipass/-/minipass-3.3.6.tgz",
"integrity": "sha512-DxiNidxSEK+tHG6zOIklvNOwm3hvCrbUrdtzY74U6HKTJxvIDfOUL5W5P2Ghd3DTkhhKPYGqeNUIh5qcM4YBfw==",
"optional": true,
"dependencies": {
"yallist": "^4.0.0"
},
"engines": {
"node": ">=8"
}
},
"node_modules/mkdirp": {
"version": "1.0.4",
"resolved": "https://registry.npmjs.org/mkdirp/-/mkdirp-1.0.4.tgz",
"integrity": "sha512-vVqVZQyf3WLx2Shd0qJ9xuvqgAyKPLAiqITEtqW0oIUjzo3PePDd6fW9iFz30ef7Ysp/oiWqbhszeGWW2T6Gzw==",
"optional": true,
"bin": {
"mkdirp": "bin/cmd.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/ms": {
"version": "2.1.2",
"resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
"integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w==",
"optional": true
},
"node_modules/nan": {
"version": "2.19.0",
"resolved": "https://registry.npmjs.org/nan/-/nan-2.19.0.tgz",
"integrity": "sha512-nO1xXxfh/RWNxfd/XPfbIfFk5vgLsAxUR9y5O0cHMJu/AW9U95JLXqthYHjEp+8gQ5p96K9jUp8nbVOxCdRbtw==",
"optional": true
},
"node_modules/node-fetch": {
"version": "2.7.0",
"resolved": "https://registry.npmjs.org/node-fetch/-/node-fetch-2.7.0.tgz",
"integrity": "sha512-c4FRfUm/dbcWZ7U+1Wq0AwCyFL+3nt2bEw05wfxSz+DWpWsitgmSgYmy2dQdWyKC1694ELPqMs/YzUSNozLt8A==",
"optional": true,
"dependencies": {
"whatwg-url": "^5.0.0"
},
"engines": {
"node": "4.x || >=6.0.0"
},
"peerDependencies": {
"encoding": "^0.1.0"
},
"peerDependenciesMeta": {
"encoding": {
"optional": true
}
}
},
"node_modules/nopt": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/nopt/-/nopt-5.0.0.tgz",
"integrity": "sha512-Tbj67rffqceeLpcRXrT7vKAN8CwfPeIBgM7E6iBkmKLV7bEMwpGgYLGv0jACUsECaa/vuxP0IjEont6umdMgtQ==",
"optional": true,
"dependencies": {
"abbrev": "1"
},
"bin": {
"nopt": "bin/nopt.js"
},
"engines": {
"node": ">=6"
}
},
"node_modules/npmlog": {
"version": "5.0.1",
"resolved": "https://registry.npmjs.org/npmlog/-/npmlog-5.0.1.tgz",
"integrity": "sha512-AqZtDUWOMKs1G/8lwylVjrdYgqA4d9nu8hc+0gzRxlDb1I10+FHBGMXs6aiQHFdCUUlqH99MUMuLfzWDNDtfxw==",
"optional": true,
"dependencies": {
"are-we-there-yet": "^2.0.0",
"console-control-strings": "^1.1.0",
"gauge": "^3.0.0",
"set-blocking": "^2.0.0"
}
},
"node_modules/object-assign": {
"version": "4.1.1",
"resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
"integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/once": {
"version": "1.4.0",
"resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
"integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
"optional": true,
"dependencies": {
"wrappy": "1"
}
},
"node_modules/path-is-absolute": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
"integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
"optional": true,
"engines": {
"node": ">=0.10.0"
}
},
"node_modules/prettier": {
"version": "3.2.5",
"resolved": "https://registry.npmjs.org/prettier/-/prettier-3.2.5.tgz",
@ -78,10 +637,176 @@
"url": "https://github.com/prettier/prettier?sponsor=1"
}
},
"node_modules/readable-stream": {
"version": "3.6.2",
"resolved": "https://registry.npmjs.org/readable-stream/-/readable-stream-3.6.2.tgz",
"integrity": "sha512-9u/sniCrY3D5WdsERHzHE4G2YCXqoG5FTHUiCC4SIbr6XcLZBY05ya9EKjYek9O5xOAwjGq+1JdGBAS7Q9ScoA==",
"optional": true,
"dependencies": {
"inherits": "^2.0.3",
"string_decoder": "^1.1.1",
"util-deprecate": "^1.0.1"
},
"engines": {
"node": ">= 6"
}
},
"node_modules/rimraf": {
"version": "3.0.2",
"resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
"integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
"optional": true,
"dependencies": {
"glob": "^7.1.3"
},
"bin": {
"rimraf": "bin.js"
},
"funding": {
"url": "https://github.com/sponsors/isaacs"
}
},
"node_modules/safe-buffer": {
"version": "5.2.1",
"resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
"integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"optional": true
},
"node_modules/semver": {
"version": "7.6.0",
"resolved": "https://registry.npmjs.org/semver/-/semver-7.6.0.tgz",
"integrity": "sha512-EnwXhrlwXMk9gKu5/flx5sv/an57AkRplG3hTK68W7FRDN+k+OWBj65M7719OkA82XLBxrcX0KSHj+X5COhOVg==",
"optional": true,
"dependencies": {
"lru-cache": "^6.0.0"
},
"bin": {
"semver": "bin/semver.js"
},
"engines": {
"node": ">=10"
}
},
"node_modules/set-blocking": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/set-blocking/-/set-blocking-2.0.0.tgz",
"integrity": "sha512-KiKBS8AnWGEyLzofFfmvKwpdPzqiy16LvQfK3yv/fVH7Bj13/wl3JSR1J+rfgRE9q7xUJK4qvgS8raSOeLUehw==",
"optional": true
},
"node_modules/signal-exit": {
"version": "3.0.7",
"resolved": "https://registry.npmjs.org/signal-exit/-/signal-exit-3.0.7.tgz",
"integrity": "sha512-wnD2ZE+l+SPC/uoS0vXeE9L1+0wuaMqKlfz9AMUo38JsyLSBWSFcHR1Rri62LZc12vLr1gb3jl7iwQhgwpAbGQ==",
"optional": true
},
"node_modules/simple-concat": {
"version": "1.0.1",
"resolved": "https://registry.npmjs.org/simple-concat/-/simple-concat-1.0.1.tgz",
"integrity": "sha512-cSFtAPtRhljv69IK0hTVZQ+OfE9nePi/rtJmw5UjHeVyVroEqJXP1sFztKUy1qU+xvz3u/sfYJLa947b7nAN2Q==",
"funding": [
{
"type": "github",
"url": "https://github.com/sponsors/feross"
},
{
"type": "patreon",
"url": "https://www.patreon.com/feross"
},
{
"type": "consulting",
"url": "https://feross.org/support"
}
],
"optional": true
},
"node_modules/simple-get": {
"version": "3.1.1",
"resolved": "https://registry.npmjs.org/simple-get/-/simple-get-3.1.1.tgz",
"integrity": "sha512-CQ5LTKGfCpvE1K0n2us+kuMPbk/q0EKl82s4aheV9oXjFEz6W/Y7oQFVJuU6QG77hRT4Ghb5RURteF5vnWjupA==",
"optional": true,
"dependencies": {
"decompress-response": "^4.2.0",
"once": "^1.3.1",
"simple-concat": "^1.0.0"
}
},
"node_modules/string_decoder": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/string_decoder/-/string_decoder-1.3.0.tgz",
"integrity": "sha512-hkRX8U1WjJFd8LsDJ2yQ/wWWxaopEsABU1XfkM8A+j0+85JAGppt16cr1Whg6KIbb4okU6Mql6BOj+uup/wKeA==",
"optional": true,
"dependencies": {
"safe-buffer": "~5.2.0"
}
},
"node_modules/string-width": {
"version": "4.2.3",
"resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
"integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
"optional": true,
"dependencies": {
"emoji-regex": "^8.0.0",
"is-fullwidth-code-point": "^3.0.0",
"strip-ansi": "^6.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/strip-ansi": {
"version": "6.0.1",
"resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
"integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
"optional": true,
"dependencies": {
"ansi-regex": "^5.0.1"
},
"engines": {
"node": ">=8"
}
},
"node_modules/tar": {
"version": "6.2.1",
"resolved": "https://registry.npmjs.org/tar/-/tar-6.2.1.tgz",
"integrity": "sha512-DZ4yORTwrbTj/7MZYq2w+/ZFdI6OZ/f9SFHR+71gIVUZhOQPHzVCLpvRnPgyaMpfWxxk/4ONva3GQSyNIKRv6A==",
"optional": true,
"dependencies": {
"chownr": "^2.0.0",
"fs-minipass": "^2.0.0",
"minipass": "^5.0.0",
"minizlib": "^2.1.1",
"mkdirp": "^1.0.3",
"yallist": "^4.0.0"
},
"engines": {
"node": ">=10"
}
},
"node_modules/three": {
"version": "0.161.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.161.0.tgz",
"integrity": "sha512-LC28VFtjbOyEu5b93K0bNRLw1rQlMJ85lilKsYj6dgTu+7i17W+JCCEbvrpmNHF1F3NAUqDSWq50UD7w9H2xQw=="
"integrity": "sha512-LC28VFtjbOyEu5b93K0bNRLw1rQlMJ85lilKsYj6dgTu+7i17W+JCCEbvrpmNHF1F3NAUqDSWq50UD7w9H2xQw==",
"optional": true
},
"node_modules/tr46": {
"version": "0.0.3",
"resolved": "https://registry.npmjs.org/tr46/-/tr46-0.0.3.tgz",
"integrity": "sha512-N3WMsuqV66lT30CrXNbEjx4GEwlow3v6rr4mCcv6prnfwhS01rkgyFdjPNBYd9br7LpXV1+Emh01fHnq2Gdgrw==",
"optional": true
},
"node_modules/typescript": {
"version": "5.3.3",
@ -101,6 +826,49 @@
"resolved": "https://registry.npmjs.org/undici-types/-/undici-types-5.26.5.tgz",
"integrity": "sha512-JlCMO+ehdEIKqlFxk6IfVoAUVmgz7cU7zD/h9XZ0qzeosSHmUJVOzSQvvYSYWXkFXC+IfLKSIffhv0sVZup6pA==",
"dev": true
},
"node_modules/util-deprecate": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/util-deprecate/-/util-deprecate-1.0.2.tgz",
"integrity": "sha512-EPD5q1uXyFxJpCrLnCc1nHnq3gOa6DZBocAIiI2TaSCA7VCJ1UJDMagCzIkXNsUYfD1daK//LTEQ8xiIbrHtcw==",
"optional": true
},
"node_modules/webidl-conversions": {
"version": "3.0.1",
"resolved": "https://registry.npmjs.org/webidl-conversions/-/webidl-conversions-3.0.1.tgz",
"integrity": "sha512-2JAn3z8AR6rjK8Sm8orRC0h/bcl/DqL7tRPdGZ4I1CjdF+EaMLmYxBHyXuKL849eucPFhvBoxMsflfOb8kxaeQ==",
"optional": true
},
"node_modules/whatwg-url": {
"version": "5.0.0",
"resolved": "https://registry.npmjs.org/whatwg-url/-/whatwg-url-5.0.0.tgz",
"integrity": "sha512-saE57nupxk6v3HY35+jzBwYa0rKSy0XR8JSxZPwgLr7ys0IBzhGviA1/TUGJLmSVqs8pb9AnvICXEuOHLprYTw==",
"optional": true,
"dependencies": {
"tr46": "~0.0.3",
"webidl-conversions": "^3.0.0"
}
},
"node_modules/wide-align": {
"version": "1.1.5",
"resolved": "https://registry.npmjs.org/wide-align/-/wide-align-1.1.5.tgz",
"integrity": "sha512-eDMORYaPNZ4sQIuuYPDHdQvf4gyCF9rEEV/yPxGfwPkRodwEgiMUUXTx/dex+Me0wxx53S+NgUHaP7y3MGlDmg==",
"optional": true,
"dependencies": {
"string-width": "^1.0.2 || 2 || 3 || 4"
}
},
"node_modules/wrappy": {
"version": "1.0.2",
"resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
"integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ==",
"optional": true
},
"node_modules/yallist": {
"version": "4.0.0",
"resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
"integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
"optional": true
}
}
}

View File

@ -17,6 +17,11 @@
"prettier": "^3.2.5",
"typescript": "^5.3.3"
},
"optionalDependencies": {
"buffer": "^6.0.3",
"canvas": "^2.11.2",
"commander": "^12.0.0"
},
"dependencies": {
"three": "^0.161.0"
}

87
src/cli.ts Normal file
View File

@ -0,0 +1,87 @@
import { resolve } from 'path';
import { ModelStore, MsonEvaluate } from './mson';
import { fillStoreFromFilesystem, saveGeometry, saveModel } from './node';
import { ThreeBuilder } from './three';
import { MeshStandardMaterial } from 'three';
import { Command } from 'commander';
import { saveGLTF } from './node';
interface CliOptions {
format: string;
evaluated?: boolean;
store: string;
target: string;
}
const store = new ModelStore();
const evaluate = new MsonEvaluate(store);
const exportModel = async (model: string, options: CliOptions) => {
console.log(`\nStarting to evaluate model ${model}`);
console.log(` ... Evaluating Mson model`);
const evaluated = evaluate.evaluateModel(model);
console.log(` ... Building geometry`);
const mat = new MeshStandardMaterial();
const builder = new ThreeBuilder(mat);
const geometry = builder.buildGeometry(evaluated);
const outputName = model.replace(/[:\/]/g, '-');
console.log(`Saving as ${outputName}.${options.format}`);
const outputs = resolve(process.cwd(), options.target || 'outputs');
if (options.evaluated) {
await saveModel(outputs, outputName, evaluated);
}
switch (options.format) {
case 'json':
await saveGeometry(outputs, outputName, geometry);
break;
case 'gltf':
case 'glb':
await saveGLTF(outputs, outputName, geometry, options.format === 'glb');
break;
default:
throw new Error(`Unexpected format ${options.format}`);
}
};
const program = new Command();
program
.name('mson-three')
.description('Convert Mson models to THREE.js models (and others)')
.version('0.0.1');
program
.command('export')
.description('Export a Mson model')
.argument('models...', 'Model(s) to export')
.option(
'-f, --format <name>',
'Output format, one of: json, gltf, glb',
'json',
)
.option('-e, --evaluated', 'Save the evaluated final Mson json file as well')
.option('-s, --store <path>', 'Path containing Mson models', 'inputs')
.option('-t, --target <path>', 'Path to place outputs into', 'outputs')
.action(async (models: string[], options: CliOptions) => {
console.log(` ... Reading Mson resources`);
await fillStoreFromFilesystem(
store,
resolve(process.cwd(), options.store || 'inputs'),
);
for (const model of models) {
try {
await exportModel(model, options);
} catch (error: any) {
console.error(
` !!! Model "${model}" failed: ${error.message}`,
error.stack,
);
}
}
});
program.parse();

View File

@ -1 +1,2 @@
export * from './mson';
export * from './three';

23
src/node/gltf.ts Normal file
View File

@ -0,0 +1,23 @@
import { Object3D } from 'three';
export class GLTF {
static async export<BoolValue = true>(
input: Object3D,
binary: BoolValue,
): Promise<ArrayBuffer>;
static async export<BoolValue = false>(
input: Object3D,
binary: BoolValue,
): Promise<{ [x: string]: any }>;
static async export<BoolValue extends boolean>(
input: Object3D,
binary?: BoolValue,
) {
return import('three/examples/jsm/exporters/GLTFExporter.js').then(
({ GLTFExporter }) =>
new GLTFExporter().parseAsync(input, {
binary,
}),
);
}
}

View File

@ -1,3 +1,7 @@
// Browser API mocks in node.js
import './vendor';
// ...
import { GLTF } from './gltf';
import { ModelStore, MsonEvaluatedModel } from '../mson';
import { promises as fs } from 'node:fs';
import { join } from 'node:path';
@ -55,6 +59,19 @@ export const saveGeometry = async (
JSON.stringify(object.toJSON()),
);
export const saveGLTF = async (
root: string,
name: string,
object: Object3D,
binary = false,
) => {
const model = await GLTF.export(object, binary);
await fs.writeFile(
join(root, `${name}.${binary ? 'glb' : 'gltf'}`),
binary ? Buffer.from(model) : JSON.stringify(model),
);
};
export const saveModel = async (
root: string,
name: string,

87
src/node/vendor/eventtarget.ts vendored Normal file
View File

@ -0,0 +1,87 @@
//
// None of the following code was written for mson-three explicity.
// attribution is provided where necessary.
//
// all of this junk was copied from abandoned npm modules which mimic browser
// APIs in Node.js in order to make GLTFExporter work.
//
// this is taken from abandoned npm module "eventtarget" by Jesús Leganés Combarro "Piranna"
//
export class EventTarget {
listeners: any = {};
addEventListener(type: string | number, listener: any) {
if (!listener) return;
var listeners_type = this.listeners[type];
if (listeners_type === undefined)
this.listeners[type] = listeners_type = [];
for (var i = 0, l; (l = listeners_type[i]); i++) if (l === listener) return;
listeners_type.push(listener);
}
dispatchEvent(event: {
type: any;
message?: any;
_dispatched?: any;
cancelable?: any;
defaultPrevented?: any;
isTrusted?: any;
preventDefault?: any;
stopImmediatePropagation?: any;
target?: any;
timeStamp?: any;
}) {
if (event._dispatched) throw 'InvalidStateError';
event._dispatched = true;
var type = event.type;
if (type == undefined || type == '') throw 'UNSPECIFIED_EVENT_TYPE_ERR';
var listenerArray = this.listeners[type] || [];
var dummyListener = (this as any)['on' + type];
if (typeof dummyListener == 'function')
listenerArray = listenerArray.concat(dummyListener);
var stopImmediatePropagation = false;
// [ToDo] Use read-only properties instead of attributes when availables
event.cancelable = true;
event.defaultPrevented = false;
event.isTrusted = false;
event.preventDefault = function () {
if (this.cancelable) this.defaultPrevented = true;
};
event.stopImmediatePropagation = function () {
stopImmediatePropagation = true;
};
event.target = this;
event.timeStamp = new Date().getTime();
for (var i = 0, listener; (listener = listenerArray[i]); i++) {
if (stopImmediatePropagation) break;
listener.call(this, event);
}
return !event.defaultPrevented;
}
removeEventListener(type: string | number, listener: any) {
if (!listener) return;
var listeners_type = this.listeners[type];
if (listeners_type === undefined) return;
for (var i = 0, l; (l = listeners_type[i]); i++)
if (l === listener) {
listeners_type.splice(i, 1);
break;
}
if (!listeners_type.length) delete this.listeners[type];
}
}

18
src/node/vendor/index.ts vendored Normal file
View File

@ -0,0 +1,18 @@
import { Canvas } from 'canvas';
import { Blob, FileReader } from './vblob';
// Patch global scope to imitate browser environment.
global.Blob = Blob as any;
global.FileReader = FileReader as any;
global.document = {
createElement: (nodeName: string) => {
if (nodeName !== 'canvas')
throw new Error(`Cannot create node ${nodeName}`);
const canvas = new Canvas(256, 256);
// This isn't working — currently need to avoid toBlob(), so export to embedded .gltf not .glb.
// canvas.toBlob = function () {
// return new Blob([this.toBuffer()]);
// };
return canvas;
},
} as any;

438
src/node/vendor/vblob.ts vendored Normal file
View File

@ -0,0 +1,438 @@
//
// None of the following code was written for mson-three explicity.
// attribution is provided where necessary.
//
// all of this junk was copied from abandoned npm modules which mimic browser
// APIs in Node.js in order to make GLTFExporter work.
//
// this code is taken from npm package "vblob" by karikera
import * as fs from 'fs';
import { randomBytes } from 'crypto';
import { tmpdir } from 'os';
import { join } from 'path';
import { Blob as NodeBlob } from 'buffer';
import { EventTarget } from './eventtarget';
interface EventBeforeDispatch {
[key: string]: any;
type: string;
}
interface Event {
readonly cancelable: boolean;
readonly defaultPrevented: boolean;
readonly isTrusted: boolean;
readonly target: EventTarget | null;
readonly timeStamp: number;
readonly bubbles: boolean;
/** @deprecated */
cancelBubble: boolean;
readonly composed: boolean;
readonly currentTarget: EventTarget | null;
readonly eventPhase: number;
/** @deprecated */
returnValue: boolean;
/** @deprecated */
readonly srcElement: EventTarget | null;
readonly type: string;
preventDefault(): void;
stopPropagation(): void;
stopImmediatePropagation(): void;
composedPath(): EventTarget[];
/** @deprecated */
initEvent(type: string, bubbles: boolean, cancelable: boolean): void;
readonly NONE: number;
readonly CAPTURING_PHASE: number;
readonly AT_TARGET: number;
readonly BUBBLING_PHASE: number;
}
interface ProgressEvent<T extends EventTarget = EventTarget> extends Event {
readonly lengthComputable: boolean;
readonly loaded: number;
readonly target: T | null;
readonly total: number;
}
interface EventTargetConstructor {
new (): EventTarget;
}
interface FileReaderEvent extends Event {}
function getTempPath(): Promise<string> {
const file = `vblob-${randomBytes(4).readUInt32LE(0)}`;
const path = join(tmpdir(), file);
tempFiles.add(path);
return Promise.resolve(path);
}
function fdopen(path: string, flags: string): Promise<number> {
return new Promise<number>((resolve, reject) =>
fs.open(path, flags, (err, fd) => {
if (err) reject(err);
else resolve(fd);
}),
);
}
function fdclose(fd: number): Promise<void> {
return new Promise((resolve, reject) =>
fs.close(fd, (err) => {
if (err) reject(err);
else resolve();
}),
);
}
function fdwriteFile(fd: number, path: string): Promise<void> {
return new Promise<void>((resolve, reject) => {
const writer = fs.createWriteStream(<any>null, { fd });
const reader = fs.createReadStream(path);
reader.on('error', reject);
reader.on('end', resolve);
writer.on('error', reject);
reader.pipe(writer, { end: false });
});
}
function fdwrite(fd: number, str: string | Uint8Array): Promise<void> {
return new Promise((resolve, reject) =>
fs.write(fd, str as any, (err) => {
if (err) reject(err);
else resolve();
}),
);
}
function fdread(fd: number, size: number, position: number): Promise<Buffer> {
const buffer = Buffer.alloc(size);
return new Promise<Buffer>((resolve, reject) =>
fs.read(fd, buffer, 0, size, position, (err) => {
if (err) reject(err);
else resolve(buffer);
}),
);
}
const tempFiles: Set<string> = new Set();
const onExit: Array<() => void> = [];
process.on('exit', (code) => {
for (const cb of onExit) cb();
process.exit(code);
});
onExit.push(() => {
for (const file of tempFiles) {
fs.unlinkSync(file);
}
});
interface BlobPropertyBag {
type?: string;
ending?: 'transparent' | 'native';
}
export interface Blob {
readonly size: number;
readonly type: string;
slice(start?: number, end?: number, contentType?: string): Blob;
}
export interface FileReader extends EventTarget {
readonly error: any | null;
readonly readyState: number;
readonly result: any;
readonly EMPTY: number;
readonly LOADING: number;
readonly DONE: number;
onabort: ((ev: ProgressEvent<FileReader>) => void) | null;
onerror: ((ev: FileReaderEvent) => void) | null;
onload: ((ev: FileReaderEvent) => void) | null;
onloadstart: ((ev: FileReaderEvent) => void) | null;
onloadend: ((ev: FileReaderEvent) => void) | null;
onprogress: ((ev: FileReaderEvent) => void) | null;
abort(): void;
readAsArrayBuffer(blob: Blob): void;
readAsBinaryString(blob: Blob): void;
readAsDataURL(blob: Blob): void;
readAsText(blob: Blob): void;
}
export class VBlob implements Blob {
_path: string = '';
_size: number;
_offset: number = 0;
_type: string;
_writeTask: Promise<number> = Promise.resolve(0);
private _write(fn: (fd: number) => void | Promise<void>): void {
this._writeTask = this._writeTask.then(async (fd) => {
if (!fd) {
this._path = await getTempPath();
fd = await fdopen(this._path, 'w+');
}
await fn(fd);
return fd;
});
}
private _writeEnd(): void {
this._writeTask = this._writeTask.then((fd) => fdclose(fd)).then(() => 0);
}
constructor(array?: any[], options?: BlobPropertyBag) {
this._type = (options && options.type) || '';
if (!array) {
this._path = '';
this._size = 0;
} else {
var size = 0;
for (const value of array) {
if (value instanceof ArrayBuffer) {
if (value.byteLength === 0) continue;
this._write((fd) => fdwrite(fd, new Uint8Array(value)));
size += value.byteLength;
} else if (value instanceof Uint8Array) {
if (value.byteLength === 0) continue;
this._write((fd) => fdwrite(fd, value));
size += value.byteLength;
} else if (
value instanceof Int8Array ||
value instanceof Uint8ClampedArray ||
value instanceof Int16Array ||
value instanceof Uint16Array ||
value instanceof Int32Array ||
value instanceof Uint32Array ||
value instanceof Float32Array ||
value instanceof Float64Array ||
value instanceof DataView
) {
if (value.byteLength === 0) continue;
this._write((fd) =>
fdwrite(
fd,
new Uint8Array(value.buffer, value.byteOffset, value.byteLength),
),
);
size += value.byteLength;
} else if (value instanceof VBlob) {
if (value._size === 0) continue;
this._write((fd) => fdwriteFile(fd, value._path));
size += value._size;
} else {
const str = value + '';
if (str.length === 0) continue;
this._write((fd) => fdwrite(fd, str));
size += str.length;
}
}
this._writeEnd();
this._size = size;
}
}
get size(): number {
return this._size;
}
get type(): string {
return this._type;
}
slice(start?: number, end?: number, contentType?: string): Blob {
if (!start) start = 0;
else if (start < 0) start = this._size + start;
if (!end) end = this._size;
if (end < 0) end = this._size - end;
else if (end >= this._size) end = this._size;
if (start >= end) return new VBlob([]);
const newblob = new VBlob();
newblob._type = contentType || this._type;
newblob._writeTask = this._writeTask;
newblob._offset = this._offset + start;
newblob._size = end - start;
this._writeTask.then(() => (newblob._path = this._path));
return newblob;
}
readBuffer(fd: number): Promise<ArrayBuffer> {
return fdread(fd, this._size, this._offset).then((buffer) => buffer.buffer);
}
}
export var Blob: { new (array?: any[], options?: BlobPropertyBag): Blob } =
global['Blob'] || VBlob;
interface Aborted {
aborted: boolean;
}
export class VFileReader extends EventTarget implements FileReader {
static readonly EMPTY = 0;
static readonly LOADING = 1;
static readonly DONE = 2;
readonly EMPTY = 0;
readonly LOADING = 1;
readonly DONE = 2;
onabort: ((ev: ProgressEvent<FileReader>) => void) | null = null;
onerror: ((ev: FileReaderEvent) => void) | null = null;
onload: ((ev: FileReaderEvent) => void) | null = null;
onloadstart: ((ev: FileReaderEvent) => void) | null = null;
onloadend: ((ev: FileReaderEvent) => void) | null = null;
onprogress: ((ev: FileReaderEvent) => void) | null = null;
private _readyState: 0 | 1 | 2;
private _abort: (() => void) | null = null;
private _abortPromise: Promise<null> | null = null;
public result: any;
public error: any | null = null;
constructor() {
super();
this._readyState = 0;
}
get readyState(): 0 | 1 | 2 {
return this._readyState;
}
abort(): void {
if (this._abort !== null) {
this._abort();
this._abort = null;
this._abortPromise = null;
this.dispatchEvent({ type: 'abort' });
}
if (this._readyState === 1) {
this._finishWork();
}
}
private _startWork(methodName: string): Aborted {
if (this._readyState === 1) {
throw Error(
`Failed to execute '${methodName}' on 'FileReader': The object is already busy reading Blobs.`,
);
}
this.result = null;
this.error = null;
this._readyState = 1;
const aborted: Aborted = { aborted: false };
if (this._abortPromise === null) {
this._abortPromise = new Promise<null>((resolve) => {
this._abort = () => {
aborted.aborted = true;
resolve(null);
};
});
}
return aborted;
}
private _finishWork() {
this.dispatchEvent({ type: 'loadend' });
this._readyState = 2;
}
private _readBuffer(
methodName: string,
blob: Blob,
cb: (buffer: Buffer) => any,
): Promise<void> {
const aborted = this._startWork(methodName);
if (!(blob instanceof VBlob) && !(blob instanceof NodeBlob)) {
throw TypeError(
`vblob cannot handle the ${blob.constructor.name} class.`,
);
}
const prom = new Promise((resolve) => process.nextTick(resolve)).then(
() => {
if (aborted.aborted) return null;
this.dispatchEvent({ type: 'loadstart' });
if (blob instanceof VBlob) {
return this._readVBlob(blob);
} else if (blob instanceof NodeBlob) {
return this._readNodeBlob(blob);
} else {
return null;
}
},
);
return Promise.race([this._abortPromise, prom]).then(
(data) => {
if (data === null) return;
if (aborted.aborted) return;
this.result = cb(data);
this.dispatchEvent({ type: 'load' });
this._finishWork();
},
(err) => {
if (aborted.aborted) return;
this.error = err;
this.dispatchEvent({
type: 'error',
message: err ? err.message : 'Error',
});
this._finishWork();
},
);
}
private async _readVBlob(blob: VBlob): Promise<Buffer> {
if (blob._size === 0) {
return Buffer.alloc(0);
} else {
await blob._writeTask;
const fd = await fdopen(blob._path, 'r');
try {
return await fdread(fd, blob._size, blob._offset);
} finally {
fdclose(fd);
}
}
}
private async _readNodeBlob(blob: NodeBlob): Promise<Buffer> {
const buf = await blob.arrayBuffer();
return Buffer.from(buf);
}
readAsArrayBuffer(blob: Blob): void {
this._readBuffer('readAsArrayBuffer', blob, (data) => data.buffer);
}
readAsBinaryString(blob: Blob): void {
this._readBuffer('readAsBinaryString', blob, (data) =>
data.toString('binary'),
);
}
readAsDataURL(blob: Blob): void {
this._readBuffer(
'readAsDataURL',
blob,
(data) =>
'data:' +
(blob.type || 'application/octet-stream') +
';base64,' +
data.toString('base64'),
);
}
readAsText(blob: Blob): void {
this._readBuffer('readAsText', blob, (data) => data.toString());
}
}
export var FileReader: { new (): FileReader } = VFileReader;

View File

@ -1,27 +0,0 @@
import { resolve } from 'path';
import { ModelStore, MsonEvaluate } from '.';
import { fillStoreFromFilesystem, saveGeometry, saveModel } from './util/node';
import { ThreeBuilder } from './three';
import { DoubleSide, MeshLambertMaterial } from 'three';
async function init() {
const store = new ModelStore();
const evaluate = new MsonEvaluate(store);
const mat = new MeshLambertMaterial();
// mat.side = DoubleSide;
const builder = new ThreeBuilder(mat);
await fillStoreFromFilesystem(store, resolve(process.cwd(), 'inputs'));
// mson:steve
// minelittlepony:steve_pony
const final = evaluate.evaluateModel('minelittlepony:races/steve/alicorn');
// console.log(final.texture);
const geometry = builder.buildGeometry(final);
const outputName = 'alicorn';
const outputs = resolve(process.cwd(), 'outputs');
await saveGeometry(outputs, outputName, geometry);
await saveModel(outputs, outputName, final);
}
init().catch(console.error);

View File

@ -1,5 +1,6 @@
import {
BoxGeometry,
BufferGeometry,
CylinderGeometry,
Material,
MathUtils,
@ -66,7 +67,10 @@ export class ThreeBuilder {
new Vector3(pos.x + size.x / 2, pos.y - size.y / 2, pos.z),
};
constructor(private readonly material: Material) {}
/**
* @param material Material to use for the whole model.
*/
constructor(protected readonly material: Material) {}
/**
* Create a THREE.js object from MSON evaluated model data.
@ -245,10 +249,7 @@ export class ThreeBuilder {
adjustedTranslate.z,
);
// TODO: FIXME: this crap is to hack .toJSON() into including the entire geometry
// instead of just the properties used to initialize it.
(geometry as any).type = 'BufferGeometry';
delete (geometry as any).parameters;
ThreeBuilder.obfuscateGeometry(geometry);
// Map UVs to box
UVMapper.mapBoxUVs(size, geometry, texture);
@ -307,10 +308,7 @@ export class ThreeBuilder {
geometry = geometry.toNonIndexed() as CylinderGeometry;
geometry.computeVertexNormals();
// TODO: FIXME: this crap is to hack .toJSON() into including the entire geometry
// instead of just the properties used to initialize it.
(geometry as any).type = 'BufferGeometry';
delete (geometry as any).parameters;
ThreeBuilder.obfuscateGeometry(geometry);
const mesh = new Mesh(geometry, this.material);
mesh.name = `${name}__cone`;
@ -341,10 +339,7 @@ export class ThreeBuilder {
adjustedTranslate.z,
);
// TODO: FIXME: this crap is to hack .toJSON() into including the entire geometry
// instead of just the properties used to initialize it.
(planeGeom as any).type = 'BufferGeometry';
delete (planeGeom as any).parameters;
ThreeBuilder.obfuscateGeometry(planeGeom);
UVMapper.mapPlanarUvs(face, size, planeGeom, texture, mirror);
@ -465,4 +460,14 @@ export class ThreeBuilder {
wrapper.updateMatrix();
return wrapper;
}
/**
* This crap is to hack `Object3D.toJSON()` into including the entire geometry
* instead of just the properties used to initialize it.
* @param geometry Geometry
*/
static obfuscateGeometry(geometry: BufferGeometry) {
(geometry as any).type = 'BufferGeometry';
delete (geometry as any).parameters;
}
}

View File

@ -1,16 +1,16 @@
/**
* Simple object check.
* @param item
* @returns {boolean}
* @returns {boolean} Is Object, not array, null or undefined.
*/
export function isObject(item: any) {
export function isObject(item: any): boolean {
return item && typeof item === 'object' && !Array.isArray(item);
}
/**
* Deep merge two objects.
* @param target
* @param ...sources
* @param target Merge `sources` into this object.
* @param ...sources Objects to use in merge.
*/
export function mergeDeep(target: any, ...sources: any[]) {
if (!sources.length) return target;

View File

@ -1,3 +1,13 @@
/**
* Convert number from one range to another.
* @example
* // returns 0.5
* convertRange(0, [-1, 1], [0, 1]);
* @param value Value to convert
* @param r1 Source range
* @param r2 Target range
* @returns Value in new range
*/
export function convertRange(
value: number,
r1: [number, number],

View File

@ -9,7 +9,7 @@
// "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. */
"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. */
@ -22,7 +22,7 @@
// "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. */
"module": "Node16" /* 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. */
@ -50,7 +50,7 @@
// "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. */
"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. */
@ -71,11 +71,11 @@
// "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. */
"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. */
"forceConsistentCasingInFileNames": true /* Ensure that casing is correct in imports. */,
/* Type Checking */
"strict": true, /* Enable all strict type-checking options. */
"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. */