Compare commits
6 Commits
Author | SHA1 | Date |
---|---|---|
|
cb5cfae82c | |
|
79971a79be | |
|
be9651b9d6 | |
|
7f7c32d16a | |
|
3758035bf8 | |
|
47f34b856c |
30
README.md
30
README.md
|
@ -12,6 +12,7 @@ Three.js powered Minecraft skin viewer.
|
|||
* 1.8 Skins
|
||||
* HD Skins
|
||||
* Capes
|
||||
* Ears
|
||||
* Elytras
|
||||
* Slim Arms
|
||||
* Automatic model detection (Slim / Default)
|
||||
|
@ -104,5 +105,34 @@ skinViewer.globalLight.intensity = 0.1;
|
|||
Setting `globalLight.intensity` to `1.0` and `cameraLight.intensity` to `0.0`
|
||||
will completely disable shadows.
|
||||
|
||||
## Ears
|
||||
skinview3d supports two types of ear texture:
|
||||
* `standalone`: 14x7 image that contains the ear ([example](https://github.com/bs-community/skinview3d/blob/master/examples/img/ears.png))
|
||||
* `skin`: Skin texture that contains the ear (e.g. [deadmau5's skin](https://minecraft.fandom.com/wiki/Easter_eggs#Deadmau5.27s_ears))
|
||||
|
||||
Usage:
|
||||
```js
|
||||
// You can specify ears in the constructor:
|
||||
new skinview3d.SkinViewer({
|
||||
skin: "img/deadmau5.png",
|
||||
|
||||
// Use ears drawn on the current skin (img/deadmau5.png)
|
||||
ears: "current-skin",
|
||||
|
||||
// Or use ears from other textures
|
||||
ears: {
|
||||
textureType: "standalone", // "standalone" or "skin"
|
||||
source: "img/ears.png"
|
||||
}
|
||||
});
|
||||
|
||||
// Show ears when loading skins:
|
||||
skinViewer.loadSkin("img/deadmau5.png", { ears: true });
|
||||
|
||||
// Use ears from other textures:
|
||||
skinViewer.loadEars("img/ears.png", { textureType: "standalone" });
|
||||
skinViewer.loadEars("img/deadmau5.png", { textureType: "skin" });
|
||||
```
|
||||
|
||||
# Build
|
||||
`npm run build`
|
||||
|
|
Binary file not shown.
After Width: | Height: | Size: 3.5 KiB |
Binary file not shown.
After Width: | Height: | Size: 233 B |
|
@ -118,11 +118,11 @@
|
|||
</div>
|
||||
<div>
|
||||
<label class="control">FOV: <input id="fov" type="number" value="70" step="1" min="1" max="179" size="2"></label>
|
||||
<label class="control">Zoom: <input id="zoom" type="number" value="0.90" step="0.01" min="0.01" max="2.00" size="2"></label>
|
||||
<label class="control">Zoom: <input id="zoom" type="number" value="0.90" step="0.01" min="0.01" max="2.00" size="4"></label>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="light">
|
||||
<div class="control-section">
|
||||
<h1>Light</h1>
|
||||
<label class="control">Global: <input id="global_light" type="number" value="0.40" step="0.01" min="0.00" max="2.00" size="4"></label>
|
||||
<label class="control">Camera: <input id="camera_light" type="number" value="0.60" step="0.01" min="0.00" max="2.00" size="4"></label>
|
||||
|
@ -215,6 +215,7 @@
|
|||
<option value="img/hatsune_miku.png">
|
||||
<option value="img/ironman_hd.png">
|
||||
<option value="img/sethbling.png">
|
||||
<option value="img/deadmau5.png">
|
||||
</datalist>
|
||||
<input id="skin_url_upload" type="file" class="hidden" accept="image/*">
|
||||
<button id="skin_url_unset" type="button" class="control hidden">Unset</button>
|
||||
|
@ -250,6 +251,32 @@
|
|||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-section">
|
||||
<h1>Ears</h1>
|
||||
<div>
|
||||
<label class="control">Source:
|
||||
<select id="ears_source">
|
||||
<option value="none">None</option>
|
||||
<option value="current_skin">Current skin</option>
|
||||
<option value="skin">Skin texture</option>
|
||||
<option value="standalone">Standalone texture</option>
|
||||
</select>
|
||||
</label>
|
||||
</div>
|
||||
<div id="ears_texture_input">
|
||||
<label class="control">URL: <input id="ears_url" type="text" value="" placeholder="none" list="default_ears" size="20"></label>
|
||||
<datalist id="default_ears">
|
||||
<option value="">
|
||||
<option value="img/ears.png" data-texture-type="standalone">
|
||||
<option value="img/deadmau5.png" data-texture-type="skin">
|
||||
</datalist>
|
||||
<input id="ears_url_upload" type="file" class="hidden" accept="image/*">
|
||||
<button id="ears_url_unset" type="button" class="control hidden">Unset</button>
|
||||
<button type="button" class="control"
|
||||
onclick="document.getElementById('ears_url_upload').click();">Browse...</button>
|
||||
</div>
|
||||
</div>
|
||||
|
||||
<div class="control-section">
|
||||
<h1>Panorama</h1>
|
||||
<div class="control">
|
||||
|
@ -328,7 +355,10 @@
|
|||
skinViewer.loadSkin(null);
|
||||
input.setCustomValidity("");
|
||||
} else {
|
||||
skinViewer.loadSkin(url, document.getElementById("skin_model").value)
|
||||
skinViewer.loadSkin(url, {
|
||||
model: document.getElementById("skin_model").value,
|
||||
ears: document.getElementById("ears_source").value === "current_skin"
|
||||
})
|
||||
.then(() => input.setCustomValidity(""))
|
||||
.catch(e => {
|
||||
input.setCustomValidity("Image can't be loaded.");
|
||||
|
@ -354,6 +384,46 @@
|
|||
}
|
||||
}
|
||||
|
||||
function reloadEars(skipSkinReload = false) {
|
||||
const sourceType = document.getElementById("ears_source").value;
|
||||
let hideInput = true;
|
||||
if (sourceType === "none") {
|
||||
skinViewer.loadEars(null);
|
||||
} else if (sourceType === "current_skin") {
|
||||
if (!skipSkinReload){
|
||||
reloadSkin();
|
||||
}
|
||||
} else {
|
||||
hideInput = false;
|
||||
document.querySelectorAll("#default_ears option[data-texture-type]").forEach(opt => {
|
||||
opt.disabled = opt.dataset.textureType !== sourceType;
|
||||
});
|
||||
|
||||
const input = document.getElementById("ears_url");
|
||||
const url = obtainTextureUrl("ears_url");
|
||||
if (url === "") {
|
||||
skinViewer.loadEars(null);
|
||||
input.setCustomValidity("");
|
||||
} else {
|
||||
skinViewer.loadEars(url, { textureType: sourceType })
|
||||
.then(() => input.setCustomValidity(""))
|
||||
.catch(e => {
|
||||
input.setCustomValidity("Image can't be loaded.");
|
||||
console.error(e);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const el = document.getElementById("ears_texture_input");
|
||||
if (hideInput) {
|
||||
if (!(el.classList.contains("hidden"))){
|
||||
el.classList.add("hidden");
|
||||
}
|
||||
} else {
|
||||
el.classList.remove("hidden");
|
||||
}
|
||||
}
|
||||
|
||||
function reloadPanorama() {
|
||||
const input = document.getElementById("panorama_url");
|
||||
const url = obtainTextureUrl("panorama_url");
|
||||
|
@ -440,11 +510,14 @@
|
|||
};
|
||||
initializeUploadButton("skin_url", reloadSkin);
|
||||
initializeUploadButton("cape_url", reloadCape);
|
||||
initializeUploadButton("ears_url", reloadEars);
|
||||
initializeUploadButton("panorama_url", reloadPanorama);
|
||||
|
||||
document.getElementById("skin_url").addEventListener("change", () => reloadSkin());
|
||||
document.getElementById("skin_model").addEventListener("change", () => reloadSkin());
|
||||
document.getElementById("cape_url").addEventListener("change", () => reloadCape());
|
||||
document.getElementById("ears_source").addEventListener("change", () => reloadEars());
|
||||
document.getElementById("ears_url").addEventListener("change", () => reloadEars());
|
||||
document.getElementById("panorama_url").addEventListener("change", () => reloadPanorama());
|
||||
|
||||
for (const el of document.querySelectorAll('input[type="radio"][name="back_equipment"]')) {
|
||||
|
@ -500,6 +573,7 @@
|
|||
}
|
||||
reloadSkin();
|
||||
reloadCape();
|
||||
reloadEars(true);
|
||||
reloadPanorama();
|
||||
}
|
||||
|
||||
|
|
|
@ -10,7 +10,7 @@
|
|||
"license": "MIT",
|
||||
"dependencies": {
|
||||
"@types/three": "^0.136.1",
|
||||
"skinview-utils": "^0.6.2",
|
||||
"skinview-utils": "^0.7.0",
|
||||
"three": "^0.136.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
@ -3641,15 +3641,15 @@
|
|||
}
|
||||
},
|
||||
"node_modules/shell-quote": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
|
||||
"integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==",
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz",
|
||||
"integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
|
||||
"dev": true
|
||||
},
|
||||
"node_modules/skinview-utils": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/skinview-utils/-/skinview-utils-0.6.2.tgz",
|
||||
"integrity": "sha512-UdjWwXCVZobtG+dc7ilvMRbtXYSqPJtWKPFdgWc44Gs4aoOmZML2lErr77h7uussXo9zrcR+fPizGshGpdETvQ==",
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/skinview-utils/-/skinview-utils-0.7.0.tgz",
|
||||
"integrity": "sha512-ecbbUp0AuvZX5fCIOOwbNPxr/JIbrAGhCcdLcww0Ov9PbvAbeYjNDSIu1ebCJBe4CWc6ZYYn8MFNp68DDta0iQ==",
|
||||
"dependencies": {
|
||||
"@types/offscreencanvas": "^2019.6.4"
|
||||
}
|
||||
|
@ -7032,15 +7032,15 @@
|
|||
"dev": true
|
||||
},
|
||||
"shell-quote": {
|
||||
"version": "1.7.2",
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.2.tgz",
|
||||
"integrity": "sha512-mRz/m/JVscCrkMyPqHc/bczi3OQHkLTqXHEFu0zDhK/qfv3UcOA4SVmRCLmos4bhjr9ekVQubj/R7waKapmiQg==",
|
||||
"version": "1.7.3",
|
||||
"resolved": "https://registry.npmjs.org/shell-quote/-/shell-quote-1.7.3.tgz",
|
||||
"integrity": "sha512-Vpfqwm4EnqGdlsBFNmHhxhElJYrdfcxPThu+ryKS5J8L/fhAwLazFZtq+S+TWZ9ANj2piSQLGj6NQg+lKPmxrw==",
|
||||
"dev": true
|
||||
},
|
||||
"skinview-utils": {
|
||||
"version": "0.6.2",
|
||||
"resolved": "https://registry.npmjs.org/skinview-utils/-/skinview-utils-0.6.2.tgz",
|
||||
"integrity": "sha512-UdjWwXCVZobtG+dc7ilvMRbtXYSqPJtWKPFdgWc44Gs4aoOmZML2lErr77h7uussXo9zrcR+fPizGshGpdETvQ==",
|
||||
"version": "0.7.0",
|
||||
"resolved": "https://registry.npmjs.org/skinview-utils/-/skinview-utils-0.7.0.tgz",
|
||||
"integrity": "sha512-ecbbUp0AuvZX5fCIOOwbNPxr/JIbrAGhCcdLcww0Ov9PbvAbeYjNDSIu1ebCJBe4CWc6ZYYn8MFNp68DDta0iQ==",
|
||||
"requires": {
|
||||
"@types/offscreencanvas": "^2019.6.4"
|
||||
}
|
||||
|
|
|
@ -39,7 +39,7 @@
|
|||
],
|
||||
"dependencies": {
|
||||
"@types/three": "^0.136.1",
|
||||
"skinview-utils": "^0.6.2",
|
||||
"skinview-utils": "^0.7.0",
|
||||
"three": "^0.136.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
|
|
37
src/model.ts
37
src/model.ts
|
@ -332,6 +332,33 @@ export class ElytraObject extends Group {
|
|||
}
|
||||
}
|
||||
|
||||
export class EarsObject extends Group {
|
||||
|
||||
readonly rightEar: Mesh;
|
||||
readonly leftEar: Mesh;
|
||||
|
||||
constructor(texture: Texture) {
|
||||
super();
|
||||
|
||||
const material = new MeshStandardMaterial({
|
||||
map: texture,
|
||||
side: FrontSide
|
||||
});
|
||||
const earBox = new BoxGeometry(8, 8, 4 / 3);
|
||||
setUVs(earBox, 0, 0, 6, 6, 1, 14, 7);
|
||||
|
||||
this.rightEar = new Mesh(earBox, material);
|
||||
this.rightEar.name = "rightEar";
|
||||
this.rightEar.position.x = -6;
|
||||
this.add(this.rightEar);
|
||||
|
||||
this.leftEar = new Mesh(earBox, material);
|
||||
this.leftEar.name = "leftEar";
|
||||
this.leftEar.position.x = 6;
|
||||
this.add(this.leftEar);
|
||||
}
|
||||
}
|
||||
|
||||
export type BackEquipment = "cape" | "elytra";
|
||||
|
||||
export class PlayerObject extends Group {
|
||||
|
@ -339,8 +366,9 @@ export class PlayerObject extends Group {
|
|||
readonly skin: SkinObject;
|
||||
readonly cape: CapeObject;
|
||||
readonly elytra: ElytraObject;
|
||||
readonly ears: EarsObject;
|
||||
|
||||
constructor(skinTexture: Texture, capeTexture: Texture) {
|
||||
constructor(skinTexture: Texture, capeTexture: Texture, earsTexture: Texture) {
|
||||
super();
|
||||
|
||||
this.skin = new SkinObject(skinTexture);
|
||||
|
@ -362,6 +390,13 @@ export class PlayerObject extends Group {
|
|||
this.elytra.position.z = -2;
|
||||
this.elytra.visible = false;
|
||||
this.add(this.elytra);
|
||||
|
||||
this.ears = new EarsObject(earsTexture);
|
||||
this.ears.name = "ears";
|
||||
this.ears.position.y = 10;
|
||||
this.ears.position.z = 2 / 3;
|
||||
this.ears.visible = false;
|
||||
this.skin.head.add(this.ears);
|
||||
}
|
||||
|
||||
get backEquipment(): BackEquipment | null {
|
||||
|
|
120
src/viewer.ts
120
src/viewer.ts
|
@ -1,4 +1,4 @@
|
|||
import { inferModelType, isTextureSource, loadCapeToCanvas, loadImage, loadSkinToCanvas, ModelType, RemoteImage, TextureSource } from "skinview-utils";
|
||||
import { inferModelType, isTextureSource, loadCapeToCanvas, loadEarsToCanvas, loadEarsToCanvasFromSkin, loadImage, loadSkinToCanvas, ModelType, RemoteImage, TextureSource } from "skinview-utils";
|
||||
import { Color, ColorRepresentation, PointLight, EquirectangularReflectionMapping, Group, NearestFilter, PerspectiveCamera, Scene, Texture, Vector2, WebGLRenderer, AmbientLight, Mapping } from "three";
|
||||
import { RootAnimation } from "./animation.js";
|
||||
import { BackEquipment, PlayerObject } from "./model.js";
|
||||
|
@ -10,6 +10,21 @@ export interface LoadOptions {
|
|||
makeVisible?: boolean;
|
||||
}
|
||||
|
||||
export interface SkinLoadOptions extends LoadOptions {
|
||||
/**
|
||||
* The model type of skin. Default is "auto-detect".
|
||||
*/
|
||||
model?: ModelType | "auto-detect";
|
||||
|
||||
/**
|
||||
* true: Loads the ears drawn on the skin texture, and show it.
|
||||
* "load-only": Loads the ears drawn on the skin texture, but do not make it visible.
|
||||
* false: Do not load ears from the skin texture.
|
||||
* Default is false.
|
||||
*/
|
||||
ears?: boolean | "load-only";
|
||||
}
|
||||
|
||||
export interface CapeLoadOptions extends LoadOptions {
|
||||
/**
|
||||
* The equipment (cape or elytra) to show, defaults to "cape".
|
||||
|
@ -18,6 +33,15 @@ export interface CapeLoadOptions extends LoadOptions {
|
|||
backEquipment?: BackEquipment;
|
||||
}
|
||||
|
||||
export interface EarsLoadOptions extends LoadOptions {
|
||||
/**
|
||||
* "standalone": The texture is a 14x7 image that only contains the ears;
|
||||
* "skin": The texture is a skin that contains ears, and we only show its ear part.
|
||||
* Default is "standalone".
|
||||
*/
|
||||
textureType?: "standalone" | "skin";
|
||||
}
|
||||
|
||||
export interface SkinViewerOptions {
|
||||
width?: number;
|
||||
height?: number;
|
||||
|
@ -25,6 +49,17 @@ export interface SkinViewerOptions {
|
|||
model?: ModelType | "auto-detect";
|
||||
cape?: RemoteImage | TextureSource;
|
||||
|
||||
/**
|
||||
* If you want to show the ears drawn on the current skin, set this to "current-skin".
|
||||
* To show ears that come from a separate texture, you have to specify 'textureType' ("standalone" or "skin") and 'source'.
|
||||
* "standalone" means the provided texture is a 14x7 image that only contains the ears.
|
||||
* "skin" means the provided texture is a skin that contains ears, and we only show its ear part.
|
||||
*/
|
||||
ears?: "current-skin" | {
|
||||
textureType: "standalone" | "skin",
|
||||
source: RemoteImage | TextureSource
|
||||
}
|
||||
|
||||
/**
|
||||
* Whether the canvas contains an alpha buffer. Default is true.
|
||||
* This option can be turned off if you use an opaque background.
|
||||
|
@ -85,8 +120,10 @@ export class SkinViewer {
|
|||
|
||||
readonly skinCanvas: HTMLCanvasElement;
|
||||
readonly capeCanvas: HTMLCanvasElement;
|
||||
readonly earsCanvas: HTMLCanvasElement;
|
||||
private readonly skinTexture: Texture;
|
||||
private readonly capeTexture: Texture;
|
||||
private readonly earsTexture: Texture;
|
||||
private backgroundTexture: Texture | null = null;
|
||||
|
||||
private _disposed: boolean = false;
|
||||
|
@ -111,6 +148,11 @@ export class SkinViewer {
|
|||
this.capeTexture.magFilter = NearestFilter;
|
||||
this.capeTexture.minFilter = NearestFilter;
|
||||
|
||||
this.earsCanvas = document.createElement("canvas");
|
||||
this.earsTexture = new Texture(this.earsCanvas);
|
||||
this.earsTexture.magFilter = NearestFilter;
|
||||
this.earsTexture.minFilter = NearestFilter;
|
||||
|
||||
this.scene = new Scene();
|
||||
|
||||
this.camera = new PerspectiveCamera();
|
||||
|
@ -126,7 +168,7 @@ export class SkinViewer {
|
|||
});
|
||||
this.renderer.setPixelRatio(window.devicePixelRatio);
|
||||
|
||||
this.playerObject = new PlayerObject(this.skinTexture, this.capeTexture);
|
||||
this.playerObject = new PlayerObject(this.skinTexture, this.capeTexture, this.earsTexture);
|
||||
this.playerObject.name = "player";
|
||||
this.playerObject.skin.visible = false;
|
||||
this.playerObject.cape.visible = false;
|
||||
|
@ -135,11 +177,19 @@ export class SkinViewer {
|
|||
this.scene.add(this.playerWrapper);
|
||||
|
||||
if (options.skin !== undefined) {
|
||||
this.loadSkin(options.skin, options.model);
|
||||
this.loadSkin(options.skin, {
|
||||
model: options.model,
|
||||
ears: options.ears === "current-skin"
|
||||
});
|
||||
}
|
||||
if (options.cape !== undefined) {
|
||||
this.loadCape(options.cape);
|
||||
}
|
||||
if (options.ears !== undefined && options.ears !== "current-skin") {
|
||||
this.loadEars(options.ears.source, {
|
||||
textureType: options.ears.textureType
|
||||
});
|
||||
}
|
||||
if (options.width !== undefined) {
|
||||
this.width = options.width;
|
||||
}
|
||||
|
@ -184,27 +234,40 @@ export class SkinViewer {
|
|||
loadSkin(empty: null): void;
|
||||
loadSkin<S extends TextureSource | RemoteImage>(
|
||||
source: S,
|
||||
model?: ModelType | "auto-detect",
|
||||
options?: LoadOptions
|
||||
options?: SkinLoadOptions
|
||||
): S extends TextureSource ? void : Promise<void>;
|
||||
|
||||
loadSkin(
|
||||
source: TextureSource | RemoteImage | null,
|
||||
model: ModelType | "auto-detect" = "auto-detect",
|
||||
options: LoadOptions = {}
|
||||
options: SkinLoadOptions = {}
|
||||
): void | Promise<void> {
|
||||
if (source === null) {
|
||||
this.resetSkin();
|
||||
|
||||
} else if (isTextureSource(source)) {
|
||||
loadSkinToCanvas(this.skinCanvas, source);
|
||||
const actualModel = model === "auto-detect" ? inferModelType(this.skinCanvas) : model;
|
||||
this.skinTexture.needsUpdate = true;
|
||||
this.playerObject.skin.modelType = actualModel;
|
||||
|
||||
if (options.model === undefined || options.model === "auto-detect") {
|
||||
this.playerObject.skin.modelType = inferModelType(this.skinCanvas);
|
||||
} else {
|
||||
this.playerObject.skin.modelType = options.model;
|
||||
}
|
||||
|
||||
if (options.makeVisible !== false) {
|
||||
this.playerObject.skin.visible = true;
|
||||
}
|
||||
|
||||
if (options.ears === true || options.ears == "load-only") {
|
||||
loadEarsToCanvasFromSkin(this.earsCanvas, source);
|
||||
this.earsTexture.needsUpdate = true;
|
||||
if (options.ears === true) {
|
||||
this.playerObject.ears.visible = true;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
return loadImage(source).then(image => this.loadSkin(image, model, options));
|
||||
return loadImage(source).then(image => this.loadSkin(image, options));
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -224,12 +287,15 @@ export class SkinViewer {
|
|||
): void | Promise<void> {
|
||||
if (source === null) {
|
||||
this.resetCape();
|
||||
|
||||
} else if (isTextureSource(source)) {
|
||||
loadCapeToCanvas(this.capeCanvas, source);
|
||||
this.capeTexture.needsUpdate = true;
|
||||
|
||||
if (options.makeVisible !== false) {
|
||||
this.playerObject.backEquipment = options.backEquipment === undefined ? "cape" : options.backEquipment;
|
||||
}
|
||||
|
||||
} else {
|
||||
return loadImage(source).then(image => this.loadCape(image, options));
|
||||
}
|
||||
|
@ -239,6 +305,40 @@ export class SkinViewer {
|
|||
this.playerObject.backEquipment = null;
|
||||
}
|
||||
|
||||
loadEars(empty: null): void;
|
||||
loadEars<S extends TextureSource | RemoteImage>(
|
||||
source: S,
|
||||
options?: EarsLoadOptions
|
||||
): S extends TextureSource ? void : Promise<void>;
|
||||
|
||||
loadEars(
|
||||
source: TextureSource | RemoteImage | null,
|
||||
options: EarsLoadOptions = {}
|
||||
): void | Promise<void> {
|
||||
if (source === null) {
|
||||
this.resetEars();
|
||||
|
||||
} else if (isTextureSource(source)) {
|
||||
if (options.textureType === "skin") {
|
||||
loadEarsToCanvasFromSkin(this.earsCanvas, source);
|
||||
} else {
|
||||
loadEarsToCanvas(this.earsCanvas, source);
|
||||
}
|
||||
this.earsTexture.needsUpdate = true;
|
||||
|
||||
if (options.makeVisible !== false) {
|
||||
this.playerObject.ears.visible = true;
|
||||
}
|
||||
|
||||
} else {
|
||||
return loadImage(source).then(image => this.loadEars(image, options));
|
||||
}
|
||||
}
|
||||
|
||||
resetEars(): void {
|
||||
this.playerObject.ears.visible = false;
|
||||
}
|
||||
|
||||
loadPanorama<S extends TextureSource | RemoteImage>(
|
||||
source: S
|
||||
): S extends TextureSource ? void : Promise<void> {
|
||||
|
|
Loading…
Reference in New Issue