UNPKG

three-aseprite

Version:

Three.js mesh rendering for Aseprite JSON sprite sheets

824 lines 33.4 kB
"use strict"; var __createBinding = (this && this.__createBinding) || (Object.create ? (function(o, m, k, k2) { if (k2 === undefined) k2 = k; var desc = Object.getOwnPropertyDescriptor(m, k); if (!desc || ("get" in desc ? !m.__esModule : desc.writable || desc.configurable)) { desc = { enumerable: true, get: function() { return m[k]; } }; } Object.defineProperty(o, k2, desc); }) : (function(o, m, k, k2) { if (k2 === undefined) k2 = k; o[k2] = m[k]; })); var __exportStar = (this && this.__exportStar) || function(m, exports) { for (var p in m) if (p !== "default" && !Object.prototype.hasOwnProperty.call(exports, p)) __createBinding(exports, m, p); }; Object.defineProperty(exports, "__esModule", { value: true }); exports.ThreeAseprite = exports.StandardEvents = exports.defaultTagName = exports.defaultLayerName = void 0; const three_1 = require("three"); const aseprite_shader_material_1 = require("./aseprite-shader-material"); __exportStar(require("./aseprite-export-types"), exports); // Empty frame definition for internal use. const emptyFrameDef = { frame: { x: 0, y: 0, w: 0, h: 0 }, rotated: false, trimmed: true, spriteSourceSize: { x: 0, y: 0, w: 0, h: 0 }, sourceSize: { w: 0, h: 0 }, duration: 100, }; exports.defaultLayerName = "Default"; exports.defaultTagName = "Default"; var StandardEvents; (function (StandardEvents) { StandardEvents["animationComplete"] = "animationComplete"; StandardEvents["tagSwitched"] = "tagSwitched"; })(StandardEvents = exports.StandardEvents || (exports.StandardEvents = {})); const ANIMATION_COMPLETE_EVENT = { type: StandardEvents.animationComplete, }; const TAG_SWITCHED_EVENT = { type: StandardEvents.tagSwitched, }; /** * Three.js aseprite sprite renderer class. */ class ThreeAseprite extends three_1.EventDispatcher { constructor(options) { var _a, _b, _c, _d, _e, _f, _g, _h; super(); this.playingAnimation = true; this.playingAnimationBackwards = false; this.frames = {}; this.tags = {}; this.layerGroups = {}; this.minFrame = 0; this.maxFrame = 0; this.currentFrame = 0; this.currentFrameTime = 0; this.currentTag = null; this.currentTagFrame = null; this.offset = new three_1.Vector2(); this.triggers = {}; // Preserve options to allow easy cloning later. this.options = options; this.sourceJSON = options.sourceJSON; if (options.offset) { this.offset.x = options.offset.x; this.offset.y = options.offset.y; } // Pick a z offset per layer so that higher layers are above lower layers. const layerDepth = (_a = options.layerDepth) !== null && _a !== void 0 ? _a : 0.05; // Assign texture, size. this.texture = options.texture; // We assume that textures are loaded through Three.TextureLoaded // and contain references to images. Video textures not supported. this.textureWidth = this.texture.image.naturalWidth; this.textureHeight = this.texture.image.naturalHeight; // Extract layers and groups from the sprite sheet definitions. if (options.layers) { this.orderedLayers = options.layers; } else if ((_c = (_b = options.sourceJSON.meta.layers) === null || _b === void 0 ? void 0 : _b.length) !== null && _c !== void 0 ? _c : 0 > 0) { this.orderedLayers = []; for (const layerInfo of (_d = options.sourceJSON.meta.layers) !== null && _d !== void 0 ? _d : []) { if (layerInfo.opacity !== undefined) { this.orderedLayers.push(layerInfo.name); } else { if (!this.layerGroups[layerInfo.name]) this.layerGroups[layerInfo.name] = []; } if (layerInfo.group !== undefined) { if (!this.layerGroups[layerInfo.group]) this.layerGroups[layerInfo.group] = []; this.layerGroups[layerInfo.group].push(layerInfo.name); } } } else { this.orderedLayers = [exports.defaultLayerName]; } // If tags are available, populate tags and min/max frames. if ((_e = options.sourceJSON.meta.frameTags) === null || _e === void 0 ? void 0 : _e.length) { this.currentTag = (_g = (_f = options.sourceJSON.meta.frameTags[0]) === null || _f === void 0 ? void 0 : _f.name) !== null && _g !== void 0 ? _g : null; for (const frameTag of options.sourceJSON.meta.frameTags) { this.tags[frameTag.name] = frameTag; if (this.minFrame === -1) { this.minFrame = frameTag.from; } else { this.minFrame = Math.min(this.minFrame, frameTag.from); } this.maxFrame = Math.max(this.maxFrame, frameTag.to); } } // Get the hash of frame filenames to frames. let framesByFilename; if (Array.isArray(options.sourceJSON.frames)) { framesByFilename = {}; for (const frame of options.sourceJSON.frames) { framesByFilename[frame.filename] = frame; } } else { framesByFilename = options.sourceJSON.frames; } // Ensure we have at least one frame. const framesByFilenameKeys = Object.keys(framesByFilename); if (framesByFilenameKeys.length === 0) throw new Error("[ThreeAseprite]: no frames present in source JSON."); // Populate frames. This is easy if either: // - there are layers, tags, and there's a frameName function. // - there are no layers or tags. // TODO: support pattern matching. if (this.minFrame === -1) { this.minFrame = Number.parseInt(framesByFilenameKeys[0]); if (Number.isNaN(this.minFrame)) throw new Error("[ThreeAseprite]: unable to resolve first frame index for sprite."); } if (this.maxFrame === -1) { if (this.orderedLayers.length > 1) console.warn(`[ThreeAseprite]: layers were supplied but frames were not - using best guess based on frame defs length / layer count.`); this.maxFrame = this.minFrame + Math.floor(framesByFilenameKeys.length / this.orderedLayers.length) - 1; } if (this.minFrame > this.maxFrame) throw new Error("[ThreeAseprite]: unable to resolve frame range."); this.currentFrame = this.minFrame; // Extract and map frames. if (options.frameNameToFrameParams) { const frameNameToFrameParams = options.frameNameToFrameParams; for (const [frameName, frame] of Object.entries(framesByFilename)) { const frameParams = frameNameToFrameParams(frameName); const frameIndex = frameParams.frame; const frameLayer = (_h = frameParams.layerName) !== null && _h !== void 0 ? _h : exports.defaultLayerName; if (frameIndex === undefined) { console.warn("[ThreeAseprite]: failed to get frame index from frameNameToFrameParams."); continue; } let frameInfo = this.frames[frameIndex]; if (frameInfo === undefined) { frameInfo = { duration: frame.duration, layerFrames: {}, }; this.frames[frameIndex] = frameInfo; } frameInfo.layerFrames[frameLayer] = frame; } } else { let frameKeyIndex = 0; for (let frameIndex = this.minFrame; frameIndex <= this.maxFrame; frameIndex++) { const frameInfo = { duration: 0, layerFrames: {}, }; for (let layerIndex = 0; layerIndex < this.orderedLayers.length; layerIndex++) { const layerName = this.orderedLayers[layerIndex]; let frameName; if (options.frameName) { frameName = options.frameName({ frame: frameIndex, layerName: layerName, }); } else { // This makes a huge assumption that Aseprite will always export things in the expected order. frameName = framesByFilenameKeys[frameKeyIndex++]; } if (frameName === undefined) throw new Error(`[ThreeAsperite]: unable to identify frame name (frame ${frameIndex}, layer ${layerName}).`); const frameDef = framesByFilename[frameName]; if (frameDef === undefined) continue; frameInfo.duration = Math.max(frameInfo.duration, frameDef.duration); frameInfo.layerFrames[layerName] = frameDef; } this.frames[frameIndex] = frameInfo; } } // Create geometry. const quadCount = this.orderedLayers.length; this.vtxIndex = new Uint16Array(6 * quadCount); this.vtxPos = new Float32Array(12 * quadCount); this.vtxUV = new Float32Array(8 * quadCount); this.vtxOpacity = new Float32Array(4 * quadCount); this.vtxColor = new Float32Array(12 * quadCount); this.vtxFade = new Float32Array(16 * quadCount); this.vtxOutlineSpread = new Float32Array(8 * quadCount); this.geometry = new three_1.BufferGeometry(); this.geometry.setAttribute("position", new three_1.BufferAttribute(this.vtxPos, 3)); this.geometry.setAttribute("uv", new three_1.BufferAttribute(this.vtxUV, 2)); this.geometry.setAttribute("vtxOpacity", new three_1.BufferAttribute(this.vtxOpacity, 1)); this.geometry.setAttribute("vtxColor", new three_1.BufferAttribute(this.vtxColor, 3)); this.geometry.setAttribute("vtxFade", new three_1.BufferAttribute(this.vtxFade, 4)); this.geometry.setAttribute("vtxOutlineSpread", new three_1.BufferAttribute(this.vtxOutlineSpread, 2)); this.geometry.setIndex(new three_1.BufferAttribute(this.vtxIndex, 1)); this.vtxOpacity.fill(1); this.vtxColor.fill(1); this.vtxOutlineSpread.fill(1); // Initialize indices and layer z values. for (let qi = 0; qi < quadCount; qi++) { const qi6 = qi * 6; const qi4 = qi * 4; this.vtxIndex[qi6 + 0] = qi4 + 0; this.vtxIndex[qi6 + 1] = qi4 + 1; this.vtxIndex[qi6 + 2] = qi4 + 2; this.vtxIndex[qi6 + 3] = qi4 + 2; this.vtxIndex[qi6 + 4] = qi4 + 3; this.vtxIndex[qi6 + 5] = qi4 + 0; const qi12 = qi * 12; this.vtxPos[qi12 + 2] = qi * layerDepth; this.vtxPos[qi12 + 5] = qi * layerDepth; this.vtxPos[qi12 + 8] = qi * layerDepth; this.vtxPos[qi12 + 11] = qi * layerDepth; } // Create material. this.material = (0, aseprite_shader_material_1.createAspriteShaderMaterial)({ texture: options.texture, }); // Create mesh. this.mesh = new three_1.Mesh(this.geometry, this.material); // We assume the coordinate system is the same as screen-space coordinates in all math, // buy typical camera coordinates are Y-up. this.mesh.scale.y = -1; // Initialize geometry to default tag and frame. this.updateGeometryToFrame(this.currentFrame); } clone() { // TODO: advance to the correct frame. // TODO: copy opacity/color configs. return new ThreeAseprite(this.options); } getCurrentFrame() { return this.currentFrame; } getFrameDuration(frameNumber) { const frame = this.frames[frameNumber]; if (frame === undefined) return 0; return frame.duration; } getCurrentFrameDuration() { const frame = this.frames[this.currentFrame]; if (frame === undefined) return 0; return frame.duration; } getCurrentTag() { return this.currentTag; } getCurrentTagFrame() { if (this.currentTag === null) return null; const tag = this.tags[this.currentTag]; return this.currentTagFrame; } getCurrentTagFrameCount() { if (this.currentTag === null) return null; const tag = this.tags[this.currentTag]; return tag.from - tag.to + 1; } animate(deltaMs) { if (!this.playingAnimation) return; let frameNo = this.currentFrame; let frame = this.frames[frameNo]; if (frame === undefined) return; if (frame.duration >= this.currentFrameTime + deltaMs) { this.currentFrameTime += deltaMs; return; } const tag = this.currentTag ? this.tags[this.currentTag] : undefined; const step = this.playingAnimationBackwards ? -1 : 1; let remainingDeltaMs = this.currentFrameTime + deltaMs; let frameNoMin = this.minFrame; let frameNoRange = this.maxFrame - this.minFrame + 1; if (tag !== undefined) { frameNoMin = tag.from; frameNoRange = tag.to - tag.from + 1; } let eventsToDispatch = []; while (remainingDeltaMs > frame.duration) { remainingDeltaMs -= frame.duration || 1; frameNo = ((frameNo - frameNoMin + step + frameNoRange) % frameNoRange) + frameNoMin; if (this.playingAnimationBackwards) { if (frameNo === frameNoMin + frameNoRange - 1) { eventsToDispatch.push(ANIMATION_COMPLETE_EVENT); } } else { if (frameNo === frameNoMin) { eventsToDispatch.push(ANIMATION_COMPLETE_EVENT); } } frame = this.frames[frameNo]; if (frame === undefined) return; this.currentFrame = frameNo; const triggers = this.triggers[frameNo]; if (triggers !== undefined) { for (const trigger of triggers) { if (trigger.tagName !== null && trigger.tagName !== this.currentTag) continue; eventsToDispatch.push({ type: trigger.eventName, }); } } } this.currentFrameTime = -remainingDeltaMs; if (tag !== undefined) { this.currentTagFrame = frameNo - tag.from; } this.updateGeometryToFrame(frameNo); for (const event of eventsToDispatch) { this.dispatchEvent(event); } } gotoTag(tagName) { if (this.currentTag === tagName) return; if (tagName === null) { this.currentTag = null; this.currentTagFrame = null; return; } const tag = this.tags[tagName]; if (tag === undefined) return; this.currentTag = tagName; this.currentTagFrame = 0; this.currentFrame = tag.from; this.currentFrameTime = 0; this.updateGeometryToFrame(this.currentFrame); this.dispatchEvent(TAG_SWITCHED_EVENT); } gotoFrame(frameNo) { if (this.currentFrame === frameNo) return; this.currentFrame = frameNo; this.currentFrameTime = 0; this.updateGeometryToFrame(frameNo); } gotoTagFrame(tagFrameNo) { if (this.currentTag === null) return; const tag = this.tags[this.currentTag]; if (tag === undefined) return; this.currentTagFrame = tagFrameNo; this.currentFrame = tagFrameNo + tag.from; this.currentFrameTime = 0; this.updateGeometryToFrame(this.currentFrame); } setClipping(clipping) { this.clipping = clipping !== null && clipping !== void 0 ? clipping : undefined; } updateGeometryToFrame(frameNo) { var _a; const { textureWidth, textureHeight, offset, clipping, outlineSpread } = this; const { x: xOffset, y: yOffset } = offset; const invWidth = 1 / textureWidth; const invHeight = 1 / textureHeight; const frameDef = this.frames[frameNo]; if (frameDef === undefined) throw new Error(`[ThreeAseprite]: unknown frame "#${frameNo}".`); for (let li = 0; li < this.orderedLayers.length; li++) { const layerName = this.orderedLayers[li]; const layerFrameDef = (_a = frameDef.layerFrames[layerName]) !== null && _a !== void 0 ? _a : emptyFrameDef; let { x, y, w, h } = layerFrameDef.frame; let { x: sx, y: sy } = layerFrameDef.spriteSourceSize; let { w: sw, h: sh } = layerFrameDef.sourceSize; // Apply optional clipping. if (clipping !== undefined) { const { xMin, xMax, yMin, yMax } = clipping; if (xMin !== undefined && xMin > sx) { const deltaX = xMin - sx; x += deltaX; sx += deltaX; w -= deltaX; } if (xMax !== undefined && xMax < sx + w) { const deltaX = sx + w - xMax; w -= deltaX; } if (yMin !== undefined && yMin > sy) { const deltaY = yMin - sy; y += deltaY; sy += deltaY; h -= deltaY; } if (yMax !== undefined && yMax < sy + h) { const deltaY = sy + h - yMax; h -= deltaY; } if (w <= 0) { x = 0; w = 0; sx = 0; sw = 0; } if (h <= 0) { y = 0; h = 0; } } // Apply optional outlining. if (outlineSpread !== undefined && layerFrameDef !== emptyFrameDef) { sx -= outlineSpread; sy -= outlineSpread; x -= outlineSpread; y -= outlineSpread; w += outlineSpread * 2; h += outlineSpread * 2; } // Update vertex positions. const vtxI = 12 * li; this.vtxPos[vtxI + 0] = w * 0 + sx - sw * 0.5 + xOffset; this.vtxPos[vtxI + 1] = h * 0 + sy - sh * 0.5 + yOffset; this.vtxPos[vtxI + 3] = w * 1 + sx - sw * 0.5 + xOffset; this.vtxPos[vtxI + 4] = h * 0 + sy - sh * 0.5 + yOffset; this.vtxPos[vtxI + 6] = w * 1 + sx - sw * 0.5 + xOffset; this.vtxPos[vtxI + 7] = h * 1 + sy - sh * 0.5 + yOffset; this.vtxPos[vtxI + 9] = w * 0 + sx - sw * 0.5 + xOffset; this.vtxPos[vtxI + 10] = h * 1 + sy - sh * 0.5 + yOffset; this.geometry.getAttribute("position").needsUpdate = true; // Update texture coordinates. const vtxUVI = 8 * li; this.vtxUV[vtxUVI + 0] = x * invWidth; this.vtxUV[vtxUVI + 1] = 1 - y * invHeight; this.vtxUV[vtxUVI + 2] = (x + w) * invWidth; this.vtxUV[vtxUVI + 3] = 1 - y * invHeight; this.vtxUV[vtxUVI + 4] = (x + w) * invWidth; this.vtxUV[vtxUVI + 5] = 1 - (y + h) * invHeight; this.vtxUV[vtxUVI + 6] = x * invWidth; this.vtxUV[vtxUVI + 7] = 1 - (y + h) * invHeight; this.geometry.getAttribute("uv").needsUpdate = true; // Update bounding box. this.geometry.computeBoundingBox(); } } expandLayerGroups(attrMap) { const assignments = {}; const toAssign = [...Object.entries(attrMap)]; while (toAssign.length > 0) { const assignment = toAssign.pop(); if (assignment === undefined) break; const [layer, value] = assignment; assignments[layer] = value; if (this.layerGroups[layer]) { for (const subLayer of this.layerGroups[layer]) { toAssign.push([subLayer, value]); } } } return assignments; } /** * Sets the sprite's opacity. * @param opacity */ setOpacity(opacity) { this.material.uniforms.opacity.value = opacity; this.material.uniformsNeedUpdate = true; } /** * Sets an optional multiplicative color for the sprite. * @param color */ setColor(color) { this.material.uniforms.color.value.set(color); this.material.uniformsNeedUpdate = true; } /** * Sets an optional fade color for the sprite. * @param fadeColor * @param fadeAmount */ setFade(fadeColor, fadeAmount) { const color = new three_1.Color(fadeColor); const fade4 = this.material.uniforms.fade.value; fade4.set(color.r, color.g, color.b, fadeAmount); this.material.uniformsNeedUpdate = true; } /** * Sets an optional opacity per layer, plus an optional default to apply to all others. * This is useful when sprites have multiple conditional layers. * @param opacityMap * @param defaultOpacity */ setLayerOpacities(opacityMap, defaultOpacity) { var _a; const opacityAssignments = this.expandLayerGroups(opacityMap); for (let li = 0; li < this.orderedLayers.length; li++) { const layerName = this.orderedLayers[li]; const layerOpacity = (_a = opacityAssignments[layerName]) !== null && _a !== void 0 ? _a : defaultOpacity; if (layerOpacity === undefined) continue; const oi = li * 4; this.vtxOpacity[oi + 0] = layerOpacity; this.vtxOpacity[oi + 1] = layerOpacity; this.vtxOpacity[oi + 2] = layerOpacity; this.vtxOpacity[oi + 3] = layerOpacity; } this.geometry.getAttribute("vtxOpacity").needsUpdate = true; } /** * Sets an optional multiplicative color per layer. This is useful for sprite recoloration. * @param colorMap */ setLayerColors(colorMap) { const colorAssignments = this.expandLayerGroups(colorMap); const color = new three_1.Color(); for (let li = 0; li < this.orderedLayers.length; li++) { const layerName = this.orderedLayers[li]; const colorAssignment = colorAssignments[layerName]; if (colorAssignment === undefined) continue; color.set(colorAssignment); const ci = li * 12; color.toArray(this.vtxColor, ci + 0); color.toArray(this.vtxColor, ci + 3); color.toArray(this.vtxColor, ci + 6); color.toArray(this.vtxColor, ci + 9); } this.geometry.getAttribute("vtxColor").needsUpdate = true; } /** * Sets an optional fade color and amount per layer. This is useful for sprite recoloration. * @param colorMap */ setLayerFades(colorMap) { const fadeAssignments = this.expandLayerGroups(colorMap); const color = new three_1.Color(); for (let li = 0; li < this.orderedLayers.length; li++) { const layerName = this.orderedLayers[li]; const fade = fadeAssignments[layerName]; if (fade === undefined) continue; const [colorRep, fadeAmount] = fade; color.set(colorRep); const fi = li * 16; color.toArray(this.vtxFade, fi + 0); color.toArray(this.vtxFade, fi + 4); color.toArray(this.vtxFade, fi + 8); color.toArray(this.vtxFade, fi + 12); this.vtxFade[fi + 3] = fadeAmount; this.vtxFade[fi + 7] = fadeAmount; this.vtxFade[fi + 11] = fadeAmount; this.vtxFade[fi + 15] = fadeAmount; } this.geometry.getAttribute("vtxFade").needsUpdate = true; } /** * Sets an optional outline on the sprite. Outlines expand the boundaries * of the sprite by a specified number of pixels with a specified color and opacity. * * When using this feature, ensure the sprite sheet has a padding equal or greater * than 2 * outlineWidth; otherwise, collisions with other frames will occur. * @param outlineWidth - width, in sprite-space pixels, of the outline. * @param outlineColor - color of the outline. * @param outlineOpacity - opacity of the outline. */ setOutline(outlineWidth, outlineColor, outlineOpacity) { const uOutlineSpread = this.material.uniforms.outlineSpread .value; const uOutline = this.material.uniforms.outline.value; if (outlineWidth > 0 && outlineColor !== undefined) { const outlineColorAsColor = new three_1.Color(outlineColor); this.outlineSpread = outlineWidth; uOutlineSpread.set(outlineWidth / this.textureWidth, outlineWidth / this.textureHeight); uOutline.set(outlineColorAsColor.r, outlineColorAsColor.g, outlineColorAsColor.b, outlineOpacity !== null && outlineOpacity !== void 0 ? outlineOpacity : 1); } else { this.outlineSpread = 0; uOutlineSpread.set(0, 0); uOutline.set(0, 0, 0, 0); } this.material.uniformsNeedUpdate = true; } /** * Sets optional outline distances for each layer of the sprite. * These widths are relative to the outline width set in setOutline. */ setRealtiveLayerOutlines(outlineSizeMap) { const outlineAssignments = this.expandLayerGroups(outlineSizeMap); for (let li = 0; li < this.orderedLayers.length; li++) { const layerName = this.orderedLayers[li]; const size = outlineAssignments[layerName]; if (size === undefined) continue; const fi = li * 8; for (let oi = 0; oi < 8; oi++) this.vtxOutlineSpread[fi + oi] = size; } this.geometry.getAttribute("vtxOutlineSpread").needsUpdate = true; } /** * Get available layers. * @returns */ getLayers() { return this.orderedLayers; } /** * Get available layer groups. * @returns */ getLayerGroups() { return this.layerGroups; } /** * Get available tags. * @returns */ getTags() { return this.tags; } /** * Adds an event trigger at the specified frame. * @param frameNo * @param eventName */ addFrameTrigger(frameNo, eventName) { const trigger = { tagName: null, eventName, }; if (this.triggers[frameNo] === undefined) this.triggers[frameNo] = []; this.triggers[frameNo].push(trigger); } /** * Adds an event trigger at the specified tag and frame. * @param tagName * @param tagFrameNo * @param eventName * @returns */ addTagFrameTrigger(tagName, tagFrameNo, eventName) { const tag = this.tags[tagName]; if (tag === undefined) return; const frameNo = tag.from + tagFrameNo; const trigger = { tagName, eventName, }; if (this.triggers[frameNo] === undefined) this.triggers[frameNo] = []; this.triggers[frameNo].push(trigger); } /** * Removes an event trigger. * @param frameNo * @param eventName */ removeFrameTrigger(frameNo, eventName) { if (this.triggers[frameNo]) this.triggers[frameNo] = this.triggers[frameNo].filter((t) => { if (t.eventName !== eventName) return true; if (t.tagName !== null) return true; return false; }); } /** * Removes a tagged event trigger. */ removeTagFrameTrigger(tagName, tagFrameNo, eventName) { const tag = this.tags[tagName]; if (tag === undefined) return; const frameNo = tag.from + tagFrameNo; if (this.triggers[frameNo]) this.triggers[frameNo] = this.triggers[frameNo].filter((t) => { if (t.eventName !== eventName) return true; if (t.tagName !== tagName) return true; return false; }); } /** * Determines whether a given layer of layer group is present within a given tag. * This is primairly used by the example to produce a clickable group/tag matrix. * @param layerOrGroupName * @param tagName * @param detectEmpty */ hasLayerAtTag(layerOrGroupName, tagName, detectEmpty) { const allSubLayers = Object.keys(this.expandLayerGroups({ [layerOrGroupName]: true })); const tag = this.tags[tagName]; if (tag === undefined) return false; for (let fi = tag.from; fi <= tag.to; fi++) { const frame = this.frames[fi]; if (frame === undefined) continue; for (const layerName of allSubLayers) { const layerInfo = frame.layerFrames[layerName]; if (layerInfo === undefined) continue; if (detectEmpty && layerInfo.frame.w < 2 && layerInfo.frame.h < 2) continue; return true; } } return false; } /** * Dispose of geometry and material. */ dispose() { this.geometry.dispose(); this.material.dispose(); } /** * Gets the bounding box, in pixels, of a given layer. * @param layerName */ getLayerBoundingBox(layerName, tagName, frameNo) { const initialLayers = {}; if (Array.isArray(layerName)) { for (const singleLayerName of layerName) { initialLayers[singleLayerName] = true; } } else { initialLayers[layerName] = true; } const allSubLayers = Object.keys(this.expandLayerGroups(initialLayers)); return this.getMultiLayerBoundingBox(allSubLayers, tagName, frameNo); } getMultiLayerBoundingBox(layerNames, tagName, frameNo) { let frameIndex = this.currentFrame; if (tagName && frameNo) { frameIndex = this.tags[tagName].from + frameNo; } else if (tagName) { frameIndex = this.tags[tagName].from; } else if (frameNo) { frameIndex = frameNo; } const offset = this.offset; const frame = this.frames[frameIndex]; if (frame === undefined) throw new Error("[ThreeAseprite]: frame not found."); let bounds = null; for (const layerName of layerNames) { const layerFrame = frame.layerFrames[layerName]; if (layerFrame === undefined) continue; const { x, y, w, h } = layerFrame.spriteSourceSize; const { w: sw, h: sh } = layerFrame.sourceSize; // Empty layers don't have bounding boxes. if (x === 0 && y === 0 && w === 1 && h === 1) continue; const leftBound = w * 0 + x - sw * 0.5 + offset.x; const rightBound = w * 1 + x - sw * 0.5 + offset.x; const bottomBound = h * 0 + y - sh * 0.5 + offset.y; const topBound = h * 1 + y - sh * 0.5 + offset.y; if (bounds === null) { bounds = { xMin: leftBound, xMax: rightBound, yMin: bottomBound, yMax: topBound, }; } else { bounds.xMin = Math.min(bounds.xMin, leftBound); bounds.xMax = Math.max(bounds.xMax, rightBound); bounds.yMin = Math.min(bounds.yMin, bottomBound); bounds.yMax = Math.max(bounds.yMax, topBound); } } return bounds; } // Redeclare parent class's method signature. // This addresses a TS / VsCode bug where these methods aren't available. addEventListener(type, listener) { super.addEventListener(type, listener); } hasEventListener(type, listener) { return super.hasEventListener(type, listener); } removeEventListener(type, listener) { super.removeEventListener(type, listener); } } exports.ThreeAseprite = ThreeAseprite; //# sourceMappingURL=index.js.map