playcanvas
Version:
Open-source WebGL/WebGPU 3D engine for the web
259 lines (258 loc) • 6.38 kB
JavaScript
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 {
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();
}
}
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);
}
get frameKeys() {
return this._frameKeys;
}
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);
}
get atlas() {
return this._atlas;
}
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();
}
}
}
get pixelsPerUnit() {
return this._pixelsPerUnit;
}
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();
}
}
}
}
get renderMode() {
return this._renderMode;
}
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;
}
destroy() {
for (const mesh of this._meshes) {
if (mesh) {
mesh.destroy();
}
}
this._meshes.length = 0;
}
}
export {
Sprite
};