├── .gitattributes ├── .gitignore ├── .prettierrc.json ├── .vscode └── settings.json ├── LICENSE ├── README.md ├── dist ├── Animations │ ├── Animation.d.ts │ ├── AnimationEffect.d.ts │ ├── Animator.d.ts │ ├── ParticleEffect.d.ts │ └── SoundEffect.d.ts ├── Cube.d.ts ├── CubeFaces.d.ts ├── Disposable.d.ts ├── Model.d.ts ├── PolyMesh.d.ts ├── Schema │ ├── Animation.d.ts │ └── Model.d.ts ├── main.d.ts ├── model-viewer.es.js └── model-viewer.umd.js ├── lib ├── Animations │ ├── Animation.ts │ ├── AnimationEffect.ts │ ├── Animator.ts │ ├── ParticleEffect.ts │ └── SoundEffect.ts ├── Cube.ts ├── CubeFaces.ts ├── Disposable.ts ├── Model.ts ├── PolyMesh.ts ├── Schema │ ├── Animation.ts │ └── Model.ts └── main.ts ├── package-lock.json ├── package.json ├── playground ├── index.html ├── model.js ├── pesky_dragon.png ├── pink.png └── test.js ├── tsconfig.json └── vite.config.ts /.gitattributes: -------------------------------------------------------------------------------- 1 | # Auto detect text files and perform LF normalization 2 | * text=auto 3 | -------------------------------------------------------------------------------- /.gitignore: -------------------------------------------------------------------------------- 1 | node_modules 2 | .DS_Store 3 | 4 | 5 | tsconfig.tsbuildinfo 6 | .DS_STORE -------------------------------------------------------------------------------- /.prettierrc.json: -------------------------------------------------------------------------------- 1 | { 2 | "trailingComma": "es5", 3 | "useTabs": true, 4 | "tabWidth": 4, 5 | "semi": false, 6 | "singleQuote": true, 7 | "printWidth": 80 8 | } -------------------------------------------------------------------------------- /.vscode/settings.json: -------------------------------------------------------------------------------- 1 | { 2 | "editor.defaultFormatter": "esbenp.prettier-vscode", 3 | "editor.formatOnSave": true 4 | } -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2020 solvedDev 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # bridge-model-viewer 2 | 3 | Renders Minecraft models to an existing ThreeJS scene or a canvas element 4 | -------------------------------------------------------------------------------- /dist/Animations/Animation.d.ts: -------------------------------------------------------------------------------- 1 | import { Molang } from '@bridge-editor/molang'; 2 | import { ISingleAnimation, TBoneModifier } from '../Schema/Animation'; 3 | import { SoundEffect } from './SoundEffect'; 4 | import { ParticleEffect } from './ParticleEffect'; 5 | import { Animator } from './Animator'; 6 | export declare class Animation { 7 | protected animator: Animator; 8 | protected animationData: ISingleAnimation; 9 | protected startTimestamp: number; 10 | protected lastFrameTimestamp: number; 11 | protected isRunning: boolean; 12 | protected env: { 13 | 'query.anim_time': () => number; 14 | 'query.delta_time': () => number; 15 | 'query.life_time': () => number; 16 | }; 17 | protected molang: Molang; 18 | protected soundEffects: SoundEffect; 19 | protected particleEffects: ParticleEffect; 20 | constructor(animator: Animator, animationData: ISingleAnimation); 21 | getAnimator(): Animator; 22 | protected execute(expr: string): unknown; 23 | parseBoneModifier(transform: TBoneModifier): number[] | undefined; 24 | tick(): void; 25 | play(): void; 26 | pause(): void; 27 | loop(): void; 28 | dispose(): void; 29 | get currentTime(): number; 30 | get roundedCurrentTime(): number; 31 | get shouldTick(): boolean; 32 | } 33 | -------------------------------------------------------------------------------- /dist/Animations/AnimationEffect.d.ts: -------------------------------------------------------------------------------- 1 | import { ITimestamp } from '../Schema/Animation'; 2 | import { Animation } from './Animation'; 3 | export declare abstract class AnimationEffect { 4 | protected animation: Animation; 5 | abstract tick(): void; 6 | protected currentEffectIndex: number; 7 | protected effects: (readonly [number, T[]])[]; 8 | protected tickingEffects: { 9 | tick: () => void; 10 | }[]; 11 | constructor(animation: Animation, timestampObj: ITimestamp); 12 | getCurrentEffects(): T[] | undefined; 13 | reset(): void; 14 | } 15 | -------------------------------------------------------------------------------- /dist/Animations/Animator.d.ts: -------------------------------------------------------------------------------- 1 | import { ISingleAnimation } from '../Schema/Animation'; 2 | import { Model } from '../Model'; 3 | import { Animation } from './Animation'; 4 | import Wintersky from 'wintersky'; 5 | export declare class Animator { 6 | protected model: Model; 7 | winterskyScene?: Wintersky.Scene; 8 | protected animations: Map; 9 | protected particleEmitters: Map; 10 | constructor(model: Model); 11 | setupDefaultBonePoses(): void; 12 | dispose(): void; 13 | disposeAnimations(): void; 14 | setupWintersky(winterskyScene: Wintersky.Scene): void; 15 | addAnimation(id: string, animationData: ISingleAnimation): void; 16 | addEmitter(shortName: string, emitterConfig: Wintersky.Config): void; 17 | getEmitter(shortName: string): Wintersky.Config | undefined; 18 | play(id: string): void; 19 | pause(id: string): void; 20 | pauseAll(): void; 21 | tick(): void; 22 | get shouldTick(): boolean; 23 | getModel(): Model; 24 | } 25 | -------------------------------------------------------------------------------- /dist/Animations/ParticleEffect.d.ts: -------------------------------------------------------------------------------- 1 | import { IParticleEffect } from '../Schema/Animation'; 2 | import { AnimationEffect } from './AnimationEffect'; 3 | import { IDisposable } from '../Disposable'; 4 | export declare class ParticleEffect extends AnimationEffect { 5 | protected disposables: IDisposable[]; 6 | tick(): void; 7 | dispose(): void; 8 | } 9 | -------------------------------------------------------------------------------- /dist/Animations/SoundEffect.d.ts: -------------------------------------------------------------------------------- 1 | import { ISoundEffect } from '../Schema/Animation'; 2 | import { AnimationEffect } from './AnimationEffect'; 3 | export declare class SoundEffect extends AnimationEffect { 4 | tick(): void; 5 | } 6 | -------------------------------------------------------------------------------- /dist/Cube.d.ts: -------------------------------------------------------------------------------- 1 | import { BufferGeometry, Group, Material } from 'three'; 2 | export interface IUVObj { 3 | north: IUVConfig; 4 | south: IUVConfig; 5 | east: IUVConfig; 6 | west: IUVConfig; 7 | up: IUVConfig; 8 | down: IUVConfig; 9 | } 10 | export interface IUVConfig { 11 | uv: [number, number]; 12 | uv_size: [number, number]; 13 | } 14 | export interface ICubeConfig { 15 | width: number; 16 | height: number; 17 | depth: number; 18 | startUV?: [number, number] | IUVObj; 19 | textureSize: [number, number]; 20 | textureDiscrepancyFactor: [number, number]; 21 | mirror: boolean; 22 | material: Material; 23 | origin: [number, number, number]; 24 | pivot?: [number, number, number]; 25 | rotation?: [number, number, number]; 26 | inflate?: number; 27 | } 28 | export declare class Cube { 29 | protected positions: number[]; 30 | protected indices: number[]; 31 | protected normals: number[]; 32 | protected uvs: number[]; 33 | protected geometry: BufferGeometry; 34 | protected group: Group; 35 | constructor(cubeConfig: ICubeConfig); 36 | protected createGeometry(): void; 37 | protected createMesh({ material, width, height, depth, pivot, rotation, origin, inflate, }: ICubeConfig): void; 38 | getGroup(): Group; 39 | } 40 | -------------------------------------------------------------------------------- /dist/CubeFaces.d.ts: -------------------------------------------------------------------------------- 1 | export declare const CubeFaces: readonly [{ 2 | readonly name: "west"; 3 | readonly baseUV: readonly [2, 1]; 4 | readonly dir: readonly [-1, 0, 0]; 5 | readonly corners: readonly [{ 6 | readonly pos: readonly [-0.5, 1, 0]; 7 | readonly uv: readonly [0, 1]; 8 | }, { 9 | readonly pos: readonly [-0.5, 0, 0]; 10 | readonly uv: readonly [0, 0]; 11 | }, { 12 | readonly pos: readonly [-0.5, 1, 1]; 13 | readonly uv: readonly [1, 1]; 14 | }, { 15 | readonly pos: readonly [-0.5, 0, 1]; 16 | readonly uv: readonly [1, 0]; 17 | }]; 18 | }, { 19 | readonly name: "east"; 20 | readonly baseUV: readonly [0, 1]; 21 | readonly dir: readonly [1, 0, 0]; 22 | readonly corners: readonly [{ 23 | readonly pos: readonly [0.5, 1, 1]; 24 | readonly uv: readonly [0, 1]; 25 | }, { 26 | readonly pos: readonly [0.5, 0, 1]; 27 | readonly uv: readonly [0, 0]; 28 | }, { 29 | readonly pos: readonly [0.5, 1, 0]; 30 | readonly uv: readonly [1, 1]; 31 | }, { 32 | readonly pos: readonly [0.5, 0, 0]; 33 | readonly uv: readonly [1, 0]; 34 | }]; 35 | }, { 36 | readonly name: "down"; 37 | readonly baseUV: readonly [2, 0]; 38 | readonly dir: readonly [0, -1, 0]; 39 | readonly corners: readonly [{ 40 | readonly pos: readonly [0.5, 0, 1]; 41 | readonly uv: readonly [0, 1]; 42 | }, { 43 | readonly pos: readonly [-0.5, 0, 1]; 44 | readonly uv: readonly [1, 1]; 45 | }, { 46 | readonly pos: readonly [0.5, 0, 0]; 47 | readonly uv: readonly [0, 0]; 48 | }, { 49 | readonly pos: readonly [-0.5, 0, 0]; 50 | readonly uv: readonly [1, 0]; 51 | }]; 52 | }, { 53 | readonly name: "up"; 54 | readonly baseUV: readonly [1, 0]; 55 | readonly dir: readonly [0, 1, 0]; 56 | readonly corners: readonly [{ 57 | readonly pos: readonly [-0.5, 1, 1]; 58 | readonly uv: readonly [1, 1]; 59 | }, { 60 | readonly pos: readonly [0.5, 1, 1]; 61 | readonly uv: readonly [0, 1]; 62 | }, { 63 | readonly pos: readonly [-0.5, 1, 0]; 64 | readonly uv: readonly [1, 0]; 65 | }, { 66 | readonly pos: readonly [0.5, 1, 0]; 67 | readonly uv: readonly [0, 0]; 68 | }]; 69 | }, { 70 | readonly name: "north"; 71 | readonly baseUV: readonly [1, 1]; 72 | readonly dir: readonly [0, 0, -1]; 73 | readonly corners: readonly [{ 74 | readonly pos: readonly [-0.5, 0, 0]; 75 | readonly uv: readonly [1, 0]; 76 | }, { 77 | readonly pos: readonly [0.5, 0, 0]; 78 | readonly uv: readonly [0, 0]; 79 | }, { 80 | readonly pos: readonly [-0.5, 1, 0]; 81 | readonly uv: readonly [1, 1]; 82 | }, { 83 | readonly pos: readonly [0.5, 1, 0]; 84 | readonly uv: readonly [0, 1]; 85 | }]; 86 | }, { 87 | readonly name: "south"; 88 | readonly baseUV: readonly [3, 1]; 89 | readonly dir: readonly [0, 0, 1]; 90 | readonly corners: readonly [{ 91 | readonly pos: readonly [-0.5, 0, 1]; 92 | readonly uv: readonly [0, 0]; 93 | }, { 94 | readonly pos: readonly [0.5, 0, 1]; 95 | readonly uv: readonly [1, 0]; 96 | }, { 97 | readonly pos: readonly [-0.5, 1, 1]; 98 | readonly uv: readonly [0, 1]; 99 | }, { 100 | readonly pos: readonly [0.5, 1, 1]; 101 | readonly uv: readonly [1, 1]; 102 | }]; 103 | }]; 104 | -------------------------------------------------------------------------------- /dist/Disposable.d.ts: -------------------------------------------------------------------------------- 1 | export interface IDisposable { 2 | dispose: () => void; 3 | } 4 | -------------------------------------------------------------------------------- /dist/Model.d.ts: -------------------------------------------------------------------------------- 1 | import { Group, Texture } from 'three'; 2 | import { Animator } from './Animations/Animator'; 3 | import { IGeoSchema } from './Schema/Model'; 4 | export declare class Model { 5 | protected modelData: IGeoSchema; 6 | protected texturePath: string; 7 | protected model: Group; 8 | protected boneMap: Map; 9 | protected locators: Map; 10 | readonly animator: Animator; 11 | constructor(modelData: IGeoSchema, texturePath: string); 12 | create(): Promise; 13 | getGroup(): Group; 14 | getBoneMap(): Map; 15 | getLocator(name: string): Group | undefined; 16 | tick(): void; 17 | get shouldTick(): boolean; 18 | createOutlineBox(color: `#${string}`, position: { 19 | x: number; 20 | y: number; 21 | z: number; 22 | }, size: { 23 | x: number; 24 | y: number; 25 | z: number; 26 | }): { 27 | dispose: () => void; 28 | }; 29 | hideBone(name: string): void; 30 | showBone(name: string): void; 31 | get bones(): string[]; 32 | dispose(): void; 33 | protected loadTexture(url: string): Promise; 34 | } 35 | -------------------------------------------------------------------------------- /dist/PolyMesh.d.ts: -------------------------------------------------------------------------------- 1 | import { BufferGeometry, Group, Material } from 'three'; 2 | import { IPolyMesh } from './Schema/Model'; 3 | export interface IUVObj { 4 | north: IUVConfig; 5 | south: IUVConfig; 6 | east: IUVConfig; 7 | west: IUVConfig; 8 | up: IUVConfig; 9 | down: IUVConfig; 10 | } 11 | export interface IUVConfig { 12 | uv: [number, number]; 13 | uv_size: [number, number]; 14 | } 15 | export interface IPolyMeshConfig extends IPolyMesh { 16 | textureSize: [number, number]; 17 | mirror: boolean; 18 | material: Material; 19 | origin: [number, number, number]; 20 | pivot?: [number, number, number]; 21 | rotation?: [number, number, number]; 22 | inflate?: number; 23 | } 24 | export declare class PolyMesh { 25 | protected positions: number[]; 26 | protected indices: number[]; 27 | protected normals: number[]; 28 | protected uvs: number[]; 29 | protected geometry: BufferGeometry; 30 | protected group: Group; 31 | constructor(polyMeshConfig: IPolyMeshConfig); 32 | protected createGeometry(): void; 33 | protected createMesh({ material }: IPolyMeshConfig): void; 34 | getGroup(): Group; 35 | } 36 | -------------------------------------------------------------------------------- /dist/Schema/Animation.d.ts: -------------------------------------------------------------------------------- 1 | export interface IAnimations { 2 | format_version: '1.8.0'; 3 | animations: { 4 | [animationID: string]: Partial; 5 | }; 6 | } 7 | export interface ISingleAnimation { 8 | loop: boolean; 9 | animation_length: number; 10 | anim_time_update: string; 11 | blend_weight: string; 12 | override_previous_animation: true; 13 | bones: { 14 | [boneName: string]: IBoneAnim; 15 | }; 16 | sound_effects: ITimestamp; 17 | particle_effects: ITimestamp; 18 | } 19 | export interface ITimestamp { 20 | [timeStamp: string]: T | T[]; 21 | } 22 | export interface ISoundEffect { 23 | effect?: string; 24 | pre_effect_script?: string; 25 | } 26 | export interface IParticleEffect { 27 | effect?: string; 28 | locator?: string; 29 | pre_effect_script?: string; 30 | } 31 | export interface IBoneAnim { 32 | position: TBoneModifier; 33 | rotation: TBoneModifier; 34 | scale: TBoneModifier; 35 | } 36 | export declare type TBoneModifier = string | [string, string, string] | ITimestamp; 37 | export declare type TTimestampEntry = [number, number, number] | { 38 | lerp_mode: 'linear' | 'catmullrom'; 39 | pre: [string, string, string]; 40 | post: [string, string, string]; 41 | }; 42 | -------------------------------------------------------------------------------- /dist/Schema/Model.d.ts: -------------------------------------------------------------------------------- 1 | export interface IGeoSchema { 2 | description?: IGeoDescriptionSchema; 3 | bones?: IBoneSchema[]; 4 | } 5 | export interface IGeoDescriptionSchema { 6 | identifier?: string; 7 | texture_width?: number; 8 | texture_height?: number; 9 | } 10 | export interface IBoneSchema { 11 | name?: string; 12 | parent?: string; 13 | inflate?: number; 14 | pivot?: [number, number, number]; 15 | rotation?: [number, number, number]; 16 | bind_pose_rotation?: [number, number, number]; 17 | mirror?: boolean; 18 | cubes?: ICubeSchema[]; 19 | locators?: Record; 23 | poly_mesh?: IPolyMesh; 24 | } 25 | export declare type TVector = [number, number, number]; 26 | export interface IPolyMesh { 27 | normalized_uvs?: boolean; 28 | positions?: TVector[]; 29 | normals?: TVector[]; 30 | uvs?: [number, number][]; 31 | polys?: [TVector, TVector, TVector][] | [TVector, TVector, TVector, TVector][] | 'tri_list' | 'quad_list'; 32 | } 33 | export interface ICubeSchema { 34 | origin?: [number, number, number]; 35 | size?: [number, number, number]; 36 | uv?: [number, number] | IUVObj; 37 | rotation?: [number, number, number]; 38 | pivot?: [number, number, number]; 39 | inflate?: number; 40 | mirror?: boolean; 41 | } 42 | export interface IUVObj { 43 | north: IUVConfig; 44 | south: IUVConfig; 45 | east: IUVConfig; 46 | west: IUVConfig; 47 | up: IUVConfig; 48 | down: IUVConfig; 49 | } 50 | export interface IUVConfig { 51 | uv: [number, number]; 52 | uv_size: [number, number]; 53 | } 54 | -------------------------------------------------------------------------------- /dist/main.d.ts: -------------------------------------------------------------------------------- 1 | import { PerspectiveCamera, Scene, WebGLRenderer } from 'three'; 2 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls'; 3 | import { Model } from './Model'; 4 | import { IGeoSchema } from './Schema/Model'; 5 | export { Model } from './Model'; 6 | export * from './Schema/Model'; 7 | export * from './Schema/Animation'; 8 | export interface IOptions { 9 | alpha?: boolean; 10 | antialias?: boolean; 11 | width?: number; 12 | height?: number; 13 | } 14 | export declare class StandaloneModelViewer { 15 | protected canvasElement: HTMLCanvasElement; 16 | protected texturePath: string; 17 | protected options: IOptions; 18 | protected renderer: WebGLRenderer; 19 | protected model: Model; 20 | readonly scene: Scene; 21 | protected camera: PerspectiveCamera; 22 | protected renderingRequested: boolean; 23 | readonly controls: OrbitControls; 24 | readonly loadedModel: Promise; 25 | constructor(canvasElement: HTMLCanvasElement, modelData: IGeoSchema, texturePath: string, options: IOptions); 26 | protected loadModel(): Promise; 27 | protected get width(): number; 28 | protected get height(): number; 29 | protected render(checkShouldTick?: boolean): void; 30 | requestRendering(immediate?: boolean): void; 31 | protected onResize(): void; 32 | dispose(): void; 33 | addHelpers(): void; 34 | getModel(): Model; 35 | positionCamera(scale?: number, rotate?: boolean): void; 36 | } 37 | -------------------------------------------------------------------------------- /dist/model-viewer.es.js: -------------------------------------------------------------------------------- 1 | import { EventDispatcher as Fe, Vector3 as I, MOUSE as F, TOUCH as D, Quaternion as fe, Spherical as ge, Vector2 as O, MathUtils as _, BufferGeometry as Te, Group as H, BufferAttribute as G, Mesh as Ae, NearestFilter as ye, MeshLambertMaterial as De, DoubleSide as we, LineBasicMaterial as He, BoxGeometry as Ge, EdgesGeometry as Xe, LineSegments as Be, TextureLoader as Ze, WebGLRenderer as Ke, PerspectiveCamera as Ve, Scene as qe, AmbientLight as Ce, Color as We, AxesHelper as $e, GridHelper as Qe, BoxHelper as Je, Box3 as et, Sphere as tt } from "three"; 2 | import { Molang as ot } from "@bridge-editor/molang"; 3 | import it from "wintersky"; 4 | const be = { type: "change" }, $ = { type: "start" }, Ee = { type: "end" }; 5 | class nt extends Fe { 6 | constructor(o, i) { 7 | super(), this.object = o, this.domElement = i, this.domElement.style.touchAction = "none", this.enabled = !0, this.target = new I(), this.minDistance = 0, this.maxDistance = 1 / 0, this.minZoom = 0, this.maxZoom = 1 / 0, this.minPolarAngle = 0, this.maxPolarAngle = Math.PI, this.minAzimuthAngle = -1 / 0, this.maxAzimuthAngle = 1 / 0, this.enableDamping = !1, this.dampingFactor = 0.05, this.enableZoom = !0, this.zoomSpeed = 1, this.enableRotate = !0, this.rotateSpeed = 1, this.enablePan = !0, this.panSpeed = 1, this.screenSpacePanning = !0, this.keyPanSpeed = 7, this.autoRotate = !1, this.autoRotateSpeed = 2, this.keys = { LEFT: "ArrowLeft", UP: "ArrowUp", RIGHT: "ArrowRight", BOTTOM: "ArrowDown" }, this.mouseButtons = { LEFT: F.ROTATE, MIDDLE: F.DOLLY, RIGHT: F.PAN }, this.touches = { ONE: D.ROTATE, TWO: D.DOLLY_PAN }, this.target0 = this.target.clone(), this.position0 = this.object.position.clone(), this.zoom0 = this.object.zoom, this._domElementKeyEvents = null, this.getPolarAngle = function() { 8 | return a.phi; 9 | }, this.getAzimuthalAngle = function() { 10 | return a.theta; 11 | }, this.getDistance = function() { 12 | return this.object.position.distanceTo(this.target); 13 | }, this.listenToKeyEvents = function(t) { 14 | t.addEventListener("keydown", he), this._domElementKeyEvents = t; 15 | }, this.saveState = function() { 16 | e.target0.copy(e.target), e.position0.copy(e.object.position), e.zoom0 = e.object.zoom; 17 | }, this.reset = function() { 18 | e.target.copy(e.target0), e.object.position.copy(e.position0), e.object.zoom = e.zoom0, e.object.updateProjectionMatrix(), e.dispatchEvent(be), e.update(), s = n.NONE; 19 | }, this.update = function() { 20 | const t = new I(), r = new fe().setFromUnitVectors(o.up, new I(0, 1, 0)), E = r.clone().invert(), T = new I(), v = new fe(), U = 2 * Math.PI; 21 | return function() { 22 | const me = e.object.position; 23 | t.copy(me).sub(e.target), t.applyQuaternion(r), a.setFromVector3(t), e.autoRotate && s === n.NONE && M(j()), e.enableDamping ? (a.theta += h.theta * e.dampingFactor, a.phi += h.phi * e.dampingFactor) : (a.theta += h.theta, a.phi += h.phi); 24 | let L = e.minAzimuthAngle, N = e.maxAzimuthAngle; 25 | return isFinite(L) && isFinite(N) && (L < -Math.PI ? L += U : L > Math.PI && (L -= U), N < -Math.PI ? N += U : N > Math.PI && (N -= U), L <= N ? a.theta = Math.max(L, Math.min(N, a.theta)) : a.theta = a.theta > (L + N) / 2 ? Math.max(L, a.theta) : Math.min(N, a.theta)), a.phi = Math.max(e.minPolarAngle, Math.min(e.maxPolarAngle, a.phi)), a.makeSafe(), a.radius *= l, a.radius = Math.max(e.minDistance, Math.min(e.maxDistance, a.radius)), e.enableDamping === !0 ? e.target.addScaledVector(g, e.dampingFactor) : e.target.add(g), t.setFromSpherical(a), t.applyQuaternion(E), me.copy(e.target).add(t), e.object.lookAt(e.target), e.enableDamping === !0 ? (h.theta *= 1 - e.dampingFactor, h.phi *= 1 - e.dampingFactor, g.multiplyScalar(1 - e.dampingFactor)) : (h.set(0, 0, 0), g.set(0, 0, 0)), l = 1, c || T.distanceToSquared(e.object.position) > f || 8 * (1 - v.dot(e.object.quaternion)) > f ? (e.dispatchEvent(be), T.copy(e.object.position), v.copy(e.object.quaternion), c = !1, !0) : !1; 26 | }; 27 | }(), this.dispose = function() { 28 | e.domElement.removeEventListener("contextmenu", ue), e.domElement.removeEventListener("pointerdown", re), e.domElement.removeEventListener("pointercancel", ce), e.domElement.removeEventListener("wheel", le), e.domElement.removeEventListener("pointermove", q), e.domElement.removeEventListener("pointerup", C), e._domElementKeyEvents !== null && e._domElementKeyEvents.removeEventListener("keydown", he); 29 | }; 30 | const e = this, n = { 31 | NONE: -1, 32 | ROTATE: 0, 33 | DOLLY: 1, 34 | PAN: 2, 35 | TOUCH_ROTATE: 3, 36 | TOUCH_PAN: 4, 37 | TOUCH_DOLLY_PAN: 5, 38 | TOUCH_DOLLY_ROTATE: 6 39 | }; 40 | let s = n.NONE; 41 | const f = 1e-6, a = new ge(), h = new ge(); 42 | let l = 1; 43 | const g = new I(); 44 | let c = !1; 45 | const p = new O(), y = new O(), b = new O(), A = new O(), d = new O(), w = new O(), m = new O(), P = new O(), R = new O(), u = [], Y = {}; 46 | function j() { 47 | return 2 * Math.PI / 60 / 60 * e.autoRotateSpeed; 48 | } 49 | function x() { 50 | return Math.pow(0.95, e.zoomSpeed); 51 | } 52 | function M(t) { 53 | h.theta -= t; 54 | } 55 | function z(t) { 56 | h.phi -= t; 57 | } 58 | const X = function() { 59 | const t = new I(); 60 | return function(E, T) { 61 | t.setFromMatrixColumn(T, 0), t.multiplyScalar(-E), g.add(t); 62 | }; 63 | }(), B = function() { 64 | const t = new I(); 65 | return function(E, T) { 66 | e.screenSpacePanning === !0 ? t.setFromMatrixColumn(T, 1) : (t.setFromMatrixColumn(T, 0), t.crossVectors(e.object.up, t)), t.multiplyScalar(E), g.add(t); 67 | }; 68 | }(), k = function() { 69 | const t = new I(); 70 | return function(E, T) { 71 | const v = e.domElement; 72 | if (e.object.isPerspectiveCamera) { 73 | const U = e.object.position; 74 | t.copy(U).sub(e.target); 75 | let Z = t.length(); 76 | Z *= Math.tan(e.object.fov / 2 * Math.PI / 180), X(2 * E * Z / v.clientHeight, e.object.matrix), B(2 * T * Z / v.clientHeight, e.object.matrix); 77 | } else 78 | e.object.isOrthographicCamera ? (X(E * (e.object.right - e.object.left) / e.object.zoom / v.clientWidth, e.object.matrix), B(T * (e.object.top - e.object.bottom) / e.object.zoom / v.clientHeight, e.object.matrix)) : (console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan disabled."), e.enablePan = !1); 79 | }; 80 | }(); 81 | function V(t) { 82 | e.object.isPerspectiveCamera ? l /= t : e.object.isOrthographicCamera ? (e.object.zoom = Math.max(e.minZoom, Math.min(e.maxZoom, e.object.zoom * t)), e.object.updateProjectionMatrix(), c = !0) : (console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."), e.enableZoom = !1); 83 | } 84 | function Q(t) { 85 | e.object.isPerspectiveCamera ? l *= t : e.object.isOrthographicCamera ? (e.object.zoom = Math.max(e.minZoom, Math.min(e.maxZoom, e.object.zoom / t)), e.object.updateProjectionMatrix(), c = !0) : (console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."), e.enableZoom = !1); 86 | } 87 | function J(t) { 88 | p.set(t.clientX, t.clientY); 89 | } 90 | function Pe(t) { 91 | m.set(t.clientX, t.clientY); 92 | } 93 | function ee(t) { 94 | A.set(t.clientX, t.clientY); 95 | } 96 | function xe(t) { 97 | y.set(t.clientX, t.clientY), b.subVectors(y, p).multiplyScalar(e.rotateSpeed); 98 | const r = e.domElement; 99 | M(2 * Math.PI * b.x / r.clientHeight), z(2 * Math.PI * b.y / r.clientHeight), p.copy(y), e.update(); 100 | } 101 | function ke(t) { 102 | P.set(t.clientX, t.clientY), R.subVectors(P, m), R.y > 0 ? V(x()) : R.y < 0 && Q(x()), m.copy(P), e.update(); 103 | } 104 | function ve(t) { 105 | d.set(t.clientX, t.clientY), w.subVectors(d, A).multiplyScalar(e.panSpeed), k(w.x, w.y), A.copy(d), e.update(); 106 | } 107 | function Re(t) { 108 | t.deltaY < 0 ? Q(x()) : t.deltaY > 0 && V(x()), e.update(); 109 | } 110 | function Oe(t) { 111 | let r = !1; 112 | switch (t.code) { 113 | case e.keys.UP: 114 | t.ctrlKey || t.metaKey || t.shiftKey ? z(2 * Math.PI * e.rotateSpeed / e.domElement.clientHeight) : k(0, e.keyPanSpeed), r = !0; 115 | break; 116 | case e.keys.BOTTOM: 117 | t.ctrlKey || t.metaKey || t.shiftKey ? z(-2 * Math.PI * e.rotateSpeed / e.domElement.clientHeight) : k(0, -e.keyPanSpeed), r = !0; 118 | break; 119 | case e.keys.LEFT: 120 | t.ctrlKey || t.metaKey || t.shiftKey ? M(2 * Math.PI * e.rotateSpeed / e.domElement.clientHeight) : k(e.keyPanSpeed, 0), r = !0; 121 | break; 122 | case e.keys.RIGHT: 123 | t.ctrlKey || t.metaKey || t.shiftKey ? M(-2 * Math.PI * e.rotateSpeed / e.domElement.clientHeight) : k(-e.keyPanSpeed, 0), r = !0; 124 | break; 125 | } 126 | r && (t.preventDefault(), e.update()); 127 | } 128 | function te() { 129 | if (u.length === 1) 130 | p.set(u[0].pageX, u[0].pageY); 131 | else { 132 | const t = 0.5 * (u[0].pageX + u[1].pageX), r = 0.5 * (u[0].pageY + u[1].pageY); 133 | p.set(t, r); 134 | } 135 | } 136 | function oe() { 137 | if (u.length === 1) 138 | A.set(u[0].pageX, u[0].pageY); 139 | else { 140 | const t = 0.5 * (u[0].pageX + u[1].pageX), r = 0.5 * (u[0].pageY + u[1].pageY); 141 | A.set(t, r); 142 | } 143 | } 144 | function ie() { 145 | const t = u[0].pageX - u[1].pageX, r = u[0].pageY - u[1].pageY, E = Math.sqrt(t * t + r * r); 146 | m.set(0, E); 147 | } 148 | function Se() { 149 | e.enableZoom && ie(), e.enablePan && oe(); 150 | } 151 | function Le() { 152 | e.enableZoom && ie(), e.enableRotate && te(); 153 | } 154 | function ne(t) { 155 | if (u.length == 1) 156 | y.set(t.pageX, t.pageY); 157 | else { 158 | const E = W(t), T = 0.5 * (t.pageX + E.x), v = 0.5 * (t.pageY + E.y); 159 | y.set(T, v); 160 | } 161 | b.subVectors(y, p).multiplyScalar(e.rotateSpeed); 162 | const r = e.domElement; 163 | M(2 * Math.PI * b.x / r.clientHeight), z(2 * Math.PI * b.y / r.clientHeight), p.copy(y); 164 | } 165 | function se(t) { 166 | if (u.length === 1) 167 | d.set(t.pageX, t.pageY); 168 | else { 169 | const r = W(t), E = 0.5 * (t.pageX + r.x), T = 0.5 * (t.pageY + r.y); 170 | d.set(E, T); 171 | } 172 | w.subVectors(d, A).multiplyScalar(e.panSpeed), k(w.x, w.y), A.copy(d); 173 | } 174 | function ae(t) { 175 | const r = W(t), E = t.pageX - r.x, T = t.pageY - r.y, v = Math.sqrt(E * E + T * T); 176 | P.set(0, v), R.set(0, Math.pow(P.y / m.y, e.zoomSpeed)), V(R.y), m.copy(P); 177 | } 178 | function Ne(t) { 179 | e.enableZoom && ae(t), e.enablePan && se(t); 180 | } 181 | function je(t) { 182 | e.enableZoom && ae(t), e.enableRotate && ne(t); 183 | } 184 | function re(t) { 185 | e.enabled !== !1 && (u.length === 0 && (e.domElement.setPointerCapture(t.pointerId), e.domElement.addEventListener("pointermove", q), e.domElement.addEventListener("pointerup", C)), Ue(t), t.pointerType === "touch" ? Ye(t) : Ie(t)); 186 | } 187 | function q(t) { 188 | e.enabled !== !1 && (t.pointerType === "touch" ? ze(t) : _e(t)); 189 | } 190 | function C(t) { 191 | de(t), u.length === 0 && (e.domElement.releasePointerCapture(t.pointerId), e.domElement.removeEventListener("pointermove", q), e.domElement.removeEventListener("pointerup", C)), e.dispatchEvent(Ee), s = n.NONE; 192 | } 193 | function ce(t) { 194 | de(t); 195 | } 196 | function Ie(t) { 197 | let r; 198 | switch (t.button) { 199 | case 0: 200 | r = e.mouseButtons.LEFT; 201 | break; 202 | case 1: 203 | r = e.mouseButtons.MIDDLE; 204 | break; 205 | case 2: 206 | r = e.mouseButtons.RIGHT; 207 | break; 208 | default: 209 | r = -1; 210 | } 211 | switch (r) { 212 | case F.DOLLY: 213 | if (e.enableZoom === !1) 214 | return; 215 | Pe(t), s = n.DOLLY; 216 | break; 217 | case F.ROTATE: 218 | if (t.ctrlKey || t.metaKey || t.shiftKey) { 219 | if (e.enablePan === !1) 220 | return; 221 | ee(t), s = n.PAN; 222 | } else { 223 | if (e.enableRotate === !1) 224 | return; 225 | J(t), s = n.ROTATE; 226 | } 227 | break; 228 | case F.PAN: 229 | if (t.ctrlKey || t.metaKey || t.shiftKey) { 230 | if (e.enableRotate === !1) 231 | return; 232 | J(t), s = n.ROTATE; 233 | } else { 234 | if (e.enablePan === !1) 235 | return; 236 | ee(t), s = n.PAN; 237 | } 238 | break; 239 | default: 240 | s = n.NONE; 241 | } 242 | s !== n.NONE && e.dispatchEvent($); 243 | } 244 | function _e(t) { 245 | switch (s) { 246 | case n.ROTATE: 247 | if (e.enableRotate === !1) 248 | return; 249 | xe(t); 250 | break; 251 | case n.DOLLY: 252 | if (e.enableZoom === !1) 253 | return; 254 | ke(t); 255 | break; 256 | case n.PAN: 257 | if (e.enablePan === !1) 258 | return; 259 | ve(t); 260 | break; 261 | } 262 | } 263 | function le(t) { 264 | e.enabled === !1 || e.enableZoom === !1 || s !== n.NONE || (t.preventDefault(), e.dispatchEvent($), Re(t), e.dispatchEvent(Ee)); 265 | } 266 | function he(t) { 267 | e.enabled === !1 || e.enablePan === !1 || Oe(t); 268 | } 269 | function Ye(t) { 270 | switch (pe(t), u.length) { 271 | case 1: 272 | switch (e.touches.ONE) { 273 | case D.ROTATE: 274 | if (e.enableRotate === !1) 275 | return; 276 | te(), s = n.TOUCH_ROTATE; 277 | break; 278 | case D.PAN: 279 | if (e.enablePan === !1) 280 | return; 281 | oe(), s = n.TOUCH_PAN; 282 | break; 283 | default: 284 | s = n.NONE; 285 | } 286 | break; 287 | case 2: 288 | switch (e.touches.TWO) { 289 | case D.DOLLY_PAN: 290 | if (e.enableZoom === !1 && e.enablePan === !1) 291 | return; 292 | Se(), s = n.TOUCH_DOLLY_PAN; 293 | break; 294 | case D.DOLLY_ROTATE: 295 | if (e.enableZoom === !1 && e.enableRotate === !1) 296 | return; 297 | Le(), s = n.TOUCH_DOLLY_ROTATE; 298 | break; 299 | default: 300 | s = n.NONE; 301 | } 302 | break; 303 | default: 304 | s = n.NONE; 305 | } 306 | s !== n.NONE && e.dispatchEvent($); 307 | } 308 | function ze(t) { 309 | switch (pe(t), s) { 310 | case n.TOUCH_ROTATE: 311 | if (e.enableRotate === !1) 312 | return; 313 | ne(t), e.update(); 314 | break; 315 | case n.TOUCH_PAN: 316 | if (e.enablePan === !1) 317 | return; 318 | se(t), e.update(); 319 | break; 320 | case n.TOUCH_DOLLY_PAN: 321 | if (e.enableZoom === !1 && e.enablePan === !1) 322 | return; 323 | Ne(t), e.update(); 324 | break; 325 | case n.TOUCH_DOLLY_ROTATE: 326 | if (e.enableZoom === !1 && e.enableRotate === !1) 327 | return; 328 | je(t), e.update(); 329 | break; 330 | default: 331 | s = n.NONE; 332 | } 333 | } 334 | function ue(t) { 335 | e.enabled !== !1 && t.preventDefault(); 336 | } 337 | function Ue(t) { 338 | u.push(t); 339 | } 340 | function de(t) { 341 | delete Y[t.pointerId]; 342 | for (let r = 0; r < u.length; r++) 343 | if (u[r].pointerId == t.pointerId) { 344 | u.splice(r, 1); 345 | return; 346 | } 347 | } 348 | function pe(t) { 349 | let r = Y[t.pointerId]; 350 | r === void 0 && (r = new O(), Y[t.pointerId] = r), r.set(t.pageX, t.pageY); 351 | } 352 | function W(t) { 353 | const r = t.pointerId === u[0].pointerId ? u[1] : u[0]; 354 | return Y[r.pointerId]; 355 | } 356 | e.domElement.addEventListener("contextmenu", ue), e.domElement.addEventListener("pointerdown", re), e.domElement.addEventListener("pointercancel", ce), e.domElement.addEventListener("wheel", le, { passive: !1 }), this.update(); 357 | } 358 | } 359 | class Me { 360 | constructor(o, i) { 361 | this.animation = o, this.currentEffectIndex = 0, this.tickingEffects = [], this.effects = Object.entries(i).map( 362 | ([e, n]) => [ 363 | Number(e), 364 | Array.isArray(n) ? n : [n] 365 | ] 366 | ).sort(([e], [n]) => e - n); 367 | } 368 | getCurrentEffects() { 369 | if (this.currentEffectIndex >= this.effects.length) 370 | return; 371 | const o = this.effects[this.currentEffectIndex]; 372 | if (!(o[0] > this.animation.roundedCurrentTime)) 373 | return this.currentEffectIndex++, o[1]; 374 | } 375 | reset() { 376 | this.currentEffectIndex = 0; 377 | } 378 | } 379 | class st extends Me { 380 | tick() { 381 | const o = super.getCurrentEffects() ?? []; 382 | o.length > 0 && console.log( 383 | `Playing sound effects: "${o.map((i) => i.effect).join(", ")}"` 384 | ); 385 | } 386 | } 387 | class at extends Me { 388 | constructor() { 389 | super(...arguments), this.disposables = []; 390 | } 391 | tick() { 392 | this.tickingEffects.forEach((i) => i.tick()); 393 | const o = super.getCurrentEffects() ?? []; 394 | for (const { locator: i, effect: e, pre_effect_script: n } of o) { 395 | if (!e) 396 | return; 397 | const s = this.animation.getAnimator(), f = s.getModel(), a = s.getEmitter(e); 398 | if (!a || !s.winterskyScene) 399 | return; 400 | const h = i ? f.getLocator(i) : void 0, l = new it.Emitter( 401 | s.winterskyScene, 402 | a, 403 | { 404 | parent_mode: h ? "locator" : "entity", 405 | loop_mode: "once" 406 | } 407 | ); 408 | h && (h.add(l.local_space), l.local_space.parent = h); 409 | const g = { 410 | tick: () => { 411 | l.tick(), l.enabled || (l.delete(), this.tickingEffects = this.tickingEffects.filter( 412 | (c) => c !== g 413 | )); 414 | } 415 | }; 416 | this.tickingEffects.push(g), this.disposables.push({ 417 | dispose: () => { 418 | l.delete(), this.tickingEffects = this.tickingEffects.filter( 419 | (c) => c !== g 420 | ); 421 | } 422 | }), l.start(), l.tick(); 423 | } 424 | } 425 | dispose() { 426 | this.disposables.forEach((o) => o.dispose()), this.disposables = []; 427 | } 428 | } 429 | class rt { 430 | constructor(o, i) { 431 | this.animator = o, this.animationData = i, this.startTimestamp = 0, this.lastFrameTimestamp = 0, this.isRunning = !1, this.env = { 432 | "query.anim_time": () => this.currentTime, 433 | "query.delta_time": () => this.startTimestamp - this.lastFrameTimestamp, 434 | "query.life_time": () => this.currentTime 435 | }, this.molang = new ot(this.env, { 436 | convertUndefined: !0 437 | }), this.soundEffects = new st( 438 | this, 439 | this.animationData.sound_effects ?? {} 440 | ), this.particleEffects = new at( 441 | this, 442 | this.animationData.particle_effects ?? {} 443 | ); 444 | } 445 | getAnimator() { 446 | return this.animator; 447 | } 448 | execute(o) { 449 | return this.molang.executeAndCatch(o); 450 | } 451 | parseBoneModifier(o) { 452 | if (typeof o == "number") 453 | return [o, o, o]; 454 | if (typeof o == "string") { 455 | const i = typeof o == "string" ? this.execute(o) : o; 456 | return [i, i, i]; 457 | } else { 458 | if (Array.isArray(o)) 459 | return o.map( 460 | (i) => typeof i == "string" ? this.execute(i) : i 461 | ); 462 | if (o !== void 0) { 463 | const i = Object.entries(o).map( 464 | ([e, n]) => [Number(e), n] 465 | ).sort(([e], [n]) => e - n); 466 | for (let e = i.length - 1; e >= 0; e--) { 467 | let [n, s] = i[e]; 468 | if (!(n > this.currentTime)) 469 | if (n === this.currentTime) { 470 | if (Array.isArray(s)) 471 | return s.map( 472 | (f) => typeof f == "string" ? this.execute(f) : f 473 | ); 474 | throw new Error("Format not supported yet"); 475 | } else { 476 | let [f, a] = i[_.euclideanModulo(e + 1, i.length)], h = f - n; 477 | if (Array.isArray(s) && Array.isArray(a)) 478 | return s = s.map( 479 | (l) => typeof l == "string" ? this.execute(l) : l 480 | ), a = a.map( 481 | (l) => typeof l == "string" ? this.execute(l) : l 482 | ), s.map( 483 | (l, g) => l + (a[g] - l) / h * (this.currentTime - n) 484 | ); 485 | throw new Error("Format not supported yet"); 486 | } 487 | } 488 | return [0, 0, 0]; 489 | } 490 | } 491 | } 492 | tick() { 493 | this.soundEffects.tick(), this.particleEffects.tick(); 494 | const o = this.animator.getModel().getBoneMap(); 495 | for (let i in this.animationData.bones) { 496 | const e = o.get(i); 497 | if (!e) 498 | continue; 499 | const { position: n, rotation: s, scale: f } = this.animationData.bones[i], [a, h, l] = [ 500 | n, 501 | s, 502 | f 503 | ].map((g) => this.parseBoneModifier(g)); 504 | if (a) { 505 | const g = e.position.toArray(); 506 | e.position.set( 507 | ...a.map( 508 | (c, p) => (p === 0 ? -1 : 1) * c + g[p] 509 | ) 510 | ); 511 | } 512 | if (h) { 513 | const g = e.rotation.toArray(); 514 | e.rotation.set( 515 | ...h.map((c) => _.degToRad(c)).map( 516 | (c, p) => g[p] + (p === 2 ? c : -c) 517 | ) 518 | ); 519 | } 520 | l && e.scale.set(...l); 521 | } 522 | this.currentTime > this.animationData.animation_length && (this.animationData.loop ? this.loop() : this.pause()), this.lastFrameTimestamp = Date.now(); 523 | } 524 | play() { 525 | this.isRunning = !0, this.startTimestamp = Date.now(); 526 | } 527 | pause() { 528 | this.isRunning = !1; 529 | } 530 | loop() { 531 | this.startTimestamp = Date.now(), this.soundEffects.reset(), this.particleEffects.reset(); 532 | } 533 | dispose() { 534 | this.particleEffects.dispose(); 535 | } 536 | get currentTime() { 537 | return (Date.now() - this.startTimestamp) / 1e3; 538 | } 539 | get roundedCurrentTime() { 540 | return Math.round((Date.now() - this.startTimestamp) / 50) / 20; 541 | } 542 | get shouldTick() { 543 | return this.isRunning; 544 | } 545 | } 546 | class ct { 547 | constructor(o) { 548 | this.model = o, this.animations = /* @__PURE__ */ new Map(), this.particleEmitters = /* @__PURE__ */ new Map(); 549 | } 550 | setupDefaultBonePoses() { 551 | for (let o of this.model.getBoneMap().values()) 552 | o.userData.defaultRotation = o.rotation.toArray(), o.userData.defaultPosition = o.position.toArray(); 553 | } 554 | dispose() { 555 | this.disposeAnimations(); 556 | for (let o of this.model.getBoneMap().values()) 557 | delete o.userData.defaultRotation, delete o.userData.defaultPosition; 558 | } 559 | disposeAnimations() { 560 | this.animations.forEach((o) => o.dispose()); 561 | } 562 | setupWintersky(o) { 563 | this.winterskyScene = o; 564 | } 565 | addAnimation(o, i) { 566 | this.animations.set(o, new rt(this, i)); 567 | } 568 | addEmitter(o, i) { 569 | this.particleEmitters.set(o, i); 570 | } 571 | getEmitter(o) { 572 | return this.particleEmitters.get(o); 573 | } 574 | play(o) { 575 | const i = this.animations.get(o); 576 | if (!i) 577 | throw new Error(`Unknown animation: "${o}"`); 578 | i.play(); 579 | } 580 | pause(o) { 581 | const i = this.animations.get(o); 582 | if (!i) 583 | throw new Error(`Unknown animation: "${o}"`); 584 | i.pause(); 585 | } 586 | pauseAll() { 587 | for (const o of this.animations.values()) 588 | o.pause(); 589 | } 590 | tick() { 591 | for (let o of this.model.getBoneMap().values()) 592 | o.rotation.set( 593 | ...o.userData.defaultRotation 594 | ), o.position.set( 595 | ...o.userData.defaultPosition 596 | ); 597 | this.animations.forEach( 598 | (o) => o.shouldTick && o.tick() 599 | ); 600 | } 601 | get shouldTick() { 602 | return [...this.animations.values()].some( 603 | (o) => o.shouldTick 604 | ); 605 | } 606 | getModel() { 607 | return this.model; 608 | } 609 | } 610 | const lt = [ 611 | // Right 612 | { 613 | /** 614 | * Position of the texture for this specific cube 615 | * 616 | * baseUV[0] <-> X | baseUV[1] <-> Y 617 | * 618 | * How baseUV maps to a Minecraft cube texture: 619 | * @example 620 | * | X | 0 | 1 | 2 | 3 | 621 | * ----------------------- 622 | * Y | | | | | | 623 | * ----------------------- 624 | * 0 | | | X | X | | 625 | * ----------------------- 626 | * 1 | | X | X | X | X | 627 | */ 628 | name: "west", 629 | baseUV: [2, 1], 630 | dir: [-1, 0, 0], 631 | corners: [ 632 | { pos: [-0.5, 1, 0], uv: [0, 1] }, 633 | { pos: [-0.5, 0, 0], uv: [0, 0] }, 634 | { pos: [-0.5, 1, 1], uv: [1, 1] }, 635 | { pos: [-0.5, 0, 1], uv: [1, 0] } 636 | ] 637 | }, 638 | // Left 639 | { 640 | name: "east", 641 | baseUV: [0, 1], 642 | dir: [1, 0, 0], 643 | corners: [ 644 | { pos: [0.5, 1, 1], uv: [0, 1] }, 645 | { pos: [0.5, 0, 1], uv: [0, 0] }, 646 | { pos: [0.5, 1, 0], uv: [1, 1] }, 647 | { pos: [0.5, 0, 0], uv: [1, 0] } 648 | ] 649 | }, 650 | // Bottom 651 | { 652 | name: "down", 653 | baseUV: [2, 0], 654 | dir: [0, -1, 0], 655 | corners: [ 656 | { pos: [0.5, 0, 1], uv: [0, 1] }, 657 | { pos: [-0.5, 0, 1], uv: [1, 1] }, 658 | { pos: [0.5, 0, 0], uv: [0, 0] }, 659 | { pos: [-0.5, 0, 0], uv: [1, 0] } 660 | ] 661 | }, 662 | // Top 663 | { 664 | name: "up", 665 | baseUV: [1, 0], 666 | dir: [0, 1, 0], 667 | corners: [ 668 | { pos: [-0.5, 1, 1], uv: [1, 1] }, 669 | { pos: [0.5, 1, 1], uv: [0, 1] }, 670 | { pos: [-0.5, 1, 0], uv: [1, 0] }, 671 | { pos: [0.5, 1, 0], uv: [0, 0] } 672 | ] 673 | }, 674 | //Front 675 | { 676 | name: "north", 677 | baseUV: [1, 1], 678 | dir: [0, 0, -1], 679 | corners: [ 680 | { pos: [-0.5, 0, 0], uv: [1, 0] }, 681 | { pos: [0.5, 0, 0], uv: [0, 0] }, 682 | { pos: [-0.5, 1, 0], uv: [1, 1] }, 683 | { pos: [0.5, 1, 0], uv: [0, 1] } 684 | ] 685 | }, 686 | //Back 687 | { 688 | name: "south", 689 | baseUV: [3, 1], 690 | dir: [0, 0, 1], 691 | corners: [ 692 | { pos: [-0.5, 0, 1], uv: [0, 0] }, 693 | { pos: [0.5, 0, 1], uv: [1, 0] }, 694 | { pos: [-0.5, 1, 1], uv: [0, 1] }, 695 | { pos: [0.5, 1, 1], uv: [1, 1] } 696 | ] 697 | } 698 | ], K = 0.03; 699 | class ht { 700 | constructor(o) { 701 | var d, w; 702 | this.positions = [], this.indices = [], this.normals = [], this.uvs = [], this.geometry = new Te(), this.group = new H(); 703 | const { 704 | textureSize: [i, e], 705 | textureDiscrepancyFactor: [ 706 | n, 707 | s 708 | ], 709 | mirror: f, 710 | width: a, 711 | height: h, 712 | depth: l 713 | } = o, [g, c] = [ 714 | i * n, 715 | e * s 716 | ], p = o.startUV ?? [0, 0], y = !Array.isArray(p); 717 | let b = 0, A = 0; 718 | y || ([b, A] = p); 719 | for (let { 720 | name: m, 721 | dir: P, 722 | corners: R, 723 | baseUV: [u, Y] 724 | } of lt) { 725 | const j = this.positions.length / 3; 726 | let x, M; 727 | if (y) { 728 | if (p[m] === void 0) 729 | continue; 730 | [b, A] = ((d = p[m]) == null ? void 0 : d.uv) || [], [x, M] = ((w = p[m]) == null ? void 0 : w.uv_size) || [], x *= n, M *= s, b *= n, A *= s, u = 0, Y = 0; 731 | } 732 | for (const { 733 | pos: [z, X, B], 734 | uv: k 735 | } of R) 736 | this.positions.push( 737 | (f ? -z : z) * a, 738 | X * h, 739 | B * l 740 | ), this.normals.push(...P), this.uvs.push( 741 | //Base offset of the current cube 742 | (b + //Horizontal offset for the current face 743 | (+(u > 0) + +(u > 2)) * Math.floor(x ?? l) + +(u > 1) * Math.floor(x ?? a) + //Face corner specific offsets 744 | k[0] * Math.floor(m === "west" || m === "east" ? x ?? l : x ?? a) + (k[0] === 0 ? K : -K)) / (g / (y ? 1 : n)), 745 | //Align uv to top left corner 746 | 1 - //Base offset of the current cube 747 | (A + //Vertical offset for the current face 748 | Y * Math.floor(M ?? l) + Math.floor(m === "up" || m === "down" ? M ?? l : M ?? h) - //Face corner specific offsets 749 | k[1] * Math.floor(m === "up" || m === "down" ? M ?? l : M ?? h) + (k[1] === 0 ? -K : K)) / (c / (y ? 1 : s)) 750 | ); 751 | this.indices.push(j, j + 1, j + 2, j + 2, j + 1, j + 3); 752 | } 753 | this.createGeometry(), this.createMesh(o); 754 | } 755 | createGeometry() { 756 | this.geometry.setAttribute( 757 | "position", 758 | new G(new Float32Array(this.positions), 3) 759 | ), this.geometry.setAttribute( 760 | "normal", 761 | new G(new Float32Array(this.normals), 3) 762 | ), this.geometry.setAttribute( 763 | "uv", 764 | new G(new Float32Array(this.uvs), 2) 765 | ), this.geometry.setIndex(this.indices); 766 | } 767 | createMesh({ 768 | material: o, 769 | width: i, 770 | height: e, 771 | depth: n, 772 | pivot: s, 773 | rotation: f, 774 | origin: a, 775 | inflate: h = 0 776 | }) { 777 | const l = h * 2 + i, g = new Ae(this.geometry, o); 778 | if (this.group.rotation.order = "ZYX", s === void 0 && (s = [l / 2, e / 2, n / 2]), this.group.add(g), f) { 779 | this.group.position.set(-s[0], s[1], s[2]), g.position.set( 780 | -a[0] - l / 2 + s[0] + h, 781 | a[1] - s[1] - h, 782 | a[2] - s[2] - h 783 | ); 784 | const [c, p, y] = f; 785 | this.group.rotation.set( 786 | _.degToRad(-c), 787 | _.degToRad(-p), 788 | _.degToRad(y) 789 | ); 790 | } else 791 | this.group.position.set( 792 | -a[0] - l / 2 + h, 793 | a[1] - h, 794 | a[2] - h 795 | ); 796 | h && this.group.scale.set( 797 | i !== 0 ? 1 + h / (i / 2) : 1, 798 | e !== 0 ? 1 + h / (e / 2) : 1, 799 | n !== 0 ? 1 + h / (n / 2) : 1 800 | ); 801 | } 802 | getGroup() { 803 | return this.group; 804 | } 805 | } 806 | class ut { 807 | constructor(o) { 808 | var e, n, s, f; 809 | if (this.positions = [], this.indices = [], this.normals = [], this.uvs = [], this.geometry = new Te(), this.group = new H(), !Array.isArray(o.polys)) 810 | throw new Error("Format not supported yet!"); 811 | o.normalized_uvs || (o.uvs = (e = o == null ? void 0 : o.uvs) == null ? void 0 : e.map(([a, h]) => [ 812 | a / o.textureSize[0], 813 | h / o.textureSize[1] 814 | ])); 815 | let i = 0; 816 | for (const a of o.polys) { 817 | for (const [h, l, g] of a) 818 | this.positions.push( 819 | ...((n = o == null ? void 0 : o.positions) == null ? void 0 : n[h]) ?? [] 820 | ), this.normals.push( 821 | ...((s = o == null ? void 0 : o.normals) == null ? void 0 : s[l]) ?? [] 822 | ), this.uvs.push(...((f = o == null ? void 0 : o.uvs) == null ? void 0 : f[g]) ?? []); 823 | a.length === 3 ? this.indices.push(i, i + 1, i + 2) : this.indices.push(i + 2, i + 1, i, i + 2, i, i + 3), i += a.length; 824 | } 825 | this.createGeometry(), this.createMesh(o); 826 | } 827 | createGeometry() { 828 | this.geometry.setAttribute( 829 | "position", 830 | new G(new Float32Array(this.positions), 3) 831 | ), this.geometry.setAttribute( 832 | "normal", 833 | new G(new Float32Array(this.normals), 3) 834 | ), this.geometry.setAttribute( 835 | "uv", 836 | new G(new Float32Array(this.uvs), 2) 837 | ), this.geometry.setIndex(this.indices); 838 | } 839 | createMesh({ material: o }) { 840 | const i = new Ae(this.geometry, o); 841 | this.group.add(i); 842 | } 843 | getGroup() { 844 | return this.group; 845 | } 846 | } 847 | class dt { 848 | constructor(o, i) { 849 | var n; 850 | this.modelData = o, this.texturePath = i, this.boneMap = /* @__PURE__ */ new Map(), this.locators = /* @__PURE__ */ new Map(), this.animator = new ct(this); 851 | const e = ((n = o == null ? void 0 : o.description) == null ? void 0 : n.identifier) ?? "geometry.unknown"; 852 | this.model = new H(), this.model.name = e; 853 | } 854 | async create() { 855 | var a, h, l, g; 856 | const o = this.modelData, i = await this.loadTexture(this.texturePath), e = [ 857 | ((a = o == null ? void 0 : o.description) == null ? void 0 : a.texture_width) ?? i.image.width, 858 | ((h = o == null ? void 0 : o.description) == null ? void 0 : h.texture_height) ?? i.image.height 859 | ], n = [ 860 | i.image.width / e[0], 861 | i.image.height / e[1] 862 | ], s = /* @__PURE__ */ new Map(); 863 | i.magFilter = ye, i.minFilter = ye; 864 | const f = new De({ 865 | side: we, 866 | alphaTest: 0.2, 867 | transparent: !0, 868 | map: i 869 | }); 870 | (l = o == null ? void 0 : o.bones) == null || l.forEach((c) => { 871 | var A; 872 | const p = new H(); 873 | if (p.name = c.name ?? "unknown", c.poly_mesh) { 874 | const d = new ut({ 875 | ...c.poly_mesh, 876 | textureSize: e, 877 | material: f, 878 | mirror: c.mirror ?? !1, 879 | origin: [0, 0, 0], 880 | inflate: c.inflate, 881 | rotation: [0, 0, 0], 882 | pivot: c.pivot 883 | }).getGroup(); 884 | d.name = `#bone.${c.name}#polyMesh`, p.add(d); 885 | } 886 | (A = c.cubes) == null || A.forEach((d, w) => { 887 | var P, R, u; 888 | const m = new ht({ 889 | width: ((P = d.size) == null ? void 0 : P[0]) ?? 0, 890 | height: ((R = d.size) == null ? void 0 : R[1]) ?? 0, 891 | depth: ((u = d.size) == null ? void 0 : u[2]) ?? 0, 892 | startUV: d.uv, 893 | textureSize: e, 894 | textureDiscrepancyFactor: n, 895 | material: f, 896 | mirror: d.mirror === void 0 && d.rotation === void 0 ? c.mirror ?? !1 : d.mirror ?? !1, 897 | origin: d.origin ?? [0, 0, 0], 898 | inflate: d.inflate ?? c.inflate, 899 | rotation: d.rotation, 900 | pivot: d.pivot ?? c.pivot 901 | }).getGroup(); 902 | m.name = `#bone.${c.name}#cube.${w}`, p.add(m); 903 | }); 904 | const y = new H(); 905 | if (y.rotation.order = "ZYX", c.pivot) { 906 | const [d, w, m] = c.pivot; 907 | y.position.set(-d, w, m), p.position.set(d, -w, -m); 908 | } else 909 | y.position.set(0, 0, 0); 910 | if (y.add(p), y.name = `#pivot.${c.name}`, c.rotation) { 911 | const [d, w, m] = c.rotation; 912 | y.rotation.set( 913 | _.degToRad(-d), 914 | _.degToRad(-w), 915 | _.degToRad(m) 916 | ); 917 | } 918 | const b = c.locators ?? {}; 919 | for (const d in b) { 920 | const w = new H(); 921 | w.name = `locator#${d}`; 922 | const m = b[d]; 923 | Array.isArray(m) ? w.position.set(...m) : typeof m == "object" && (w.position.set(...m.offset ?? [0, 0, 0]), w.rotation.set(...m.rotation ?? [0, 0, 0])), this.locators.set(d, w), y.add(w); 924 | } 925 | c.parent || this.model.add(y), c.name && (s.set(c.name, [c.parent, y]), this.boneMap.set(c.name, y)); 926 | }); 927 | for (let [c, [p, y]] of s) 928 | if (p) { 929 | const b = (g = s.get(p)) == null ? void 0 : g[1]; 930 | b && b.name.startsWith("#pivot.") ? b.children[0].add(y) : b && b.add(y); 931 | } 932 | this.animator.setupDefaultBonePoses(); 933 | } 934 | getGroup() { 935 | return this.model; 936 | } 937 | getBoneMap() { 938 | return this.boneMap; 939 | } 940 | getLocator(o) { 941 | return this.locators.get(o); 942 | } 943 | tick() { 944 | this.animator.tick(); 945 | } 946 | get shouldTick() { 947 | return this.animator.shouldTick; 948 | } 949 | createOutlineBox(o, i, e) { 950 | const n = new He({ 951 | side: we, 952 | color: o, 953 | linewidth: 20 954 | }), s = new Ge(e.x, e.y, e.z), f = new Xe(s), a = new Be(f, n); 955 | return a.position.set(i.x, i.y + e.y / 2, i.z), a.name = "helperBox", this.model.add(a), { 956 | dispose: () => { 957 | this.model.remove(a); 958 | } 959 | }; 960 | } 961 | hideBone(o) { 962 | const i = this.boneMap.get(o); 963 | i && (i.visible = !1); 964 | } 965 | showBone(o) { 966 | const i = this.boneMap.get(o); 967 | i && (i.visible = !0); 968 | } 969 | get bones() { 970 | return [...this.boneMap.keys()]; 971 | } 972 | dispose() { 973 | this.animator.dispose(); 974 | } 975 | loadTexture(o) { 976 | return new Promise((i, e) => { 977 | new Ze().load(o, (s) => { 978 | i(s); 979 | }); 980 | }); 981 | } 982 | } 983 | class gt { 984 | constructor(o, i, e, n) { 985 | this.canvasElement = o, this.texturePath = e, this.options = n, this.renderingRequested = !1, this.renderer = new Ke({ 986 | canvas: o, 987 | alpha: n.alpha ?? !1, 988 | antialias: n.antialias ?? !1 989 | }), this.renderer.setPixelRatio(window.devicePixelRatio), this.camera = new Ve(70, 2, 0.1, 1e3), this.camera.position.x = -20, this.camera.position.y = 20, this.camera.position.z = -20, this.camera.updateProjectionMatrix(), this.controls = new nt(this.camera, o), this.scene = new qe(), this.scene.add(new Ce(16777215)), n.alpha && (this.scene.background = new We(13299960)), this.model = new dt(i, e), this.scene.add(this.model.getGroup()), window.addEventListener("resize", this.onResize.bind(this)), this.controls.addEventListener("change", () => this.requestRendering()), this.onResize(), this.loadedModel = this.loadModel().then(() => this.requestRendering()); 990 | } 991 | async loadModel() { 992 | await this.model.create(); 993 | } 994 | get width() { 995 | return this.options.width ?? window.innerWidth; 996 | } 997 | get height() { 998 | return this.options.height ?? window.innerHeight; 999 | } 1000 | render(o = !0) { 1001 | var i; 1002 | this.controls.update(), this.renderer.render(this.scene, this.camera), this.renderingRequested = !1, o && this.model.shouldTick && (this.model.tick(), (i = this.model.animator.winterskyScene) == null || i.updateFacingRotation( 1003 | this.camera 1004 | ), this.requestRendering()); 1005 | } 1006 | requestRendering(o = !1) { 1007 | if (o) 1008 | return this.render(!1); 1009 | this.renderingRequested || (this.renderingRequested = !0, requestAnimationFrame(() => this.render())); 1010 | } 1011 | onResize() { 1012 | this.renderer.setSize(this.width, this.height, !0), this.camera.aspect = this.width / this.height, this.positionCamera(), this.requestRendering(); 1013 | } 1014 | dispose() { 1015 | window.removeEventListener("resize", this.onResize), this.controls.removeEventListener("change", this.requestRendering); 1016 | } 1017 | addHelpers() { 1018 | this.scene.add(new $e(50)), this.scene.add(new Qe(20, 20)), this.scene.add(new Je(this.model.getGroup(), 16776960)), this.requestRendering(); 1019 | } 1020 | getModel() { 1021 | return this.model; 1022 | } 1023 | // From: https://github.com/mrdoob/three.js/issues/6784#issuecomment-315963625 1024 | positionCamera(o = 1.5, i = !0) { 1025 | i && this.model.getGroup().rotation.set(0, Math.PI, 0); 1026 | const e = new et().setFromObject(this.model.getGroup()).getBoundingSphere(new tt()), n = this.camera.fov * Math.PI / 180 * o, s = e.radius / Math.tan(n / 2), f = Math.sqrt( 1027 | Math.pow(s, 2) + Math.pow(s, 2) 1028 | ); 1029 | this.camera.position.set(f, f, f), this.controls.update(), this.camera.lookAt(e.center), this.controls.target.set( 1030 | e.center.x, 1031 | e.center.y, 1032 | e.center.z 1033 | ), this.camera.updateProjectionMatrix(); 1034 | } 1035 | } 1036 | export { 1037 | dt as Model, 1038 | gt as StandaloneModelViewer 1039 | }; 1040 | -------------------------------------------------------------------------------- /dist/model-viewer.umd.js: -------------------------------------------------------------------------------- 1 | (function(R,n){typeof exports=="object"&&typeof module<"u"?n(exports,require("three"),require("@bridge-editor/molang"),require("wintersky")):typeof define=="function"&&define.amd?define(["exports","three","@bridge-editor/molang","wintersky"],n):(R=typeof globalThis<"u"?globalThis:R||self,n(R.ModelViewer={},R.three,R.molang,R.Wintersky))})(this,function(R,n,me,fe){"use strict";const q={type:"change"},D={type:"start"},K={type:"end"};class ge extends n.EventDispatcher{constructor(i,o){super(),this.object=i,this.domElement=o,this.domElement.style.touchAction="none",this.enabled=!0,this.target=new n.Vector3,this.minDistance=0,this.maxDistance=1/0,this.minZoom=0,this.maxZoom=1/0,this.minPolarAngle=0,this.maxPolarAngle=Math.PI,this.minAzimuthAngle=-1/0,this.maxAzimuthAngle=1/0,this.enableDamping=!1,this.dampingFactor=.05,this.enableZoom=!0,this.zoomSpeed=1,this.enableRotate=!0,this.rotateSpeed=1,this.enablePan=!0,this.panSpeed=1,this.screenSpacePanning=!0,this.keyPanSpeed=7,this.autoRotate=!1,this.autoRotateSpeed=2,this.keys={LEFT:"ArrowLeft",UP:"ArrowUp",RIGHT:"ArrowRight",BOTTOM:"ArrowDown"},this.mouseButtons={LEFT:n.MOUSE.ROTATE,MIDDLE:n.MOUSE.DOLLY,RIGHT:n.MOUSE.PAN},this.touches={ONE:n.TOUCH.ROTATE,TWO:n.TOUCH.DOLLY_PAN},this.target0=this.target.clone(),this.position0=this.object.position.clone(),this.zoom0=this.object.zoom,this._domElementKeyEvents=null,this.getPolarAngle=function(){return r.phi},this.getAzimuthalAngle=function(){return r.theta},this.getDistance=function(){return this.object.position.distanceTo(this.target)},this.listenToKeyEvents=function(t){t.addEventListener("keydown",le),this._domElementKeyEvents=t},this.saveState=function(){e.target0.copy(e.target),e.position0.copy(e.object.position),e.zoom0=e.object.zoom},this.reset=function(){e.target.copy(e.target0),e.object.position.copy(e.position0),e.object.zoom=e.zoom0,e.object.updateProjectionMatrix(),e.dispatchEvent(q),e.update(),a=s.NONE},this.update=function(){const t=new n.Vector3,c=new n.Quaternion().setFromUnitVectors(i.up,new n.Vector3(0,1,0)),T=c.clone().invert(),M=new n.Vector3,v=new n.Quaternion,Y=2*Math.PI;return function(){const pe=e.object.position;t.copy(pe).sub(e.target),t.applyQuaternion(c),r.setFromVector3(t),e.autoRotate&&a===s.NONE&&P(j()),e.enableDamping?(r.theta+=h.theta*e.dampingFactor,r.phi+=h.phi*e.dampingFactor):(r.theta+=h.theta,r.phi+=h.phi);let N=e.minAzimuthAngle,U=e.maxAzimuthAngle;return isFinite(N)&&isFinite(U)&&(N<-Math.PI?N+=Y:N>Math.PI&&(N-=Y),U<-Math.PI?U+=Y:U>Math.PI&&(U-=Y),N<=U?r.theta=Math.max(N,Math.min(U,r.theta)):r.theta=r.theta>(N+U)/2?Math.max(N,r.theta):Math.min(U,r.theta)),r.phi=Math.max(e.minPolarAngle,Math.min(e.maxPolarAngle,r.phi)),r.makeSafe(),r.radius*=u,r.radius=Math.max(e.minDistance,Math.min(e.maxDistance,r.radius)),e.enableDamping===!0?e.target.addScaledVector(y,e.dampingFactor):e.target.add(y),t.setFromSpherical(r),t.applyQuaternion(T),pe.copy(e.target).add(t),e.object.lookAt(e.target),e.enableDamping===!0?(h.theta*=1-e.dampingFactor,h.phi*=1-e.dampingFactor,y.multiplyScalar(1-e.dampingFactor)):(h.set(0,0,0),y.set(0,0,0)),u=1,l||M.distanceToSquared(e.object.position)>g||8*(1-v.dot(e.object.quaternion))>g?(e.dispatchEvent(q),M.copy(e.object.position),v.copy(e.object.quaternion),l=!1,!0):!1}}(),this.dispose=function(){e.domElement.removeEventListener("contextmenu",ue),e.domElement.removeEventListener("pointerdown",ae),e.domElement.removeEventListener("pointercancel",re),e.domElement.removeEventListener("wheel",ce),e.domElement.removeEventListener("pointermove",B),e.domElement.removeEventListener("pointerup",X),e._domElementKeyEvents!==null&&e._domElementKeyEvents.removeEventListener("keydown",le)};const e=this,s={NONE:-1,ROTATE:0,DOLLY:1,PAN:2,TOUCH_ROTATE:3,TOUCH_PAN:4,TOUCH_DOLLY_PAN:5,TOUCH_DOLLY_ROTATE:6};let a=s.NONE;const g=1e-6,r=new n.Spherical,h=new n.Spherical;let u=1;const y=new n.Vector3;let l=!1;const m=new n.Vector2,b=new n.Vector2,E=new n.Vector2,A=new n.Vector2,p=new n.Vector2,w=new n.Vector2,f=new n.Vector2,x=new n.Vector2,S=new n.Vector2,d=[],I={};function j(){return 2*Math.PI/60/60*e.autoRotateSpeed}function O(){return Math.pow(.95,e.zoomSpeed)}function P(t){h.theta-=t}function _(t){h.phi-=t}const V=function(){const t=new n.Vector3;return function(T,M){t.setFromMatrixColumn(M,0),t.multiplyScalar(-T),y.add(t)}}(),F=function(){const t=new n.Vector3;return function(T,M){e.screenSpacePanning===!0?t.setFromMatrixColumn(M,1):(t.setFromMatrixColumn(M,0),t.crossVectors(e.object.up,t)),t.multiplyScalar(T),y.add(t)}}(),k=function(){const t=new n.Vector3;return function(T,M){const v=e.domElement;if(e.object.isPerspectiveCamera){const Y=e.object.position;t.copy(Y).sub(e.target);let H=t.length();H*=Math.tan(e.object.fov/2*Math.PI/180),V(2*T*H/v.clientHeight,e.object.matrix),F(2*M*H/v.clientHeight,e.object.matrix)}else e.object.isOrthographicCamera?(V(T*(e.object.right-e.object.left)/e.object.zoom/v.clientWidth,e.object.matrix),F(M*(e.object.top-e.object.bottom)/e.object.zoom/v.clientHeight,e.object.matrix)):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - pan disabled."),e.enablePan=!1)}}();function G(t){e.object.isPerspectiveCamera?u/=t:e.object.isOrthographicCamera?(e.object.zoom=Math.max(e.minZoom,Math.min(e.maxZoom,e.object.zoom*t)),e.object.updateProjectionMatrix(),l=!0):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),e.enableZoom=!1)}function $(t){e.object.isPerspectiveCamera?u*=t:e.object.isOrthographicCamera?(e.object.zoom=Math.max(e.minZoom,Math.min(e.maxZoom,e.object.zoom/t)),e.object.updateProjectionMatrix(),l=!0):(console.warn("WARNING: OrbitControls.js encountered an unknown camera type - dolly/zoom disabled."),e.enableZoom=!1)}function Q(t){m.set(t.clientX,t.clientY)}function xe(t){f.set(t.clientX,t.clientY)}function J(t){A.set(t.clientX,t.clientY)}function Oe(t){b.set(t.clientX,t.clientY),E.subVectors(b,m).multiplyScalar(e.rotateSpeed);const c=e.domElement;P(2*Math.PI*E.x/c.clientHeight),_(2*Math.PI*E.y/c.clientHeight),m.copy(b),e.update()}function ke(t){x.set(t.clientX,t.clientY),S.subVectors(x,f),S.y>0?G(O()):S.y<0&&$(O()),f.copy(x),e.update()}function ve(t){p.set(t.clientX,t.clientY),w.subVectors(p,A).multiplyScalar(e.panSpeed),k(w.x,w.y),A.copy(p),e.update()}function Re(t){t.deltaY<0?$(O()):t.deltaY>0&&G(O()),e.update()}function Se(t){let c=!1;switch(t.code){case e.keys.UP:t.ctrlKey||t.metaKey||t.shiftKey?_(2*Math.PI*e.rotateSpeed/e.domElement.clientHeight):k(0,e.keyPanSpeed),c=!0;break;case e.keys.BOTTOM:t.ctrlKey||t.metaKey||t.shiftKey?_(-2*Math.PI*e.rotateSpeed/e.domElement.clientHeight):k(0,-e.keyPanSpeed),c=!0;break;case e.keys.LEFT:t.ctrlKey||t.metaKey||t.shiftKey?P(2*Math.PI*e.rotateSpeed/e.domElement.clientHeight):k(e.keyPanSpeed,0),c=!0;break;case e.keys.RIGHT:t.ctrlKey||t.metaKey||t.shiftKey?P(-2*Math.PI*e.rotateSpeed/e.domElement.clientHeight):k(-e.keyPanSpeed,0),c=!0;break}c&&(t.preventDefault(),e.update())}function ee(){if(d.length===1)m.set(d[0].pageX,d[0].pageY);else{const t=.5*(d[0].pageX+d[1].pageX),c=.5*(d[0].pageY+d[1].pageY);m.set(t,c)}}function te(){if(d.length===1)A.set(d[0].pageX,d[0].pageY);else{const t=.5*(d[0].pageX+d[1].pageX),c=.5*(d[0].pageY+d[1].pageY);A.set(t,c)}}function ie(){const t=d[0].pageX-d[1].pageX,c=d[0].pageY-d[1].pageY,T=Math.sqrt(t*t+c*c);f.set(0,T)}function Le(){e.enableZoom&&ie(),e.enablePan&&te()}function Ne(){e.enableZoom&&ie(),e.enableRotate&&ee()}function oe(t){if(d.length==1)b.set(t.pageX,t.pageY);else{const T=Z(t),M=.5*(t.pageX+T.x),v=.5*(t.pageY+T.y);b.set(M,v)}E.subVectors(b,m).multiplyScalar(e.rotateSpeed);const c=e.domElement;P(2*Math.PI*E.x/c.clientHeight),_(2*Math.PI*E.y/c.clientHeight),m.copy(b)}function ne(t){if(d.length===1)p.set(t.pageX,t.pageY);else{const c=Z(t),T=.5*(t.pageX+c.x),M=.5*(t.pageY+c.y);p.set(T,M)}w.subVectors(p,A).multiplyScalar(e.panSpeed),k(w.x,w.y),A.copy(p)}function se(t){const c=Z(t),T=t.pageX-c.x,M=t.pageY-c.y,v=Math.sqrt(T*T+M*M);x.set(0,v),S.set(0,Math.pow(x.y/f.y,e.zoomSpeed)),G(S.y),f.copy(x)}function Ue(t){e.enableZoom&&se(t),e.enablePan&&ne(t)}function je(t){e.enableZoom&&se(t),e.enableRotate&&oe(t)}function ae(t){e.enabled!==!1&&(d.length===0&&(e.domElement.setPointerCapture(t.pointerId),e.domElement.addEventListener("pointermove",B),e.domElement.addEventListener("pointerup",X)),Ve(t),t.pointerType==="touch"?Ye(t):Ie(t))}function B(t){e.enabled!==!1&&(t.pointerType==="touch"?ze(t):_e(t))}function X(t){he(t),d.length===0&&(e.domElement.releasePointerCapture(t.pointerId),e.domElement.removeEventListener("pointermove",B),e.domElement.removeEventListener("pointerup",X)),e.dispatchEvent(K),a=s.NONE}function re(t){he(t)}function Ie(t){let c;switch(t.button){case 0:c=e.mouseButtons.LEFT;break;case 1:c=e.mouseButtons.MIDDLE;break;case 2:c=e.mouseButtons.RIGHT;break;default:c=-1}switch(c){case n.MOUSE.DOLLY:if(e.enableZoom===!1)return;xe(t),a=s.DOLLY;break;case n.MOUSE.ROTATE:if(t.ctrlKey||t.metaKey||t.shiftKey){if(e.enablePan===!1)return;J(t),a=s.PAN}else{if(e.enableRotate===!1)return;Q(t),a=s.ROTATE}break;case n.MOUSE.PAN:if(t.ctrlKey||t.metaKey||t.shiftKey){if(e.enableRotate===!1)return;Q(t),a=s.ROTATE}else{if(e.enablePan===!1)return;J(t),a=s.PAN}break;default:a=s.NONE}a!==s.NONE&&e.dispatchEvent(D)}function _e(t){switch(a){case s.ROTATE:if(e.enableRotate===!1)return;Oe(t);break;case s.DOLLY:if(e.enableZoom===!1)return;ke(t);break;case s.PAN:if(e.enablePan===!1)return;ve(t);break}}function ce(t){e.enabled===!1||e.enableZoom===!1||a!==s.NONE||(t.preventDefault(),e.dispatchEvent(D),Re(t),e.dispatchEvent(K))}function le(t){e.enabled===!1||e.enablePan===!1||Se(t)}function Ye(t){switch(de(t),d.length){case 1:switch(e.touches.ONE){case n.TOUCH.ROTATE:if(e.enableRotate===!1)return;ee(),a=s.TOUCH_ROTATE;break;case n.TOUCH.PAN:if(e.enablePan===!1)return;te(),a=s.TOUCH_PAN;break;default:a=s.NONE}break;case 2:switch(e.touches.TWO){case n.TOUCH.DOLLY_PAN:if(e.enableZoom===!1&&e.enablePan===!1)return;Le(),a=s.TOUCH_DOLLY_PAN;break;case n.TOUCH.DOLLY_ROTATE:if(e.enableZoom===!1&&e.enableRotate===!1)return;Ne(),a=s.TOUCH_DOLLY_ROTATE;break;default:a=s.NONE}break;default:a=s.NONE}a!==s.NONE&&e.dispatchEvent(D)}function ze(t){switch(de(t),a){case s.TOUCH_ROTATE:if(e.enableRotate===!1)return;oe(t),e.update();break;case s.TOUCH_PAN:if(e.enablePan===!1)return;ne(t),e.update();break;case s.TOUCH_DOLLY_PAN:if(e.enableZoom===!1&&e.enablePan===!1)return;Ue(t),e.update();break;case s.TOUCH_DOLLY_ROTATE:if(e.enableZoom===!1&&e.enableRotate===!1)return;je(t),e.update();break;default:a=s.NONE}}function ue(t){e.enabled!==!1&&t.preventDefault()}function Ve(t){d.push(t)}function he(t){delete I[t.pointerId];for(let c=0;c[Number(e),Array.isArray(s)?s:[s]]).sort(([e],[s])=>e-s)}getCurrentEffects(){if(this.currentEffectIndex>=this.effects.length)return;const i=this.effects[this.currentEffectIndex];if(!(i[0]>this.animation.roundedCurrentTime))return this.currentEffectIndex++,i[1]}reset(){this.currentEffectIndex=0}}class ye extends C{tick(){const i=super.getCurrentEffects()??[];i.length>0&&console.log(`Playing sound effects: "${i.map(o=>o.effect).join(", ")}"`)}}class be extends C{constructor(){super(...arguments),this.disposables=[]}tick(){this.tickingEffects.forEach(o=>o.tick());const i=super.getCurrentEffects()??[];for(const{locator:o,effect:e,pre_effect_script:s}of i){if(!e)return;const a=this.animation.getAnimator(),g=a.getModel(),r=a.getEmitter(e);if(!r||!a.winterskyScene)return;const h=o?g.getLocator(o):void 0,u=new fe.Emitter(a.winterskyScene,r,{parent_mode:h?"locator":"entity",loop_mode:"once"});h&&(h.add(u.local_space),u.local_space.parent=h);const y={tick:()=>{u.tick(),u.enabled||(u.delete(),this.tickingEffects=this.tickingEffects.filter(l=>l!==y))}};this.tickingEffects.push(y),this.disposables.push({dispose:()=>{u.delete(),this.tickingEffects=this.tickingEffects.filter(l=>l!==y)}}),u.start(),u.tick()}}dispose(){this.disposables.forEach(i=>i.dispose()),this.disposables=[]}}class we{constructor(i,o){this.animator=i,this.animationData=o,this.startTimestamp=0,this.lastFrameTimestamp=0,this.isRunning=!1,this.env={"query.anim_time":()=>this.currentTime,"query.delta_time":()=>this.startTimestamp-this.lastFrameTimestamp,"query.life_time":()=>this.currentTime},this.molang=new me.Molang(this.env,{convertUndefined:!0}),this.soundEffects=new ye(this,this.animationData.sound_effects??{}),this.particleEffects=new be(this,this.animationData.particle_effects??{})}getAnimator(){return this.animator}execute(i){return this.molang.executeAndCatch(i)}parseBoneModifier(i){if(typeof i=="number")return[i,i,i];if(typeof i=="string"){const o=typeof i=="string"?this.execute(i):i;return[o,o,o]}else{if(Array.isArray(i))return i.map(o=>typeof o=="string"?this.execute(o):o);if(i!==void 0){const o=Object.entries(i).map(([e,s])=>[Number(e),s]).sort(([e],[s])=>e-s);for(let e=o.length-1;e>=0;e--){let[s,a]=o[e];if(!(s>this.currentTime))if(s===this.currentTime){if(Array.isArray(a))return a.map(g=>typeof g=="string"?this.execute(g):g);throw new Error("Format not supported yet")}else{let[g,r]=o[n.MathUtils.euclideanModulo(e+1,o.length)],h=g-s;if(Array.isArray(a)&&Array.isArray(r))return a=a.map(u=>typeof u=="string"?this.execute(u):u),r=r.map(u=>typeof u=="string"?this.execute(u):u),a.map((u,y)=>u+(r[y]-u)/h*(this.currentTime-s));throw new Error("Format not supported yet")}}return[0,0,0]}}}tick(){this.soundEffects.tick(),this.particleEffects.tick();const i=this.animator.getModel().getBoneMap();for(let o in this.animationData.bones){const e=i.get(o);if(!e)continue;const{position:s,rotation:a,scale:g}=this.animationData.bones[o],[r,h,u]=[s,a,g].map(y=>this.parseBoneModifier(y));if(r){const y=e.position.toArray();e.position.set(...r.map((l,m)=>(m===0?-1:1)*l+y[m]))}if(h){const y=e.rotation.toArray();e.rotation.set(...h.map(l=>n.MathUtils.degToRad(l)).map((l,m)=>y[m]+(m===2?l:-l)))}u&&e.scale.set(...u)}this.currentTime>this.animationData.animation_length&&(this.animationData.loop?this.loop():this.pause()),this.lastFrameTimestamp=Date.now()}play(){this.isRunning=!0,this.startTimestamp=Date.now()}pause(){this.isRunning=!1}loop(){this.startTimestamp=Date.now(),this.soundEffects.reset(),this.particleEffects.reset()}dispose(){this.particleEffects.dispose()}get currentTime(){return(Date.now()-this.startTimestamp)/1e3}get roundedCurrentTime(){return Math.round((Date.now()-this.startTimestamp)/50)/20}get shouldTick(){return this.isRunning}}class Ee{constructor(i){this.model=i,this.animations=new Map,this.particleEmitters=new Map}setupDefaultBonePoses(){for(let i of this.model.getBoneMap().values())i.userData.defaultRotation=i.rotation.toArray(),i.userData.defaultPosition=i.position.toArray()}dispose(){this.disposeAnimations();for(let i of this.model.getBoneMap().values())delete i.userData.defaultRotation,delete i.userData.defaultPosition}disposeAnimations(){this.animations.forEach(i=>i.dispose())}setupWintersky(i){this.winterskyScene=i}addAnimation(i,o){this.animations.set(i,new we(this,o))}addEmitter(i,o){this.particleEmitters.set(i,o)}getEmitter(i){return this.particleEmitters.get(i)}play(i){const o=this.animations.get(i);if(!o)throw new Error(`Unknown animation: "${i}"`);o.play()}pause(i){const o=this.animations.get(i);if(!o)throw new Error(`Unknown animation: "${i}"`);o.pause()}pauseAll(){for(const i of this.animations.values())i.pause()}tick(){for(let i of this.model.getBoneMap().values())i.rotation.set(...i.userData.defaultRotation),i.position.set(...i.userData.defaultPosition);this.animations.forEach(i=>i.shouldTick&&i.tick())}get shouldTick(){return[...this.animations.values()].some(i=>i.shouldTick)}getModel(){return this.model}}const Te=[{name:"west",baseUV:[2,1],dir:[-1,0,0],corners:[{pos:[-.5,1,0],uv:[0,1]},{pos:[-.5,0,0],uv:[0,0]},{pos:[-.5,1,1],uv:[1,1]},{pos:[-.5,0,1],uv:[1,0]}]},{name:"east",baseUV:[0,1],dir:[1,0,0],corners:[{pos:[.5,1,1],uv:[0,1]},{pos:[.5,0,1],uv:[0,0]},{pos:[.5,1,0],uv:[1,1]},{pos:[.5,0,0],uv:[1,0]}]},{name:"down",baseUV:[2,0],dir:[0,-1,0],corners:[{pos:[.5,0,1],uv:[0,1]},{pos:[-.5,0,1],uv:[1,1]},{pos:[.5,0,0],uv:[0,0]},{pos:[-.5,0,0],uv:[1,0]}]},{name:"up",baseUV:[1,0],dir:[0,1,0],corners:[{pos:[-.5,1,1],uv:[1,1]},{pos:[.5,1,1],uv:[0,1]},{pos:[-.5,1,0],uv:[1,0]},{pos:[.5,1,0],uv:[0,0]}]},{name:"north",baseUV:[1,1],dir:[0,0,-1],corners:[{pos:[-.5,0,0],uv:[1,0]},{pos:[.5,0,0],uv:[0,0]},{pos:[-.5,1,0],uv:[1,1]},{pos:[.5,1,0],uv:[0,1]}]},{name:"south",baseUV:[3,1],dir:[0,0,1],corners:[{pos:[-.5,0,1],uv:[0,0]},{pos:[.5,0,1],uv:[1,0]},{pos:[-.5,1,1],uv:[0,1]},{pos:[.5,1,1],uv:[1,1]}]}],z=.03;class Me{constructor(i){var p,w;this.positions=[],this.indices=[],this.normals=[],this.uvs=[],this.geometry=new n.BufferGeometry,this.group=new n.Group;const{textureSize:[o,e],textureDiscrepancyFactor:[s,a],mirror:g,width:r,height:h,depth:u}=i,[y,l]=[o*s,e*a],m=i.startUV??[0,0],b=!Array.isArray(m);let E=0,A=0;b||([E,A]=m);for(let{name:f,dir:x,corners:S,baseUV:[d,I]}of Te){const j=this.positions.length/3;let O,P;if(b){if(m[f]===void 0)continue;[E,A]=((p=m[f])==null?void 0:p.uv)||[],[O,P]=((w=m[f])==null?void 0:w.uv_size)||[],O*=s,P*=a,E*=s,A*=a,d=0,I=0}for(const{pos:[_,V,F],uv:k}of S)this.positions.push((g?-_:_)*r,V*h,F*u),this.normals.push(...x),this.uvs.push((E+(+(d>0)+ +(d>2))*Math.floor(O??u)+ +(d>1)*Math.floor(O??r)+k[0]*Math.floor(f==="west"||f==="east"?O??u:O??r)+(k[0]===0?z:-z))/(y/(b?1:s)),1-(A+I*Math.floor(P??u)+Math.floor(f==="up"||f==="down"?P??u:P??h)-k[1]*Math.floor(f==="up"||f==="down"?P??u:P??h)+(k[1]===0?-z:z))/(l/(b?1:a)));this.indices.push(j,j+1,j+2,j+2,j+1,j+3)}this.createGeometry(),this.createMesh(i)}createGeometry(){this.geometry.setAttribute("position",new n.BufferAttribute(new Float32Array(this.positions),3)),this.geometry.setAttribute("normal",new n.BufferAttribute(new Float32Array(this.normals),3)),this.geometry.setAttribute("uv",new n.BufferAttribute(new Float32Array(this.uvs),2)),this.geometry.setIndex(this.indices)}createMesh({material:i,width:o,height:e,depth:s,pivot:a,rotation:g,origin:r,inflate:h=0}){const u=h*2+o,y=new n.Mesh(this.geometry,i);if(this.group.rotation.order="ZYX",a===void 0&&(a=[u/2,e/2,s/2]),this.group.add(y),g){this.group.position.set(-a[0],a[1],a[2]),y.position.set(-r[0]-u/2+a[0]+h,r[1]-a[1]-h,r[2]-a[2]-h);const[l,m,b]=g;this.group.rotation.set(n.MathUtils.degToRad(-l),n.MathUtils.degToRad(-m),n.MathUtils.degToRad(b))}else this.group.position.set(-r[0]-u/2+h,r[1]-h,r[2]-h);h&&this.group.scale.set(o!==0?1+h/(o/2):1,e!==0?1+h/(e/2):1,s!==0?1+h/(s/2):1)}getGroup(){return this.group}}class Ae{constructor(i){var e,s,a,g;if(this.positions=[],this.indices=[],this.normals=[],this.uvs=[],this.geometry=new n.BufferGeometry,this.group=new n.Group,!Array.isArray(i.polys))throw new Error("Format not supported yet!");i.normalized_uvs||(i.uvs=(e=i==null?void 0:i.uvs)==null?void 0:e.map(([r,h])=>[r/i.textureSize[0],h/i.textureSize[1]]));let o=0;for(const r of i.polys){for(const[h,u,y]of r)this.positions.push(...((s=i==null?void 0:i.positions)==null?void 0:s[h])??[]),this.normals.push(...((a=i==null?void 0:i.normals)==null?void 0:a[u])??[]),this.uvs.push(...((g=i==null?void 0:i.uvs)==null?void 0:g[y])??[]);r.length===3?this.indices.push(o,o+1,o+2):this.indices.push(o+2,o+1,o,o+2,o,o+3),o+=r.length}this.createGeometry(),this.createMesh(i)}createGeometry(){this.geometry.setAttribute("position",new n.BufferAttribute(new Float32Array(this.positions),3)),this.geometry.setAttribute("normal",new n.BufferAttribute(new Float32Array(this.normals),3)),this.geometry.setAttribute("uv",new n.BufferAttribute(new Float32Array(this.uvs),2)),this.geometry.setIndex(this.indices)}createMesh({material:i}){const o=new n.Mesh(this.geometry,i);this.group.add(o)}getGroup(){return this.group}}class W{constructor(i,o){var s;this.modelData=i,this.texturePath=o,this.boneMap=new Map,this.locators=new Map,this.animator=new Ee(this);const e=((s=i==null?void 0:i.description)==null?void 0:s.identifier)??"geometry.unknown";this.model=new n.Group,this.model.name=e}async create(){var r,h,u,y;const i=this.modelData,o=await this.loadTexture(this.texturePath),e=[((r=i==null?void 0:i.description)==null?void 0:r.texture_width)??o.image.width,((h=i==null?void 0:i.description)==null?void 0:h.texture_height)??o.image.height],s=[o.image.width/e[0],o.image.height/e[1]],a=new Map;o.magFilter=n.NearestFilter,o.minFilter=n.NearestFilter;const g=new n.MeshLambertMaterial({side:n.DoubleSide,alphaTest:.2,transparent:!0,map:o});(u=i==null?void 0:i.bones)==null||u.forEach(l=>{var A;const m=new n.Group;if(m.name=l.name??"unknown",l.poly_mesh){const p=new Ae({...l.poly_mesh,textureSize:e,material:g,mirror:l.mirror??!1,origin:[0,0,0],inflate:l.inflate,rotation:[0,0,0],pivot:l.pivot}).getGroup();p.name=`#bone.${l.name}#polyMesh`,m.add(p)}(A=l.cubes)==null||A.forEach((p,w)=>{var x,S,d;const f=new Me({width:((x=p.size)==null?void 0:x[0])??0,height:((S=p.size)==null?void 0:S[1])??0,depth:((d=p.size)==null?void 0:d[2])??0,startUV:p.uv,textureSize:e,textureDiscrepancyFactor:s,material:g,mirror:p.mirror===void 0&&p.rotation===void 0?l.mirror??!1:p.mirror??!1,origin:p.origin??[0,0,0],inflate:p.inflate??l.inflate,rotation:p.rotation,pivot:p.pivot??l.pivot}).getGroup();f.name=`#bone.${l.name}#cube.${w}`,m.add(f)});const b=new n.Group;if(b.rotation.order="ZYX",l.pivot){const[p,w,f]=l.pivot;b.position.set(-p,w,f),m.position.set(p,-w,-f)}else b.position.set(0,0,0);if(b.add(m),b.name=`#pivot.${l.name}`,l.rotation){const[p,w,f]=l.rotation;b.rotation.set(n.MathUtils.degToRad(-p),n.MathUtils.degToRad(-w),n.MathUtils.degToRad(f))}const E=l.locators??{};for(const p in E){const w=new n.Group;w.name=`locator#${p}`;const f=E[p];Array.isArray(f)?w.position.set(...f):typeof f=="object"&&(w.position.set(...f.offset??[0,0,0]),w.rotation.set(...f.rotation??[0,0,0])),this.locators.set(p,w),b.add(w)}l.parent||this.model.add(b),l.name&&(a.set(l.name,[l.parent,b]),this.boneMap.set(l.name,b))});for(let[l,[m,b]]of a)if(m){const E=(y=a.get(m))==null?void 0:y[1];E&&E.name.startsWith("#pivot.")?E.children[0].add(b):E&&E.add(b)}this.animator.setupDefaultBonePoses()}getGroup(){return this.model}getBoneMap(){return this.boneMap}getLocator(i){return this.locators.get(i)}tick(){this.animator.tick()}get shouldTick(){return this.animator.shouldTick}createOutlineBox(i,o,e){const s=new n.LineBasicMaterial({side:n.DoubleSide,color:i,linewidth:20}),a=new n.BoxGeometry(e.x,e.y,e.z),g=new n.EdgesGeometry(a),r=new n.LineSegments(g,s);return r.position.set(o.x,o.y+e.y/2,o.z),r.name="helperBox",this.model.add(r),{dispose:()=>{this.model.remove(r)}}}hideBone(i){const o=this.boneMap.get(i);o&&(o.visible=!1)}showBone(i){const o=this.boneMap.get(i);o&&(o.visible=!0)}get bones(){return[...this.boneMap.keys()]}dispose(){this.animator.dispose()}loadTexture(i){return new Promise((o,e)=>{new n.TextureLoader().load(i,a=>{o(a)})})}}class Pe{constructor(i,o,e,s){this.canvasElement=i,this.texturePath=e,this.options=s,this.renderingRequested=!1,this.renderer=new n.WebGLRenderer({canvas:i,alpha:s.alpha??!1,antialias:s.antialias??!1}),this.renderer.setPixelRatio(window.devicePixelRatio),this.camera=new n.PerspectiveCamera(70,2,.1,1e3),this.camera.position.x=-20,this.camera.position.y=20,this.camera.position.z=-20,this.camera.updateProjectionMatrix(),this.controls=new ge(this.camera,i),this.scene=new n.Scene,this.scene.add(new n.AmbientLight(16777215)),s.alpha&&(this.scene.background=new n.Color(13299960)),this.model=new W(o,e),this.scene.add(this.model.getGroup()),window.addEventListener("resize",this.onResize.bind(this)),this.controls.addEventListener("change",()=>this.requestRendering()),this.onResize(),this.loadedModel=this.loadModel().then(()=>this.requestRendering())}async loadModel(){await this.model.create()}get width(){return this.options.width??window.innerWidth}get height(){return this.options.height??window.innerHeight}render(i=!0){var o;this.controls.update(),this.renderer.render(this.scene,this.camera),this.renderingRequested=!1,i&&this.model.shouldTick&&(this.model.tick(),(o=this.model.animator.winterskyScene)==null||o.updateFacingRotation(this.camera),this.requestRendering())}requestRendering(i=!1){if(i)return this.render(!1);this.renderingRequested||(this.renderingRequested=!0,requestAnimationFrame(()=>this.render()))}onResize(){this.renderer.setSize(this.width,this.height,!0),this.camera.aspect=this.width/this.height,this.positionCamera(),this.requestRendering()}dispose(){window.removeEventListener("resize",this.onResize),this.controls.removeEventListener("change",this.requestRendering)}addHelpers(){this.scene.add(new n.AxesHelper(50)),this.scene.add(new n.GridHelper(20,20)),this.scene.add(new n.BoxHelper(this.model.getGroup(),16776960)),this.requestRendering()}getModel(){return this.model}positionCamera(i=1.5,o=!0){o&&this.model.getGroup().rotation.set(0,Math.PI,0);const e=new n.Box3().setFromObject(this.model.getGroup()).getBoundingSphere(new n.Sphere),s=this.camera.fov*Math.PI/180*i,a=e.radius/Math.tan(s/2),g=Math.sqrt(Math.pow(a,2)+Math.pow(a,2));this.camera.position.set(g,g,g),this.controls.update(),this.camera.lookAt(e.center),this.controls.target.set(e.center.x,e.center.y,e.center.z),this.camera.updateProjectionMatrix()}}R.Model=W,R.StandaloneModelViewer=Pe,Object.defineProperty(R,Symbol.toStringTag,{value:"Module"})}); 2 | -------------------------------------------------------------------------------- /lib/Animations/Animation.ts: -------------------------------------------------------------------------------- 1 | import { Molang } from '@bridge-editor/molang' 2 | import { 3 | ISingleAnimation, 4 | TBoneModifier, 5 | TTimestampEntry, 6 | } from '../Schema/Animation' 7 | import { MathUtils } from 'three' 8 | import { SoundEffect } from './SoundEffect' 9 | import { ParticleEffect } from './ParticleEffect' 10 | import { Animator } from './Animator' 11 | 12 | export class Animation { 13 | protected startTimestamp = 0 14 | protected lastFrameTimestamp = 0 15 | protected isRunning = false 16 | protected env = { 17 | 'query.anim_time': () => this.currentTime, 18 | 'query.delta_time': () => this.startTimestamp - this.lastFrameTimestamp, 19 | 'query.life_time': () => this.currentTime, 20 | } 21 | protected molang = new Molang(this.env, { 22 | convertUndefined: true, 23 | }) 24 | protected soundEffects: SoundEffect 25 | protected particleEffects: ParticleEffect 26 | 27 | constructor( 28 | protected animator: Animator, 29 | protected animationData: ISingleAnimation 30 | ) { 31 | this.soundEffects = new SoundEffect( 32 | this, 33 | this.animationData.sound_effects ?? {} 34 | ) 35 | this.particleEffects = new ParticleEffect( 36 | this, 37 | this.animationData.particle_effects ?? {} 38 | ) 39 | } 40 | 41 | getAnimator() { 42 | return this.animator 43 | } 44 | 45 | protected execute(expr: string) { 46 | return this.molang.executeAndCatch(expr) 47 | } 48 | 49 | parseBoneModifier(transform: TBoneModifier) { 50 | if (typeof transform === 'number') { 51 | return [transform, transform, transform] 52 | } else if (typeof transform === 'string') { 53 | const res = 54 | typeof transform === 'string' 55 | ? this.execute(transform) 56 | : transform 57 | return [res, res, res] as [number, number, number] 58 | } else if (Array.isArray(transform)) { 59 | return transform.map((t) => 60 | typeof t === 'string' ? this.execute(t) : t 61 | ) as [number, number, number] 62 | } else if (transform !== undefined) { 63 | const timestamps = Object.entries(transform) 64 | .map( 65 | ([time, transform]) => 66 | [Number(time), transform] as [number, TTimestampEntry] 67 | ) 68 | .sort(([a], [b]) => a - b) 69 | 70 | for (let i = timestamps.length - 1; i >= 0; i--) { 71 | let [time, transform] = timestamps[i] 72 | 73 | if (time > this.currentTime) { 74 | continue 75 | } else if (time === this.currentTime) { 76 | if (Array.isArray(transform)) { 77 | return transform.map((t) => 78 | typeof t === 'string' ? this.execute(t) : t 79 | ) as [number, number, number] 80 | } else { 81 | // TODO 82 | throw new Error('Format not supported yet') 83 | } 84 | } else { 85 | // Interpolation between two timestamps 86 | let [nextTime, nextTransform] = 87 | timestamps[ 88 | MathUtils.euclideanModulo(i + 1, timestamps.length) 89 | ] 90 | let timeDelta = nextTime - time 91 | 92 | if ( 93 | Array.isArray(transform) && 94 | Array.isArray(nextTransform) 95 | ) { 96 | transform = <[number, number, number]>( 97 | transform.map((t) => 98 | typeof t === 'string' ? this.execute(t) : t 99 | ) 100 | ) 101 | nextTransform = <[number, number, number]>( 102 | nextTransform.map((t) => 103 | typeof t === 'string' ? this.execute(t) : t 104 | ) 105 | ) 106 | 107 | return transform.map( 108 | (n, i) => 109 | n + 110 | (((<[number, number, number]>nextTransform)[i] - 111 | n) / 112 | timeDelta) * 113 | (this.currentTime - time) 114 | ) as [number, number, number] 115 | } else { 116 | // TODO 117 | throw new Error('Format not supported yet') 118 | } 119 | } 120 | } 121 | return [0, 0, 0] 122 | } 123 | } 124 | 125 | tick() { 126 | this.soundEffects.tick() 127 | this.particleEffects.tick() 128 | 129 | const boneMap = this.animator.getModel().getBoneMap() 130 | 131 | for (let boneName in this.animationData.bones) { 132 | const bone = boneMap.get(boneName) 133 | if (!bone) continue 134 | const { position, rotation, scale } = 135 | this.animationData.bones[boneName] 136 | 137 | const [positionMod, rotationMod, scaleMod] = [ 138 | position, 139 | rotation, 140 | scale, 141 | ].map((mod) => this.parseBoneModifier(mod)) 142 | 143 | if (positionMod) { 144 | const currentPosition = bone.position.toArray() 145 | bone.position.set( 146 | ...(positionMod.map( 147 | (val, i) => 148 | (i === 0 ? -1 : 1) * val + currentPosition[i] 149 | ) as [number, number, number]) 150 | ) 151 | } 152 | 153 | if (rotationMod) { 154 | const currentRotation = bone.rotation.toArray() 155 | bone.rotation.set( 156 | ...(rotationMod 157 | .map((n) => MathUtils.degToRad(n)) 158 | .map( 159 | (val, i) => 160 | (currentRotation[i] as number) + 161 | (i === 2 ? val : -val) 162 | ) as [number, number, number]) 163 | ) 164 | } 165 | 166 | if (scaleMod) 167 | bone.scale.set(...(scaleMod as [number, number, number])) 168 | } 169 | 170 | if (this.currentTime > this.animationData.animation_length) { 171 | if (this.animationData.loop) this.loop() 172 | else this.pause() 173 | } 174 | 175 | // Update lastFrameTimestamp for query.delta_time 176 | this.lastFrameTimestamp = Date.now() 177 | } 178 | 179 | play() { 180 | this.isRunning = true 181 | this.startTimestamp = Date.now() 182 | } 183 | pause() { 184 | this.isRunning = false 185 | } 186 | loop() { 187 | this.startTimestamp = Date.now() 188 | this.soundEffects.reset() 189 | this.particleEffects.reset() 190 | } 191 | dispose() { 192 | this.particleEffects.dispose() 193 | } 194 | 195 | get currentTime() { 196 | return (Date.now() - this.startTimestamp) / 1000 197 | } 198 | get roundedCurrentTime() { 199 | return Math.round((Date.now() - this.startTimestamp) / 50) / 20 200 | } 201 | get shouldTick() { 202 | return this.isRunning 203 | } 204 | } 205 | -------------------------------------------------------------------------------- /lib/Animations/AnimationEffect.ts: -------------------------------------------------------------------------------- 1 | import { ITimestamp } from '../Schema/Animation' 2 | import { Animation } from './Animation' 3 | 4 | export abstract class AnimationEffect { 5 | abstract tick(): void 6 | 7 | protected currentEffectIndex = 0 8 | protected effects: (readonly [number, T[]])[] 9 | protected tickingEffects: { tick: () => void }[] = [] 10 | 11 | constructor(protected animation: Animation, timestampObj: ITimestamp) { 12 | this.effects = Object.entries(timestampObj) 13 | .map( 14 | ([time, timestampEntry]) => 15 | [ 16 | Number(time), 17 | Array.isArray(timestampEntry) 18 | ? timestampEntry 19 | : [timestampEntry], 20 | ] 21 | ) 22 | .sort(([a], [b]) => a - b) 23 | } 24 | 25 | getCurrentEffects() { 26 | // We have no effects to play anymore 27 | if (this.currentEffectIndex >= this.effects.length) return 28 | 29 | const currentEffect = this.effects[this.currentEffectIndex] 30 | // Time of currentEffect not reached yet, early return 31 | if (currentEffect[0] > this.animation.roundedCurrentTime) return 32 | 33 | this.currentEffectIndex++ 34 | return currentEffect[1] 35 | } 36 | 37 | reset() { 38 | this.currentEffectIndex = 0 39 | } 40 | } 41 | -------------------------------------------------------------------------------- /lib/Animations/Animator.ts: -------------------------------------------------------------------------------- 1 | import { ISingleAnimation } from '../Schema/Animation' 2 | import { Model } from '../Model' 3 | import { Animation } from './Animation' 4 | import Wintersky from 'wintersky' 5 | 6 | export class Animator { 7 | public winterskyScene?: Wintersky.Scene 8 | 9 | protected animations = new Map() 10 | protected particleEmitters = new Map() 11 | 12 | constructor(protected model: Model) {} 13 | 14 | setupDefaultBonePoses() { 15 | //Save default rotation & position 16 | for (let bone of this.model.getBoneMap().values()) { 17 | bone.userData.defaultRotation = bone.rotation.toArray() 18 | bone.userData.defaultPosition = bone.position.toArray() 19 | } 20 | } 21 | 22 | dispose() { 23 | this.disposeAnimations() 24 | // Remove custom animation data from bones 25 | for (let bone of this.model.getBoneMap().values()) { 26 | delete bone.userData.defaultRotation 27 | delete bone.userData.defaultPosition 28 | } 29 | } 30 | disposeAnimations() { 31 | this.animations.forEach((anim) => anim.dispose()) 32 | } 33 | 34 | setupWintersky(winterskyScene: Wintersky.Scene) { 35 | this.winterskyScene = winterskyScene 36 | } 37 | addAnimation(id: string, animationData: ISingleAnimation) { 38 | this.animations.set(id, new Animation(this, animationData)) 39 | } 40 | addEmitter(shortName: string, emitterConfig: Wintersky.Config) { 41 | this.particleEmitters.set(shortName, emitterConfig) 42 | } 43 | getEmitter(shortName: string) { 44 | return this.particleEmitters.get(shortName) 45 | } 46 | 47 | play(id: string) { 48 | const animation = this.animations.get(id) 49 | if (!animation) throw new Error(`Unknown animation: "${id}"`) 50 | animation.play() 51 | } 52 | pause(id: string) { 53 | const animation = this.animations.get(id) 54 | if (!animation) throw new Error(`Unknown animation: "${id}"`) 55 | animation.pause() 56 | } 57 | pauseAll() { 58 | for (const animation of this.animations.values()) { 59 | animation.pause() 60 | } 61 | } 62 | 63 | tick() { 64 | // Reset currentTick data 65 | for (let bone of this.model.getBoneMap().values()) { 66 | bone.rotation.set( 67 | ...(bone.userData.defaultRotation as [number, number, number]) 68 | ) 69 | bone.position.set( 70 | ...(bone.userData.defaultPosition as [number, number, number]) 71 | ) 72 | } 73 | 74 | this.animations.forEach( 75 | (animation) => animation.shouldTick && animation.tick() 76 | ) 77 | } 78 | get shouldTick() { 79 | return [...this.animations.values()].some( 80 | (animation) => animation.shouldTick 81 | ) 82 | } 83 | 84 | getModel() { 85 | return this.model 86 | } 87 | } 88 | -------------------------------------------------------------------------------- /lib/Animations/ParticleEffect.ts: -------------------------------------------------------------------------------- 1 | import { IParticleEffect } from '../Schema/Animation' 2 | import { AnimationEffect } from './AnimationEffect' 3 | import Wintersky from 'wintersky' 4 | import { IDisposable } from '../Disposable' 5 | 6 | export class ParticleEffect extends AnimationEffect { 7 | protected disposables: IDisposable[] = [] 8 | 9 | tick() { 10 | this.tickingEffects.forEach((effect) => effect.tick()) 11 | 12 | const currentEffects = super.getCurrentEffects() ?? [] 13 | for (const { locator, effect, pre_effect_script } of currentEffects) { 14 | if (!effect) return 15 | 16 | const animator = this.animation.getAnimator() 17 | const model = animator.getModel() 18 | const emitterConfig = animator.getEmitter(effect) 19 | if (!emitterConfig || !animator.winterskyScene) return 20 | 21 | const locatorGroup = locator ? model.getLocator(locator) : undefined 22 | 23 | const emitter = new Wintersky.Emitter( 24 | animator.winterskyScene, 25 | emitterConfig, 26 | { 27 | parent_mode: locatorGroup ? 'locator' : 'entity', 28 | loop_mode: 'once', 29 | } 30 | ) 31 | 32 | if (locatorGroup) { 33 | locatorGroup.add(emitter.local_space) 34 | emitter.local_space.parent = locatorGroup 35 | } 36 | 37 | const tickable = { 38 | tick: () => { 39 | emitter.tick() 40 | 41 | // @ts-ignore 42 | if (!emitter.enabled) { 43 | emitter.delete() 44 | 45 | this.tickingEffects = this.tickingEffects.filter( 46 | (current) => current !== tickable 47 | ) 48 | } 49 | }, 50 | } 51 | this.tickingEffects.push(tickable) 52 | this.disposables.push({ 53 | dispose: () => { 54 | emitter.delete() 55 | this.tickingEffects = this.tickingEffects.filter( 56 | (current) => current !== tickable 57 | ) 58 | }, 59 | }) 60 | 61 | emitter.start() 62 | emitter.tick() 63 | } 64 | } 65 | 66 | dispose() { 67 | this.disposables.forEach((disposable) => disposable.dispose()) 68 | this.disposables = [] 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /lib/Animations/SoundEffect.ts: -------------------------------------------------------------------------------- 1 | import { ISoundEffect } from '../Schema/Animation' 2 | import { AnimationEffect } from './AnimationEffect' 3 | 4 | export class SoundEffect extends AnimationEffect { 5 | tick() { 6 | const timestampEntry = super.getCurrentEffects() ?? [] 7 | if (timestampEntry.length > 0) 8 | console.log( 9 | `Playing sound effects: "${timestampEntry 10 | .map((entry) => entry.effect) 11 | .join(', ')}"` 12 | ) 13 | } 14 | } 15 | -------------------------------------------------------------------------------- /lib/Cube.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BufferAttribute, 3 | BufferGeometry, 4 | Group, 5 | Material, 6 | MathUtils, 7 | Mesh, 8 | } from 'three' 9 | import { CubeFaces } from './CubeFaces' 10 | 11 | export interface IUVObj { 12 | north: IUVConfig 13 | south: IUVConfig 14 | east: IUVConfig 15 | west: IUVConfig 16 | up: IUVConfig 17 | down: IUVConfig 18 | } 19 | 20 | export interface IUVConfig { 21 | uv: [number, number] 22 | uv_size: [number, number] 23 | } 24 | 25 | export interface ICubeConfig { 26 | width: number 27 | height: number 28 | depth: number 29 | startUV?: [number, number] | IUVObj 30 | textureSize: [number, number] 31 | textureDiscrepancyFactor: [number, number] 32 | mirror: boolean 33 | material: Material 34 | origin: [number, number, number] 35 | pivot?: [number, number, number] 36 | rotation?: [number, number, number] 37 | inflate?: number 38 | } 39 | const ReduceUvConst = 0.03 40 | 41 | export class Cube { 42 | protected positions: number[] = [] 43 | protected indices: number[] = [] 44 | protected normals: number[] = [] 45 | protected uvs: number[] = [] 46 | protected geometry = new BufferGeometry() 47 | protected group = new Group() 48 | 49 | constructor(cubeConfig: ICubeConfig) { 50 | const { 51 | textureSize: [tW, tH], 52 | textureDiscrepancyFactor: [ 53 | textureDiscrepancyW, 54 | textureDiscrepancyH, 55 | ], 56 | mirror, 57 | width, 58 | height, 59 | depth, 60 | } = cubeConfig 61 | const [realTextureW, realTextureH] = [ 62 | tW * textureDiscrepancyW, 63 | tH * textureDiscrepancyH, 64 | ] 65 | 66 | // The base UV provided by the model file for this cube 67 | const startUV = cubeConfig.startUV ?? [0, 0] 68 | const usesUVObj = !Array.isArray(startUV) 69 | let uvX: number = 0, 70 | uvY: number = 0 71 | if (!usesUVObj) [uvX, uvY] = startUV as [number, number] 72 | 73 | for (let { 74 | name, 75 | dir, 76 | corners, 77 | baseUV: [baseUVX, baseUVY], 78 | } of CubeFaces) { 79 | const ndx = this.positions.length / 3 80 | let uvSizeX, uvSizeY 81 | if (usesUVObj) { 82 | if ((startUV as IUVObj)[name] === undefined) continue 83 | ;[uvX, uvY] = (startUV as IUVObj)[name]?.uv || [] 84 | ;[uvSizeX, uvSizeY] = (startUV as IUVObj)[name]?.uv_size || [] 85 | 86 | uvSizeX *= textureDiscrepancyW 87 | uvSizeY *= textureDiscrepancyH 88 | uvX *= textureDiscrepancyW 89 | uvY *= textureDiscrepancyH 90 | 91 | baseUVX = 0 92 | baseUVY = 0 93 | } 94 | 95 | for (const { 96 | pos: [oX, oY, oZ], 97 | uv, 98 | } of corners) { 99 | this.positions.push( 100 | (mirror ? -oX : oX) * width, 101 | oY * height, 102 | oZ * depth 103 | ) 104 | this.normals.push(...dir) 105 | 106 | this.uvs.push( 107 | //Base offset of the current cube 108 | (uvX + 109 | //Horizontal offset for the current face 110 | (Number(baseUVX > 0) + Number(baseUVX > 2)) * 111 | Math.floor(uvSizeX ?? depth) + 112 | Number(baseUVX > 1) * Math.floor(uvSizeX ?? width) + 113 | //Face corner specific offsets 114 | uv[0] * 115 | (name === 'west' || name === 'east' 116 | ? Math.floor(uvSizeX ?? depth) 117 | : Math.floor(uvSizeX ?? width)) + 118 | (uv[0] === 0 ? ReduceUvConst : -ReduceUvConst)) / 119 | (realTextureW / (!usesUVObj ? textureDiscrepancyW : 1)), 120 | //Align uv to top left corner 121 | 1 - 122 | //Base offset of the current cube 123 | (uvY + 124 | //Vertical offset for the current face 125 | baseUVY * Math.floor(uvSizeY ?? depth) + 126 | (name === 'up' || name === 'down' 127 | ? Math.floor(uvSizeY ?? depth) 128 | : Math.floor(uvSizeY ?? height)) - 129 | //Face corner specific offsets 130 | uv[1] * 131 | (name === 'up' || name === 'down' 132 | ? Math.floor(uvSizeY ?? depth) 133 | : Math.floor(uvSizeY ?? height)) + 134 | (uv[1] === 0 ? -ReduceUvConst : ReduceUvConst)) / 135 | (realTextureH / 136 | (!usesUVObj ? textureDiscrepancyH : 1)) 137 | ) 138 | } 139 | 140 | this.indices.push(ndx, ndx + 1, ndx + 2, ndx + 2, ndx + 1, ndx + 3) 141 | } 142 | 143 | this.createGeometry() 144 | this.createMesh(cubeConfig) 145 | } 146 | 147 | protected createGeometry() { 148 | this.geometry.setAttribute( 149 | 'position', 150 | new BufferAttribute(new Float32Array(this.positions), 3) 151 | ) 152 | this.geometry.setAttribute( 153 | 'normal', 154 | new BufferAttribute(new Float32Array(this.normals), 3) 155 | ) 156 | this.geometry.setAttribute( 157 | 'uv', 158 | new BufferAttribute(new Float32Array(this.uvs), 2) 159 | ) 160 | this.geometry.setIndex(this.indices) 161 | } 162 | 163 | protected createMesh({ 164 | material, 165 | width, 166 | height, 167 | depth, 168 | pivot, 169 | rotation, 170 | origin, 171 | inflate = 0, 172 | }: ICubeConfig) { 173 | const calculatedWidth = inflate * 2 + width 174 | const mesh = new Mesh(this.geometry, material) 175 | 176 | this.group.rotation.order = 'ZYX' 177 | 178 | if (pivot === undefined) 179 | //Rotate around center of cube without pivot 180 | pivot = [calculatedWidth / 2, height / 2, depth / 2] 181 | 182 | this.group.add(mesh) 183 | 184 | if (rotation) { 185 | this.group.position.set(-pivot[0], pivot[1], pivot[2]) 186 | mesh.position.set( 187 | -origin[0] - calculatedWidth / 2 + pivot[0] + inflate, 188 | origin[1] - pivot[1] - inflate, 189 | origin[2] - pivot[2] - inflate 190 | ) 191 | 192 | const [rX, rY, rZ] = rotation 193 | this.group.rotation.set( 194 | MathUtils.degToRad(-rX), 195 | MathUtils.degToRad(-rY), 196 | MathUtils.degToRad(rZ) 197 | ) 198 | } else { 199 | this.group.position.set( 200 | -origin[0] - calculatedWidth / 2 + inflate, 201 | origin[1] - inflate, 202 | origin[2] - inflate 203 | ) 204 | } 205 | 206 | if (inflate) 207 | this.group.scale.set( 208 | width !== 0 ? 1 + inflate / (width / 2) : 1, 209 | height !== 0 ? 1 + inflate / (height / 2) : 1, 210 | depth !== 0 ? 1 + inflate / (depth / 2) : 1 211 | ) 212 | } 213 | 214 | getGroup() { 215 | return this.group 216 | } 217 | } 218 | -------------------------------------------------------------------------------- /lib/CubeFaces.ts: -------------------------------------------------------------------------------- 1 | /** 2 | * Store data to create a Minecraft model cube 3 | */ 4 | export const CubeFaces = [ 5 | // Right 6 | { 7 | /** 8 | * Position of the texture for this specific cube 9 | * 10 | * baseUV[0] <-> X | baseUV[1] <-> Y 11 | * 12 | * How baseUV maps to a Minecraft cube texture: 13 | * @example 14 | * | X | 0 | 1 | 2 | 3 | 15 | * ----------------------- 16 | * Y | | | | | | 17 | * ----------------------- 18 | * 0 | | | X | X | | 19 | * ----------------------- 20 | * 1 | | X | X | X | X | 21 | */ 22 | name: 'west', 23 | baseUV: [2, 1], 24 | dir: [-1, 0, 0], 25 | corners: [ 26 | { pos: [-0.5, 1, 0], uv: [0, 1] }, 27 | { pos: [-0.5, 0, 0], uv: [0, 0] }, 28 | { pos: [-0.5, 1, 1], uv: [1, 1] }, 29 | { pos: [-0.5, 0, 1], uv: [1, 0] }, 30 | ], 31 | }, 32 | 33 | // Left 34 | { 35 | name: 'east', 36 | baseUV: [0, 1], 37 | dir: [1, 0, 0], 38 | corners: [ 39 | { pos: [0.5, 1, 1], uv: [0, 1] }, 40 | { pos: [0.5, 0, 1], uv: [0, 0] }, 41 | { pos: [0.5, 1, 0], uv: [1, 1] }, 42 | { pos: [0.5, 0, 0], uv: [1, 0] }, 43 | ], 44 | }, 45 | 46 | // Bottom 47 | { 48 | name: 'down', 49 | baseUV: [2, 0], 50 | dir: [0, -1, 0], 51 | corners: [ 52 | { pos: [0.5, 0, 1], uv: [0, 1] }, 53 | { pos: [-0.5, 0, 1], uv: [1, 1] }, 54 | { pos: [0.5, 0, 0], uv: [0, 0] }, 55 | { pos: [-0.5, 0, 0], uv: [1, 0] }, 56 | ], 57 | }, 58 | 59 | // Top 60 | { 61 | name: 'up', 62 | baseUV: [1, 0], 63 | dir: [0, 1, 0], 64 | corners: [ 65 | { pos: [-0.5, 1, 1], uv: [1, 1] }, 66 | { pos: [0.5, 1, 1], uv: [0, 1] }, 67 | { pos: [-0.5, 1, 0], uv: [1, 0] }, 68 | { pos: [0.5, 1, 0], uv: [0, 0] }, 69 | ], 70 | }, 71 | 72 | //Front 73 | { 74 | name: 'north', 75 | baseUV: [1, 1], 76 | dir: [0, 0, -1], 77 | corners: [ 78 | { pos: [-0.5, 0, 0], uv: [1, 0] }, 79 | { pos: [0.5, 0, 0], uv: [0, 0] }, 80 | { pos: [-0.5, 1, 0], uv: [1, 1] }, 81 | { pos: [0.5, 1, 0], uv: [0, 1] }, 82 | ], 83 | }, 84 | 85 | //Back 86 | { 87 | name: 'south', 88 | baseUV: [3, 1], 89 | dir: [0, 0, 1], 90 | corners: [ 91 | { pos: [-0.5, 0, 1], uv: [0, 0] }, 92 | { pos: [0.5, 0, 1], uv: [1, 0] }, 93 | { pos: [-0.5, 1, 1], uv: [0, 1] }, 94 | { pos: [0.5, 1, 1], uv: [1, 1] }, 95 | ], 96 | }, 97 | ] as const 98 | -------------------------------------------------------------------------------- /lib/Disposable.ts: -------------------------------------------------------------------------------- 1 | export interface IDisposable { 2 | dispose: () => void 3 | } 4 | -------------------------------------------------------------------------------- /lib/Model.ts: -------------------------------------------------------------------------------- 1 | import { 2 | BoxGeometry, 3 | DoubleSide, 4 | EdgesGeometry, 5 | Group, 6 | LineBasicMaterial, 7 | LineSegments, 8 | MathUtils, 9 | MeshLambertMaterial, 10 | NearestFilter, 11 | Texture, 12 | TextureLoader, 13 | } from 'three' 14 | import { Animator } from './Animations/Animator' 15 | import { Cube } from './Cube' 16 | import { PolyMesh } from './PolyMesh' 17 | import { IGeoSchema } from './Schema/Model' 18 | 19 | export class Model { 20 | protected model: Group 21 | protected boneMap = new Map() 22 | protected locators = new Map() 23 | public readonly animator = new Animator(this) 24 | 25 | constructor( 26 | protected modelData: IGeoSchema, 27 | protected texturePath: string 28 | ) { 29 | const id = modelData?.description?.identifier ?? 'geometry.unknown' 30 | 31 | this.model = new Group() 32 | this.model.name = id 33 | } 34 | 35 | async create() { 36 | const modelData = this.modelData 37 | const texture = await this.loadTexture(this.texturePath) 38 | 39 | const textureSize: [number, number] = [ 40 | modelData?.description?.texture_width ?? texture.image.width, 41 | modelData?.description?.texture_height ?? texture.image.height, 42 | ] 43 | const textureDiscrepancyFactor: [number, number] = [ 44 | texture.image.width / textureSize[0], 45 | texture.image.height / textureSize[1], 46 | ] 47 | const boneParentMap = new Map() 48 | 49 | texture.magFilter = NearestFilter 50 | texture.minFilter = NearestFilter 51 | const modelMaterial = new MeshLambertMaterial({ 52 | side: DoubleSide, 53 | alphaTest: 0.2, 54 | transparent: true, 55 | map: texture, 56 | }) 57 | 58 | modelData?.bones?.forEach((boneData) => { 59 | const currBone = new Group() 60 | currBone.name = boneData.name ?? 'unknown' 61 | 62 | if (boneData.poly_mesh) { 63 | const polyMeshGroup = new PolyMesh({ 64 | ...boneData.poly_mesh, 65 | textureSize, 66 | material: modelMaterial, 67 | mirror: boneData.mirror ?? false, 68 | origin: [0, 0, 0], 69 | inflate: boneData.inflate, 70 | rotation: [0, 0, 0], 71 | pivot: boneData.pivot, 72 | }).getGroup() 73 | polyMeshGroup.name = `#bone.${boneData.name}#polyMesh` 74 | 75 | currBone.add(polyMeshGroup) 76 | } 77 | 78 | boneData.cubes?.forEach((cubeData, i) => { 79 | const group = new Cube({ 80 | width: cubeData.size?.[0] ?? 0, 81 | height: cubeData.size?.[1] ?? 0, 82 | depth: cubeData.size?.[2] ?? 0, 83 | startUV: cubeData.uv, 84 | textureSize, 85 | textureDiscrepancyFactor, 86 | material: modelMaterial, 87 | mirror: 88 | cubeData.mirror === undefined && 89 | cubeData.rotation === undefined //Only cubes without rotation inherit mirror arg from bone 90 | ? boneData.mirror ?? false 91 | : cubeData.mirror ?? false, 92 | origin: cubeData.origin ?? [0, 0, 0], 93 | inflate: cubeData.inflate ?? boneData.inflate, 94 | rotation: cubeData.rotation, 95 | pivot: cubeData.pivot ?? boneData.pivot, 96 | }).getGroup() 97 | 98 | group.name = `#bone.${boneData.name}#cube.${i}` 99 | currBone.add(group) 100 | }) 101 | 102 | const pivotGroup = new Group() 103 | pivotGroup.rotation.order = 'ZYX' 104 | if (boneData.pivot) { 105 | const [pX, pY, pZ] = boneData.pivot 106 | pivotGroup.position.set(-pX, pY, pZ) 107 | currBone.position.set(pX, -pY, -pZ) 108 | } else { 109 | pivotGroup.position.set(0, 0, 0) 110 | } 111 | 112 | pivotGroup.add(currBone) 113 | pivotGroup.name = `#pivot.${boneData.name}` 114 | 115 | // TODO: Seems to be a lot of work for rendering a few legacy entities 116 | // if (boneData.bind_pose_rotation) { 117 | // 118 | // } 119 | 120 | if (boneData.rotation) { 121 | const [rX, rY, rZ] = boneData.rotation 122 | 123 | pivotGroup.rotation.set( 124 | MathUtils.degToRad(-rX), 125 | MathUtils.degToRad(-rY), 126 | MathUtils.degToRad(rZ) 127 | ) 128 | } 129 | 130 | const locators = boneData.locators ?? {} 131 | for (const locatorName in locators) { 132 | const locator = new Group() 133 | locator.name = `locator#${locatorName}` 134 | const locData = locators[locatorName] 135 | 136 | if (Array.isArray(locData)) { 137 | locator.position.set(...locData) 138 | } else if (typeof locData === 'object') { 139 | locator.position.set(...(locData.offset ?? [0, 0, 0])) 140 | locator.rotation.set(...(locData.rotation ?? [0, 0, 0])) 141 | } 142 | 143 | this.locators.set(locatorName, locator) 144 | pivotGroup.add(locator) 145 | } 146 | 147 | if (!boneData.parent) this.model.add(pivotGroup) 148 | if (boneData.name) { 149 | boneParentMap.set(boneData.name, [boneData.parent, pivotGroup]) 150 | this.boneMap.set(boneData.name, pivotGroup) 151 | } 152 | }) 153 | 154 | //Set bone parents 155 | for (let [_, [parent, bone]] of boneParentMap) 156 | if (parent) { 157 | const parentGroup = boneParentMap.get(parent)?.[1] 158 | if (parentGroup && parentGroup.name.startsWith('#pivot.')) 159 | parentGroup.children[0].add(bone) 160 | else if (parentGroup) parentGroup.add(bone) 161 | } 162 | 163 | this.animator.setupDefaultBonePoses() 164 | } 165 | 166 | getGroup() { 167 | return this.model 168 | } 169 | 170 | getBoneMap() { 171 | return this.boneMap 172 | } 173 | getLocator(name: string) { 174 | return this.locators.get(name) 175 | } 176 | 177 | tick() { 178 | this.animator.tick() 179 | } 180 | get shouldTick() { 181 | return this.animator.shouldTick 182 | } 183 | 184 | createOutlineBox( 185 | color: `#${string}`, 186 | position: { x: number; y: number; z: number }, 187 | size: { x: number; y: number; z: number } 188 | ) { 189 | const outlineMaterial = new LineBasicMaterial({ 190 | side: DoubleSide, 191 | color: color, 192 | linewidth: 20, 193 | }) 194 | const cube = new BoxGeometry(size.x, size.y, size.z) 195 | const edges = new EdgesGeometry(cube) 196 | 197 | const mesh = new LineSegments(edges, outlineMaterial) 198 | mesh.position.set(position.x, position.y + size.y / 2, position.z) 199 | mesh.name = 'helperBox' 200 | 201 | this.model.add(mesh) 202 | 203 | return { 204 | dispose: () => { 205 | this.model.remove(mesh) 206 | }, 207 | } 208 | } 209 | 210 | hideBone(name: string) { 211 | const bone = this.boneMap.get(name) 212 | if (bone) bone.visible = false 213 | } 214 | showBone(name: string) { 215 | const bone = this.boneMap.get(name) 216 | if (bone) bone.visible = true 217 | } 218 | get bones() { 219 | return [...this.boneMap.keys()] 220 | } 221 | 222 | dispose() { 223 | this.animator.dispose() 224 | } 225 | 226 | protected loadTexture(url: string) { 227 | return new Promise((resolve, reject) => { 228 | const loader = new TextureLoader() 229 | loader.load(url, (texture) => { 230 | resolve(texture) 231 | }) 232 | }) 233 | } 234 | } 235 | -------------------------------------------------------------------------------- /lib/PolyMesh.ts: -------------------------------------------------------------------------------- 1 | import { BufferAttribute, BufferGeometry, Group, Material, Mesh } from 'three' 2 | import { IPolyMesh } from './Schema/Model' 3 | 4 | export interface IUVObj { 5 | north: IUVConfig 6 | south: IUVConfig 7 | east: IUVConfig 8 | west: IUVConfig 9 | up: IUVConfig 10 | down: IUVConfig 11 | } 12 | 13 | export interface IUVConfig { 14 | uv: [number, number] 15 | uv_size: [number, number] 16 | } 17 | 18 | export interface IPolyMeshConfig extends IPolyMesh { 19 | textureSize: [number, number] 20 | mirror: boolean 21 | material: Material 22 | origin: [number, number, number] 23 | pivot?: [number, number, number] 24 | rotation?: [number, number, number] 25 | inflate?: number 26 | } 27 | 28 | export class PolyMesh { 29 | protected positions: number[] = [] 30 | protected indices: number[] = [] 31 | protected normals: number[] = [] 32 | protected uvs: number[] = [] 33 | protected geometry = new BufferGeometry() 34 | protected group = new Group() 35 | 36 | constructor(polyMeshConfig: IPolyMeshConfig) { 37 | if (!Array.isArray(polyMeshConfig.polys)) 38 | throw new Error('Format not supported yet!') 39 | if (!polyMeshConfig.normalized_uvs) 40 | polyMeshConfig.uvs = polyMeshConfig?.uvs?.map(([uvX, uvY]) => [ 41 | uvX / polyMeshConfig.textureSize[0], 42 | uvY / polyMeshConfig.textureSize[1], 43 | ]) 44 | 45 | let i = 0 46 | for (const face of polyMeshConfig.polys) { 47 | for (const [vertexIndex, normalIndex, uvIndex] of face) { 48 | this.positions.push( 49 | ...(polyMeshConfig?.positions?.[vertexIndex] ?? []) 50 | ) 51 | this.normals.push( 52 | ...(polyMeshConfig?.normals?.[normalIndex] ?? []) 53 | ) 54 | this.uvs.push(...(polyMeshConfig?.uvs?.[uvIndex] ?? [])) 55 | } 56 | 57 | if (face.length === 3) { 58 | this.indices.push(i, i + 1, i + 2) 59 | } else { 60 | this.indices.push(i + 2, i + 1, i, i + 2, i, i + 3) 61 | } 62 | 63 | i += face.length 64 | } 65 | 66 | this.createGeometry() 67 | this.createMesh(polyMeshConfig) 68 | } 69 | 70 | protected createGeometry() { 71 | this.geometry.setAttribute( 72 | 'position', 73 | new BufferAttribute(new Float32Array(this.positions), 3) 74 | ) 75 | this.geometry.setAttribute( 76 | 'normal', 77 | new BufferAttribute(new Float32Array(this.normals), 3) 78 | ) 79 | this.geometry.setAttribute( 80 | 'uv', 81 | new BufferAttribute(new Float32Array(this.uvs), 2) 82 | ) 83 | this.geometry.setIndex(this.indices) 84 | } 85 | 86 | protected createMesh({ material }: IPolyMeshConfig) { 87 | const mesh = new Mesh(this.geometry, material) 88 | this.group.add(mesh) 89 | } 90 | 91 | getGroup() { 92 | return this.group 93 | } 94 | } 95 | -------------------------------------------------------------------------------- /lib/Schema/Animation.ts: -------------------------------------------------------------------------------- 1 | export interface IAnimations { 2 | format_version: '1.8.0' 3 | animations: { 4 | [animationID: string]: Partial 5 | } 6 | } 7 | 8 | export interface ISingleAnimation { 9 | loop: boolean 10 | animation_length: number 11 | anim_time_update: string //MoLang; default: "query.anim_time + query.delta_time" 12 | blend_weight: string //MoLang 13 | override_previous_animation: true 14 | bones: { 15 | [boneName: string]: IBoneAnim 16 | } 17 | sound_effects: ITimestamp 18 | particle_effects: ITimestamp 19 | } 20 | 21 | export interface ITimestamp { 22 | [timeStamp: string]: T | T[] 23 | } 24 | 25 | export interface ISoundEffect { 26 | effect?: string 27 | pre_effect_script?: string 28 | } 29 | 30 | export interface IParticleEffect { 31 | effect?: string 32 | locator?: string 33 | pre_effect_script?: string 34 | } 35 | 36 | export interface IBoneAnim { 37 | position: TBoneModifier 38 | rotation: TBoneModifier 39 | scale: TBoneModifier 40 | } 41 | 42 | export type TBoneModifier = 43 | | string 44 | | [string, string, string] 45 | | ITimestamp 46 | export type TTimestampEntry = 47 | | [number, number, number] 48 | | { 49 | lerp_mode: 'linear' | 'catmullrom' 50 | pre: [string, string, string] 51 | post: [string, string, string] 52 | } 53 | -------------------------------------------------------------------------------- /lib/Schema/Model.ts: -------------------------------------------------------------------------------- 1 | export interface IGeoSchema { 2 | description?: IGeoDescriptionSchema 3 | bones?: IBoneSchema[] 4 | } 5 | 6 | export interface IGeoDescriptionSchema { 7 | identifier?: string 8 | texture_width?: number 9 | texture_height?: number 10 | } 11 | 12 | export interface IBoneSchema { 13 | name?: string 14 | parent?: string 15 | inflate?: number 16 | pivot?: [number, number, number] 17 | rotation?: [number, number, number] 18 | bind_pose_rotation?: [number, number, number] 19 | mirror?: boolean 20 | cubes?: ICubeSchema[] 21 | locators?: Record 22 | poly_mesh?: IPolyMesh 23 | } 24 | 25 | export type TVector = [number, number, number] 26 | export interface IPolyMesh { 27 | normalized_uvs?: boolean 28 | positions?: TVector[] 29 | normals?: TVector[] 30 | uvs?: [number, number][] 31 | polys?: 32 | | [TVector, TVector, TVector][] 33 | | [TVector, TVector, TVector, TVector][] 34 | | 'tri_list' 35 | | 'quad_list' 36 | } 37 | export interface ICubeSchema { 38 | origin?: [number, number, number] 39 | size?: [number, number, number] 40 | uv?: [number, number] | IUVObj 41 | rotation?: [number, number, number] 42 | pivot?: [number, number, number] 43 | inflate?: number 44 | mirror?: boolean 45 | } 46 | export interface IUVObj { 47 | north: IUVConfig 48 | south: IUVConfig 49 | east: IUVConfig 50 | west: IUVConfig 51 | up: IUVConfig 52 | down: IUVConfig 53 | } 54 | export interface IUVConfig { 55 | uv: [number, number] 56 | uv_size: [number, number] 57 | } 58 | -------------------------------------------------------------------------------- /lib/main.ts: -------------------------------------------------------------------------------- 1 | import { 2 | AmbientLight, 3 | AxesHelper, 4 | Box3, 5 | BoxHelper, 6 | Color, 7 | GridHelper, 8 | PerspectiveCamera, 9 | Scene, 10 | WebGLRenderer, 11 | Sphere, 12 | } from 'three' 13 | import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls' 14 | import { Model } from './Model' 15 | import { IGeoSchema } from './Schema/Model' 16 | 17 | export { Model } from './Model' 18 | 19 | export * from './Schema/Model' 20 | export * from './Schema/Animation' 21 | 22 | export interface IOptions { 23 | alpha?: boolean 24 | antialias?: boolean 25 | width?: number 26 | height?: number 27 | } 28 | 29 | export class StandaloneModelViewer { 30 | protected renderer: WebGLRenderer 31 | protected model: Model 32 | public readonly scene: Scene 33 | protected camera: PerspectiveCamera 34 | protected renderingRequested = false 35 | public readonly controls: OrbitControls 36 | public readonly loadedModel: Promise 37 | 38 | constructor( 39 | protected canvasElement: HTMLCanvasElement, 40 | modelData: IGeoSchema, 41 | protected texturePath: string, 42 | protected options: IOptions 43 | ) { 44 | this.renderer = new WebGLRenderer({ 45 | canvas: canvasElement, 46 | alpha: options.alpha ?? false, 47 | antialias: options.antialias ?? false, 48 | }) 49 | this.renderer.setPixelRatio(window.devicePixelRatio) 50 | this.camera = new PerspectiveCamera(70, 2, 0.1, 1000) 51 | this.camera.position.x = -20 52 | this.camera.position.y = 20 53 | this.camera.position.z = -20 54 | 55 | this.camera.updateProjectionMatrix() 56 | this.controls = new OrbitControls(this.camera, canvasElement) 57 | this.scene = new Scene() 58 | this.scene.add(new AmbientLight(0xffffff)) 59 | 60 | if (options.alpha) { 61 | this.scene.background = new Color(0xcaf0f8) 62 | } 63 | 64 | this.model = new Model(modelData, texturePath) 65 | this.scene.add(this.model.getGroup()) 66 | 67 | window.addEventListener('resize', this.onResize.bind(this)) 68 | this.controls.addEventListener('change', () => this.requestRendering()) 69 | 70 | this.onResize() 71 | this.loadedModel = this.loadModel().then(() => this.requestRendering()) 72 | } 73 | 74 | protected async loadModel() { 75 | await this.model.create() 76 | } 77 | 78 | protected get width() { 79 | return this.options.width ?? window.innerWidth 80 | } 81 | protected get height() { 82 | return this.options.height ?? window.innerHeight 83 | } 84 | 85 | protected render(checkShouldTick = true) { 86 | this.controls.update() 87 | this.renderer.render(this.scene, this.camera) 88 | this.renderingRequested = false 89 | 90 | if (checkShouldTick && this.model.shouldTick) { 91 | this.model.tick() 92 | this.model.animator.winterskyScene?.updateFacingRotation( 93 | this.camera 94 | ) 95 | this.requestRendering() 96 | } 97 | } 98 | 99 | requestRendering(immediate = false) { 100 | if (immediate) return this.render(false) 101 | 102 | if (this.renderingRequested) return 103 | 104 | this.renderingRequested = true 105 | requestAnimationFrame(() => this.render()) 106 | } 107 | protected onResize() { 108 | this.renderer.setSize(this.width, this.height, true) 109 | this.camera.aspect = this.width / this.height 110 | this.positionCamera() 111 | this.requestRendering() 112 | } 113 | dispose() { 114 | window.removeEventListener('resize', this.onResize) 115 | this.controls.removeEventListener('change', this.requestRendering) 116 | } 117 | 118 | addHelpers() { 119 | this.scene.add(new AxesHelper(50)) 120 | this.scene.add(new GridHelper(20, 20)) 121 | this.scene.add(new BoxHelper(this.model.getGroup(), 0xffff00)) 122 | 123 | this.requestRendering() 124 | } 125 | getModel() { 126 | return this.model 127 | } 128 | 129 | // From: https://github.com/mrdoob/three.js/issues/6784#issuecomment-315963625 130 | positionCamera(scale = 1.5, rotate = true) { 131 | if (rotate) this.model.getGroup().rotation.set(0, Math.PI, 0) 132 | const boundingSphere = new Box3() 133 | .setFromObject(this.model.getGroup()) 134 | .getBoundingSphere(new Sphere()) 135 | 136 | const objectAngularSize = ((this.camera.fov * Math.PI) / 180) * scale 137 | const distanceToCamera = 138 | boundingSphere.radius / Math.tan(objectAngularSize / 2) 139 | const len = Math.sqrt( 140 | Math.pow(distanceToCamera, 2) + Math.pow(distanceToCamera, 2) 141 | ) 142 | 143 | this.camera.position.set(len, len, len) 144 | this.controls.update() 145 | 146 | this.camera.lookAt(boundingSphere.center) 147 | this.controls.target.set( 148 | boundingSphere.center.x, 149 | boundingSphere.center.y, 150 | boundingSphere.center.z 151 | ) 152 | 153 | this.camera.updateProjectionMatrix() 154 | } 155 | } 156 | -------------------------------------------------------------------------------- /package-lock.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bridge-editor/model-viewer", 3 | "version": "0.8.1", 4 | "lockfileVersion": 2, 5 | "requires": true, 6 | "packages": { 7 | "": { 8 | "name": "@bridge-editor/model-viewer", 9 | "version": "0.8.1", 10 | "license": "MIT", 11 | "dependencies": { 12 | "@bridge-editor/molang": "^2.0.1", 13 | "three": "^0.147.0", 14 | "wintersky": "^1.3.0" 15 | }, 16 | "devDependencies": { 17 | "@types/three": "^0.147.0", 18 | "typescript": "^4.3.5", 19 | "vite": "^4.4.9" 20 | } 21 | }, 22 | "node_modules/@bridge-editor/molang": { 23 | "version": "2.0.2", 24 | "resolved": "https://registry.npmjs.org/@bridge-editor/molang/-/molang-2.0.2.tgz", 25 | "integrity": "sha512-GmATfUFAIoc90/KnkF/4nFI3rwjBWltgpyTD3Kh50Pabq89o3H++NIZa93Fat5bie1zFsiHcG9RbooDvofgc1Q==" 26 | }, 27 | "node_modules/@esbuild/android-arm": { 28 | "version": "0.18.20", 29 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", 30 | "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", 31 | "cpu": [ 32 | "arm" 33 | ], 34 | "dev": true, 35 | "optional": true, 36 | "os": [ 37 | "android" 38 | ], 39 | "engines": { 40 | "node": ">=12" 41 | } 42 | }, 43 | "node_modules/@esbuild/android-arm64": { 44 | "version": "0.18.20", 45 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", 46 | "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", 47 | "cpu": [ 48 | "arm64" 49 | ], 50 | "dev": true, 51 | "optional": true, 52 | "os": [ 53 | "android" 54 | ], 55 | "engines": { 56 | "node": ">=12" 57 | } 58 | }, 59 | "node_modules/@esbuild/android-x64": { 60 | "version": "0.18.20", 61 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", 62 | "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", 63 | "cpu": [ 64 | "x64" 65 | ], 66 | "dev": true, 67 | "optional": true, 68 | "os": [ 69 | "android" 70 | ], 71 | "engines": { 72 | "node": ">=12" 73 | } 74 | }, 75 | "node_modules/@esbuild/darwin-arm64": { 76 | "version": "0.18.20", 77 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", 78 | "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", 79 | "cpu": [ 80 | "arm64" 81 | ], 82 | "dev": true, 83 | "optional": true, 84 | "os": [ 85 | "darwin" 86 | ], 87 | "engines": { 88 | "node": ">=12" 89 | } 90 | }, 91 | "node_modules/@esbuild/darwin-x64": { 92 | "version": "0.18.20", 93 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", 94 | "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", 95 | "cpu": [ 96 | "x64" 97 | ], 98 | "dev": true, 99 | "optional": true, 100 | "os": [ 101 | "darwin" 102 | ], 103 | "engines": { 104 | "node": ">=12" 105 | } 106 | }, 107 | "node_modules/@esbuild/freebsd-arm64": { 108 | "version": "0.18.20", 109 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", 110 | "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", 111 | "cpu": [ 112 | "arm64" 113 | ], 114 | "dev": true, 115 | "optional": true, 116 | "os": [ 117 | "freebsd" 118 | ], 119 | "engines": { 120 | "node": ">=12" 121 | } 122 | }, 123 | "node_modules/@esbuild/freebsd-x64": { 124 | "version": "0.18.20", 125 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", 126 | "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", 127 | "cpu": [ 128 | "x64" 129 | ], 130 | "dev": true, 131 | "optional": true, 132 | "os": [ 133 | "freebsd" 134 | ], 135 | "engines": { 136 | "node": ">=12" 137 | } 138 | }, 139 | "node_modules/@esbuild/linux-arm": { 140 | "version": "0.18.20", 141 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", 142 | "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", 143 | "cpu": [ 144 | "arm" 145 | ], 146 | "dev": true, 147 | "optional": true, 148 | "os": [ 149 | "linux" 150 | ], 151 | "engines": { 152 | "node": ">=12" 153 | } 154 | }, 155 | "node_modules/@esbuild/linux-arm64": { 156 | "version": "0.18.20", 157 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", 158 | "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", 159 | "cpu": [ 160 | "arm64" 161 | ], 162 | "dev": true, 163 | "optional": true, 164 | "os": [ 165 | "linux" 166 | ], 167 | "engines": { 168 | "node": ">=12" 169 | } 170 | }, 171 | "node_modules/@esbuild/linux-ia32": { 172 | "version": "0.18.20", 173 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", 174 | "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", 175 | "cpu": [ 176 | "ia32" 177 | ], 178 | "dev": true, 179 | "optional": true, 180 | "os": [ 181 | "linux" 182 | ], 183 | "engines": { 184 | "node": ">=12" 185 | } 186 | }, 187 | "node_modules/@esbuild/linux-loong64": { 188 | "version": "0.18.20", 189 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", 190 | "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", 191 | "cpu": [ 192 | "loong64" 193 | ], 194 | "dev": true, 195 | "optional": true, 196 | "os": [ 197 | "linux" 198 | ], 199 | "engines": { 200 | "node": ">=12" 201 | } 202 | }, 203 | "node_modules/@esbuild/linux-mips64el": { 204 | "version": "0.18.20", 205 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", 206 | "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", 207 | "cpu": [ 208 | "mips64el" 209 | ], 210 | "dev": true, 211 | "optional": true, 212 | "os": [ 213 | "linux" 214 | ], 215 | "engines": { 216 | "node": ">=12" 217 | } 218 | }, 219 | "node_modules/@esbuild/linux-ppc64": { 220 | "version": "0.18.20", 221 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", 222 | "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", 223 | "cpu": [ 224 | "ppc64" 225 | ], 226 | "dev": true, 227 | "optional": true, 228 | "os": [ 229 | "linux" 230 | ], 231 | "engines": { 232 | "node": ">=12" 233 | } 234 | }, 235 | "node_modules/@esbuild/linux-riscv64": { 236 | "version": "0.18.20", 237 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", 238 | "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", 239 | "cpu": [ 240 | "riscv64" 241 | ], 242 | "dev": true, 243 | "optional": true, 244 | "os": [ 245 | "linux" 246 | ], 247 | "engines": { 248 | "node": ">=12" 249 | } 250 | }, 251 | "node_modules/@esbuild/linux-s390x": { 252 | "version": "0.18.20", 253 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", 254 | "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", 255 | "cpu": [ 256 | "s390x" 257 | ], 258 | "dev": true, 259 | "optional": true, 260 | "os": [ 261 | "linux" 262 | ], 263 | "engines": { 264 | "node": ">=12" 265 | } 266 | }, 267 | "node_modules/@esbuild/linux-x64": { 268 | "version": "0.18.20", 269 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", 270 | "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", 271 | "cpu": [ 272 | "x64" 273 | ], 274 | "dev": true, 275 | "optional": true, 276 | "os": [ 277 | "linux" 278 | ], 279 | "engines": { 280 | "node": ">=12" 281 | } 282 | }, 283 | "node_modules/@esbuild/netbsd-x64": { 284 | "version": "0.18.20", 285 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", 286 | "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", 287 | "cpu": [ 288 | "x64" 289 | ], 290 | "dev": true, 291 | "optional": true, 292 | "os": [ 293 | "netbsd" 294 | ], 295 | "engines": { 296 | "node": ">=12" 297 | } 298 | }, 299 | "node_modules/@esbuild/openbsd-x64": { 300 | "version": "0.18.20", 301 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", 302 | "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", 303 | "cpu": [ 304 | "x64" 305 | ], 306 | "dev": true, 307 | "optional": true, 308 | "os": [ 309 | "openbsd" 310 | ], 311 | "engines": { 312 | "node": ">=12" 313 | } 314 | }, 315 | "node_modules/@esbuild/sunos-x64": { 316 | "version": "0.18.20", 317 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", 318 | "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", 319 | "cpu": [ 320 | "x64" 321 | ], 322 | "dev": true, 323 | "optional": true, 324 | "os": [ 325 | "sunos" 326 | ], 327 | "engines": { 328 | "node": ">=12" 329 | } 330 | }, 331 | "node_modules/@esbuild/win32-arm64": { 332 | "version": "0.18.20", 333 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", 334 | "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", 335 | "cpu": [ 336 | "arm64" 337 | ], 338 | "dev": true, 339 | "optional": true, 340 | "os": [ 341 | "win32" 342 | ], 343 | "engines": { 344 | "node": ">=12" 345 | } 346 | }, 347 | "node_modules/@esbuild/win32-ia32": { 348 | "version": "0.18.20", 349 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", 350 | "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", 351 | "cpu": [ 352 | "ia32" 353 | ], 354 | "dev": true, 355 | "optional": true, 356 | "os": [ 357 | "win32" 358 | ], 359 | "engines": { 360 | "node": ">=12" 361 | } 362 | }, 363 | "node_modules/@esbuild/win32-x64": { 364 | "version": "0.18.20", 365 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", 366 | "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", 367 | "cpu": [ 368 | "x64" 369 | ], 370 | "dev": true, 371 | "optional": true, 372 | "os": [ 373 | "win32" 374 | ], 375 | "engines": { 376 | "node": ">=12" 377 | } 378 | }, 379 | "node_modules/@types/three": { 380 | "version": "0.147.1", 381 | "resolved": "https://registry.npmjs.org/@types/three/-/three-0.147.1.tgz", 382 | "integrity": "sha512-1dGYrF9E7frAXu3CRUYtTFj97PlA/Q0OedDQBROn3fKjtKXNhXc6/VNgkGod3axJMeNPNFDa6ur9eOcQ+aD0zw==", 383 | "dev": true, 384 | "dependencies": { 385 | "@types/webxr": "*" 386 | } 387 | }, 388 | "node_modules/@types/webxr": { 389 | "version": "0.5.5", 390 | "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.5.tgz", 391 | "integrity": "sha512-HVOsSRTQYx3zpVl0c0FBmmmcY/60BkQLzVnpE9M1aG4f2Z0aKlBWfj4XZ2zr++XNBfkQWYcwhGlmuu44RJPDqg==", 392 | "dev": true 393 | }, 394 | "node_modules/esbuild": { 395 | "version": "0.18.20", 396 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", 397 | "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", 398 | "dev": true, 399 | "hasInstallScript": true, 400 | "bin": { 401 | "esbuild": "bin/esbuild" 402 | }, 403 | "engines": { 404 | "node": ">=12" 405 | }, 406 | "optionalDependencies": { 407 | "@esbuild/android-arm": "0.18.20", 408 | "@esbuild/android-arm64": "0.18.20", 409 | "@esbuild/android-x64": "0.18.20", 410 | "@esbuild/darwin-arm64": "0.18.20", 411 | "@esbuild/darwin-x64": "0.18.20", 412 | "@esbuild/freebsd-arm64": "0.18.20", 413 | "@esbuild/freebsd-x64": "0.18.20", 414 | "@esbuild/linux-arm": "0.18.20", 415 | "@esbuild/linux-arm64": "0.18.20", 416 | "@esbuild/linux-ia32": "0.18.20", 417 | "@esbuild/linux-loong64": "0.18.20", 418 | "@esbuild/linux-mips64el": "0.18.20", 419 | "@esbuild/linux-ppc64": "0.18.20", 420 | "@esbuild/linux-riscv64": "0.18.20", 421 | "@esbuild/linux-s390x": "0.18.20", 422 | "@esbuild/linux-x64": "0.18.20", 423 | "@esbuild/netbsd-x64": "0.18.20", 424 | "@esbuild/openbsd-x64": "0.18.20", 425 | "@esbuild/sunos-x64": "0.18.20", 426 | "@esbuild/win32-arm64": "0.18.20", 427 | "@esbuild/win32-ia32": "0.18.20", 428 | "@esbuild/win32-x64": "0.18.20" 429 | } 430 | }, 431 | "node_modules/fsevents": { 432 | "version": "2.3.3", 433 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 434 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 435 | "dev": true, 436 | "hasInstallScript": true, 437 | "optional": true, 438 | "os": [ 439 | "darwin" 440 | ], 441 | "engines": { 442 | "node": "^8.16.0 || ^10.6.0 || >=11.0.0" 443 | } 444 | }, 445 | "node_modules/molangjs": { 446 | "version": "1.6.3", 447 | "resolved": "https://registry.npmjs.org/molangjs/-/molangjs-1.6.3.tgz", 448 | "integrity": "sha512-53yOSZpHuR7HUZBZ34uTQBd71kxS/yln/oEtUagkRVukHZJf7GSL9WoE7DzOHfoAOUok8ZGQLWiAqLfZomJBKA==" 449 | }, 450 | "node_modules/nanoid": { 451 | "version": "3.3.6", 452 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", 453 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", 454 | "dev": true, 455 | "funding": [ 456 | { 457 | "type": "github", 458 | "url": "https://github.com/sponsors/ai" 459 | } 460 | ], 461 | "bin": { 462 | "nanoid": "bin/nanoid.cjs" 463 | }, 464 | "engines": { 465 | "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1" 466 | } 467 | }, 468 | "node_modules/picocolors": { 469 | "version": "1.0.0", 470 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 471 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 472 | "dev": true 473 | }, 474 | "node_modules/postcss": { 475 | "version": "8.4.30", 476 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz", 477 | "integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==", 478 | "dev": true, 479 | "funding": [ 480 | { 481 | "type": "opencollective", 482 | "url": "https://opencollective.com/postcss/" 483 | }, 484 | { 485 | "type": "tidelift", 486 | "url": "https://tidelift.com/funding/github/npm/postcss" 487 | }, 488 | { 489 | "type": "github", 490 | "url": "https://github.com/sponsors/ai" 491 | } 492 | ], 493 | "dependencies": { 494 | "nanoid": "^3.3.6", 495 | "picocolors": "^1.0.0", 496 | "source-map-js": "^1.0.2" 497 | }, 498 | "engines": { 499 | "node": "^10 || ^12 || >=14" 500 | } 501 | }, 502 | "node_modules/rollup": { 503 | "version": "3.29.4", 504 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", 505 | "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", 506 | "dev": true, 507 | "bin": { 508 | "rollup": "dist/bin/rollup" 509 | }, 510 | "engines": { 511 | "node": ">=14.18.0", 512 | "npm": ">=8.0.0" 513 | }, 514 | "optionalDependencies": { 515 | "fsevents": "~2.3.2" 516 | } 517 | }, 518 | "node_modules/source-map-js": { 519 | "version": "1.0.2", 520 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 521 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 522 | "dev": true, 523 | "engines": { 524 | "node": ">=0.10.0" 525 | } 526 | }, 527 | "node_modules/three": { 528 | "version": "0.147.0", 529 | "resolved": "https://registry.npmjs.org/three/-/three-0.147.0.tgz", 530 | "integrity": "sha512-LPTOslYQXFkmvceQjFTNnVVli2LaVF6C99Pv34fJypp8NbQLbTlu3KinZ0zURghS5zEehK+VQyvWuPZ/Sm8fzw==" 531 | }, 532 | "node_modules/tinycolor2": { 533 | "version": "1.4.2", 534 | "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", 535 | "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==", 536 | "engines": { 537 | "node": "*" 538 | } 539 | }, 540 | "node_modules/typescript": { 541 | "version": "4.3.5", 542 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", 543 | "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", 544 | "dev": true, 545 | "bin": { 546 | "tsc": "bin/tsc", 547 | "tsserver": "bin/tsserver" 548 | }, 549 | "engines": { 550 | "node": ">=4.2.0" 551 | } 552 | }, 553 | "node_modules/vite": { 554 | "version": "4.4.9", 555 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", 556 | "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", 557 | "dev": true, 558 | "dependencies": { 559 | "esbuild": "^0.18.10", 560 | "postcss": "^8.4.27", 561 | "rollup": "^3.27.1" 562 | }, 563 | "bin": { 564 | "vite": "bin/vite.js" 565 | }, 566 | "engines": { 567 | "node": "^14.18.0 || >=16.0.0" 568 | }, 569 | "funding": { 570 | "url": "https://github.com/vitejs/vite?sponsor=1" 571 | }, 572 | "optionalDependencies": { 573 | "fsevents": "~2.3.2" 574 | }, 575 | "peerDependencies": { 576 | "@types/node": ">= 14", 577 | "less": "*", 578 | "lightningcss": "^1.21.0", 579 | "sass": "*", 580 | "stylus": "*", 581 | "sugarss": "*", 582 | "terser": "^5.4.0" 583 | }, 584 | "peerDependenciesMeta": { 585 | "@types/node": { 586 | "optional": true 587 | }, 588 | "less": { 589 | "optional": true 590 | }, 591 | "lightningcss": { 592 | "optional": true 593 | }, 594 | "sass": { 595 | "optional": true 596 | }, 597 | "stylus": { 598 | "optional": true 599 | }, 600 | "sugarss": { 601 | "optional": true 602 | }, 603 | "terser": { 604 | "optional": true 605 | } 606 | } 607 | }, 608 | "node_modules/wintersky": { 609 | "version": "1.3.0", 610 | "resolved": "https://registry.npmjs.org/wintersky/-/wintersky-1.3.0.tgz", 611 | "integrity": "sha512-ibeUF+sgoIrFAfgqcNsPlr8+4l0KOfhGw+JgjxKcgtbckSy3gAxqAeNX5LjuQK9y7jcBZDwUIXg9sZDGQ1W+hQ==", 612 | "dependencies": { 613 | "molangjs": "^1.6.3", 614 | "three": "^0.134.0", 615 | "tinycolor2": "^1.4.2" 616 | } 617 | }, 618 | "node_modules/wintersky/node_modules/three": { 619 | "version": "0.134.0", 620 | "resolved": "https://registry.npmjs.org/three/-/three-0.134.0.tgz", 621 | "integrity": "sha512-LbBerg7GaSPjYtTOnu41AMp7tV6efUNR3p4Wk5NzkSsNTBuA5mDGOfwwZL1jhhVMLx9V20HolIUo0+U3AXehbg==" 622 | } 623 | }, 624 | "dependencies": { 625 | "@bridge-editor/molang": { 626 | "version": "2.0.2", 627 | "resolved": "https://registry.npmjs.org/@bridge-editor/molang/-/molang-2.0.2.tgz", 628 | "integrity": "sha512-GmATfUFAIoc90/KnkF/4nFI3rwjBWltgpyTD3Kh50Pabq89o3H++NIZa93Fat5bie1zFsiHcG9RbooDvofgc1Q==" 629 | }, 630 | "@esbuild/android-arm": { 631 | "version": "0.18.20", 632 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm/-/android-arm-0.18.20.tgz", 633 | "integrity": "sha512-fyi7TDI/ijKKNZTUJAQqiG5T7YjJXgnzkURqmGj13C6dCqckZBLdl4h7bkhHt/t0WP+zO9/zwroDvANaOqO5Sw==", 634 | "dev": true, 635 | "optional": true 636 | }, 637 | "@esbuild/android-arm64": { 638 | "version": "0.18.20", 639 | "resolved": "https://registry.npmjs.org/@esbuild/android-arm64/-/android-arm64-0.18.20.tgz", 640 | "integrity": "sha512-Nz4rJcchGDtENV0eMKUNa6L12zz2zBDXuhj/Vjh18zGqB44Bi7MBMSXjgunJgjRhCmKOjnPuZp4Mb6OKqtMHLQ==", 641 | "dev": true, 642 | "optional": true 643 | }, 644 | "@esbuild/android-x64": { 645 | "version": "0.18.20", 646 | "resolved": "https://registry.npmjs.org/@esbuild/android-x64/-/android-x64-0.18.20.tgz", 647 | "integrity": "sha512-8GDdlePJA8D6zlZYJV/jnrRAi6rOiNaCC/JclcXpB+KIuvfBN4owLtgzY2bsxnx666XjJx2kDPUmnTtR8qKQUg==", 648 | "dev": true, 649 | "optional": true 650 | }, 651 | "@esbuild/darwin-arm64": { 652 | "version": "0.18.20", 653 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-arm64/-/darwin-arm64-0.18.20.tgz", 654 | "integrity": "sha512-bxRHW5kHU38zS2lPTPOyuyTm+S+eobPUnTNkdJEfAddYgEcll4xkT8DB9d2008DtTbl7uJag2HuE5NZAZgnNEA==", 655 | "dev": true, 656 | "optional": true 657 | }, 658 | "@esbuild/darwin-x64": { 659 | "version": "0.18.20", 660 | "resolved": "https://registry.npmjs.org/@esbuild/darwin-x64/-/darwin-x64-0.18.20.tgz", 661 | "integrity": "sha512-pc5gxlMDxzm513qPGbCbDukOdsGtKhfxD1zJKXjCCcU7ju50O7MeAZ8c4krSJcOIJGFR+qx21yMMVYwiQvyTyQ==", 662 | "dev": true, 663 | "optional": true 664 | }, 665 | "@esbuild/freebsd-arm64": { 666 | "version": "0.18.20", 667 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-arm64/-/freebsd-arm64-0.18.20.tgz", 668 | "integrity": "sha512-yqDQHy4QHevpMAaxhhIwYPMv1NECwOvIpGCZkECn8w2WFHXjEwrBn3CeNIYsibZ/iZEUemj++M26W3cNR5h+Tw==", 669 | "dev": true, 670 | "optional": true 671 | }, 672 | "@esbuild/freebsd-x64": { 673 | "version": "0.18.20", 674 | "resolved": "https://registry.npmjs.org/@esbuild/freebsd-x64/-/freebsd-x64-0.18.20.tgz", 675 | "integrity": "sha512-tgWRPPuQsd3RmBZwarGVHZQvtzfEBOreNuxEMKFcd5DaDn2PbBxfwLcj4+aenoh7ctXcbXmOQIn8HI6mCSw5MQ==", 676 | "dev": true, 677 | "optional": true 678 | }, 679 | "@esbuild/linux-arm": { 680 | "version": "0.18.20", 681 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm/-/linux-arm-0.18.20.tgz", 682 | "integrity": "sha512-/5bHkMWnq1EgKr1V+Ybz3s1hWXok7mDFUMQ4cG10AfW3wL02PSZi5kFpYKrptDsgb2WAJIvRcDm+qIvXf/apvg==", 683 | "dev": true, 684 | "optional": true 685 | }, 686 | "@esbuild/linux-arm64": { 687 | "version": "0.18.20", 688 | "resolved": "https://registry.npmjs.org/@esbuild/linux-arm64/-/linux-arm64-0.18.20.tgz", 689 | "integrity": "sha512-2YbscF+UL7SQAVIpnWvYwM+3LskyDmPhe31pE7/aoTMFKKzIc9lLbyGUpmmb8a8AixOL61sQ/mFh3jEjHYFvdA==", 690 | "dev": true, 691 | "optional": true 692 | }, 693 | "@esbuild/linux-ia32": { 694 | "version": "0.18.20", 695 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ia32/-/linux-ia32-0.18.20.tgz", 696 | "integrity": "sha512-P4etWwq6IsReT0E1KHU40bOnzMHoH73aXp96Fs8TIT6z9Hu8G6+0SHSw9i2isWrD2nbx2qo5yUqACgdfVGx7TA==", 697 | "dev": true, 698 | "optional": true 699 | }, 700 | "@esbuild/linux-loong64": { 701 | "version": "0.18.20", 702 | "resolved": "https://registry.npmjs.org/@esbuild/linux-loong64/-/linux-loong64-0.18.20.tgz", 703 | "integrity": "sha512-nXW8nqBTrOpDLPgPY9uV+/1DjxoQ7DoB2N8eocyq8I9XuqJ7BiAMDMf9n1xZM9TgW0J8zrquIb/A7s3BJv7rjg==", 704 | "dev": true, 705 | "optional": true 706 | }, 707 | "@esbuild/linux-mips64el": { 708 | "version": "0.18.20", 709 | "resolved": "https://registry.npmjs.org/@esbuild/linux-mips64el/-/linux-mips64el-0.18.20.tgz", 710 | "integrity": "sha512-d5NeaXZcHp8PzYy5VnXV3VSd2D328Zb+9dEq5HE6bw6+N86JVPExrA6O68OPwobntbNJ0pzCpUFZTo3w0GyetQ==", 711 | "dev": true, 712 | "optional": true 713 | }, 714 | "@esbuild/linux-ppc64": { 715 | "version": "0.18.20", 716 | "resolved": "https://registry.npmjs.org/@esbuild/linux-ppc64/-/linux-ppc64-0.18.20.tgz", 717 | "integrity": "sha512-WHPyeScRNcmANnLQkq6AfyXRFr5D6N2sKgkFo2FqguP44Nw2eyDlbTdZwd9GYk98DZG9QItIiTlFLHJHjxP3FA==", 718 | "dev": true, 719 | "optional": true 720 | }, 721 | "@esbuild/linux-riscv64": { 722 | "version": "0.18.20", 723 | "resolved": "https://registry.npmjs.org/@esbuild/linux-riscv64/-/linux-riscv64-0.18.20.tgz", 724 | "integrity": "sha512-WSxo6h5ecI5XH34KC7w5veNnKkju3zBRLEQNY7mv5mtBmrP/MjNBCAlsM2u5hDBlS3NGcTQpoBvRzqBcRtpq1A==", 725 | "dev": true, 726 | "optional": true 727 | }, 728 | "@esbuild/linux-s390x": { 729 | "version": "0.18.20", 730 | "resolved": "https://registry.npmjs.org/@esbuild/linux-s390x/-/linux-s390x-0.18.20.tgz", 731 | "integrity": "sha512-+8231GMs3mAEth6Ja1iK0a1sQ3ohfcpzpRLH8uuc5/KVDFneH6jtAJLFGafpzpMRO6DzJ6AvXKze9LfFMrIHVQ==", 732 | "dev": true, 733 | "optional": true 734 | }, 735 | "@esbuild/linux-x64": { 736 | "version": "0.18.20", 737 | "resolved": "https://registry.npmjs.org/@esbuild/linux-x64/-/linux-x64-0.18.20.tgz", 738 | "integrity": "sha512-UYqiqemphJcNsFEskc73jQ7B9jgwjWrSayxawS6UVFZGWrAAtkzjxSqnoclCXxWtfwLdzU+vTpcNYhpn43uP1w==", 739 | "dev": true, 740 | "optional": true 741 | }, 742 | "@esbuild/netbsd-x64": { 743 | "version": "0.18.20", 744 | "resolved": "https://registry.npmjs.org/@esbuild/netbsd-x64/-/netbsd-x64-0.18.20.tgz", 745 | "integrity": "sha512-iO1c++VP6xUBUmltHZoMtCUdPlnPGdBom6IrO4gyKPFFVBKioIImVooR5I83nTew5UOYrk3gIJhbZh8X44y06A==", 746 | "dev": true, 747 | "optional": true 748 | }, 749 | "@esbuild/openbsd-x64": { 750 | "version": "0.18.20", 751 | "resolved": "https://registry.npmjs.org/@esbuild/openbsd-x64/-/openbsd-x64-0.18.20.tgz", 752 | "integrity": "sha512-e5e4YSsuQfX4cxcygw/UCPIEP6wbIL+se3sxPdCiMbFLBWu0eiZOJ7WoD+ptCLrmjZBK1Wk7I6D/I3NglUGOxg==", 753 | "dev": true, 754 | "optional": true 755 | }, 756 | "@esbuild/sunos-x64": { 757 | "version": "0.18.20", 758 | "resolved": "https://registry.npmjs.org/@esbuild/sunos-x64/-/sunos-x64-0.18.20.tgz", 759 | "integrity": "sha512-kDbFRFp0YpTQVVrqUd5FTYmWo45zGaXe0X8E1G/LKFC0v8x0vWrhOWSLITcCn63lmZIxfOMXtCfti/RxN/0wnQ==", 760 | "dev": true, 761 | "optional": true 762 | }, 763 | "@esbuild/win32-arm64": { 764 | "version": "0.18.20", 765 | "resolved": "https://registry.npmjs.org/@esbuild/win32-arm64/-/win32-arm64-0.18.20.tgz", 766 | "integrity": "sha512-ddYFR6ItYgoaq4v4JmQQaAI5s7npztfV4Ag6NrhiaW0RrnOXqBkgwZLofVTlq1daVTQNhtI5oieTvkRPfZrePg==", 767 | "dev": true, 768 | "optional": true 769 | }, 770 | "@esbuild/win32-ia32": { 771 | "version": "0.18.20", 772 | "resolved": "https://registry.npmjs.org/@esbuild/win32-ia32/-/win32-ia32-0.18.20.tgz", 773 | "integrity": "sha512-Wv7QBi3ID/rROT08SABTS7eV4hX26sVduqDOTe1MvGMjNd3EjOz4b7zeexIR62GTIEKrfJXKL9LFxTYgkyeu7g==", 774 | "dev": true, 775 | "optional": true 776 | }, 777 | "@esbuild/win32-x64": { 778 | "version": "0.18.20", 779 | "resolved": "https://registry.npmjs.org/@esbuild/win32-x64/-/win32-x64-0.18.20.tgz", 780 | "integrity": "sha512-kTdfRcSiDfQca/y9QIkng02avJ+NCaQvrMejlsB3RRv5sE9rRoeBPISaZpKxHELzRxZyLvNts1P27W3wV+8geQ==", 781 | "dev": true, 782 | "optional": true 783 | }, 784 | "@types/three": { 785 | "version": "0.147.1", 786 | "resolved": "https://registry.npmjs.org/@types/three/-/three-0.147.1.tgz", 787 | "integrity": "sha512-1dGYrF9E7frAXu3CRUYtTFj97PlA/Q0OedDQBROn3fKjtKXNhXc6/VNgkGod3axJMeNPNFDa6ur9eOcQ+aD0zw==", 788 | "dev": true, 789 | "requires": { 790 | "@types/webxr": "*" 791 | } 792 | }, 793 | "@types/webxr": { 794 | "version": "0.5.5", 795 | "resolved": "https://registry.npmjs.org/@types/webxr/-/webxr-0.5.5.tgz", 796 | "integrity": "sha512-HVOsSRTQYx3zpVl0c0FBmmmcY/60BkQLzVnpE9M1aG4f2Z0aKlBWfj4XZ2zr++XNBfkQWYcwhGlmuu44RJPDqg==", 797 | "dev": true 798 | }, 799 | "esbuild": { 800 | "version": "0.18.20", 801 | "resolved": "https://registry.npmjs.org/esbuild/-/esbuild-0.18.20.tgz", 802 | "integrity": "sha512-ceqxoedUrcayh7Y7ZX6NdbbDzGROiyVBgC4PriJThBKSVPWnnFHZAkfI1lJT8QFkOwH4qOS2SJkS4wvpGl8BpA==", 803 | "dev": true, 804 | "requires": { 805 | "@esbuild/android-arm": "0.18.20", 806 | "@esbuild/android-arm64": "0.18.20", 807 | "@esbuild/android-x64": "0.18.20", 808 | "@esbuild/darwin-arm64": "0.18.20", 809 | "@esbuild/darwin-x64": "0.18.20", 810 | "@esbuild/freebsd-arm64": "0.18.20", 811 | "@esbuild/freebsd-x64": "0.18.20", 812 | "@esbuild/linux-arm": "0.18.20", 813 | "@esbuild/linux-arm64": "0.18.20", 814 | "@esbuild/linux-ia32": "0.18.20", 815 | "@esbuild/linux-loong64": "0.18.20", 816 | "@esbuild/linux-mips64el": "0.18.20", 817 | "@esbuild/linux-ppc64": "0.18.20", 818 | "@esbuild/linux-riscv64": "0.18.20", 819 | "@esbuild/linux-s390x": "0.18.20", 820 | "@esbuild/linux-x64": "0.18.20", 821 | "@esbuild/netbsd-x64": "0.18.20", 822 | "@esbuild/openbsd-x64": "0.18.20", 823 | "@esbuild/sunos-x64": "0.18.20", 824 | "@esbuild/win32-arm64": "0.18.20", 825 | "@esbuild/win32-ia32": "0.18.20", 826 | "@esbuild/win32-x64": "0.18.20" 827 | } 828 | }, 829 | "fsevents": { 830 | "version": "2.3.3", 831 | "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.3.tgz", 832 | "integrity": "sha512-5xoDfX+fL7faATnagmWPpbFtwh/R77WmMMqqHGS65C3vvB0YHrgF+B1YmZ3441tMj5n63k0212XNoJwzlhffQw==", 833 | "dev": true, 834 | "optional": true 835 | }, 836 | "molangjs": { 837 | "version": "1.6.3", 838 | "resolved": "https://registry.npmjs.org/molangjs/-/molangjs-1.6.3.tgz", 839 | "integrity": "sha512-53yOSZpHuR7HUZBZ34uTQBd71kxS/yln/oEtUagkRVukHZJf7GSL9WoE7DzOHfoAOUok8ZGQLWiAqLfZomJBKA==" 840 | }, 841 | "nanoid": { 842 | "version": "3.3.6", 843 | "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.6.tgz", 844 | "integrity": "sha512-BGcqMMJuToF7i1rt+2PWSNVnWIkGCU78jBG3RxO/bZlnZPK2Cmi2QaffxGO/2RvWi9sL+FAiRiXMgsyxQ1DIDA==", 845 | "dev": true 846 | }, 847 | "picocolors": { 848 | "version": "1.0.0", 849 | "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz", 850 | "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ==", 851 | "dev": true 852 | }, 853 | "postcss": { 854 | "version": "8.4.30", 855 | "resolved": "https://registry.npmjs.org/postcss/-/postcss-8.4.30.tgz", 856 | "integrity": "sha512-7ZEao1g4kd68l97aWG/etQKPKq07us0ieSZ2TnFDk11i0ZfDW2AwKHYU8qv4MZKqN2fdBfg+7q0ES06UA73C1g==", 857 | "dev": true, 858 | "requires": { 859 | "nanoid": "^3.3.6", 860 | "picocolors": "^1.0.0", 861 | "source-map-js": "^1.0.2" 862 | } 863 | }, 864 | "rollup": { 865 | "version": "3.29.4", 866 | "resolved": "https://registry.npmjs.org/rollup/-/rollup-3.29.4.tgz", 867 | "integrity": "sha512-oWzmBZwvYrU0iJHtDmhsm662rC15FRXmcjCk1xD771dFDx5jJ02ufAQQTn0etB2emNk4J9EZg/yWKpsn9BWGRw==", 868 | "dev": true, 869 | "requires": { 870 | "fsevents": "~2.3.2" 871 | } 872 | }, 873 | "source-map-js": { 874 | "version": "1.0.2", 875 | "resolved": "https://registry.npmjs.org/source-map-js/-/source-map-js-1.0.2.tgz", 876 | "integrity": "sha512-R0XvVJ9WusLiqTCEiGCmICCMplcCkIwwR11mOSD9CR5u+IXYdiseeEuXCVAjS54zqwkLcPNnmU4OeJ6tUrWhDw==", 877 | "dev": true 878 | }, 879 | "three": { 880 | "version": "0.147.0", 881 | "resolved": "https://registry.npmjs.org/three/-/three-0.147.0.tgz", 882 | "integrity": "sha512-LPTOslYQXFkmvceQjFTNnVVli2LaVF6C99Pv34fJypp8NbQLbTlu3KinZ0zURghS5zEehK+VQyvWuPZ/Sm8fzw==" 883 | }, 884 | "tinycolor2": { 885 | "version": "1.4.2", 886 | "resolved": "https://registry.npmjs.org/tinycolor2/-/tinycolor2-1.4.2.tgz", 887 | "integrity": "sha512-vJhccZPs965sV/L2sU4oRQVAos0pQXwsvTLkWYdqJ+a8Q5kPFzJTuOFwy7UniPli44NKQGAglksjvOcpo95aZA==" 888 | }, 889 | "typescript": { 890 | "version": "4.3.5", 891 | "resolved": "https://registry.npmjs.org/typescript/-/typescript-4.3.5.tgz", 892 | "integrity": "sha512-DqQgihaQ9cUrskJo9kIyW/+g0Vxsk8cDtZ52a3NGh0YNTfpUSArXSohyUGnvbPazEPLu398C0UxmKSOrPumUzA==", 893 | "dev": true 894 | }, 895 | "vite": { 896 | "version": "4.4.9", 897 | "resolved": "https://registry.npmjs.org/vite/-/vite-4.4.9.tgz", 898 | "integrity": "sha512-2mbUn2LlUmNASWwSCNSJ/EG2HuSRTnVNaydp6vMCm5VIqJsjMfbIWtbH2kDuwUVW5mMUKKZvGPX/rqeqVvv1XA==", 899 | "dev": true, 900 | "requires": { 901 | "esbuild": "^0.18.10", 902 | "fsevents": "~2.3.2", 903 | "postcss": "^8.4.27", 904 | "rollup": "^3.27.1" 905 | } 906 | }, 907 | "wintersky": { 908 | "version": "1.3.0", 909 | "resolved": "https://registry.npmjs.org/wintersky/-/wintersky-1.3.0.tgz", 910 | "integrity": "sha512-ibeUF+sgoIrFAfgqcNsPlr8+4l0KOfhGw+JgjxKcgtbckSy3gAxqAeNX5LjuQK9y7jcBZDwUIXg9sZDGQ1W+hQ==", 911 | "requires": { 912 | "molangjs": "^1.6.3", 913 | "three": "^0.134.0", 914 | "tinycolor2": "^1.4.2" 915 | }, 916 | "dependencies": { 917 | "three": { 918 | "version": "0.134.0", 919 | "resolved": "https://registry.npmjs.org/three/-/three-0.134.0.tgz", 920 | "integrity": "sha512-LbBerg7GaSPjYtTOnu41AMp7tV6efUNR3p4Wk5NzkSsNTBuA5mDGOfwwZL1jhhVMLx9V20HolIUo0+U3AXehbg==" 921 | } 922 | } 923 | } 924 | } 925 | } 926 | -------------------------------------------------------------------------------- /package.json: -------------------------------------------------------------------------------- 1 | { 2 | "name": "@bridge-editor/model-viewer", 3 | "version": "0.8.1", 4 | "description": "A fast parser for Minecraft's Molang", 5 | "directories": { 6 | "lib": "lib" 7 | }, 8 | "scripts": { 9 | "build:types": "tsc --project tsconfig.json", 10 | "build": "vite build && npm run build:types", 11 | "dev": "vite dev ./playground" 12 | }, 13 | "files": [ 14 | "dist" 15 | ], 16 | "main": "./dist/model-viewer.umd.js", 17 | "module": "./dist/model-viewer.es.js", 18 | "types": "./dist/main.d.ts", 19 | "exports": { 20 | ".": { 21 | "import": { 22 | "types": "./dist/main.d.ts", 23 | "default": "./dist/model-viewer.es.js" 24 | }, 25 | "require": { 26 | "types": "./dist/main.d.ts", 27 | "default": "./dist/model-viewer.umd.js" 28 | } 29 | } 30 | }, 31 | "author": "solvedDev", 32 | "license": "MIT", 33 | "bugs": { 34 | "url": "https://github.com/bridge-core/model-viewer/issues" 35 | }, 36 | "homepage": "https://github.com/bridge-core/model-viewer#readme", 37 | "devDependencies": { 38 | "@types/three": "^0.147.0", 39 | "typescript": "^4.3.5", 40 | "vite": "^4.4.9" 41 | }, 42 | "dependencies": { 43 | "@bridge-editor/molang": "^2.0.1", 44 | "three": "^0.147.0", 45 | "wintersky": "^1.3.0" 46 | } 47 | } 48 | -------------------------------------------------------------------------------- /playground/index.html: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | model-viewer playground 7 | 8 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | -------------------------------------------------------------------------------- /playground/model.js: -------------------------------------------------------------------------------- 1 | export const sleepAnimation = { 2 | loop: true, 3 | animation_length: 2, 4 | particle_effects: { 5 | '2.0': [ 6 | { 7 | effect: 'heart', 8 | locator: 'above_head', 9 | }, 10 | { 11 | effect: 'heart', 12 | }, 13 | ], 14 | }, 15 | bones: { 16 | body: { 17 | rotation: [60, 0, 90], 18 | position: [0, -5.5, 0], 19 | scale: { 20 | '0.0': [1, 1, 1], 21 | '1.0': [1.025, 1.025, 1.025], 22 | '2.0': [1, 1, 1], 23 | }, 24 | }, 25 | left_front_leg: { 26 | rotation: [70, -2, 120], 27 | position: [-6, -12, -3], 28 | }, 29 | right_front_leg: { 30 | rotation: [25, -280, 40], 31 | position: { 32 | '0.0': [1, -4, 0], 33 | '1.0': [0.75, -3.75, 0], 34 | '2.0': [1, -4, 0], 35 | }, 36 | }, 37 | left_back_leg: { 38 | rotation: [30, 30, 50], 39 | position: { 40 | '0.0': [-8, 3, 7], 41 | '1.0': [-8.25, 3.25, 7], 42 | '2.0': [-8, 3, 7], 43 | }, 44 | }, 45 | right_back_leg: { 46 | rotation: [0, 0, 95], 47 | position: [-1, -2, 7], 48 | }, 49 | head: { 50 | rotation: { 51 | '0.0': [25, -15, 90], 52 | '1.0': [25, -14.5, 90], 53 | '2.0': [25, -15, 90], 54 | }, 55 | position: [0, -12, -5], 56 | }, 57 | tail: { 58 | rotation: { 59 | '0.0': [-20, 15, 60], 60 | '1.0': [-20.5, 14.75, 60.5], 61 | '2.0': [-20, 15, 60], 62 | }, 63 | position: { 64 | '0.0': [0, -1, 3], 65 | '1.0': [0, -1, 3.25], 66 | '2.0': [0, -1, 3], 67 | }, 68 | }, 69 | left_wing: { 70 | rotation: { 71 | '0.0': [75, 0, 15], 72 | '1.0': [75, 0, 17], 73 | '2.0': [75, 0, 15], 74 | }, 75 | position: [3, -11, -4], 76 | }, 77 | right_wing: { 78 | rotation: { 79 | '0.0': [70, 0, -190], 80 | '1.0': [70, 0, -192], 81 | '2.0': [70, 0, -190], 82 | }, 83 | position: [6, -8, -5], 84 | }, 85 | }, 86 | } 87 | 88 | export const animation = { 89 | loop: true, 90 | animation_length: 2, 91 | bones: { 92 | body: { 93 | position: { 94 | '0.0': [0, 0, 0], 95 | '1.0': [0, 4, 0], 96 | '2.0': [0, 0, 0], 97 | }, 98 | }, 99 | left_front_leg: { 100 | rotation: { 101 | '0.0': [0, 0, 0], 102 | '1.0': [20, -10, -20], 103 | '2.0': [0, 0, 0], 104 | }, 105 | position: { 106 | '0.0': [0, 0, 0], 107 | '1.0': [0, 4, 0], 108 | '2.0': [0, 0, 0], 109 | }, 110 | }, 111 | right_front_leg: { 112 | rotation: { 113 | '0.0': [0, 0, 0], 114 | '1.0': [20, 10, 20], 115 | '2.0': [0, 0, 0], 116 | }, 117 | position: { 118 | '0.0': [0, 0, 0], 119 | '1.0': [0, 4, 0], 120 | '2.0': [0, 0, 0], 121 | }, 122 | }, 123 | left_back_leg: { 124 | rotation: { 125 | '0.0': [0, 0, 0], 126 | '1.0': [10, -15, -7], 127 | '2.0': [0, 0, 0], 128 | }, 129 | position: { 130 | '0.0': [0, 0, 0], 131 | '1.0': [0, 4, 0], 132 | '2.0': [0, 0, 0], 133 | }, 134 | }, 135 | right_back_leg: { 136 | rotation: { 137 | '0.0': [0, 0, 0], 138 | '1.0': [10, 15, 7], 139 | '2.0': [0, 0, 0], 140 | }, 141 | position: { 142 | '0.0': [0, 0, 0], 143 | '1.0': [0, 4, 0], 144 | '2.0': [0, 0, 0], 145 | }, 146 | }, 147 | head: { 148 | rotation: { 149 | '0.0': [0, 0, 0], 150 | '1.0': [10, 0, 0], 151 | '2.0': [0, 0, 0], 152 | }, 153 | position: { 154 | '0.0': [0, 0, 0], 155 | '1.0': [0, 4, 0], 156 | '2.0': [0, 0, 0], 157 | }, 158 | }, 159 | tail: { 160 | rotation: { 161 | '0.0': [0, 0, 0], 162 | '1.0': [-15, 0, 0], 163 | '2.0': [0, 0, 0], 164 | }, 165 | position: { 166 | '0.0': [0, 0, 0], 167 | '1.0': [0, 4, 0], 168 | '2.0': [0, 0, 0], 169 | }, 170 | }, 171 | left_wing: { 172 | rotation: { 173 | '0.0': [0, 0, 0], 174 | 0.25: [0, -40, 0], 175 | 0.5: [0, 0, 0], 176 | 0.75: [0, -40, 0], 177 | '1.0': [0, 0, 0], 178 | 1.25: [0, -40, 0], 179 | 1.75: [0, -40, 0], 180 | '2.0': [0, 0, 0], 181 | }, 182 | position: { 183 | '0.0': [0, 0, 0], 184 | '1.0': [0, 4, 0], 185 | '2.0': [0, 0, 0], 186 | }, 187 | }, 188 | right_wing: { 189 | rotation: { 190 | '0.0': [0, 0, 0], 191 | 0.25: [0, 40, 0], 192 | 0.4833: [0, 0, 0], 193 | 0.75: [0, 40, 0], 194 | '1.0': [0, 0, 0], 195 | 1.25: [0, 40, 0], 196 | 1.75: [0, 40, 0], 197 | '2.0': [0, 0, 0], 198 | }, 199 | position: { 200 | '0.0': [0, 0, 0], 201 | '1.0': [0, 4, 0], 202 | '2.0': [0, 0, 0], 203 | }, 204 | }, 205 | }, 206 | } 207 | 208 | export const model = { 209 | description: { 210 | identifier: 'geometry.pesky_dragon', 211 | texture_width: 64, 212 | texture_height: 64, 213 | }, 214 | 215 | bones: [ 216 | { 217 | name: 'head', 218 | pivot: [0, 16.5, -2.5], 219 | locators: { 220 | above_head: [0, 5, 0], 221 | }, 222 | cubes: [ 223 | { 224 | origin: [-3, 15, -8], 225 | size: [6, 5, 7], 226 | uv: [0, 20], 227 | }, 228 | ], 229 | }, 230 | 231 | { 232 | name: 'body', 233 | pivot: [0, 9.5, 0], 234 | rotation: [15, 0, 0], 235 | cubes: [ 236 | { 237 | origin: [-4, 3, -3], 238 | size: [8, 13, 6], 239 | uv: [0, 1], 240 | }, 241 | ], 242 | }, 243 | { 244 | name: 'left_front_leg', 245 | pivot: [3.5, 12.5, -3.5], 246 | rotation: [-20, 0, 0], 247 | cubes: [ 248 | { 249 | origin: [2, 8, -5], 250 | size: [3, 5, 3], 251 | uv: [0, 32], 252 | }, 253 | ], 254 | }, 255 | { 256 | name: 'right_front_leg', 257 | pivot: [-3.5, 12.5, -3.5], 258 | rotation: [-20, 0, 0], 259 | cubes: [ 260 | { 261 | origin: [-5, 8, -5], 262 | size: [3, 5, 3], 263 | uv: [26, 28], 264 | }, 265 | ], 266 | }, 267 | { 268 | name: 'left_back_leg', 269 | pivot: [3.5, 3.5, -1.5], 270 | cubes: [ 271 | { 272 | origin: [2, 0, -3], 273 | size: [3, 5, 3], 274 | uv: [32, 6], 275 | }, 276 | ], 277 | }, 278 | { 279 | name: 'right_back_leg', 280 | pivot: [-3.5, 3.5, -1.5], 281 | 282 | cubes: [ 283 | { 284 | origin: [-5, 0, -3], 285 | size: [3, 5, 3], 286 | uv: [12, 32], 287 | }, 288 | ], 289 | }, 290 | 291 | { 292 | name: 'tail', 293 | pivot: [0, 5, 4.5], 294 | rotation: [-15, 0, 0], 295 | cubes: [ 296 | { 297 | origin: [-1, 4, 3], 298 | size: [2, 2, 7], 299 | uv: [21, 13], 300 | }, 301 | ], 302 | }, 303 | { 304 | name: 'left_wing', 305 | pivot: [1, 13.5, 2], 306 | rotation: [15, -15, -5], 307 | cubes: [ 308 | { 309 | origin: [0, 10, 2], 310 | size: [10, 6, 0], 311 | uv: [26, 22], 312 | }, 313 | ], 314 | }, 315 | { 316 | name: 'right_wing', 317 | pivot: [-1, 13.5, 2], 318 | rotation: [15, 15, 5], 319 | cubes: [ 320 | { 321 | origin: [-10, 10, 2], 322 | size: [10, 6, 0], 323 | uv: [22, 0], 324 | }, 325 | ], 326 | }, 327 | ], 328 | } 329 | 330 | export const particle = { 331 | format_version: '1.10.0', 332 | particle_effect: { 333 | description: { 334 | identifier: 'minecraft:heart_particle', 335 | basic_render_parameters: { 336 | material: 'particles_alpha', 337 | texture: 'textures/particle/particles', 338 | }, 339 | }, 340 | components: { 341 | 'minecraft:emitter_local_space': { 342 | position: true, 343 | rotation: true, 344 | velocity: false, 345 | }, 346 | 'minecraft:emitter_rate_steady': { 347 | spawn_rate: 1, 348 | max_particles: 100, 349 | }, 350 | 'minecraft:emitter_lifetime_once': { 351 | active_time: 1, 352 | }, 353 | 'minecraft:emitter_shape_sphere': { 354 | radius: 0.025, 355 | direction: [0, 0.1, 0], 356 | }, 357 | 'minecraft:particle_lifetime_expression': { 358 | max_lifetime: 'Math.random(0.2, 1.5)', 359 | }, 360 | 'minecraft:particle_initial_speed': -2.5, 361 | 'minecraft:particle_motion_dynamic': { 362 | linear_drag_coefficient: 5, 363 | }, 364 | 'minecraft:particle_appearance_billboard': { 365 | size: [ 366 | '(0.2 + variable.particle_random_1*0.1)', 367 | '(0.2 + variable.particle_random_1*0.1)', 368 | ], 369 | facing_camera_mode: 'lookat_xyz', 370 | uv: { 371 | texture_width: 128, 372 | texture_height: 128, 373 | uv: [0, 40], 374 | uv_size: [8, 8], 375 | }, 376 | }, 377 | 'minecraft:particle_appearance_lighting': {}, 378 | }, 379 | }, 380 | } 381 | -------------------------------------------------------------------------------- /playground/pesky_dragon.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridge-core/model-viewer/63ab6a3ab1546f84723a2d6ee6a64399350ccc29/playground/pesky_dragon.png -------------------------------------------------------------------------------- /playground/pink.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/bridge-core/model-viewer/63ab6a3ab1546f84723a2d6ee6a64399350ccc29/playground/pink.png -------------------------------------------------------------------------------- /playground/test.js: -------------------------------------------------------------------------------- 1 | import { StandaloneModelViewer } from '../lib/main' 2 | import { 3 | animation, 4 | model as modelJson, 5 | particle, 6 | sleepAnimation, 7 | } from './model' 8 | import Wintersky from 'wintersky' 9 | 10 | const canvas = document.getElementById('renderTarget') 11 | 12 | export const viewer = new StandaloneModelViewer( 13 | canvas, 14 | modelJson, 15 | 'pesky_dragon.png', 16 | { 17 | antialias: true, 18 | } 19 | ) 20 | 21 | await viewer.loadedModel 22 | // viewer.addHelpers() 23 | viewer.positionCamera() 24 | 25 | const model = viewer.getModel() 26 | const winterskyScene = new Wintersky.Scene() 27 | winterskyScene.global_options.loop_mode = 'once' 28 | winterskyScene.global_options.scale = 16 29 | 30 | viewer.scene.add(winterskyScene.space) 31 | 32 | model.animator.setupWintersky(winterskyScene) 33 | model.animator.addEmitter( 34 | 'heart', 35 | new Wintersky.Config(winterskyScene, particle) 36 | ) 37 | model.animator.addAnimation('idle', animation) 38 | model.animator.addAnimation('sleep', sleepAnimation) 39 | 40 | setTimeout(() => viewer.requestRendering(), 100) 41 | model.animator.play('sleep') 42 | // model.animator.play('idle') 43 | console.log(model) 44 | 45 | model.createOutlineBox('#ff6700', { x: 0, y: 0, z: 0 }, { x: 16, y: 8, z: 16 }) 46 | 47 | model.createOutlineBox('#006712', { x: 0, y: 0, z: 0 }, { x: 12, y: 20, z: 8 }) 48 | -------------------------------------------------------------------------------- /tsconfig.json: -------------------------------------------------------------------------------- 1 | { 2 | "compilerOptions": { 3 | /* Basic Options */ 4 | "target": "ES2021", 5 | "module": "CommonJS", 6 | "moduleResolution": "node", 7 | "outDir": "./dist/", 8 | "rootDir": "./lib/", 9 | "composite": false, 10 | "removeComments": true, 11 | "sourceMap": false, 12 | "declaration": true, 13 | "emitDeclarationOnly": true, 14 | 15 | /* Strict Type-Checking Options */ 16 | "strict": true, 17 | "strictFunctionTypes": true, 18 | "noImplicitAny": true, 19 | 20 | /* Additional Checks */ 21 | "esModuleInterop": true, 22 | "resolveJsonModule": true 23 | }, 24 | "include": ["./lib/**/*.ts"] 25 | } 26 | -------------------------------------------------------------------------------- /vite.config.ts: -------------------------------------------------------------------------------- 1 | import { defineConfig } from 'vite' 2 | import { resolve } from 'path' 3 | 4 | export default defineConfig({ 5 | build: { 6 | lib: { 7 | entry: resolve(__dirname, 'lib/main.ts'), 8 | name: 'ModelViewer', 9 | fileName: (format) => `model-viewer.${format}.js`, 10 | }, 11 | rollupOptions: { 12 | external: ['@bridge-editor/molang', 'three', 'wintersky'], 13 | }, 14 | }, 15 | }) 16 | --------------------------------------------------------------------------------