This commit is contained in:
James Harrison 2020-11-19 17:52:27 +00:00
commit 6b752c423a
10 changed files with 361 additions and 435 deletions

View File

@ -12,6 +12,7 @@ Three.js powered Minecraft skin viewer.
* 1.8 Skins * 1.8 Skins
* HD Skins * HD Skins
* Capes * Capes
* Elytras
* Slim Arms * Slim Arms
* Automatic model detection (Slim / Default) * Automatic model detection (Slim / Default)
@ -37,7 +38,10 @@ Three.js powered Minecraft skin viewer.
// Load a cape // Load a cape
skinViewer.loadCape("img/cape.png"); skinViewer.loadCape("img/cape.png");
// Unload(hide) the cape // Load a elytra (from a cape texture)
skinViewer.loadCape("img/cape.png", { backEquipment: "elytra" });
// Unload(hide) the cape / elytra
skinViewer.loadCape(null); skinViewer.loadCape(null);
// Control objects with your mouse! // Control objects with your mouse!

View File

@ -128,11 +128,12 @@
<label class="control">Speed: <input id="rotate_animation_speed" type="number" value="1" step="0.1"></label> <label class="control">Speed: <input id="rotate_animation_speed" type="number" value="1" step="0.1"></label>
</div> </div>
<div> <div>
<h2>Walk / Run</h2> <h2>Walk / Run / Fly</h2>
<div class="control"> <div class="control">
<label><input type="radio" id="primary_animation_none" name="primary_animation" value="" checked> None</label> <label><input type="radio" id="primary_animation_none" name="primary_animation" value="" checked> None</label>
<label><input type="radio" id="primary_animation_walk" name="primary_animation" value="walk"> Walk</label> <label><input type="radio" id="primary_animation_walk" name="primary_animation" value="walk"> Walk</label>
<label><input type="radio" id="primary_animation_run" name="primary_animation" value="run"> Run</label> <label><input type="radio" id="primary_animation_run" name="primary_animation" value="run"> Run</label>
<label><input type="radio" id="primary_animation_fly" name="primary_animation" value="fly"> Fly</label>
</div> </div>
<label class="control">Speed: <input id="primary_animation_speed" type="number" value="1" step="0.1"></label> <label class="control">Speed: <input id="primary_animation_speed" type="number" value="1" step="0.1"></label>
</div> </div>
@ -182,6 +183,13 @@
</tr> </tr>
</tbody> </tbody>
</table> </table>
<div>
<h2>Back Equipment</h2>
<div class="control">
<label><input type="radio" id="back_equipment_cape" name="back_equipment" value="cape" checked> Cape</label>
<label><input type="radio" id="back_equipment_elytra" name="back_equipment" value="elytra"> Elytra</label>
</div>
</div>
</div> </div>
<div class="control-section"> <div class="control-section">
@ -261,11 +269,12 @@
const skinLayers = ["innerLayer", "outerLayer"]; const skinLayers = ["innerLayer", "outerLayer"];
const availableAnimations = { const availableAnimations = {
walk: skinview3d.WalkingAnimation, walk: skinview3d.WalkingAnimation,
run: skinview3d.RunningAnimation run: skinview3d.RunningAnimation,
fly: skinview3d.FlyingAnimation
}; };
let skinViewer; let skinViewer;
let oribitControl; let orbitControl;
let rotateAnimation; let rotateAnimation;
let primaryAnimation; let primaryAnimation;
@ -303,7 +312,13 @@
skinViewer.loadCustomCape(null); skinViewer.loadCustomCape(null);
input.setCustomValidity(""); input.setCustomValidity("");
} else { } else {
skinViewer.loadCustomCape(url); const selectedBackEquipment = document.querySelector('input[type="radio"][name="back_equipment"]:checked');
skinViewer.loadCustomCape(url, { backEquipment: selectedBackEquipment.value })
.then(() => input.setCustomValidity(""))
.catch(e => {
input.setCustomValidity("Image can't be loaded.");
console.error(e);
});
} }
} }
@ -343,9 +358,9 @@
primaryAnimation.speed = e.target.value; primaryAnimation.speed = e.target.value;
} }
}); });
document.getElementById("control_rotate").addEventListener("change", e => oribitControl.enableRotate = e.target.checked); document.getElementById("control_rotate").addEventListener("change", e => orbitControl.enableRotate = e.target.checked);
document.getElementById("control_zoom").addEventListener("change", e => oribitControl.enableZoom = e.target.checked); document.getElementById("control_zoom").addEventListener("change", e => orbitControl.enableZoom = e.target.checked);
document.getElementById("control_pan").addEventListener("change", e => oribitControl.enablePan = e.target.checked); document.getElementById("control_pan").addEventListener("change", e => orbitControl.enablePan = e.target.checked);
for (const part of skinParts) { for (const part of skinParts) {
for (const layer of skinLayers) { for (const layer of skinLayers) {
document.querySelector(`#layers_table input[type="checkbox"][data-part="${part}"][data-layer="${layer}"]`) document.querySelector(`#layers_table input[type="checkbox"][data-part="${part}"][data-layer="${layer}"]`)
@ -389,8 +404,21 @@
document.getElementById("skin_model").addEventListener("change", () => reloadSkin()); document.getElementById("skin_model").addEventListener("change", () => reloadSkin());
document.getElementById("ears_url").addEventListener("change", () => reloadEars()); document.getElementById("ears_url").addEventListener("change", () => reloadEars());
document.getElementById("cape_url").addEventListener("change", () => reloadCape()); document.getElementById("cape_url").addEventListener("change", () => reloadCape());
for (const el of document.querySelectorAll('input[type="radio"][name="back_equipment"]')) {
el.addEventListener("change", e => {
if (skinViewer.playerObject.backEquipment === null) {
// cape texture hasn't been loaded yet
// this option will be processed on texture loading
} else {
skinViewer.playerObject.backEquipment = e.target.value;
}
});
}
document.getElementById("reset_all").addEventListener("click", () => { document.getElementById("reset_all").addEventListener("click", () => {
skinViewer.dispose(); skinViewer.dispose();
orbitControl.dispose();
initializeViewer(); initializeViewer();
}); });
} }
@ -401,7 +429,7 @@
alpha: false alpha: false
}); });
skinViewer.renderer.setClearColor(0x5a76f3); skinViewer.renderer.setClearColor(0x5a76f3);
oribitControl = skinview3d.createOrbitControls(skinViewer); orbitControl = skinview3d.createOrbitControls(skinViewer);
rotateAnimation = null; rotateAnimation = null;
primaryAnimation = null; primaryAnimation = null;
@ -417,9 +445,9 @@
primaryAnimation = skinViewer.animations.add(availableAnimations[primaryAnimationName]); primaryAnimation = skinViewer.animations.add(availableAnimations[primaryAnimationName]);
primaryAnimation.speed = document.getElementById("primary_animation_speed").value; primaryAnimation.speed = document.getElementById("primary_animation_speed").value;
} }
oribitControl.enableRotate = document.getElementById("control_rotate").checked; orbitControl.enableRotate = document.getElementById("control_rotate").checked;
oribitControl.enableZoom = document.getElementById("control_zoom").checked; orbitControl.enableZoom = document.getElementById("control_zoom").checked;
oribitControl.enablePan = document.getElementById("control_pan").checked; orbitControl.enablePan = document.getElementById("control_pan").checked;
for (const part of skinParts) { for (const part of skinParts) {
for (const layer of skinLayers) { for (const layer of skinLayers) {
skinViewer.playerObject.skin[part][layer].visible = skinViewer.playerObject.skin[part][layer].visible =

View File

@ -12,7 +12,7 @@
<div id="rendered_imgs"></div> <div id="rendered_imgs"></div>
<script src="../bundles/skinview3d.bundle.js"></script> <script src="../bundles/skinview3d.bundle.js"></script>
<script> <script>
const textures = [ const configurations = [
{ {
skin: "img/skin.png", skin: "img/skin.png",
ears: null, ears: null,
@ -24,14 +24,13 @@
cape: "img/cape.png", cape: "img/cape.png",
}, },
{ {
skin: "img/skin.png", skin: "img/haka.png",
ears: null, cape: "img/mojang_cape.png"
cape: "img/animated.png",
}, },
{ {
skin: "img/skin.png", skin: "img/hatsune_miku.png",
ears: "img/ears.png", cape: "img/mojang_cape.png",
cape: null, backEquipment: "elytra"
}, },
{ {
skin: "img/skin.png", skin: "img/skin.png",
@ -57,11 +56,15 @@
skinViewer.camera.rotation.y = 0.534; skinViewer.camera.rotation.y = 0.534;
skinViewer.camera.rotation.z = 0.348; skinViewer.camera.rotation.z = 0.348;
skinViewer.camera.position.x = 30.5; skinViewer.camera.position.x = 30.5;
skinViewer.camera.position.y = 18.0; skinViewer.camera.position.y = 22.0;
skinViewer.camera.position.z = 42.0; skinViewer.camera.position.z = 42.0;
for (const { skin, cape } of textures) { for (const config of configurations) {
await Promise.all([skinViewer.loadSkin(skin), skinViewer.loadEars(skin), skinViewer.loadCustomCape(cape)]); await Promise.all([
skinViewer.loadSkin(config.skin),
skinViewer.loadEars(skin),
skinViewer.loadCustomCape(config.cape, { backEquipment: config.backEquipment })
]);
skinViewer.render(); skinViewer.render();
const image = skinViewer.canvas.toDataURL(); const image = skinViewer.canvas.toDataURL();

242
package-lock.json generated
View File

@ -1,6 +1,6 @@
{ {
"name": "skinview3d", "name": "skinview3d",
"version": "2.0.0-alpha.10", "version": "2.0.0-beta.1",
"lockfileVersion": 1, "lockfileVersion": 1,
"requires": true, "requires": true,
"dependencies": { "dependencies": {
@ -31,9 +31,9 @@
} }
}, },
"@eslint/eslintrc": { "@eslint/eslintrc": {
"version": "0.1.3", "version": "0.2.1",
"resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.1.3.tgz", "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.2.1.tgz",
"integrity": "sha512-4YVwPkANLeNtRjMekzux1ci8hIaH5eGKktGqR0d3LWsKNn5B2X/1Z6Trxy7jQXl9EBGE6Yj02O+t09FMeRllaA==", "integrity": "sha512-XRUeBZ5zBWLYgSANMpThFddrZZkEbGHgUdt5UJjZfnlN9BGCiUBrf+nvbRupSjMvqzwnQN0qwCmOxITt1cfywA==",
"dev": true, "dev": true,
"requires": { "requires": {
"ajv": "^6.12.4", "ajv": "^6.12.4",
@ -92,9 +92,9 @@
} }
}, },
"@rollup/plugin-node-resolve": { "@rollup/plugin-node-resolve": {
"version": "9.0.0", "version": "10.0.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-9.0.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-node-resolve/-/plugin-node-resolve-10.0.0.tgz",
"integrity": "sha512-gPz+utFHLRrd41WMP13Jq5mqqzHL3OXrfj3/MkSyB6UBIcuNt9j60GCbarzMzdf1VHFpOxfQh/ez7wyadLMqkg==", "integrity": "sha512-sNijGta8fqzwA1VwUEtTvWCx2E7qC70NMsDh4ZG13byAXYigBNZMxALhKUSycBks5gupJdq0lFrKumFrRZ8H3A==",
"dev": true, "dev": true,
"requires": { "requires": {
"@rollup/pluginutils": "^3.1.0", "@rollup/pluginutils": "^3.1.0",
@ -106,20 +106,21 @@
}, },
"dependencies": { "dependencies": {
"resolve": { "resolve": {
"version": "1.17.0", "version": "1.18.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz",
"integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==",
"dev": true, "dev": true,
"requires": { "requires": {
"is-core-module": "^2.0.0",
"path-parse": "^1.0.6" "path-parse": "^1.0.6"
} }
} }
} }
}, },
"@rollup/plugin-typescript": { "@rollup/plugin-typescript": {
"version": "6.0.0", "version": "6.1.0",
"resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-6.0.0.tgz", "resolved": "https://registry.npmjs.org/@rollup/plugin-typescript/-/plugin-typescript-6.1.0.tgz",
"integrity": "sha512-Y5U2L4eaF3wUSgCZRMdvNmuzWkKMyN3OwvhAdbzAi5sUqedaBk/XbzO4T7RlViDJ78MOPhwAIv2FtId/jhMtbg==", "integrity": "sha512-hJxaiE6WyNOsK+fZpbFh9CUijZYqPQuAOWO5khaGTUkM8DYNNyA2TDlgamecE+qLOG1G1+CwbWMAx3rbqpp6xQ==",
"dev": true, "dev": true,
"requires": { "requires": {
"@rollup/pluginutils": "^3.1.0", "@rollup/pluginutils": "^3.1.0",
@ -127,11 +128,12 @@
}, },
"dependencies": { "dependencies": {
"resolve": { "resolve": {
"version": "1.17.0", "version": "1.18.1",
"resolved": "https://registry.npmjs.org/resolve/-/resolve-1.17.0.tgz", "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.18.1.tgz",
"integrity": "sha512-ic+7JYiV8Vi2yzQGFWOkiZD5Z9z7O2Zhm9XMaTxdJExKasieFCr+yXZ/WmXsckHiKl12ar0y6XiXDx3m4RHn1w==", "integrity": "sha512-lDfCPaMKfOJXjy0dPayzPdF1phampNWr3qFCjAu+rw/qbQmr5jWH5xN2hwh9QKfw9E5v4hwV7A+jrCmL8yjjqA==",
"dev": true, "dev": true,
"requires": { "requires": {
"is-core-module": "^2.0.0",
"path-parse": "^1.0.6" "path-parse": "^1.0.6"
} }
} }
@ -154,12 +156,6 @@
"integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==", "integrity": "sha512-RbzJvlNzmRq5c3O09UipeuXno4tA1FE6ikOjxZK0tuxVv3412l64l5t1W5pj4+rJq9vpkm/kwiR07aZXnsKPxw==",
"dev": true "dev": true
}, },
"@types/color-name": {
"version": "1.1.1",
"resolved": "https://registry.npmjs.org/@types/color-name/-/color-name-1.1.1.tgz",
"integrity": "sha512-rr+OQyAjxze7GgWrSaJwydHStIhHq2lvY3BOC2Mj7KnzI7XK0Uw1TOOdI9lDoajEbSWLiYgoo4f1R51erQfhPQ==",
"dev": true
},
"@types/estree": { "@types/estree": {
"version": "0.0.39", "version": "0.0.39",
"resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz", "resolved": "https://registry.npmjs.org/@types/estree/-/estree-0.0.39.tgz",
@ -188,13 +184,13 @@
} }
}, },
"@typescript-eslint/eslint-plugin": { "@typescript-eslint/eslint-plugin": {
"version": "4.1.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.1.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/eslint-plugin/-/eslint-plugin-4.6.0.tgz",
"integrity": "sha512-U+nRJx8XDUqJxYF0FCXbpmD9nWt/xHDDG0zsw1vrVYAmEAuD/r49iowfurjSL2uTA2JsgtpsyG7mjO7PHf2dYw==", "integrity": "sha512-1+419X+Ynijytr1iWI+/IcX/kJryc78YNpdaXR1aRO1sU3bC0vZrIAF1tIX7rudVI84W7o7M4zo5p1aVt70fAg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/experimental-utils": "4.1.0", "@typescript-eslint/experimental-utils": "4.6.0",
"@typescript-eslint/scope-manager": "4.1.0", "@typescript-eslint/scope-manager": "4.6.0",
"debug": "^4.1.1", "debug": "^4.1.1",
"functional-red-black-tree": "^1.0.1", "functional-red-black-tree": "^1.0.1",
"regexpp": "^3.0.0", "regexpp": "^3.0.0",
@ -203,55 +199,55 @@
} }
}, },
"@typescript-eslint/experimental-utils": { "@typescript-eslint/experimental-utils": {
"version": "4.1.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.1.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/experimental-utils/-/experimental-utils-4.6.0.tgz",
"integrity": "sha512-paEYLA37iqRIDPeQwAmoYSiZ3PiHsaAc3igFeBTeqRHgPnHjHLJ9OGdmP6nwAkF65p2QzEsEBtpjNUBWByNWzA==", "integrity": "sha512-pnh6Beh2/4xjJVNL+keP49DFHk3orDHHFylSp3WEjtgW3y1U+6l+jNnJrGlbs6qhAz5z96aFmmbUyKhunXKvKw==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/json-schema": "^7.0.3", "@types/json-schema": "^7.0.3",
"@typescript-eslint/scope-manager": "4.1.0", "@typescript-eslint/scope-manager": "4.6.0",
"@typescript-eslint/types": "4.1.0", "@typescript-eslint/types": "4.6.0",
"@typescript-eslint/typescript-estree": "4.1.0", "@typescript-eslint/typescript-estree": "4.6.0",
"eslint-scope": "^5.0.0", "eslint-scope": "^5.0.0",
"eslint-utils": "^2.0.0" "eslint-utils": "^2.0.0"
} }
}, },
"@typescript-eslint/parser": { "@typescript-eslint/parser": {
"version": "4.1.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.1.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/parser/-/parser-4.6.0.tgz",
"integrity": "sha512-hM/WNCQTzDHgS0Ke3cR9zPndL3OTKr9OoN9CL3UqulsAjYDrglSwIIgswSmHBcSbOzLmgaMARwrQEbIumIglvQ==", "integrity": "sha512-Dj6NJxBhbdbPSZ5DYsQqpR32MwujF772F2H3VojWU6iT4AqL4BKuoNWOPFCoSZvCcADDvQjDpa6OLDAaiZPz2Q==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/scope-manager": "4.1.0", "@typescript-eslint/scope-manager": "4.6.0",
"@typescript-eslint/types": "4.1.0", "@typescript-eslint/types": "4.6.0",
"@typescript-eslint/typescript-estree": "4.1.0", "@typescript-eslint/typescript-estree": "4.6.0",
"debug": "^4.1.1" "debug": "^4.1.1"
} }
}, },
"@typescript-eslint/scope-manager": { "@typescript-eslint/scope-manager": {
"version": "4.1.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.1.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/scope-manager/-/scope-manager-4.6.0.tgz",
"integrity": "sha512-HD1/u8vFNnxwiHqlWKC/Pigdn0Mvxi84Y6GzbZ5f5sbLrFKu0al02573Er+D63Sw67IffVUXR0uR8rpdfdk+vA==", "integrity": "sha512-uZx5KvStXP/lwrMrfQQwDNvh2ppiXzz5TmyTVHb+5TfZ3sUP7U1onlz3pjoWrK9konRyFe1czyxObWTly27Ang==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/types": "4.1.0", "@typescript-eslint/types": "4.6.0",
"@typescript-eslint/visitor-keys": "4.1.0" "@typescript-eslint/visitor-keys": "4.6.0"
} }
}, },
"@typescript-eslint/types": { "@typescript-eslint/types": {
"version": "4.1.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.1.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/types/-/types-4.6.0.tgz",
"integrity": "sha512-rkBqWsO7m01XckP9R2YHVN8mySOKKY2cophGM8K5uDK89ArCgahItQYdbg/3n8xMxzu2elss+an1TphlUpDuJw==", "integrity": "sha512-5FAgjqH68SfFG4UTtIFv+rqYJg0nLjfkjD0iv+5O27a0xEeNZ5rZNDvFGZDizlCD1Ifj7MAbSW2DPMrf0E9zjA==",
"dev": true "dev": true
}, },
"@typescript-eslint/typescript-estree": { "@typescript-eslint/typescript-estree": {
"version": "4.1.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.1.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/typescript-estree/-/typescript-estree-4.6.0.tgz",
"integrity": "sha512-r6et57qqKAWU173nWyw31x7OfgmKfMEcjJl9vlJEzS+kf9uKNRr4AVTRXfTCwebr7bdiVEkfRY5xGnpPaNPe4Q==", "integrity": "sha512-s4Z9qubMrAo/tw0CbN0IN4AtfwuehGXVZM0CHNMdfYMGBDhPdwTEpBrecwhP7dRJu6d9tT9ECYNaWDHvlFSngA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/types": "4.1.0", "@typescript-eslint/types": "4.6.0",
"@typescript-eslint/visitor-keys": "4.1.0", "@typescript-eslint/visitor-keys": "4.6.0",
"debug": "^4.1.1", "debug": "^4.1.1",
"globby": "^11.0.1", "globby": "^11.0.1",
"is-glob": "^4.0.1", "is-glob": "^4.0.1",
@ -261,42 +257,34 @@
} }
}, },
"@typescript-eslint/visitor-keys": { "@typescript-eslint/visitor-keys": {
"version": "4.1.0", "version": "4.6.0",
"resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.1.0.tgz", "resolved": "https://registry.npmjs.org/@typescript-eslint/visitor-keys/-/visitor-keys-4.6.0.tgz",
"integrity": "sha512-+taO0IZGCtCEsuNTTF2Q/5o8+fHrlml8i9YsZt2AiDCdYEJzYlsmRY991l/6f3jNXFyAWepdQj7n8Na6URiDRQ==", "integrity": "sha512-38Aa9Ztl0XyFPVzmutHXqDMCu15Xx8yKvUo38Gu3GhsuckCh3StPI5t2WIO9LHEsOH7MLmlGfKUisU8eW1Sjhg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@typescript-eslint/types": "4.1.0", "@typescript-eslint/types": "4.6.0",
"eslint-visitor-keys": "^2.0.0" "eslint-visitor-keys": "^2.0.0"
} }
}, },
"@yushijinhun/three-minifier-common": { "@yushijinhun/three-minifier-common": {
"version": "0.2.0-alpha.2", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/@yushijinhun/three-minifier-common/-/three-minifier-common-0.2.0-alpha.2.tgz", "resolved": "https://registry.npmjs.org/@yushijinhun/three-minifier-common/-/three-minifier-common-0.2.0.tgz",
"integrity": "sha512-Y2NlRcyvEQmY5FlELV/Az7UElddTqKwZxA77OB0of5sj610pa2pM+IlwC20qgEXMaqfdULaENjYGi5EHPK0RSQ==", "integrity": "sha512-FmYYiqmFpoXl0AbL7ApKHx3EK7yw68yeHLwL5Po9fKXnF5zCtemDW9NzdkS6Fcmw7goFZoaOHBUeYOjD1iMNbg==",
"dev": true, "dev": true,
"requires": { "requires": {
"acorn": "^8.0.1", "acorn": "^8.0.4",
"acorn-walk": "^8.0.0", "acorn-walk": "^8.0.0",
"glsl-tokenizer": "^2.1.5" "glsl-tokenizer": "^2.1.5"
},
"dependencies": {
"acorn": {
"version": "8.0.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.1.tgz",
"integrity": "sha512-dmKn4pqZ29iQl2Pvze1zTrps2luvls2PBY//neO2WJ0s10B3AxJXshN+Ph7B4GrhfGhHXrl4dnUwyNNXQcnWGQ==",
"dev": true
}
} }
}, },
"@yushijinhun/three-minifier-rollup": { "@yushijinhun/three-minifier-rollup": {
"version": "0.2.0-alpha.2", "version": "0.2.0",
"resolved": "https://registry.npmjs.org/@yushijinhun/three-minifier-rollup/-/three-minifier-rollup-0.2.0-alpha.2.tgz", "resolved": "https://registry.npmjs.org/@yushijinhun/three-minifier-rollup/-/three-minifier-rollup-0.2.0.tgz",
"integrity": "sha512-jB9efJwsX9M4QGx4/rA760H9G44tnGq6gIG/ZlP8PWKIBdw48JmRlk20LP1mCT0SH6CKGZcdE0DwR3At+ssGXg==", "integrity": "sha512-CYHNbs0UrJEA8eSnzob3Z2GiABGdye9YHxefoME4J+MgCJAp+TaszVHBVXT/vrvheFT2oHIxGR/C9gEIUDp6NA==",
"dev": true, "dev": true,
"requires": { "requires": {
"@rollup/plugin-node-resolve": "^9.0.0", "@rollup/plugin-node-resolve": "^10.0.0",
"@yushijinhun/three-minifier-common": "^0.2.0-alpha.2", "@yushijinhun/three-minifier-common": "^0.2.0",
"magic-string": "^0.25.7" "magic-string": "^0.25.7"
} }
}, },
@ -321,9 +309,9 @@
} }
}, },
"acorn": { "acorn": {
"version": "7.4.0", "version": "8.0.4",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.0.tgz", "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.0.4.tgz",
"integrity": "sha512-+G7P8jJmCHr+S+cLfQxygbWhXy+8YTVGzAkpEbcLo2mLoL7tij/VG41QSHACSf5QgYRhMZYHuNc6drJaO0Da+w==", "integrity": "sha512-XNP0PqF1XD19ZlLKvB7cMmnZswW4C/03pRHgirB30uSJTaS3A3V1/P4sS3HPvFmjoriPCJQs+JDSbm4bL1TxGQ==",
"dev": true "dev": true
}, },
"acorn-jsx": { "acorn-jsx": {
@ -348,9 +336,9 @@
} }
}, },
"ajv": { "ajv": {
"version": "6.12.4", "version": "6.12.6",
"resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.4.tgz", "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
"integrity": "sha512-eienB2c9qVQs2KWexhkrdMLVDoIQCz5KSeLxwg9Lzk4DOfBtIK9PQwwufcsn1jjGuf9WZmqPMbGxOzfcuphJCQ==", "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
"dev": true, "dev": true,
"requires": { "requires": {
"fast-deep-equal": "^3.1.1", "fast-deep-equal": "^3.1.1",
@ -896,22 +884,22 @@
"dev": true "dev": true
}, },
"eslint": { "eslint": {
"version": "7.8.1", "version": "7.12.1",
"resolved": "https://registry.npmjs.org/eslint/-/eslint-7.8.1.tgz", "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.12.1.tgz",
"integrity": "sha512-/2rX2pfhyUG0y+A123d0ccXtMm7DV7sH1m3lk9nk2DZ2LReq39FXHueR9xZwshE5MdfSf0xunSaMWRqyIA6M1w==", "integrity": "sha512-HlMTEdr/LicJfN08LB3nM1rRYliDXOmfoO4vj39xN6BLpFzF00hbwBoqHk8UcJ2M/3nlARZWy/mslvGEuZFvsg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@babel/code-frame": "^7.0.0", "@babel/code-frame": "^7.0.0",
"@eslint/eslintrc": "^0.1.3", "@eslint/eslintrc": "^0.2.1",
"ajv": "^6.10.0", "ajv": "^6.10.0",
"chalk": "^4.0.0", "chalk": "^4.0.0",
"cross-spawn": "^7.0.2", "cross-spawn": "^7.0.2",
"debug": "^4.0.1", "debug": "^4.0.1",
"doctrine": "^3.0.0", "doctrine": "^3.0.0",
"enquirer": "^2.3.5", "enquirer": "^2.3.5",
"eslint-scope": "^5.1.0", "eslint-scope": "^5.1.1",
"eslint-utils": "^2.1.0", "eslint-utils": "^2.1.0",
"eslint-visitor-keys": "^1.3.0", "eslint-visitor-keys": "^2.0.0",
"espree": "^7.3.0", "espree": "^7.3.0",
"esquery": "^1.2.0", "esquery": "^1.2.0",
"esutils": "^2.0.2", "esutils": "^2.0.2",
@ -941,12 +929,11 @@
}, },
"dependencies": { "dependencies": {
"ansi-styles": { "ansi-styles": {
"version": "4.2.1", "version": "4.3.0",
"resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.2.1.tgz", "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
"integrity": "sha512-9VGjrMsG1vePxcSweQsN20KY/c4zN0h9fLjqAbwbPfahM3t+NL+M9HC8xeXG2I8pX5NoamTGNuomEUFI7fcUjA==", "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
"dev": true, "dev": true,
"requires": { "requires": {
"@types/color-name": "^1.1.1",
"color-convert": "^2.0.1" "color-convert": "^2.0.1"
} }
}, },
@ -986,12 +973,6 @@
"which": "^2.0.1" "which": "^2.0.1"
} }
}, },
"eslint-visitor-keys": {
"version": "1.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
"integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
"dev": true
},
"ignore": { "ignore": {
"version": "4.0.6", "version": "4.0.6",
"resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz", "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
@ -1031,12 +1012,12 @@
} }
}, },
"eslint-scope": { "eslint-scope": {
"version": "5.1.0", "version": "5.1.1",
"resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.0.tgz", "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
"integrity": "sha512-iiGRvtxWqgtx5m8EyQUJihBloE4EnYeGE/bz1wSPwJE6tZuJUtHlhqDM4Xj2ukE8Dyy1+HCZ4hE0fzIVMzb58w==", "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
"dev": true, "dev": true,
"requires": { "requires": {
"esrecurse": "^4.1.0", "esrecurse": "^4.3.0",
"estraverse": "^4.1.1" "estraverse": "^4.1.1"
} }
}, },
@ -1074,6 +1055,12 @@
"eslint-visitor-keys": "^1.3.0" "eslint-visitor-keys": "^1.3.0"
}, },
"dependencies": { "dependencies": {
"acorn": {
"version": "7.4.1",
"resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
"integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
"dev": true
},
"eslint-visitor-keys": { "eslint-visitor-keys": {
"version": "1.3.0", "version": "1.3.0",
"resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz", "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
@ -1179,9 +1166,9 @@
"dev": true "dev": true
}, },
"fastq": { "fastq": {
"version": "1.8.0", "version": "1.9.0",
"resolved": "https://registry.npmjs.org/fastq/-/fastq-1.8.0.tgz", "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.9.0.tgz",
"integrity": "sha512-SMIZoZdLh/fgofivvIkmknUXyPnvxRE3DhtZ5Me3Mrsk5gyPL42F0xr51TdRXskBxHfMp+07bcYzfsYEsSQA9Q==", "integrity": "sha512-i7FVWL8HhVY+CTkwFxkN2mk3h+787ixS5S63eb78diVRc1MCssarHq3W5cj0av7YDSwmaV928RNag+U1etRQ7w==",
"dev": true, "dev": true,
"requires": { "requires": {
"reusify": "^1.0.4" "reusify": "^1.0.4"
@ -1478,6 +1465,15 @@
"integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==", "integrity": "sha512-ESKv5sMCJB2jnHTWZ3O5itG+O128Hsus4K4Qh1h2/cgn2vbgnLSVqfV46AeJA9D5EeeLa9w81KUXMtn34zhX+Q==",
"dev": true "dev": true
}, },
"is-core-module": {
"version": "2.0.0",
"resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.0.0.tgz",
"integrity": "sha512-jq1AH6C8MuteOoBPwkxHafmByhL9j5q4OaPGdbuD+ZtQJVzH+i6E3BJDQcBA09k57i2Hh2yQbEG8yObZ0jdlWw==",
"dev": true,
"requires": {
"has": "^1.0.3"
}
},
"is-date-object": { "is-date-object": {
"version": "1.0.2", "version": "1.0.2",
"resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz", "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.2.tgz",
@ -2618,9 +2614,9 @@
} }
}, },
"rollup": { "rollup": {
"version": "2.26.11", "version": "2.32.1",
"resolved": "https://registry.npmjs.org/rollup/-/rollup-2.26.11.tgz", "resolved": "https://registry.npmjs.org/rollup/-/rollup-2.32.1.tgz",
"integrity": "sha512-xyfxxhsE6hW57xhfL1I+ixH8l2bdoIMaAecdQiWF3N7IgJEMu99JG+daBiSZQjnBpzFxa0/xZm+3pbCdAQehHw==", "integrity": "sha512-Op2vWTpvK7t6/Qnm1TTh7VjEZZkN8RWgf0DHbkKzQBwNf748YhXbozHVefqpPp/Fuyk/PQPAnYsBxAEtlMvpUw==",
"dev": true, "dev": true,
"requires": { "requires": {
"fsevents": "~2.1.2" "fsevents": "~2.1.2"
@ -2639,9 +2635,9 @@
} }
}, },
"run-parallel": { "run-parallel": {
"version": "1.1.9", "version": "1.1.10",
"resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.9.tgz", "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.1.10.tgz",
"integrity": "sha512-DEqnSRTDw/Tc3FXf49zedI638Z9onwUotBMiUFKmrO2sdFKIbXamXGQ3Axd4qgphxKB4kw/qP1w5kTxnfU1B9Q==", "integrity": "sha512-zb/1OuZ6flOlH6tQyMPUrE3x3Ulxjlo9WIVXR4yVYi4H9UXQaeIsPbLn2R3O3vQCnDKkAl2qHiuocKKX4Tz/Sw==",
"dev": true "dev": true
}, },
"safe-buffer": { "safe-buffer": {
@ -2755,9 +2751,9 @@
"dev": true "dev": true
}, },
"skinview-utils": { "skinview-utils": {
"version": "0.5.8", "version": "0.5.9",
"resolved": "https://registry.npmjs.org/skinview-utils/-/skinview-utils-0.5.8.tgz", "resolved": "https://registry.npmjs.org/skinview-utils/-/skinview-utils-0.5.9.tgz",
"integrity": "sha512-AEQXx/xerBQv/jNnG5I8mBe65bQPW1BjDt8bW5a0yd/nJ4hP22G2OrfQFxYyBno6ohWwJ6D8pxN34jWrMSyD4g==" "integrity": "sha512-XVEanmZ22BDAydrPeLxHt0oSzcU282EhLPYzhJSDXtn7qcvehWJUQDZwAdYPW8DN+xQMRNHx6ZaKATMVq81KyQ=="
}, },
"slash": { "slash": {
"version": "3.0.0", "version": "3.0.0",
@ -3052,9 +3048,9 @@
} }
}, },
"three": { "three": {
"version": "0.120.1", "version": "0.122.0",
"resolved": "https://registry.npmjs.org/three/-/three-0.120.1.tgz", "resolved": "https://registry.npmjs.org/three/-/three-0.122.0.tgz",
"integrity": "sha512-ktaCRFUR7JUZcKec+cBRz+oBex5pOVaJhrtxvFF2T7on53o9UkEux+/Nh1g/4zeb4t/pbxIFcADbn/ACu3LC1g==" "integrity": "sha512-bgYMo0WdaQhf7DhLE8OSNN/rVFO5J4K1A2VeeKqoV4MjjuHjfCP6xLpg8Xedhns7NlEnN3sZ6VJROq19Qyl6Sg=="
}, },
"through": { "through": {
"version": "2.3.8", "version": "2.3.8",
@ -3114,9 +3110,9 @@
"dev": true "dev": true
}, },
"tslib": { "tslib": {
"version": "1.13.0", "version": "1.14.1",
"resolved": "https://registry.npmjs.org/tslib/-/tslib-1.13.0.tgz", "resolved": "https://registry.npmjs.org/tslib/-/tslib-1.14.1.tgz",
"integrity": "sha512-i/6DQjL8Xf3be4K/E6Wgpekn5Qasl1usyw++dAA35Ue5orEn65VIxOA+YvNNl9HV3qv70T7CNwjODHZrLwvd1Q==", "integrity": "sha512-Xni35NKzjgMrwevysHTCArtLDpPvye8zV/0E4EyYn43P7/7qvQwPh9BGkHewbMulVntbigmcT7rdX3BNo9wRJg==",
"dev": true "dev": true
}, },
"tsscmp": { "tsscmp": {
@ -3160,9 +3156,9 @@
} }
}, },
"typescript": { "typescript": {
"version": "4.0.2", "version": "4.0.5",
"resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.2.tgz", "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.0.5.tgz",
"integrity": "sha512-e4ERvRV2wb+rRZ/IQeb3jm2VxBsirQLpQhdxplZ2MEzGvDkkMmPglecnNDfSUBivMjP93vRbngYYDQqQ/78bcQ==", "integrity": "sha512-ywmr/VrTVCmNTJ6iV2LwIrfG1P+lv6luD8sUJs+2eI9NLGigaN+nUQc13iHqisq7bra9lnmUSYqbJvegraBOPQ==",
"dev": true "dev": true
}, },
"typical": { "typical": {
@ -3193,9 +3189,9 @@
"dev": true "dev": true
}, },
"v8-compile-cache": { "v8-compile-cache": {
"version": "2.1.1", "version": "2.2.0",
"resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.1.1.tgz", "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.2.0.tgz",
"integrity": "sha512-8OQ9CL+VWyt3JStj7HX7/ciTL2V3Rl1Wf5OL+SNTm0yK1KvtReVulksyeRnCANHHuUxHlQig+JJDlUhBt1NQDQ==", "integrity": "sha512-gTpR5XQNKFwOd4clxfnhaqvfqMpqEwr4tOtCyz4MtYZX2JYhfr1JvBFKdS+7K/9rfpZR3VLX+YWBbKoxCgS43Q==",
"dev": true "dev": true
}, },
"validate-npm-package-license": { "validate-npm-package-license": {

View File

@ -1,6 +1,6 @@
{ {
"name": "skinview3d", "name": "skinview3d",
"version": "2.0.0-alpha.10", "version": "2.0.0-beta.1",
"description": "Three.js powered Minecraft skin viewer", "description": "Three.js powered Minecraft skin viewer",
"main": "libs/skinview3d.js", "main": "libs/skinview3d.js",
"type": "module", "type": "module",
@ -38,21 +38,21 @@
"bundles" "bundles"
], ],
"dependencies": { "dependencies": {
"skinview-utils": "^0.5.8", "skinview-utils": "^0.5.9",
"three": "^0.120.1" "three": "^0.122.0"
}, },
"devDependencies": { "devDependencies": {
"@rollup/plugin-node-resolve": "^9.0.0", "@rollup/plugin-node-resolve": "^10.0.0",
"@rollup/plugin-typescript": "^6.0.0", "@rollup/plugin-typescript": "^6.1.0",
"@typescript-eslint/eslint-plugin": "^4.1.0", "@typescript-eslint/eslint-plugin": "^4.6.0",
"@typescript-eslint/parser": "^4.1.0", "@typescript-eslint/parser": "^4.6.0",
"@yushijinhun/three-minifier-rollup": "^0.2.0-alpha.2", "@yushijinhun/three-minifier-rollup": "^0.2.0",
"eslint": "^7.8.1", "eslint": "^7.12.1",
"local-web-server": "^4.2.1", "local-web-server": "^4.2.1",
"npm-run-all": "^4.1.5", "npm-run-all": "^4.1.5",
"rimraf": "^3.0.2", "rimraf": "^3.0.2",
"rollup": "^2.26.11", "rollup": "^2.32.1",
"rollup-plugin-terser": "^7.0.2", "rollup-plugin-terser": "^7.0.2",
"typescript": "^4.0.2" "typescript": "^4.0.5"
} }
} }

