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; + } } }