UNPKG

@woosh/meep-engine

Version:

Pure JavaScript game engine. Fully featured and production ready.

283 lines (223 loc) • 9.2 kB
import { AnimationMixer, Group, PCFSoftShadowMap, sRGBEncoding } from 'three'; import Vector2 from '../../core/geom/Vector2.js'; import { computeFileExtension } from "../../core/path/computeFileExtension.js"; import { advanceAnimation } from "../../engine/ecs/systems/AnimationSystem.js"; import { WebGLRendererPool } from "../../engine/graphics/render/RendererPool.js"; import { makeMeshPreviewScene } from "../../engine/graphics/util/makeMeshPreviewScene.js"; import { PointerDevice } from '../../engine/input/devices/PointerDevice.js'; import dom from "../DOM.js"; import View from "../View.js"; const mapExtension2Mime = { gltf: "model/gltf+json", glb: "model/gltf", /** * @deprecated no longer supported, please don't use */ json: "three.js" }; class MeshPreview extends View { /** * * @param {string} url * @param {AssetManager} assetManager * @param hooks * @param {boolean} [allowRotationY = true] * @param {boolean} [allowRotationX = true] * @param {string|null} [animation = 'Idle'] Animation to play * @constructor */ constructor({ url, assetManager, hooks, allowRotationY = true, allowRotationX = true, animation = 'Idle' }) { super(); this.renderer = null; this.scene = null; this.camera = null; if (typeof url !== "string") { let s; try { s = JSON.stringify(url); } catch (e) { s = "$serialization error$"; } throw new Error(`Mesh URL was expected to be a string, instead was ${typeof url}: ${s}`); } const extension = computeFileExtension(url); if (extension.length === 0) { throw new Error(`No file extension on url '${url}'`); } if (!mapExtension2Mime.hasOwnProperty(extension)) { throw new Error(`No known mime-type for file extension '${extension}'`); } const mimeType = mapExtension2Mime[extension]; this.el = dom('div').addClass('ui-mesh-preview').el; const size = this.size; function addInteraction(el, mesh) { const pointer = new PointerDevice(el); pointer.start(); const origin = new Vector2(); const prevPosition = new Vector2(); pointer.on.dragStart.add(function (p, event) { event.stopPropagation(); origin.copy(p); prevPosition.copy(p); }); pointer.on.drag.add(function (p, o, last, event) { event.stopPropagation(); const delta = p.clone().sub(prevPosition); prevPosition.copy(p); // const PI2 = (2 * Math.PI); const angleDelta = new Vector2(PI2, PI2).divide(size).multiply(delta); if (allowRotationY !== false) { // noinspection JSSuspiciousNameCombination mesh.rotation.y += angleDelta.x; } if (allowRotationX !== false) { // noinspection JSSuspiciousNameCombination mesh.rotation.x += angleDelta.y; } }); } this.__addInteraction = addInteraction; const self = this; this.pContents = new Promise(function (resolve, reject) { assetManager.get({ path: url, type: mimeType, callback: function (asset) { const mesh = asset.create(); const preview = makeMeshPreviewScene(mesh, size, { x0: 0, y0: 0, x1: 1, y1: 1 }); const camera = preview.camera; const scene = preview.scene; //wrap mesh so it stays centered relative to bb const group = new Group(); const wrapAll = false; if (wrapAll) { const objects = scene.children.slice(); scene.children = []; objects.forEach(function (o) { group.add(o); }); } else { scene.remove(mesh); group.add(mesh); } scene.add(group); self.camera = camera; self.scene = scene; if (hooks !== undefined) { if (typeof hooks.meshAdded === "function") { hooks.meshAdded(mesh); } if (typeof hooks.sceneConstructed === "function") { hooks.sceneConstructed(scene, mesh, camera, group); } } mesh.rotation.y = -Math.PI / 6; // function playAnimation(animationName) { const mixer = new AnimationMixer(mesh); let clipAction = null; const animations = asset.animations; if (animations !== undefined) { self.__animationMixer = mixer; for (let i = 0; i < animations.length; i++) { const animation = animations[i]; if (animation.name === animationName) { const root = null; clipAction = mixer.clipAction(animation, root); //bail, correct animation found break; } } } if (clipAction !== null) { clipAction.play(); } } if (animation !== null) { playAnimation(animation); } resolve({ group }); }, failure: function () { console.error(arguments); reject(arguments); } }); }); this.on.linked.add(this.__update_renderer_size, this); this.bindSignal(this.size.onChanged, this.__update_renderer_size, this); } __update_renderer_size() { if (this.renderer !== null) { this.renderer.setSize(this.size.x, this.size.y); this.camera.aspect = this.size.x / this.size.y; this.camera.updateProjectionMatrix(); } } link() { super.link(); this.isRendering = true; const self = this; const size = self.size; const renderer = WebGLRendererPool.global.get(); renderer.outputEncoding = sRGBEncoding; renderer.setClearColor(0xffffff, 0); renderer.setPixelRatio(window.devicePixelRatio); renderer.setSize(size.x, size.y); renderer.shadowMap.enabled = true; renderer.shadowMap.type = PCFSoftShadowMap; self.renderer = renderer; self.pContents.then(function (contents) { if (renderer.domElement !== null) { self.__addInteraction(renderer.domElement, contents.group); } }); self.el.appendChild(renderer.domElement); let t = Date.now(); function update() { const now = Date.now(); const timeDelta = (now - t) / 1000; t = now; if (self.isRendering) { requestAnimationFrame(update); self.render(); if (self.__animationMixer !== undefined) { advanceAnimation(self.__animationMixer, timeDelta); } } } update(); } unlink() { super.unlink(); this.isRendering = false; const renderer = this.renderer; if (renderer !== null) { try { this.el.removeChild(renderer.domElement); } catch (e) { console.error("Failed to remove renderer's dom element", e); } WebGLRendererPool.global.release(this.renderer); this.renderer = null; } } render() { if (this.renderer !== null && this.scene !== null && this.camera !== null) { this.renderer.render(this.scene, this.camera); } } } export default MeshPreview;