View File

@ -202,3 +202,30 @@ export const RunningAnimation: Animation = (player, time) => {
export const RotatingAnimation: Animation = (player, time) => { export const RotatingAnimation: Animation = (player, time) => {
player.rotation.y = time; player.rotation.y = time;
}; };
function clamp(num: number, min: number, max: number): number {
return num <= min ? min : num >= max ? max : num;
}
export const FlyingAnimation: Animation = (player, time) => {
// body rotation finishes in 0.5s
// elytra expansion finishes in 3.3s
if (time < 0) time = 0;
time *= 20;
const startProgress = clamp((time * time) / 100, 0, 1);
player.rotation.x = startProgress * Math.PI / 2;
player.skin.head.rotation.x = startProgress > .5 ? Math.PI / 4 - player.rotation.x : 0;
const basicArmRotationZ = Math.PI * .25 * startProgress;
player.skin.leftArm.rotation.z = basicArmRotationZ;
player.skin.rightArm.rotation.z = -basicArmRotationZ;
const elytraRotationX = .34906584;
const elytraRotationZ = Math.PI / 2;
const interpolation = Math.pow(.9, time);
player.elytra.leftWing.rotation.x = elytraRotationX + interpolation * (.2617994 - elytraRotationX);
player.elytra.leftWing.rotation.z = elytraRotationZ + interpolation * (.2617994 - elytraRotationZ);
player.elytra.updateRightWing();
};

View File

@ -1,4 +1,5 @@
import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js"; import { EffectComposer } from "three/examples/jsm/postprocessing/EffectComposer.js";
import { Pass } from "three/examples/jsm/postprocessing/Pass.js";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js"; import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js"; import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.js";
import { FXAAShader } from "three/examples/jsm/shaders/FXAAShader.js"; import { FXAAShader } from "three/examples/jsm/shaders/FXAAShader.js";
@ -14,7 +15,7 @@ export class FXAASkinViewer extends SkinViewer {
* Note: FXAA doesn't work well with transparent backgrounds. * Note: FXAA doesn't work well with transparent backgrounds.
* It's recommended to use an opaque background and set `options.alpha` to false. * It's recommended to use an opaque background and set `options.alpha` to false.
*/ */
constructor(options: SkinViewerOptions = {}) { constructor(options?: SkinViewerOptions) {
super(options); super(options);
this.composer = new EffectComposer(this.renderer); this.composer = new EffectComposer(this.renderer);
this.renderPass = new RenderPass(this.scene, this.camera); this.renderPass = new RenderPass(this.scene, this.camera);
@ -42,4 +43,9 @@ export class FXAASkinViewer extends SkinViewer {
render(): void { render(): void {
this.composer.render(); this.composer.render();
} }
dispose(): void {
super.dispose();
(this.fxaaPass.fsQuad as Pass.FullScreenQuad).dispose();
}
} }

View File

@ -1,45 +1,47 @@
import { ModelType } from "skinview-utils"; import { ModelType } from "skinview-utils";
import { BoxGeometry, DoubleSide, FrontSide, Group, Mesh, MeshBasicMaterial, Object3D, Texture, Vector2 } from "three"; import { BoxGeometry, DoubleSide, FrontSide, Group, Mesh, MeshBasicMaterial, Object3D, Texture, Vector2 } from "three";
function toFaceVertices(x1: number, y1: number, x2: number, y2: number, w: number, h: number): Array<Vector2> { function setUVs(box: BoxGeometry, u: number, v: number, width: number, height: number, depth: number, textureWidth: number, textureHeight: number): void {
return [ const toFaceVertices = (x1: number, y1: number, x2: number, y2: number) => [
new Vector2(x1 / w, 1.0 - y2 / h), new Vector2(x1 / textureWidth, 1.0 - y2 / textureHeight),
new Vector2(x2 / w, 1.0 - y2 / h), new Vector2(x2 / textureWidth, 1.0 - y2 / textureHeight),
new Vector2(x2 / w, 1.0 - y1 / h), new Vector2(x2 / textureWidth, 1.0 - y1 / textureHeight),
new Vector2(x1 / w, 1.0 - y1 / h) new Vector2(x1 / textureWidth, 1.0 - y1 / textureHeight)
];
const top = toFaceVertices(u + depth, v, u + width + depth, v + depth);
const bottom = toFaceVertices(u + width + depth, v, u + width * 2 + depth, v + depth);
const left = toFaceVertices(u, v + depth, u + depth, v + depth + height);
const front = toFaceVertices(u + depth, v + depth, u + width + depth, v + depth + height);
const right = toFaceVertices(u + width + depth, v + depth, u + width + depth * 2, v + height + depth);
const back = toFaceVertices(u + width + depth * 2, v + depth, u + width * 2 + depth * 2, v + height + depth);
box.faceVertexUvs[0] = [
[right[3], right[0], right[2]],
[right[0], right[1], right[2]],
[left[3], left[0], left[2]],
[left[0], left[1], left[2]],
[top[3], top[0], top[2]],
[top[0], top[1], top[2]],
[bottom[0], bottom[3], bottom[1]],
[bottom[3], bottom[2], bottom[1]],
[front[3], front[0], front[2]],
[front[0], front[1], front[2]],
[back[3], back[0], back[2]],
[back[0], back[1], back[2]]
]; ];
} }
function toSkinVertices(x1: number, y1: number, x2: number, y2: number): Array<Vector2> { function setSkinUVs(box: BoxGeometry, u: number, v: number, width: number, height: number, depth: number): void {
return toFaceVertices(x1, y1, x2, y2, 64.0, 64.0); setUVs(box, u, v, width, height, depth, 64, 64);
} }
function toCapeVertices(x1: number, y1: number, x2: number, y2: number): Array<Vector2> { function setCapeUVs(box: BoxGeometry, u: number, v: number, width: number, height: number, depth: number): void {
return toFaceVertices(x1, y1, x2, y2, 64.0, 32.0); setUVs(box, u, v, width, height, depth, 64, 32);
} }
function toElytraVertices(x1: number, y1: number, x2: number, y2: number) { function setEarUVs(box: BoxGeometry, u: number, v: number, width: number, height: number, depth: number): void {
return toFaceVertices(x1 + 22, y1, x2 + 22, y2, 64.0, 32.0); setUVs(box, u, v, width, height, depth, 14, 7)
}
function toEarVertices(x1: number, y1: number, x2: number, y2: number): Array<Vector2> {
return toFaceVertices(x1, y1, x2, y2, 14.0, 7.0);
}
function setVertices(box: BoxGeometry, top: Array<Vector2>, bottom: Array<Vector2>, left: Array<Vector2>, front: Array<Vector2>, right: Array<Vector2>, back: Array<Vector2>): void {
box.faceVertexUvs[0] = [];
box.faceVertexUvs[0][0] = [right[3], right[0], right[2]];
box.faceVertexUvs[0][1] = [right[0], right[1], right[2]];
box.faceVertexUvs[0][2] = [left[3], left[0], left[2]];
box.faceVertexUvs[0][3] = [left[0], left[1], left[2]];
box.faceVertexUvs[0][4] = [top[3], top[0], top[2]];
box.faceVertexUvs[0][5] = [top[0], top[1], top[2]];
box.faceVertexUvs[0][6] = [bottom[0], bottom[3], bottom[1]];
box.faceVertexUvs[0][7] = [bottom[3], bottom[2], bottom[1]];
box.faceVertexUvs[0][8] = [front[3], front[0], front[2]];
box.faceVertexUvs[0][9] = [front[0], front[1], front[2]];
box.faceVertexUvs[0][10] = [back[3], back[0], back[2]];
box.faceVertexUvs[0][11] = [back[0], back[1], back[2]];
} }
/** /**
@ -95,231 +97,116 @@ export class SkinObject extends Group {
// Head // Head
const headBox = new BoxGeometry(8, 8, 8); const headBox = new BoxGeometry(8, 8, 8);
setVertices(headBox, setSkinUVs(headBox, 0, 0, 8, 8, 8);
toSkinVertices(8, 0, 16, 8),
toSkinVertices(16, 0, 24, 8),
toSkinVertices(0, 8, 8, 16),
toSkinVertices(8, 8, 16, 16),
toSkinVertices(16, 8, 24, 16),
toSkinVertices(24, 8, 32, 16)
);
const headMesh = new Mesh(headBox, layer1Material); const headMesh = new Mesh(headBox, layer1Material);
const head2Box = new BoxGeometry(9, 9, 9); const head2Box = new BoxGeometry(9, 9, 9);
setVertices(head2Box, setSkinUVs(head2Box, 32, 0, 8, 8, 8);
toSkinVertices(40, 0, 48, 8),
toSkinVertices(48, 0, 56, 8),
toSkinVertices(32, 8, 40, 16),
toSkinVertices(40, 8, 48, 16),
toSkinVertices(48, 8, 56, 16),
toSkinVertices(56, 8, 64, 16)
);
const head2Mesh = new Mesh(head2Box, layer2Material); const head2Mesh = new Mesh(head2Box, layer2Material);
head2Mesh.renderOrder = -1;
this.head = new BodyPart(headMesh, head2Mesh); this.head = new BodyPart(headMesh, head2Mesh);
this.head.name = "head"; this.head.name = "head";
this.head.add(headMesh, head2Mesh); this.head.add(headMesh, head2Mesh);
this.head.position.y = 4;
this.add(this.head); this.add(this.head);
// Body // Body
const bodyBox = new BoxGeometry(8, 12, 4); const bodyBox = new BoxGeometry(8, 12, 4);
setVertices(bodyBox, setSkinUVs(bodyBox, 16, 16, 8, 12, 4);
toSkinVertices(20, 16, 28, 20),
toSkinVertices(28, 16, 36, 20),
toSkinVertices(16, 20, 20, 32),
toSkinVertices(20, 20, 28, 32),
toSkinVertices(28, 20, 32, 32),
toSkinVertices(32, 20, 40, 32)
);
const bodyMesh = new Mesh(bodyBox, layer1Material); const bodyMesh = new Mesh(bodyBox, layer1Material);
const body2Box = new BoxGeometry(9, 13.5, 4.5); const body2Box = new BoxGeometry(8.5, 12.5, 4.5);
setVertices(body2Box, setSkinUVs(body2Box, 16, 32, 8, 12, 4);
toSkinVertices(20, 32, 28, 36),
toSkinVertices(28, 32, 36, 36),
toSkinVertices(16, 36, 20, 48),
toSkinVertices(20, 36, 28, 48),
toSkinVertices(28, 36, 32, 48),
toSkinVertices(32, 36, 40, 48)
);
const body2Mesh = new Mesh(body2Box, layer2Material); const body2Mesh = new Mesh(body2Box, layer2Material);
this.body = new BodyPart(bodyMesh, body2Mesh); this.body = new BodyPart(bodyMesh, body2Mesh);
this.body.name = "body"; this.body.name = "body";
this.body.add(bodyMesh, body2Mesh); this.body.add(bodyMesh, body2Mesh);
this.body.position.y = -10; this.body.position.y = -6;
this.add(this.body); this.add(this.body);
// Right Arm // Right Arm
const rightArmBox = new BoxGeometry(); const rightArmBox = new BoxGeometry();
const rightArmMesh = new Mesh(rightArmBox, layer1Material); const rightArmMesh = new Mesh(rightArmBox, layer1MaterialBiased);
this.modelListeners.push(() => { this.modelListeners.push(() => {
rightArmMesh.scale.x = this.slim ? 3 : 4; rightArmMesh.scale.x = this.slim ? 3 : 4;
rightArmMesh.scale.y = 12; rightArmMesh.scale.y = 12;
rightArmMesh.scale.z = 4; rightArmMesh.scale.z = 4;
if (this.slim) { setSkinUVs(rightArmBox, 40, 16, this.slim ? 3 : 4, 12, 4);
setVertices(rightArmBox,
toSkinVertices(44, 16, 47, 20),
toSkinVertices(47, 16, 50, 20),
toSkinVertices(40, 20, 44, 32),
toSkinVertices(44, 20, 47, 32),
toSkinVertices(47, 20, 51, 32),
toSkinVertices(51, 20, 54, 32)
);
} else {
setVertices(rightArmBox,
toSkinVertices(44, 16, 48, 20),
toSkinVertices(48, 16, 52, 20),
toSkinVertices(40, 20, 44, 32),
toSkinVertices(44, 20, 48, 32),
toSkinVertices(48, 20, 52, 32),
toSkinVertices(52, 20, 56, 32)
);
}
rightArmBox.uvsNeedUpdate = true; rightArmBox.uvsNeedUpdate = true;
rightArmBox.elementsNeedUpdate = true; rightArmBox.elementsNeedUpdate = true;
}); });
const rightArm2Box = new BoxGeometry(); const rightArm2Box = new BoxGeometry();
const rightArm2Mesh = new Mesh(rightArm2Box, layer2MaterialBiased); const rightArm2Mesh = new Mesh(rightArm2Box, layer2MaterialBiased);
rightArm2Mesh.renderOrder = 1;
this.modelListeners.push(() => { this.modelListeners.push(() => {
rightArm2Mesh.scale.x = this.slim ? 3.375 : 4.5; rightArm2Mesh.scale.x = this.slim ? 3.5 : 4.5;
rightArm2Mesh.scale.y = 13.5; rightArm2Mesh.scale.y = 12.5;
rightArm2Mesh.scale.z = 4.5; rightArm2Mesh.scale.z = 4.5;
if (this.slim) { setSkinUVs(rightArm2Box, 40, 32, this.slim ? 3 : 4, 12, 4);
setVertices(rightArm2Box,
toSkinVertices(44, 32, 47, 36),
toSkinVertices(47, 32, 50, 36),
toSkinVertices(40, 36, 44, 48),
toSkinVertices(44, 36, 47, 48),
toSkinVertices(47, 36, 51, 48),
toSkinVertices(51, 36, 54, 48)
);
} else {
setVertices(rightArm2Box,
toSkinVertices(44, 32, 48, 36),
toSkinVertices(48, 32, 52, 36),
toSkinVertices(40, 36, 44, 48),
toSkinVertices(44, 36, 48, 48),
toSkinVertices(48, 36, 52, 48),
toSkinVertices(52, 36, 56, 48)
);
}
rightArm2Box.uvsNeedUpdate = true; rightArm2Box.uvsNeedUpdate = true;
rightArm2Box.elementsNeedUpdate = true; rightArm2Box.elementsNeedUpdate = true;
}); });
const rightArmPivot = new Group(); const rightArmPivot = new Group();
rightArmPivot.add(rightArmMesh, rightArm2Mesh); rightArmPivot.add(rightArmMesh, rightArm2Mesh);
this.modelListeners.push(() => {
rightArmPivot.position.x = this.slim ? -.5 : -1;
});
rightArmPivot.position.y = -4; rightArmPivot.position.y = -4;
this.rightArm = new BodyPart(rightArmMesh, rightArm2Mesh); this.rightArm = new BodyPart(rightArmMesh, rightArm2Mesh);
this.rightArm.name = "rightArm"; this.rightArm.name = "rightArm";
this.rightArm.add(rightArmPivot); this.rightArm.add(rightArmPivot);
this.rightArm.position.y = -6; this.rightArm.position.x = -5;
this.modelListeners.push(() => { this.rightArm.position.y = -2;
this.rightArm.position.x = this.slim ? -5.5 : -6;
});
this.add(this.rightArm); this.add(this.rightArm);
// Left Arm // Left Arm
const leftArmBox = new BoxGeometry(); const leftArmBox = new BoxGeometry();
const leftArmMesh = new Mesh(leftArmBox, layer1Material); const leftArmMesh = new Mesh(leftArmBox, layer1MaterialBiased);
this.modelListeners.push(() => { this.modelListeners.push(() => {
leftArmMesh.scale.x = this.slim ? 3 : 4; leftArmMesh.scale.x = this.slim ? 3 : 4;
leftArmMesh.scale.y = 12; leftArmMesh.scale.y = 12;
leftArmMesh.scale.z = 4; leftArmMesh.scale.z = 4;
if (this.slim) { setSkinUVs(leftArmBox, 32, 48, this.slim ? 3 : 4, 12, 4);
setVertices(leftArmBox,
toSkinVertices(36, 48, 39, 52),
toSkinVertices(39, 48, 42, 52),
toSkinVertices(32, 52, 36, 64),
toSkinVertices(36, 52, 39, 64),
toSkinVertices(39, 52, 43, 64),
toSkinVertices(43, 52, 46, 64)
);
} else {
setVertices(leftArmBox,
toSkinVertices(36, 48, 40, 52),
toSkinVertices(40, 48, 44, 52),
toSkinVertices(32, 52, 36, 64),
toSkinVertices(36, 52, 40, 64),
toSkinVertices(40, 52, 44, 64),
toSkinVertices(44, 52, 48, 64)
);
}
leftArmBox.uvsNeedUpdate = true; leftArmBox.uvsNeedUpdate = true;
leftArmBox.elementsNeedUpdate = true; leftArmBox.elementsNeedUpdate = true;
}); });
const leftArm2Box = new BoxGeometry(); const leftArm2Box = new BoxGeometry();
const leftArm2Mesh = new Mesh(leftArm2Box, layer2MaterialBiased); const leftArm2Mesh = new Mesh(leftArm2Box, layer2MaterialBiased);
leftArm2Mesh.renderOrder = 1;
this.modelListeners.push(() => { this.modelListeners.push(() => {
leftArm2Mesh.scale.x = this.slim ? 3.375 : 4.5; leftArm2Mesh.scale.x = this.slim ? 3.5 : 4.5;
leftArm2Mesh.scale.y = 13.5; leftArm2Mesh.scale.y = 12.5;
leftArm2Mesh.scale.z = 4.5; leftArm2Mesh.scale.z = 4.5;
if (this.slim) { setSkinUVs(leftArm2Box, 48, 48, this.slim ? 3 : 4, 12, 4);
setVertices(leftArm2Box,
toSkinVertices(52, 48, 55, 52),
toSkinVertices(55, 48, 58, 52),
toSkinVertices(48, 52, 52, 64),
toSkinVertices(52, 52, 55, 64),
toSkinVertices(55, 52, 59, 64),
toSkinVertices(59, 52, 62, 64)
);
} else {
setVertices(leftArm2Box,
toSkinVertices(52, 48, 56, 52),
toSkinVertices(56, 48, 60, 52),
toSkinVertices(48, 52, 52, 64),
toSkinVertices(52, 52, 56, 64),
toSkinVertices(56, 52, 60, 64),
toSkinVertices(60, 52, 64, 64)
);
}
leftArm2Box.uvsNeedUpdate = true; leftArm2Box.uvsNeedUpdate = true;
leftArm2Box.elementsNeedUpdate = true; leftArm2Box.elementsNeedUpdate = true;
}); });
const leftArmPivot = new Group(); const leftArmPivot = new Group();
leftArmPivot.add(leftArmMesh, leftArm2Mesh); leftArmPivot.add(leftArmMesh, leftArm2Mesh);
this.modelListeners.push(() => {
leftArmPivot.position.x = this.slim ? 0.5 : 1;
});
leftArmPivot.position.y = -4; leftArmPivot.position.y = -4;
this.leftArm = new BodyPart(leftArmMesh, leftArm2Mesh); this.leftArm = new BodyPart(leftArmMesh, leftArm2Mesh);
this.leftArm.name = "leftArm"; this.leftArm.name = "leftArm";
this.leftArm.add(leftArmPivot); this.leftArm.add(leftArmPivot);
this.leftArm.position.y = -6; this.leftArm.position.x = 5;
this.modelListeners.push(() => { this.leftArm.position.y = -2;
this.leftArm.position.x = this.slim ? 5.5 : 6;
});
this.add(this.leftArm); this.add(this.leftArm);
// Right Leg // Right Leg
const rightLegBox = new BoxGeometry(4, 12, 4); const rightLegBox = new BoxGeometry(4, 12, 4);
setVertices(rightLegBox, setSkinUVs(rightLegBox, 0, 16, 4, 12, 4);
toSkinVertices(4, 16, 8, 20),
toSkinVertices(8, 16, 12, 20),
toSkinVertices(0, 20, 4, 32),
toSkinVertices(4, 20, 8, 32),
toSkinVertices(8, 20, 12, 32),
toSkinVertices(12, 20, 16, 32)
);
const rightLegMesh = new Mesh(rightLegBox, layer1MaterialBiased); const rightLegMesh = new Mesh(rightLegBox, layer1MaterialBiased);
const rightLeg2Box = new BoxGeometry(4.5, 13.5, 4.5); const rightLeg2Box = new BoxGeometry(4.5, 12.5, 4.5);
setVertices(rightLeg2Box, setSkinUVs(rightLeg2Box, 0, 32, 4, 12, 4);
toSkinVertices(4, 32, 8, 36),
toSkinVertices(8, 32, 12, 36),
toSkinVertices(0, 36, 4, 48),
toSkinVertices(4, 36, 8, 48),
toSkinVertices(8, 36, 12, 48),
toSkinVertices(12, 36, 16, 48)
);
const rightLeg2Mesh = new Mesh(rightLeg2Box, layer2MaterialBiased); const rightLeg2Mesh = new Mesh(rightLeg2Box, layer2MaterialBiased);
rightLeg2Mesh.renderOrder = 1;
const rightLegPivot = new Group(); const rightLegPivot = new Group();
rightLegPivot.add(rightLegMesh, rightLeg2Mesh); rightLegPivot.add(rightLegMesh, rightLeg2Mesh);
@ -328,33 +215,19 @@ export class SkinObject extends Group {
this.rightLeg = new BodyPart(rightLegMesh, rightLeg2Mesh); this.rightLeg = new BodyPart(rightLegMesh, rightLeg2Mesh);
this.rightLeg.name = "rightLeg"; this.rightLeg.name = "rightLeg";
this.rightLeg.add(rightLegPivot); this.rightLeg.add(rightLegPivot);
this.rightLeg.position.y = -16; this.rightLeg.position.x = -1.9;
this.rightLeg.position.x = -2; this.rightLeg.position.y = -12;
this.rightLeg.position.z = -.1;
this.add(this.rightLeg); this.add(this.rightLeg);
// Left Leg // Left Leg
const leftLegBox = new BoxGeometry(4, 12, 4); const leftLegBox = new BoxGeometry(4, 12, 4);
setVertices(leftLegBox, setSkinUVs(leftLegBox, 16, 48, 4, 12, 4);
toSkinVertices(20, 48, 24, 52),
toSkinVertices(24, 48, 28, 52),
toSkinVertices(16, 52, 20, 64),
toSkinVertices(20, 52, 24, 64),
toSkinVertices(24, 52, 28, 64),
toSkinVertices(28, 52, 32, 64)
);
const leftLegMesh = new Mesh(leftLegBox, layer1MaterialBiased); const leftLegMesh = new Mesh(leftLegBox, layer1MaterialBiased);
const leftLeg2Box = new BoxGeometry(4.5, 13.5, 4.5); const leftLeg2Box = new BoxGeometry(4.5, 12.5, 4.5);
setVertices(leftLeg2Box, setSkinUVs(leftLeg2Box, 0, 48, 4, 12, 4);
toSkinVertices(4, 48, 8, 52),
toSkinVertices(8, 48, 12, 52),
toSkinVertices(0, 52, 4, 64),
toSkinVertices(4, 52, 8, 64),
toSkinVertices(8, 52, 12, 64),
toSkinVertices(12, 52, 16, 64)
);
const leftLeg2Mesh = new Mesh(leftLeg2Box, layer2MaterialBiased); const leftLeg2Mesh = new Mesh(leftLeg2Box, layer2MaterialBiased);
leftLeg2Mesh.renderOrder = 1;
const leftLegPivot = new Group(); const leftLegPivot = new Group();
leftLegPivot.add(leftLegMesh, leftLeg2Mesh); leftLegPivot.add(leftLegMesh, leftLeg2Mesh);
@ -363,8 +236,9 @@ export class SkinObject extends Group {
this.leftLeg = new BodyPart(leftLegMesh, leftLeg2Mesh); this.leftLeg = new BodyPart(leftLegMesh, leftLeg2Mesh);
this.leftLeg.name = "leftLeg"; this.leftLeg.name = "leftLeg";
this.leftLeg.add(leftLegPivot); this.leftLeg.add(leftLegPivot);
this.leftLeg.position.y = -16; this.leftLeg.position.x = 1.9;
this.leftLeg.position.x = 2; this.leftLeg.position.y = -12;
this.leftLeg.position.z = -.1;
this.add(this.leftLeg); this.add(this.leftLeg);
this.modelType = "default"; this.modelType = "default";
@ -406,20 +280,13 @@ export class CapeObject extends Group {
alphaTest: 1e-5 alphaTest: 1e-5
}); });
// back = outside // +z (front) - inside of cape
// front = inside // -z (back) - outside of cape
const capeBox = new BoxGeometry(10, 16, 1); const capeBox = new BoxGeometry(10, 16, 1);
setVertices(capeBox, setCapeUVs(capeBox, 0, 0, 10, 16, 1);
toCapeVertices(11, 1, 1, 0),
toCapeVertices(21, 1, 11, 0),
toCapeVertices(11, 1, 12, 17),
toCapeVertices(12, 1, 22, 17),
toCapeVertices(0, 1, 1, 17),
toCapeVertices(1, 1, 11, 17)
);
this.cape = new Mesh(capeBox, capeMaterial); this.cape = new Mesh(capeBox, capeMaterial);
this.cape.position.y = -8; this.cape.position.y = -8;
this.cape.position.z = -0.5; this.cape.position.z = .5;
this.add(this.cape); this.add(this.cape);
} }
} }
@ -436,66 +303,47 @@ export class ElytraObject extends Group {
map: texture, map: texture,
side: DoubleSide, side: DoubleSide,
transparent: true, transparent: true,
alphaTest: 1e-5, alphaTest: 1e-5
polygonOffset: true,
polygonOffsetFactor: -1.0,
polygonOffsetUnits: -4.0
}); });
const leftWingBox = new BoxGeometry(10, 20, 2); const leftWingBox = new BoxGeometry(12, 22, 4);
setVertices(leftWingBox, setCapeUVs(leftWingBox, 22, 0, 10, 20, 2);
toElytraVertices(2, 0, 12, 2),
toElytraVertices(12, 0, 22, 2),
toElytraVertices(0, 2, 2, 22),
toElytraVertices(2, 2, 12, 22),
toElytraVertices(12, 2, 14, 22),
toElytraVertices(14, 2, 24, 22)
);
const leftWingMesh = new Mesh(leftWingBox, elytraMaterial); const leftWingMesh = new Mesh(leftWingBox, elytraMaterial);
leftWingMesh.position.x = -5; leftWingMesh.position.x = -5;
leftWingMesh.position.y = -10; leftWingMesh.position.y = -10;
leftWingMesh.position.z = -1; leftWingMesh.position.z = -1;
leftWingMesh.scale.x = -1.15
leftWingMesh.scale.y = 1.15;
leftWingMesh.scale.z = 1.15;
this.leftWing = new Group(); this.leftWing = new Group();
this.leftWing.add(leftWingMesh); this.leftWing.add(leftWingMesh);
this.leftWing.position.x = 3; // Left - right
this.leftWing.position.y = -1.5; //Up - down
this.leftWing.rotation.y = 0;
this.leftWing.rotation.z = -0.19;
this.add(this.leftWing); this.add(this.leftWing);
const rightWingBox = new BoxGeometry(10, 20, 2); const rightWingBox = new BoxGeometry(12, 22, 4);
setVertices(rightWingBox, setCapeUVs(rightWingBox, 22, 0, 10, 20, 2);
toElytraVertices(2, 0, 12, 2),
toElytraVertices(12, 0, 22, 2),
toElytraVertices(0, 2, 2, 22),
toElytraVertices(2, 2, 12, 22),
toElytraVertices(12, 2, 14, 22),
toElytraVertices(14, 2, 24, 22)
);
const rightWingMesh = new Mesh(rightWingBox, elytraMaterial); const rightWingMesh = new Mesh(rightWingBox, elytraMaterial);
rightWingMesh.scale.x = -1;
rightWingMesh.position.x = 5; rightWingMesh.position.x = 5;
rightWingMesh.position.y = -10; rightWingMesh.position.y = -10;
rightWingMesh.position.z = -1; rightWingMesh.position.z = -1;
rightWingMesh.scale.x = 1.15
rightWingMesh.scale.y = 1.15;
rightWingMesh.scale.z = 1.15;
this.rightWing = new Group(); this.rightWing = new Group();
this.rightWing.add(rightWingMesh); this.rightWing.add(rightWingMesh);
this.rightWing.position.x = -3; // Left - right
this.rightWing.position.y = -1.5; //Up - down
this.rightWing.rotation.y = 0;
this.rightWing.rotation.z = 0.19;
this.add(this.rightWing); this.add(this.rightWing);
this.leftWing.position.x = 5;
this.leftWing.rotation.x = .2617994;
this.leftWing.rotation.y = .01; // to avoid z-fighting
this.leftWing.rotation.z = .2617994;
this.updateRightWing();
}
/**
* Mirrors the position & rotation of left wing,
* and apply them to the right wing.
*/
updateRightWing(): void {
this.rightWing.position.x = -this.leftWing.position.x;
this.rightWing.position.y = this.leftWing.position.y;
this.rightWing.rotation.x = this.leftWing.rotation.x;
this.rightWing.rotation.y = -this.leftWing.rotation.y;
this.rightWing.rotation.z = -this.leftWing.rotation.z;
} }
} }
@ -544,6 +392,8 @@ export class EarsObject extends Group {
} }
} }
export type BackEquipment = "cape" | "elytra";
export class PlayerObject extends Group { export class PlayerObject extends Group {
readonly skin: SkinObject; readonly skin: SkinObject;
@ -562,15 +412,13 @@ export class PlayerObject extends Group {
this.cape = new CapeObject(capeTexture); this.cape = new CapeObject(capeTexture);
this.cape.name = "cape"; this.cape.name = "cape";
this.cape.position.z = -2; this.cape.position.z = -2;
this.cape.position.y = -6;
this.cape.rotation.x = 10.8 * Math.PI / 180; this.cape.rotation.x = 10.8 * Math.PI / 180;
this.cape.rotation.y = Math.PI;
this.add(this.cape); this.add(this.cape);
this.elytra = new ElytraObject(capeTexture); this.elytra = new ElytraObject(capeTexture);
this.elytra.name = "elytra"; this.elytra.name = "elytra";
this.elytra.position.y = -7;
this.elytra.position.z = -2; this.elytra.position.z = -2;
this.elytra.rotation.x = 10.8 * Math.PI / 180;
this.elytra.visible = false; this.elytra.visible = false;
this.add(this.elytra); this.add(this.elytra);
@ -579,4 +427,19 @@ export class PlayerObject extends Group {
this.ears.position.y = 3.5; this.ears.position.y = 3.5;
this.add(this.ears); this.add(this.ears);
} }
get backEquipment(): BackEquipment | null {
if (this.cape.visible) {
return "cape";
} else if (this.elytra.visible) {
return "elytra";
} else {
return null;
}
}
set backEquipment(value: BackEquipment | null) {
this.cape.visible = value === "cape";
this.elytra.visible = value === "elytra";
}
} }

