diff --git a/examples/index.html b/examples/index.html
index aa0407c..4bbfd97 100644
--- a/examples/index.html
+++ b/examples/index.html
@@ -231,6 +231,17 @@
onclick="document.getElementById('cape_url_upload').click();">Browse...
+
@@ -303,6 +314,22 @@
}
}
+ function reloadPanorama() {
+ const input = document.getElementById("panorama_url");
+ const url = input.value;
+ if (url === "") {
+ skinViewer.background = "white";
+ input.setCustomValidity("");
+ } else {
+ skinViewer.loadPanorama(url)
+ .then(() => input.setCustomValidity(""))
+ .catch(e => {
+ input.setCustomValidity("Image can't be loaded.");
+ console.error(e);
+ });
+ }
+ }
+
function initializeControls() {
document.getElementById("canvas_width").addEventListener("change", e => skinViewer.width = e.target.value);
document.getElementById("canvas_height").addEventListener("change", e => skinViewer.height = e.target.value);
@@ -348,31 +375,28 @@
.addEventListener("change", e => skinViewer.playerObject.skin[part][layer].visible = e.target.checked);
}
}
- const skinReader = new FileReader();
- skinReader.addEventListener("load", e => {
- document.getElementById("skin_url").value = skinReader.result;
- reloadSkin();
- });
- document.getElementById("skin_url_upload").addEventListener("change", e => {
- const file = e.target.files[0];
- if (file !== undefined) {
- skinReader.readAsDataURL(file);
- }
- });
- const capeReader = new FileReader();
- capeReader.addEventListener("load", e => {
- document.getElementById("cape_url").value = capeReader.result;
- reloadCape();
- });
- document.getElementById("cape_url_upload").addEventListener("change", e => {
- const file = e.target.files[0];
- if (file !== undefined) {
- capeReader.readAsDataURL(file);
- }
- });
+
+ const initializeUploadButton = (urlInput, browseButton, callback) => {
+ const reader = new FileReader();
+ reader.addEventListener("load", e => {
+ document.getElementById(urlInput).value = reader.result;
+ callback();
+ });
+ document.getElementById(browseButton).addEventListener("change", e => {
+ const file = e.target.files[0];
+ if (file !== undefined) {
+ reader.readAsDataURL(file);
+ }
+ });
+ };
+ initializeUploadButton("skin_url", "skin_url_upload", reloadSkin);
+ initializeUploadButton("cape_url", "cape_url_upload", reloadCape);
+ initializeUploadButton("panorama_url", "panorama_url_upload", reloadPanorama);
+
document.getElementById("skin_url").addEventListener("change", () => reloadSkin());
document.getElementById("skin_model").addEventListener("change", () => reloadSkin());
document.getElementById("cape_url").addEventListener("change", () => reloadCape());
+ document.getElementById("panorama_url").addEventListener("change", () => reloadPanorama());
for (const el of document.querySelectorAll('input[type="radio"][name="back_equipment"]')) {
el.addEventListener("change", e => {
@@ -394,8 +418,7 @@
function initializeViewer() {
skinViewer = new skinview3d.FXAASkinViewer({
- canvas: document.getElementById("skin_container"),
- background: 0x5a76f3
+ canvas: document.getElementById("skin_container")
});
orbitControl = skinview3d.createOrbitControls(skinViewer);
rotateAnimation = null;
@@ -424,6 +447,7 @@
}
reloadSkin();
reloadCape();
+ reloadPanorama();
}
initializeControls();
diff --git a/src/viewer.ts b/src/viewer.ts
index 4233cc7..efb84d8 100644
--- a/src/viewer.ts
+++ b/src/viewer.ts
@@ -1,5 +1,5 @@
import { inferModelType, isTextureSource, loadCapeToCanvas, loadImage, loadSkinToCanvas, ModelType, RemoteImage, TextureSource } from "skinview-utils";
-import { Color, ColorRepresentation, NearestFilter, PerspectiveCamera, Scene, Texture, Vector2, WebGLRenderer } from "three";
+import { Color, ColorRepresentation, EquirectangularReflectionMapping, NearestFilter, PerspectiveCamera, Scene, Texture, Vector2, WebGLRenderer } from "three";
import { RootAnimation } from "./animation.js";
import { BackEquipment, PlayerObject } from "./model.js";
@@ -66,6 +66,7 @@ export class SkinViewer {
readonly capeCanvas: HTMLCanvasElement;
private readonly skinTexture: Texture;
private readonly capeTexture: Texture;
+ private backgroundTexture: Texture | null = null;
private _disposed: boolean = false;
private _renderPaused: boolean = false;
@@ -208,6 +209,27 @@ export class SkinViewer {
this.playerObject.backEquipment = null;
}
+ loadPanorama(
+ source: S
+ ): S extends TextureSource ? void : Promise;
+
+ loadPanorama(
+ source: S
+ ): void | Promise {
+ if (isTextureSource(source)) {
+ if (this.backgroundTexture !== null) {
+ this.backgroundTexture.dispose();
+ }
+ this.backgroundTexture = new Texture();
+ this.backgroundTexture.image = source;
+ this.backgroundTexture.mapping = EquirectangularReflectionMapping;
+ this.backgroundTexture.needsUpdate = true;
+ this.scene.background = this.backgroundTexture;
+ } else {
+ return loadImage(source).then(image => this.loadPanorama(image));
+ }
+ }
+
private draw(): void {
this.animations.runAnimationLoop(this.playerObject);
this.render();
@@ -242,6 +264,10 @@ export class SkinViewer {
this.renderer.dispose();
this.skinTexture.dispose();
this.capeTexture.dispose();
+ if (this.backgroundTexture !== null) {
+ this.backgroundTexture.dispose();
+ this.backgroundTexture = null;
+ }
}
get disposed(): boolean {
@@ -294,5 +320,9 @@ export class SkinViewer {
} else {
this.scene.background = new Color(value);
}
+ if (this.backgroundTexture !== null && value !== this.backgroundTexture) {
+ this.backgroundTexture.dispose();
+ this.backgroundTexture = null;
+ }
}
}