UNPKG

playcanvas

Version:

Open-source WebGL/WebGPU 3D engine for the web

329 lines (328 loc) 8.75 kB
import { EventHandler } from "../core/event-handler.js"; import { Vec2 } from "../core/math/vec2.js"; import { SPRITE_RENDERMODE_SIMPLE, SPRITE_RENDERMODE_SLICED, SPRITE_RENDERMODE_TILED } from "./constants.js"; import { Mesh } from "./mesh.js"; import { Geometry } from "./geometry/geometry.js"; const spriteNormals = [ 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1 ]; const spriteIndices = [ 0, 1, 3, 2, 3, 1 ]; class Sprite extends EventHandler { /** * Create a new Sprite instance. * * @param {GraphicsDevice} device - The graphics device of the application. * @param {object} [options] - Options for creating the Sprite. * @param {number} [options.pixelsPerUnit] - The number of pixels that map to one PlayCanvas * unit. Defaults to 1. * @param {number} [options.renderMode] - The rendering mode of the sprite. Can be: * * - {@link SPRITE_RENDERMODE_SIMPLE} * - {@link SPRITE_RENDERMODE_SLICED} * - {@link SPRITE_RENDERMODE_TILED} * * Defaults to {@link SPRITE_RENDERMODE_SIMPLE}. * @param {TextureAtlas} [options.atlas] - The texture atlas. Defaults to null. * @param {string[]} [options.frameKeys] - The keys of the frames in the sprite atlas that this * sprite is using. Defaults to null. */ constructor(device, options) { super(); this._device = device; this._pixelsPerUnit = options && options.pixelsPerUnit !== void 0 ? options.pixelsPerUnit : 1; this._renderMode = options && options.renderMode !== void 0 ? options.renderMode : SPRITE_RENDERMODE_SIMPLE; this._atlas = options && options.atlas !== void 0 ? options.atlas : null; this._frameKeys = options && options.frameKeys !== void 0 ? options.frameKeys : null; this._meshes = []; this._updatingProperties = false; this._meshesDirty = false; if (this._atlas && this._frameKeys) { this._createMeshes(); } } /** * Sets the keys of the frames in the sprite atlas that this sprite is using. * * @type {string[]} */ set frameKeys(value) { this._frameKeys = value; if (this._atlas && this._frameKeys) { if (this._updatingProperties) { this._meshesDirty = true; } else { this._createMeshes(); } } this.fire("set:frameKeys", value); } /** * Gets the keys of the frames in the sprite atlas that this sprite is using. * * @type {string[]} */ get frameKeys() { return this._frameKeys; } /** * Sets the texture atlas. * * @type {TextureAtlas} */ set atlas(value) { if (value === this._atlas) return; if (this._atlas) { this._atlas.off("set:frames", this._onSetFrames, this); this._atlas.off("set:frame", this._onFrameChanged, this); this._atlas.off("remove:frame", this._onFrameRemoved, this); } this._atlas = value; if (this._atlas && this._frameKeys) { this._atlas.on("set:frames", this._onSetFrames, this); this._atlas.on("set:frame", this._onFrameChanged, this); this._atlas.on("remove:frame", this._onFrameRemoved, this); if (this._updatingProperties) { this._meshesDirty = true; } else { this._createMeshes(); } } this.fire("set:atlas", value); } /** * Gets the texture atlas. * * @type {TextureAtlas} */ get atlas() { return this._atlas; } /** * Sets the number of pixels that map to one PlayCanvas unit. * * @type {number} */ set pixelsPerUnit(value) { if (this._pixelsPerUnit === value) return; this._pixelsPerUnit = value; this.fire("set:pixelsPerUnit", value); if (this._atlas && this._frameKeys && this.renderMode === SPRITE_RENDERMODE_SIMPLE) { if (this._updatingProperties) { this._meshesDirty = true; } else { this._createMeshes(); } } } /** * Gets the number of pixels that map to one PlayCanvas unit. * * @type {number} */ get pixelsPerUnit() { return this._pixelsPerUnit; } /** * Sets the rendering mode of the sprite. Can be: * * - {@link SPRITE_RENDERMODE_SIMPLE} * - {@link SPRITE_RENDERMODE_SLICED} * - {@link SPRITE_RENDERMODE_TILED} * * @type {number} */ set renderMode(value) { if (this._renderMode === value) { return; } const prev = this._renderMode; this._renderMode = value; this.fire("set:renderMode", value); if (prev === SPRITE_RENDERMODE_SIMPLE || value === SPRITE_RENDERMODE_SIMPLE) { if (this._atlas && this._frameKeys) { if (this._updatingProperties) { this._meshesDirty = true; } else { this._createMeshes(); } } } } /** * Sets the rendering mode of the sprite. * * @type {number} */ get renderMode() { return this._renderMode; } /** * An array that contains a mesh for each frame. * * @type {Mesh[]} */ get meshes() { return this._meshes; } _createMeshes() { const len = this._meshes.length; for (let i = 0; i < len; i++) { const mesh = this._meshes[i]; if (mesh) { mesh.destroy(); } } const count = this._frameKeys.length; this._meshes = new Array(count); const createMeshFunc = this.renderMode === SPRITE_RENDERMODE_SLICED || this._renderMode === SPRITE_RENDERMODE_TILED ? this._create9SliceMesh : this._createSimpleMesh; for (let i = 0; i < count; i++) { const frame = this._atlas.frames[this._frameKeys[i]]; this._meshes[i] = frame ? createMeshFunc.call(this, frame) : null; } this.fire("set:meshes"); } _createSimpleMesh(frame) { const rect = frame.rect; const texWidth = this._atlas.texture.width; const texHeight = this._atlas.texture.height; const w = rect.z / this._pixelsPerUnit; const h = rect.w / this._pixelsPerUnit; const hp = frame.pivot.x; const vp = frame.pivot.y; const positions = [ -hp * w, -vp * h, 0, (1 - hp) * w, -vp * h, 0, (1 - hp) * w, (1 - vp) * h, 0, -hp * w, (1 - vp) * h, 0 ]; const lu = rect.x / texWidth; const bv = 1 - rect.y / texHeight; const ru = (rect.x + rect.z) / texWidth; const tv = 1 - (rect.y + rect.w) / texHeight; const uvs = [ lu, bv, ru, bv, ru, tv, lu, tv ]; const geom = new Geometry(); geom.positions = positions; geom.normals = spriteNormals; geom.uvs = uvs; geom.indices = spriteIndices; return Mesh.fromGeometry(this._device, geom); } _create9SliceMesh() { const he = Vec2.ONE; const ws = 3; const ls = 3; const positions = []; const normals = []; const uvs = []; const indices = []; let vcounter = 0; for (let i = 0; i <= ws; i++) { const u = i === 0 || i === ws ? 0 : 1; for (let j = 0; j <= ls; j++) { const x = -he.x + 2 * he.x * (i <= 1 ? 0 : 3) / ws; const y = 0; const z = -(-he.y + 2 * he.y * (j <= 1 ? 0 : 3) / ls); const v = j === 0 || j === ls ? 0 : 1; positions.push(-x, y, z); normals.push(0, 1, 0); uvs.push(u, v); if (i < ws && j < ls) { indices.push(vcounter + ls + 1, vcounter + 1, vcounter); indices.push(vcounter + ls + 1, vcounter + ls + 2, vcounter + 1); } vcounter++; } } const geom = new Geometry(); geom.positions = positions; geom.normals = normals; geom.uvs = uvs; geom.indices = indices; return Mesh.fromGeometry(this._device, geom); } _onSetFrames(frames) { if (this._updatingProperties) { this._meshesDirty = true; } else { this._createMeshes(); } } _onFrameChanged(frameKey, frame) { const idx = this._frameKeys.indexOf(frameKey); if (idx < 0) return; if (frame) { if (this.renderMode === SPRITE_RENDERMODE_SIMPLE) { this._meshes[idx] = this._createSimpleMesh(frame); } } else { this._meshes[idx] = null; } this.fire("set:meshes"); } _onFrameRemoved(frameKey) { const idx = this._frameKeys.indexOf(frameKey); if (idx < 0) return; this._meshes[idx] = null; this.fire("set:meshes"); } startUpdate() { this._updatingProperties = true; this._meshesDirty = false; } endUpdate() { this._updatingProperties = false; if (this._meshesDirty && this._atlas && this._frameKeys) { this._createMeshes(); } this._meshesDirty = false; } /** * Free up the meshes created by the sprite. */ destroy() { for (const mesh of this._meshes) { if (mesh) { mesh.destroy(); } } this._meshes.length = 0; } } export { Sprite };