View File

@ -7,7 +7,7 @@ export function createOrbitControls(skinViewer: SkinViewer): OrbitControls {
// default configuration // default configuration
control.enablePan = false; control.enablePan = false;
control.target = new Vector3(0, -12, 0); control.target = new Vector3(0, -8, 0);
control.minDistance = 10; control.minDistance = 10;
control.maxDistance = 256; control.maxDistance = 256;
control.update(); control.update();

View File

@ -1,16 +1,24 @@
import { applyMixins, CapeContainer, ModelType, SkinContainer, RemoteImage, TextureSource, TextureCanvas, loadImage, isTextureSource } from "skinview-utils"; import { applyMixins, CapeContainer, ModelType, SkinContainer, RemoteImage, TextureSource, TextureCanvas, loadImage, isTextureSource } from "skinview-utils";
import { NearestFilter, PerspectiveCamera, Scene, Texture, Vector2, WebGLRenderer } from "three"; import { NearestFilter, PerspectiveCamera, Scene, Texture, Vector2, WebGLRenderer } from "three";
import { RootAnimation } from "./animation.js"; import { RootAnimation } from "./animation.js";
import { PlayerObject } from "./model.js"; import { BackEquipment, PlayerObject } from "./model.js";
export type LoadOptions = { export interface LoadOptions {
/** /**
* Whether to make the object visible after the texture is loaded. Default is true. * Whether to make the object visible after the texture is loaded. Default is true.
*/ */
makeVisible?: boolean; makeVisible?: boolean;
} }
export type SkinViewerOptions = { export interface CapeLoadOptions extends LoadOptions {
/**
* The equipment (cape or elytra) to show, defaults to "cape".
* If makeVisible is set to false, this option will have no effect.
*/
backEquipment?: BackEquipment;
}
export interface SkinViewerOptions {
width?: number; width?: number;
height?: number; height?: number;
skin?: RemoteImage | TextureSource; skin?: RemoteImage | TextureSource;
@ -41,13 +49,6 @@ export type SkinViewerOptions = {
renderPaused?: boolean; renderPaused?: boolean;
} }
function toMakeVisible(options?: LoadOptions): boolean {
if (options && options.makeVisible === false) {
return false;
}
return true;
}
class SkinViewer { class SkinViewer {
readonly canvas: HTMLCanvasElement; readonly canvas: HTMLCanvasElement;
readonly scene: Scene; readonly scene: Scene;
@ -105,9 +106,8 @@ class SkinViewer {
this.scene = new Scene(); this.scene = new Scene();
// Use smaller fov to avoid distortion // Use smaller fov to avoid distortion
this.camera = new PerspectiveCamera(42); this.camera = new PerspectiveCamera(40);
this.camera.zoom this.camera.position.y = -8;
this.camera.position.y = -12;
this.camera.position.z = 60; this.camera.position.z = 60;
this.renderer = new WebGLRenderer({ this.renderer = new WebGLRenderer({
@ -148,18 +148,18 @@ class SkinViewer {
} }
} }
protected skinLoaded(model: ModelType, options?: LoadOptions): void { protected skinLoaded(model: ModelType, options: LoadOptions = {}): void {
this.skinTexture.needsUpdate = true; this.skinTexture.needsUpdate = true;
this.playerObject.skin.modelType = model; this.playerObject.skin.modelType = model;
if (toMakeVisible(options)) { if (options.makeVisible !== false) {
this.playerObject.skin.visible = true; this.playerObject.skin.visible = true;
} }
} }
protected capeLoaded(options?: LoadOptions): void { protected capeLoaded(options: CapeLoadOptions = {}): void {
this.capeTexture.needsUpdate = true; this.capeTexture.needsUpdate = true;
if (toMakeVisible(options)) { if (options.makeVisible !== false) {
this.playerObject.cape.visible = true; this.playerObject.backEquipment = options.backEquipment === undefined ? "cape" : options.backEquipment;
} }
} }
@ -175,7 +175,7 @@ class SkinViewer {
} }
protected resetCape(): void { protected resetCape(): void {
this.playerObject.cape.visible = false; this.playerObject.backEquipment = null;
} }
protected resetEars(): void { protected resetEars(): void {
@ -338,7 +338,6 @@ class SkinViewer {
this.playerObject.elytra.visible = !this.playerObject.cape.visible; this.playerObject.elytra.visible = !this.playerObject.cape.visible;
} }
} }
interface SkinViewer extends SkinContainer<LoadOptions>, CapeContainer<CapeLoadOptions> { }
interface SkinViewer extends SkinContainer<LoadOptions>, CapeContainer<LoadOptions> { }
applyMixins(SkinViewer, [SkinContainer, CapeContainer]); applyMixins(SkinViewer, [SkinContainer, CapeContainer]);
export { SkinViewer }; export { SkinViewer };