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
* HD Skins
* Capes
* Elytras
* Slim Arms
* Automatic model detection (Slim / Default)
@ -37,7 +38,10 @@ Three.js powered Minecraft skin viewer.
// Load a cape
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);
// 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>
</div>
<div>
<h2>Walk / Run</h2>
<h2>Walk / Run / Fly</h2>
<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_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_fly" name="primary_animation" value="fly"> Fly</label>
</div>
<label class="control">Speed: <input id="primary_animation_speed" type="number" value="1" step="0.1"></label>
</div>
@ -182,6 +183,13 @@
</tr>
</tbody>
</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 class="control-section">
@ -261,11 +269,12 @@
const skinLayers = ["innerLayer", "outerLayer"];
const availableAnimations = {
walk: skinview3d.WalkingAnimation,
run: skinview3d.RunningAnimation
run: skinview3d.RunningAnimation,
fly: skinview3d.FlyingAnimation
};
let skinViewer;
let oribitControl;
let orbitControl;
let rotateAnimation;
let primaryAnimation;
@ -303,7 +312,13 @@
skinViewer.loadCustomCape(null);
input.setCustomValidity("");
} 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;
}
});
document.getElementById("control_rotate").addEventListener("change", e => oribitControl.enableRotate = e.target.checked);
document.getElementById("control_zoom").addEventListener("change", e => oribitControl.enableZoom = e.target.checked);
document.getElementById("control_pan").addEventListener("change", e => oribitControl.enablePan = e.target.checked);
document.getElementById("control_rotate").addEventListener("change", e => orbitControl.enableRotate = e.target.checked);
document.getElementById("control_zoom").addEventListener("change", e => orbitControl.enableZoom = e.target.checked);
document.getElementById("control_pan").addEventListener("change", e => orbitControl.enablePan = e.target.checked);
for (const part of skinParts) {
for (const layer of skinLayers) {
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("ears_url").addEventListener("change", () => reloadEars());
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", () => {
skinViewer.dispose();
orbitControl.dispose();
initializeViewer();
});
}
@ -401,7 +429,7 @@
alpha: false
});
skinViewer.renderer.setClearColor(0x5a76f3);
oribitControl = skinview3d.createOrbitControls(skinViewer);
orbitControl = skinview3d.createOrbitControls(skinViewer);
rotateAnimation = null;
primaryAnimation = null;
@ -417,9 +445,9 @@
primaryAnimation = skinViewer.animations.add(availableAnimations[primaryAnimationName]);
primaryAnimation.speed = document.getElementById("primary_animation_speed").value;
}
oribitControl.enableRotate = document.getElementById("control_rotate").checked;
oribitControl.enableZoom = document.getElementById("control_zoom").checked;
oribitControl.enablePan = document.getElementById("control_pan").checked;
orbitControl.enableRotate = document.getElementById("control_rotate").checked;
orbitControl.enableZoom = document.getElementById("control_zoom").checked;
orbitControl.enablePan = document.getElementById("control_pan").checked;
for (const part of skinParts) {
for (const layer of skinLayers) {
skinViewer.playerObject.skin[part][layer].visible =

View File

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

242
package-lock.json generated
View File

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

View File

@ -1,6 +1,6 @@
{
"name": "skinview3d",
"version": "2.0.0-alpha.10",
"version": "2.0.0-beta.1",
"description": "Three.js powered Minecraft skin viewer",
"main": "libs/skinview3d.js",
"type": "module",
@ -38,21 +38,21 @@
"bundles"
],
"dependencies": {
"skinview-utils": "^0.5.8",
"three": "^0.120.1"
"skinview-utils": "^0.5.9",
"three": "^0.122.0"
},
"devDependencies": {
"@rollup/plugin-node-resolve": "^9.0.0",
"@rollup/plugin-typescript": "^6.0.0",
"@typescript-eslint/eslint-plugin": "^4.1.0",
"@typescript-eslint/parser": "^4.1.0",
"@yushijinhun/three-minifier-rollup": "^0.2.0-alpha.2",
"eslint": "^7.8.1",
"@rollup/plugin-node-resolve": "^10.0.0",
"@rollup/plugin-typescript": "^6.1.0",
"@typescript-eslint/eslint-plugin": "^4.6.0",
"@typescript-eslint/parser": "^4.6.0",
"@yushijinhun/three-minifier-rollup": "^0.2.0",
"eslint": "^7.12.1",
"local-web-server": "^4.2.1",
"npm-run-all": "^4.1.5",
"rimraf": "^3.0.2",
"rollup": "^2.26.11",
"rollup": "^2.32.1",
"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) => {
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 { Pass } from "three/examples/jsm/postprocessing/Pass.js";
import { RenderPass } from "three/examples/jsm/postprocessing/RenderPass.js";
import { ShaderPass } from "three/examples/jsm/postprocessing/ShaderPass.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.
* It's recommended to use an opaque background and set `options.alpha` to false.
*/
constructor(options: SkinViewerOptions = {}) {
constructor(options?: SkinViewerOptions) {
super(options);
this.composer = new EffectComposer(this.renderer);
this.renderPass = new RenderPass(this.scene, this.camera);
@ -42,4 +43,9 @@ export class FXAASkinViewer extends SkinViewer {
render(): void {
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 { 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> {
return [
new Vector2(x1 / w, 1.0 - y2 / h),
new Vector2(x2 / w, 1.0 - y2 / h),
new Vector2(x2 / w, 1.0 - y1 / h),
new Vector2(x1 / w, 1.0 - y1 / h)
function setUVs(box: BoxGeometry, u: number, v: number, width: number, height: number, depth: number, textureWidth: number, textureHeight: number): void {
const toFaceVertices = (x1: number, y1: number, x2: number, y2: number) => [
new Vector2(x1 / textureWidth, 1.0 - y2 / textureHeight),
new Vector2(x2 / textureWidth, 1.0 - y2 / textureHeight),
new Vector2(x2 / textureWidth, 1.0 - y1 / textureHeight),
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> {
return toFaceVertices(x1, y1, x2, y2, 64.0, 64.0);
function setSkinUVs(box: BoxGeometry, u: number, v: number, width: number, height: number, depth: number): void {
setUVs(box, u, v, width, height, depth, 64, 64);
}
function toCapeVertices(x1: number, y1: number, x2: number, y2: number): Array<Vector2> {
return toFaceVertices(x1, y1, x2, y2, 64.0, 32.0);
function setCapeUVs(box: BoxGeometry, u: number, v: number, width: number, height: number, depth: number): void {
setUVs(box, u, v, width, height, depth, 64, 32);
}
function toElytraVertices(x1: number, y1: number, x2: number, y2: number) {
return toFaceVertices(x1 + 22, y1, x2 + 22, y2, 64.0, 32.0);
}
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]];
function setEarUVs(box: BoxGeometry, u: number, v: number, width: number, height: number, depth: number): void {
setUVs(box, u, v, width, height, depth, 14, 7)
}
/**
@ -95,231 +97,116 @@ export class SkinObject extends Group {
// Head
const headBox = new BoxGeometry(8, 8, 8);
setVertices(headBox,
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)
);
setSkinUVs(headBox, 0, 0, 8, 8, 8);
const headMesh = new Mesh(headBox, layer1Material);
const head2Box = new BoxGeometry(9, 9, 9);
setVertices(head2Box,
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)
);
setSkinUVs(head2Box, 32, 0, 8, 8, 8);
const head2Mesh = new Mesh(head2Box, layer2Material);
head2Mesh.renderOrder = -1;
this.head = new BodyPart(headMesh, head2Mesh);
this.head.name = "head";
this.head.add(headMesh, head2Mesh);
this.head.position.y = 4;
this.add(this.head);
// Body
const bodyBox = new BoxGeometry(8, 12, 4);
setVertices(bodyBox,
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)
);
setSkinUVs(bodyBox, 16, 16, 8, 12, 4);
const bodyMesh = new Mesh(bodyBox, layer1Material);
const body2Box = new BoxGeometry(9, 13.5, 4.5);
setVertices(body2Box,
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 body2Box = new BoxGeometry(8.5, 12.5, 4.5);
setSkinUVs(body2Box, 16, 32, 8, 12, 4);
const body2Mesh = new Mesh(body2Box, layer2Material);
this.body = new BodyPart(bodyMesh, body2Mesh);
this.body.name = "body";
this.body.add(bodyMesh, body2Mesh);
this.body.position.y = -10;
this.body.position.y = -6;
this.add(this.body);
// Right Arm
const rightArmBox = new BoxGeometry();
const rightArmMesh = new Mesh(rightArmBox, layer1Material);
const rightArmMesh = new Mesh(rightArmBox, layer1MaterialBiased);
this.modelListeners.push(() => {
rightArmMesh.scale.x = this.slim ? 3 : 4;
rightArmMesh.scale.y = 12;
rightArmMesh.scale.z = 4;
if (this.slim) {
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)
);
}
setSkinUVs(rightArmBox, 40, 16, this.slim ? 3 : 4, 12, 4);
rightArmBox.uvsNeedUpdate = true;
rightArmBox.elementsNeedUpdate = true;
});
const rightArm2Box = new BoxGeometry();
const rightArm2Mesh = new Mesh(rightArm2Box, layer2MaterialBiased);
rightArm2Mesh.renderOrder = 1;
this.modelListeners.push(() => {
rightArm2Mesh.scale.x = this.slim ? 3.375 : 4.5;
rightArm2Mesh.scale.y = 13.5;
rightArm2Mesh.scale.x = this.slim ? 3.5 : 4.5;
rightArm2Mesh.scale.y = 12.5;
rightArm2Mesh.scale.z = 4.5;
if (this.slim) {
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)
);
}
setSkinUVs(rightArm2Box, 40, 32, this.slim ? 3 : 4, 12, 4);
rightArm2Box.uvsNeedUpdate = true;
rightArm2Box.elementsNeedUpdate = true;
});
const rightArmPivot = new Group();
rightArmPivot.add(rightArmMesh, rightArm2Mesh);
this.modelListeners.push(() => {
rightArmPivot.position.x = this.slim ? -.5 : -1;
});
rightArmPivot.position.y = -4;
this.rightArm = new BodyPart(rightArmMesh, rightArm2Mesh);
this.rightArm.name = "rightArm";
this.rightArm.add(rightArmPivot);
this.rightArm.position.y = -6;
this.modelListeners.push(() => {
this.rightArm.position.x = this.slim ? -5.5 : -6;
});
this.rightArm.position.x = -5;
this.rightArm.position.y = -2;
this.add(this.rightArm);
// Left Arm
const leftArmBox = new BoxGeometry();
const leftArmMesh = new Mesh(leftArmBox, layer1Material);
const leftArmMesh = new Mesh(leftArmBox, layer1MaterialBiased);
this.modelListeners.push(() => {
leftArmMesh.scale.x = this.slim ? 3 : 4;
leftArmMesh.scale.y = 12;
leftArmMesh.scale.z = 4;
if (this.slim) {
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)
);
}
setSkinUVs(leftArmBox, 32, 48, this.slim ? 3 : 4, 12, 4);
leftArmBox.uvsNeedUpdate = true;
leftArmBox.elementsNeedUpdate = true;
});
const leftArm2Box = new BoxGeometry();
const leftArm2Mesh = new Mesh(leftArm2Box, layer2MaterialBiased);
leftArm2Mesh.renderOrder = 1;
this.modelListeners.push(() => {
leftArm2Mesh.scale.x = this.slim ? 3.375 : 4.5;
leftArm2Mesh.scale.y = 13.5;
leftArm2Mesh.scale.x = this.slim ? 3.5 : 4.5;
leftArm2Mesh.scale.y = 12.5;
leftArm2Mesh.scale.z = 4.5;
if (this.slim) {
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)
);
}
setSkinUVs(leftArm2Box, 48, 48, this.slim ? 3 : 4, 12, 4);
leftArm2Box.uvsNeedUpdate = true;
leftArm2Box.elementsNeedUpdate = true;
});
const leftArmPivot = new Group();
leftArmPivot.add(leftArmMesh, leftArm2Mesh);
this.modelListeners.push(() => {
leftArmPivot.position.x = this.slim ? 0.5 : 1;
});
leftArmPivot.position.y = -4;
this.leftArm = new BodyPart(leftArmMesh, leftArm2Mesh);
this.leftArm.name = "leftArm";
this.leftArm.add(leftArmPivot);
this.leftArm.position.y = -6;
this.modelListeners.push(() => {
this.leftArm.position.x = this.slim ? 5.5 : 6;
});
this.leftArm.position.x = 5;
this.leftArm.position.y = -2;
this.add(this.leftArm);
// Right Leg
const rightLegBox = new BoxGeometry(4, 12, 4);
setVertices(rightLegBox,
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)
);
setSkinUVs(rightLegBox, 0, 16, 4, 12, 4);
const rightLegMesh = new Mesh(rightLegBox, layer1MaterialBiased);
const rightLeg2Box = new BoxGeometry(4.5, 13.5, 4.5);
setVertices(rightLeg2Box,
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 rightLeg2Box = new BoxGeometry(4.5, 12.5, 4.5);
setSkinUVs(rightLeg2Box, 0, 32, 4, 12, 4);
const rightLeg2Mesh = new Mesh(rightLeg2Box, layer2MaterialBiased);
rightLeg2Mesh.renderOrder = 1;
const rightLegPivot = new Group();
rightLegPivot.add(rightLegMesh, rightLeg2Mesh);
@ -328,33 +215,19 @@ export class SkinObject extends Group {
this.rightLeg = new BodyPart(rightLegMesh, rightLeg2Mesh);
this.rightLeg.name = "rightLeg";
this.rightLeg.add(rightLegPivot);
this.rightLeg.position.y = -16;
this.rightLeg.position.x = -2;
this.rightLeg.position.x = -1.9;
this.rightLeg.position.y = -12;
this.rightLeg.position.z = -.1;
this.add(this.rightLeg);
// Left Leg
const leftLegBox = new BoxGeometry(4, 12, 4);
setVertices(leftLegBox,
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)
);
setSkinUVs(leftLegBox, 16, 48, 4, 12, 4);
const leftLegMesh = new Mesh(leftLegBox, layer1MaterialBiased);
const leftLeg2Box = new BoxGeometry(4.5, 13.5, 4.5);
setVertices(leftLeg2Box,
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 leftLeg2Box = new BoxGeometry(4.5, 12.5, 4.5);
setSkinUVs(leftLeg2Box, 0, 48, 4, 12, 4);
const leftLeg2Mesh = new Mesh(leftLeg2Box, layer2MaterialBiased);
leftLeg2Mesh.renderOrder = 1;
const leftLegPivot = new Group();
leftLegPivot.add(leftLegMesh, leftLeg2Mesh);
@ -363,8 +236,9 @@ export class SkinObject extends Group {
this.leftLeg = new BodyPart(leftLegMesh, leftLeg2Mesh);
this.leftLeg.name = "leftLeg";
this.leftLeg.add(leftLegPivot);
this.leftLeg.position.y = -16;
this.leftLeg.position.x = 2;
this.leftLeg.position.x = 1.9;
this.leftLeg.position.y = -12;
this.leftLeg.position.z = -.1;
this.add(this.leftLeg);
this.modelType = "default";
@ -406,20 +280,13 @@ export class CapeObject extends Group {
alphaTest: 1e-5
});
// back = outside
// front = inside
// +z (front) - inside of cape
// -z (back) - outside of cape
const capeBox = new BoxGeometry(10, 16, 1);
setVertices(capeBox,
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)
);
setCapeUVs(capeBox, 0, 0, 10, 16, 1);
this.cape = new Mesh(capeBox, capeMaterial);
this.cape.position.y = -8;
this.cape.position.z = -0.5;
this.cape.position.z = .5;
this.add(this.cape);
}
}
@ -436,66 +303,47 @@ export class ElytraObject extends Group {
map: texture,
side: DoubleSide,
transparent: true,
alphaTest: 1e-5,
polygonOffset: true,
polygonOffsetFactor: -1.0,
polygonOffsetUnits: -4.0
alphaTest: 1e-5
});
const leftWingBox = new BoxGeometry(10, 20, 2);
setVertices(leftWingBox,
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 leftWingBox = new BoxGeometry(12, 22, 4);
setCapeUVs(leftWingBox, 22, 0, 10, 20, 2);
const leftWingMesh = new Mesh(leftWingBox, elytraMaterial);
leftWingMesh.position.x = -5;
leftWingMesh.position.y = -10;
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.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);
const rightWingBox = new BoxGeometry(10, 20, 2);
setVertices(rightWingBox,
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 rightWingBox = new BoxGeometry(12, 22, 4);
setCapeUVs(rightWingBox, 22, 0, 10, 20, 2);
const rightWingMesh = new Mesh(rightWingBox, elytraMaterial);
rightWingMesh.scale.x = -1;
rightWingMesh.position.x = 5;
rightWingMesh.position.y = -10;
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.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.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 {
readonly skin: SkinObject;
@ -562,15 +412,13 @@ export class PlayerObject extends Group {
this.cape = new CapeObject(capeTexture);
this.cape.name = "cape";
this.cape.position.z = -2;
this.cape.position.y = -6;
this.cape.rotation.x = 10.8 * Math.PI / 180;
this.cape.rotation.y = Math.PI;
this.add(this.cape);
this.elytra = new ElytraObject(capeTexture);
this.elytra.name = "elytra";
this.elytra.position.y = -7;
this.elytra.position.z = -2;
this.elytra.rotation.x = 10.8 * Math.PI / 180;
this.elytra.visible = false;
this.add(this.elytra);
@ -579,4 +427,19 @@ export class PlayerObject extends Group {
this.ears.position.y = 3.5;
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
control.enablePan = false;
control.target = new Vector3(0, -12, 0);
control.target = new Vector3(0, -8, 0);
control.minDistance = 10;
control.maxDistance = 256;
control.update();

View File

@ -1,16 +1,24 @@
import { applyMixins, CapeContainer, ModelType, SkinContainer, RemoteImage, TextureSource, TextureCanvas, loadImage, isTextureSource } from "skinview-utils";
import { NearestFilter, PerspectiveCamera, Scene, Texture, Vector2, WebGLRenderer } from "three";
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.
*/
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;
height?: number;
skin?: RemoteImage | TextureSource;
@ -41,13 +49,6 @@ export type SkinViewerOptions = {
renderPaused?: boolean;
}
function toMakeVisible(options?: LoadOptions): boolean {
if (options && options.makeVisible === false) {
return false;
}
return true;
}
class SkinViewer {
readonly canvas: HTMLCanvasElement;
readonly scene: Scene;
@ -105,9 +106,8 @@ class SkinViewer {
this.scene = new Scene();
// Use smaller fov to avoid distortion
this.camera = new PerspectiveCamera(42);
this.camera.zoom
this.camera.position.y = -12;
this.camera = new PerspectiveCamera(40);
this.camera.position.y = -8;
this.camera.position.z = 60;
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.playerObject.skin.modelType = model;
if (toMakeVisible(options)) {
if (options.makeVisible !== false) {
this.playerObject.skin.visible = true;
}
}
protected capeLoaded(options?: LoadOptions): void {
protected capeLoaded(options: CapeLoadOptions = {}): void {
this.capeTexture.needsUpdate = true;
if (toMakeVisible(options)) {
this.playerObject.cape.visible = true;
if (options.makeVisible !== false) {
this.playerObject.backEquipment = options.backEquipment === undefined ? "cape" : options.backEquipment;
}
}
@ -175,7 +175,7 @@ class SkinViewer {
}
protected resetCape(): void {
this.playerObject.cape.visible = false;
this.playerObject.backEquipment = null;
}
protected resetEars(): void {
@ -338,7 +338,6 @@ class SkinViewer {
this.playerObject.elytra.visible = !this.playerObject.cape.visible;
}
}
interface SkinViewer extends SkinContainer<LoadOptions>, CapeContainer<LoadOptions> { }
interface SkinViewer extends SkinContainer<LoadOptions>, CapeContainer<CapeLoadOptions> { }
applyMixins(SkinViewer, [SkinContainer, CapeContainer]);
export { SkinViewer };