@pixi-spine/base
Version:
Base of pixi-spine integration, common files for spine runtimes of different versions
1 lines • 156 kB
Source Map (JSON)
{"version":3,"file":"base.mjs","sources":["../src/core/AttachmentType.ts","../src/core/BinaryInput.ts","../src/core/IAnimation.ts","../src/core/IConstraint.ts","../src/core/ISkeleton.ts","../src/core/TextureRegion.ts","../src/core/TextureAtlas.ts","../src/core/Utils.ts","../src/core/SkeletonBoundsBase.ts","../src/settings.ts","../src/SpineBase.ts","../src/SpineDebugRenderer.ts"],"sourcesContent":["/**\n * @public\n */\nexport enum AttachmentType {\n Region,\n BoundingBox,\n Mesh,\n LinkedMesh,\n Path,\n Point,\n Clipping,\n}\n","/**\n * @public\n */\nexport class BinaryInput {\n constructor(data: Uint8Array, public strings = new Array<string>(), private index: number = 0, private buffer = new DataView(data.buffer)) {}\n\n readByte(): number {\n return this.buffer.getInt8(this.index++);\n }\n\n readUnsignedByte(): number {\n return this.buffer.getUint8(this.index++);\n }\n\n readShort(): number {\n const value = this.buffer.getInt16(this.index);\n\n this.index += 2;\n\n return value;\n }\n\n readInt32(): number {\n const value = this.buffer.getInt32(this.index);\n\n this.index += 4;\n\n return value;\n }\n\n readInt(optimizePositive: boolean) {\n let b = this.readByte();\n let result = b & 0x7f;\n\n if ((b & 0x80) != 0) {\n b = this.readByte();\n result |= (b & 0x7f) << 7;\n if ((b & 0x80) != 0) {\n b = this.readByte();\n result |= (b & 0x7f) << 14;\n if ((b & 0x80) != 0) {\n b = this.readByte();\n result |= (b & 0x7f) << 21;\n if ((b & 0x80) != 0) {\n b = this.readByte();\n result |= (b & 0x7f) << 28;\n }\n }\n }\n }\n\n return optimizePositive ? result : (result >>> 1) ^ -(result & 1);\n }\n\n readStringRef(): string | null {\n const index = this.readInt(true);\n\n return index == 0 ? null : this.strings[index - 1];\n }\n\n readString(): string | null {\n let byteCount = this.readInt(true);\n\n switch (byteCount) {\n case 0:\n return null;\n case 1:\n return '';\n }\n byteCount--;\n let chars = '';\n\n for (let i = 0; i < byteCount; ) {\n const b = this.readUnsignedByte();\n\n switch (b >> 4) {\n case 12:\n case 13:\n chars += String.fromCharCode(((b & 0x1f) << 6) | (this.readByte() & 0x3f));\n i += 2;\n break;\n case 14:\n chars += String.fromCharCode(((b & 0x0f) << 12) | ((this.readByte() & 0x3f) << 6) | (this.readByte() & 0x3f));\n i += 3;\n break;\n default:\n chars += String.fromCharCode(b);\n i++;\n }\n }\n\n return chars;\n }\n\n readFloat(): number {\n const value = this.buffer.getFloat32(this.index);\n\n this.index += 4;\n\n return value;\n }\n\n readBoolean(): boolean {\n return this.readByte() != 0;\n }\n}\n","import type { ISkeleton, ISkeletonData } from './ISkeleton';\nimport type { Map } from './Utils';\n\n// Those enums were moved from Animation.ts of spine 3.8 and 4.0\n\n/** Controls how a timeline value is mixed with the setup pose value or current pose value when a timeline's `alpha`\n * < 1.\n *\n * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}.\n * @public\n * */\nexport enum MixBlend {\n /** Transitions from the setup value to the timeline value (the current value is not used). Before the first key, the setup\n * value is set. */\n setup,\n /** Transitions from the current value to the timeline value. Before the first key, transitions from the current value to\n * the setup value. Timelines which perform instant transitions, such as DrawOrderTimeline or\n * AttachmentTimeline, use the setup value before the first key.\n *\n * `first` is intended for the first animations applied, not for animations layered on top of those. */\n first,\n /** Transitions from the current value to the timeline value. No change is made before the first key (the current value is\n * kept until the first key).\n *\n * `replace` is intended for animations layered on top of others, not for the first animations applied. */\n replace,\n /** Transitions from the current value to the current value plus the timeline value. No change is made before the first key\n * (the current value is kept until the first key).\n *\n * `add` is intended for animations layered on top of others, not for the first animations applied. Properties\n * keyed by additive animations must be set manually or by another animation before applying the additive animations, else\n * the property values will increase continually. */\n add,\n}\n\n/** Indicates whether a timeline's `alpha` is mixing out over time toward 0 (the setup or current pose value) or\n * mixing in toward 1 (the timeline's value).\n *\n * See Timeline {@link Timeline#apply(Skeleton, float, float, Array, float, MixBlend, MixDirection)}.\n * @public\n * */\nexport enum MixDirection {\n mixIn,\n mixOut,\n}\n\n/**\n * @public\n */\nexport interface IAnimation<Timeline extends ITimeline = ITimeline> {\n name: string;\n timelines: Timeline[];\n duration: number;\n}\n\n/**\n * @public\n */\nexport interface IAnimationState<AnimationStateData extends IAnimationStateData = IAnimationStateData> {\n data: AnimationStateData;\n tracks: ITrackEntry[];\n listeners: IAnimationStateListener[];\n timeScale: number;\n\n update(dt: number): void;\n apply(skeleton: ISkeleton): boolean;\n\n setAnimation(trackIndex: number, animationName: string, loop: boolean): ITrackEntry;\n addAnimation(trackIndex: number, animationName: string, loop: boolean, delay: number): ITrackEntry;\n addEmptyAnimation(trackIndex: number, mixDuration: number, delay: number): ITrackEntry;\n setEmptyAnimation(trackIndex: number, mixDuration: number): ITrackEntry;\n setEmptyAnimations(mixDuration: number): void;\n hasAnimation(animationName: string): boolean;\n addListener(listener: IAnimationStateListener): void;\n removeListener(listener: IAnimationStateListener): void;\n clearListeners(): void;\n clearTracks(): void;\n clearTrack(index: number): void;\n}\n\n/**\n * @public\n */\nexport interface IAnimationStateData<SkeletonData extends ISkeletonData = ISkeletonData, Animation extends IAnimation = IAnimation> {\n skeletonData: SkeletonData;\n animationToMixTime: Map<number>;\n defaultMix: number;\n setMix(fromName: string, toName: string, duration: number): void;\n setMixWith(from: Animation, to: Animation, duration: number): void;\n getMix(from: Animation, to: Animation): number;\n}\n\n/**\n * @public\n */\nexport interface IAnimationStateListener {\n start?(entry: ITrackEntry): void;\n interrupt?(entry: ITrackEntry): void;\n end?(entry: ITrackEntry): void;\n dispose?(entry: ITrackEntry): void;\n complete?(entry: ITrackEntry): void;\n event?(entry: ITrackEntry, event: IEvent): void;\n}\n\n/**\n * @public\n */\nexport interface ITimeline {}\n\n/**\n * @public\n */\nexport interface ITrackEntry {\n trackIndex: number;\n loop: boolean;\n animationEnd: number;\n listener: IAnimationStateListener;\n\n delay: number;\n trackTime: number;\n trackLast: number;\n nextTrackLast: number;\n trackEnd: number;\n timeScale: number;\n alpha: number;\n mixTime: number;\n mixDuration: number;\n interruptAlpha: number;\n totalAlpha: number;\n}\n\n/**\n * @public\n */\nexport interface IEventData {\n name: string;\n}\n\n/**\n * @public\n */\nexport interface IEvent {\n time: number;\n data: IEventData;\n}\n","// These enums were moved from PathConstraintData.ts of spine 3.7, 3.8 and 4.0\n\n/** Controls how the first bone is positioned along the path.\n *\n * See [Position mode](http://esotericsoftware.com/spine-path-constraints#Position-mode) in the Spine User Guide.\n * @public\n * */\nexport enum PositionMode {\n Fixed,\n Percent,\n}\n\n/** Controls how bones are rotated, translated, and scaled to match the path.\n *\n * [Rotate mode](http://esotericsoftware.com/spine-path-constraints#Rotate-mod) in the Spine User Guide.\n * @public\n * */\nexport enum RotateMode {\n Tangent,\n Chain,\n ChainScale,\n}\n\n/**\n * @public\n */\nexport interface IConstraintData {\n name: string;\n order: number;\n}\n\n/**\n * @public\n */\nexport interface IIkConstraint {\n data: IIkConstraintData;\n /** -1 | 0 | 1 */\n bendDirection: number;\n compress: boolean;\n stretch: boolean;\n\n /** A percentage (0-1) */\n mix: number;\n}\n\n/**\n * @public\n */\nexport interface IIkConstraintData extends IConstraintData {\n /** -1 | 0 | 1 */\n bendDirection: number;\n compress: boolean;\n stretch: boolean;\n uniform: boolean;\n\n /** A percentage (0-1) */\n mix: number;\n}\n\n/**\n * @public\n */\nexport interface IPathConstraint {\n data: IPathConstraintData;\n position: number;\n spacing: number;\n\n spaces: number[];\n positions: number[];\n world: number[];\n curves: number[];\n lengths: number[];\n segments: number[];\n}\n\n/**\n * @public\n */\nexport interface IPathConstraintData extends IConstraintData {\n positionMode: PositionMode;\n rotateMode: RotateMode;\n offsetRotation: number;\n position: number;\n spacing: number;\n}\n\n/**\n * @public\n */\nexport interface ITransformConstraint {\n data: ITransformConstraintData;\n}\n\n/**\n * @public\n */\nexport interface ITransformConstraintData extends IConstraintData {\n offsetRotation: number;\n offsetX: number;\n offsetY: number;\n offsetScaleX: number;\n offsetScaleY: number;\n offsetShearY: number;\n relative: boolean;\n local: boolean;\n}\n","import type { AttachmentType } from './AttachmentType';\nimport type { IAnimation, IEventData } from './IAnimation';\nimport type { IIkConstraintData, IPathConstraintData, ITransformConstraintData } from './IConstraint';\nimport type { Color, Vector2, Map } from './Utils';\nimport type { TextureRegion } from './TextureRegion';\nimport type { BLEND_MODES, Matrix } from '@pixi/core';\n\n// This enum was moved from BoneData.ts of spine 3.7, 3.8 and 4.0\n\n/** Determines how a bone inherits world transforms from parent bones.\n * @public\n * */\nexport enum TransformMode {\n Normal,\n OnlyTranslation,\n NoRotationOrReflection,\n NoScale,\n NoScaleOrReflection,\n}\n\n/**\n * @public\n */\nexport interface IBone {\n data: IBoneData;\n matrix: Matrix;\n active: boolean;\n}\n\n/**\n * @public\n */\nexport interface ISkin {\n name: string;\n attachments: Array<Map<IAttachment>>;\n\n getAttachment(slotIndex: number, name: string): IAttachment | null;\n}\n\n/**\n * @public\n */\nexport interface IAttachment {\n name: string;\n type: AttachmentType;\n readonly sequence?: ISequence;\n}\n\n/**\n * @public\n */\nexport interface IHasTextureRegion {\n /** The name used to find the {@link #region()}. */\n path: string;\n\n /** The region used to draw the attachment. After setting the region or if the region's properties are changed,\n * {@link #updateRegion()} must be called. */\n region: TextureRegion | null;\n\n /** Updates any values the attachment calculates using the {@link #getRegion()}. Must be called after setting the\n * {@link #getRegion()} or if the region's properties are changed. */\n // updateRegion (): void;\n\n /** The color to tint the attachment. */\n color: Color;\n\n readonly sequence: ISequence | null;\n}\n\n/**\n * @public\n */\nexport interface ISequence {\n id: number;\n regions: TextureRegion[];\n apply(slot: ISlot, attachment: IHasTextureRegion): void;\n}\n\n/**\n * @public\n */\nexport interface IVertexAttachment<Slot extends ISlot = ISlot> extends IAttachment {\n id: number;\n computeWorldVerticesOld(slot: Slot, worldVertices: ArrayLike<number>): void;\n computeWorldVertices(slot: Slot, start: number, count: number, worldVertices: ArrayLike<number>, offset: number, stride: number): void;\n worldVerticesLength: number;\n}\n\n/**\n * @public\n */\nexport interface IClippingAttachment extends IVertexAttachment {\n endSlot?: ISlotData;\n}\n\n/**\n * @public\n */\nexport interface IRegionAttachment extends IAttachment {\n region: TextureRegion;\n color: Color;\n x;\n y;\n scaleX;\n scaleY;\n rotation;\n width;\n height: number;\n}\n\n/**\n * @public\n */\nexport interface IMeshAttachment extends IVertexAttachment {\n region: TextureRegion;\n color: Color;\n regionUVs: Float32Array;\n triangles: number[];\n hullLength: number;\n}\n\n/**\n * @public\n */\nexport interface ISlotData {\n index: number;\n name: string;\n boneData: IBoneData;\n color: Color;\n darkColor: Color;\n attachmentName: string;\n blendMode: BLEND_MODES;\n}\n\n/**\n * @public\n */\nexport interface IBoneData {\n index: number;\n name: string;\n parent: IBoneData;\n length: number;\n x: number;\n y: number;\n rotation: number;\n scaleX: number;\n scaleY: number;\n shearX: number;\n shearY: number;\n transformMode: TransformMode;\n}\n\n/**\n * @public\n */\nexport interface ISlot {\n getAttachment(): IAttachment;\n data: ISlotData;\n color: Color;\n darkColor: Color;\n blendMode: number;\n bone: IBone;\n\n sprites?: any;\n currentSprite?: any;\n currentSpriteName?: string;\n\n meshes?: any;\n currentMesh?: any;\n currentMeshName?: string;\n currentMeshId?: number;\n\n currentGraphics?: any;\n clippingContainer?: any;\n\n hackRegion?: TextureRegion;\n hackAttachment?: IAttachment;\n}\n\n/**\n * @public\n */\nexport interface ISkeleton<SkeletonData extends ISkeletonData = ISkeletonData, Bone extends IBone = IBone, Slot extends ISlot = ISlot, Skin extends ISkin = ISkin> {\n bones: Bone[];\n slots: Slot[];\n drawOrder: Slot[];\n skin: Skin;\n data: SkeletonData;\n x: number; // added for debug purposes\n y: number; // added for debug purposes\n updateWorldTransform(): void;\n setToSetupPose(): void;\n findSlotIndex(slotName: string): number;\n getAttachmentByName(slotName: string, attachmentName: string): IAttachment;\n\n setBonesToSetupPose(): void;\n setSlotsToSetupPose(): void;\n findBone(boneName: string): Bone;\n findSlot(slotName: string): Slot;\n findBoneIndex(boneName: string): number;\n findSlotIndex(slotName: string): number;\n setSkinByName(skinName: string): void;\n setAttachment(slotName: string, attachmentName: string): void;\n getBounds(offset: Vector2, size: Vector2, temp: Array<number>): void;\n}\n\n/**\n * @public\n */\nexport interface ISkeletonParser {\n scale: number;\n}\n\n/**\n * @public\n */\nexport interface ISkeletonData<\n BoneData extends IBoneData = IBoneData,\n SlotData extends ISlotData = ISlotData,\n Skin extends ISkin = ISkin,\n Animation extends IAnimation = IAnimation,\n EventData extends IEventData = IEventData,\n IkConstraintData extends IIkConstraintData = IIkConstraintData,\n TransformConstraintData extends ITransformConstraintData = ITransformConstraintData,\n PathConstraintData extends IPathConstraintData = IPathConstraintData\n> {\n name: string;\n bones: BoneData[];\n slots: SlotData[];\n skins: Skin[];\n defaultSkin: Skin;\n events: EventData[];\n animations: Animation[];\n version: string;\n hash: string;\n width: number;\n height: number;\n ikConstraints: IkConstraintData[];\n transformConstraints: TransformConstraintData[];\n pathConstraints: PathConstraintData[];\n\n findBone(boneName: string): BoneData | null;\n findBoneIndex(boneName: string): number;\n findSlot(slotName: string): SlotData | null;\n findSlotIndex(slotName: string): number;\n findSkin(skinName: string): Skin | null;\n\n findEvent(eventDataName: string): EventData | null;\n findAnimation(animationName: string): Animation | null;\n findIkConstraint(constraintName: string): IkConstraintData | null;\n findTransformConstraint(constraintName: string): TransformConstraintData | null;\n findPathConstraint(constraintName: string): PathConstraintData | null;\n}\n","import type { Texture, Rectangle } from '@pixi/core';\n\n/**\n * @public\n */\nexport function filterFromString(text: string): TextureFilter {\n switch (text.toLowerCase()) {\n case 'nearest':\n return TextureFilter.Nearest;\n case 'linear':\n return TextureFilter.Linear;\n case 'mipmap':\n return TextureFilter.MipMap;\n case 'mipmapnearestnearest':\n return TextureFilter.MipMapNearestNearest;\n case 'mipmaplinearnearest':\n return TextureFilter.MipMapLinearNearest;\n case 'mipmapnearestlinear':\n return TextureFilter.MipMapNearestLinear;\n case 'mipmaplinearlinear':\n return TextureFilter.MipMapLinearLinear;\n default:\n throw new Error(`Unknown texture filter ${text}`);\n }\n}\n\n/**\n * @public\n */\nexport function wrapFromString(text: string): TextureWrap {\n switch (text.toLowerCase()) {\n case 'mirroredtepeat':\n return TextureWrap.MirroredRepeat;\n case 'clamptoedge':\n return TextureWrap.ClampToEdge;\n case 'repeat':\n return TextureWrap.Repeat;\n default:\n throw new Error(`Unknown texture wrap ${text}`);\n }\n}\n\n/**\n * @public\n */\nexport enum TextureFilter {\n Nearest = 9728, // WebGLRenderingContext.NEAREST\n Linear = 9729, // WebGLRenderingContext.LINEAR\n MipMap = 9987, // WebGLRenderingContext.LINEAR_MIPMAP_LINEAR\n MipMapNearestNearest = 9984, // WebGLRenderingContext.NEAREST_MIPMAP_NEAREST\n MipMapLinearNearest = 9985, // WebGLRenderingContext.LINEAR_MIPMAP_NEAREST\n MipMapNearestLinear = 9986, // WebGLRenderingContext.NEAREST_MIPMAP_LINEAR\n MipMapLinearLinear = 9987, // WebGLRenderingContext.LINEAR_MIPMAP_LINEAR\n}\n\n/**\n * @public\n */\nexport enum TextureWrap {\n MirroredRepeat = 33648, // WebGLRenderingContext.MIRRORED_REPEAT\n ClampToEdge = 33071, // WebGLRenderingContext.CLAMP_TO_EDGE\n Repeat = 10497, // WebGLRenderingContext.REPEAT\n}\n\n/**\n * @public\n */\nexport class TextureRegion {\n texture: Texture;\n\n // thats for overrides\n size: Rectangle = null;\n\n names: string[] = null;\n values: number[][] = null;\n\n renderObject: any = null;\n\n get width(): number {\n const tex = this.texture;\n\n if (tex.trim) {\n return tex.trim.width;\n }\n\n return tex.orig.width;\n }\n\n get height(): number {\n const tex = this.texture;\n\n if (tex.trim) {\n return tex.trim.height;\n }\n\n return tex.orig.height;\n }\n\n get u(): number {\n return (this.texture as any)._uvs.x0;\n }\n\n get v(): number {\n return (this.texture as any)._uvs.y0;\n }\n\n get u2(): number {\n return (this.texture as any)._uvs.x2;\n }\n\n get v2(): number {\n return (this.texture as any)._uvs.y2;\n }\n\n get offsetX(): number {\n const tex = this.texture;\n\n return tex.trim ? tex.trim.x : 0;\n }\n\n get offsetY(): number {\n // console.warn(\"Deprecation Warning: @Hackerham: I guess, if you are using PIXI-SPINE ATLAS region.offsetY, you want a texture, right? Use region.texture from now on.\");\n return this.spineOffsetY;\n }\n\n get pixiOffsetY(): number {\n const tex = this.texture;\n\n return tex.trim ? tex.trim.y : 0;\n }\n\n get spineOffsetY(): number {\n const tex = this.texture;\n\n return this.originalHeight - this.height - (tex.trim ? tex.trim.y : 0);\n }\n\n get originalWidth(): number {\n return this.texture.orig.width;\n }\n\n get originalHeight(): number {\n return this.texture.orig.height;\n }\n\n get x(): number {\n return this.texture.frame.x;\n }\n\n get y(): number {\n return this.texture.frame.y;\n }\n\n get rotate(): boolean {\n return this.texture.rotate !== 0;\n }\n\n get degrees() {\n return (360 - this.texture.rotate * 45) % 360;\n }\n}\n","import { Texture, SCALE_MODES, MIPMAP_MODES, ALPHA_MODES, Rectangle } from '@pixi/core';\nimport { TextureRegion, TextureWrap, TextureFilter, filterFromString } from './TextureRegion';\nimport type { Map, Disposable } from './Utils';\nimport type { BaseTexture } from '@pixi/core';\n\nclass RegionFields {\n x = 0;\n y = 0;\n width = 0;\n height = 0;\n offsetX = 0;\n offsetY = 0;\n originalWidth = 0;\n originalHeight = 0;\n rotate = 0;\n index = 0;\n}\n/**\n * @public\n */\nexport class TextureAtlas implements Disposable {\n pages = new Array<TextureAtlasPage>();\n regions = new Array<TextureAtlasRegion>();\n\n constructor(atlasText?: string, textureLoader?: (path: string, loaderFunction: (tex: BaseTexture) => any) => any, callback?: (obj: TextureAtlas) => any) {\n if (atlasText) {\n this.addSpineAtlas(atlasText, textureLoader, callback);\n }\n }\n\n addTexture(name: string, texture: Texture) {\n const pages = this.pages;\n let page: TextureAtlasPage = null;\n\n for (let i = 0; i < pages.length; i++) {\n if (pages[i].baseTexture === texture.baseTexture) {\n page = pages[i];\n break;\n }\n }\n if (page === null) {\n page = new TextureAtlasPage();\n page.name = 'texturePage';\n const baseTexture = texture.baseTexture;\n\n page.width = baseTexture.realWidth;\n page.height = baseTexture.realHeight;\n page.baseTexture = baseTexture;\n // those fields are not relevant in Pixi\n page.minFilter = page.magFilter = TextureFilter.Nearest;\n page.uWrap = TextureWrap.ClampToEdge;\n page.vWrap = TextureWrap.ClampToEdge;\n pages.push(page);\n }\n const region = new TextureAtlasRegion();\n\n region.name = name;\n region.page = page;\n region.texture = texture;\n region.index = -1;\n this.regions.push(region);\n\n return region;\n }\n\n addTextureHash(textures: Map<Texture>, stripExtension: boolean) {\n for (const key in textures) {\n if (textures.hasOwnProperty(key)) {\n this.addTexture(stripExtension && key.indexOf('.') !== -1 ? key.substr(0, key.lastIndexOf('.')) : key, textures[key]);\n }\n }\n }\n\n public addSpineAtlas(atlasText: string, textureLoader: (path: string, loaderFunction: (tex: BaseTexture) => any) => any, callback: (obj: TextureAtlas) => any) {\n return this.load(atlasText, textureLoader, callback);\n }\n\n private load(atlasText: string, textureLoader: (path: string, loaderFunction: (tex: BaseTexture) => any) => any, callback: (obj: TextureAtlas) => any) {\n if (textureLoader == null) {\n throw new Error('textureLoader cannot be null.');\n }\n\n const reader = new TextureAtlasReader(atlasText);\n const entry = new Array<string>(4);\n let page: TextureAtlasPage = null;\n const pageFields: Map<Function> = {};\n let region: RegionFields = null;\n\n pageFields.size = () => {\n page.width = parseInt(entry[1]);\n page.height = parseInt(entry[2]);\n };\n pageFields.format = () => {\n // page.format = Format[tuple[0]]; we don't need format in WebGL\n };\n pageFields.filter = () => {\n page.minFilter = filterFromString(entry[1]);\n page.magFilter = filterFromString(entry[2]);\n };\n pageFields.repeat = () => {\n if (entry[1].indexOf('x') != -1) page.uWrap = TextureWrap.Repeat;\n if (entry[1].indexOf('y') != -1) page.vWrap = TextureWrap.Repeat;\n };\n pageFields.pma = () => {\n page.pma = entry[1] == 'true';\n };\n\n const regionFields: Map<Function> = {};\n\n regionFields.xy = () => {\n // Deprecated, use bounds.\n region.x = parseInt(entry[1]);\n region.y = parseInt(entry[2]);\n };\n regionFields.size = () => {\n // Deprecated, use bounds.\n region.width = parseInt(entry[1]);\n region.height = parseInt(entry[2]);\n };\n regionFields.bounds = () => {\n region.x = parseInt(entry[1]);\n region.y = parseInt(entry[2]);\n region.width = parseInt(entry[3]);\n region.height = parseInt(entry[4]);\n };\n regionFields.offset = () => {\n // Deprecated, use offsets.\n region.offsetX = parseInt(entry[1]);\n region.offsetY = parseInt(entry[2]);\n };\n regionFields.orig = () => {\n // Deprecated, use offsets.\n region.originalWidth = parseInt(entry[1]);\n region.originalHeight = parseInt(entry[2]);\n };\n regionFields.offsets = () => {\n region.offsetX = parseInt(entry[1]);\n region.offsetY = parseInt(entry[2]);\n region.originalWidth = parseInt(entry[3]);\n region.originalHeight = parseInt(entry[4]);\n };\n regionFields.rotate = () => {\n const rotateValue = entry[1];\n let rotate = 0;\n\n if (rotateValue.toLocaleLowerCase() == 'true') {\n rotate = 6;\n } else if (rotateValue.toLocaleLowerCase() == 'false') {\n rotate = 0;\n } else {\n rotate = ((720 - parseFloat(rotateValue)) % 360) / 45;\n }\n region.rotate = rotate;\n };\n regionFields.index = () => {\n region.index = parseInt(entry[1]);\n };\n\n let line = reader.readLine();\n // Ignore empty lines before first entry.\n\n while (line != null && line.trim().length == 0) {\n line = reader.readLine();\n }\n // Header entries.\n while (true) {\n if (line == null || line.trim().length == 0) break;\n if (reader.readEntry(entry, line) == 0) break; // Silently ignore all header fields.\n line = reader.readLine();\n }\n\n const iterateParser = () => {\n while (true) {\n if (line == null) {\n return callback && callback(this);\n }\n if (line.trim().length == 0) {\n page = null;\n line = reader.readLine();\n } else if (page === null) {\n page = new TextureAtlasPage();\n page.name = line.trim();\n\n while (true) {\n if (reader.readEntry(entry, (line = reader.readLine())) == 0) break;\n const field: Function = pageFields[entry[0]];\n\n if (field) field();\n }\n this.pages.push(page);\n\n textureLoader(page.name, (texture: BaseTexture) => {\n if (texture === null) {\n this.pages.splice(this.pages.indexOf(page), 1);\n\n return callback && callback(null);\n }\n page.baseTexture = texture;\n // TODO: set scaleMode and mipmapMode from spine\n if (page.pma) {\n texture.alphaMode = ALPHA_MODES.PMA;\n }\n if (!texture.valid) {\n texture.setSize(page.width, page.height);\n }\n page.setFilters();\n\n if (!page.width || !page.height) {\n page.width = texture.realWidth;\n page.height = texture.realHeight;\n if (!page.width || !page.height) {\n console.log(\n `ERROR spine atlas page ${page.name}: meshes wont work if you dont specify size in atlas (http://www.html5gamedevs.com/topic/18888-pixi-spines-and-meshes/?p=107121)`\n );\n }\n }\n iterateParser();\n });\n break;\n } else {\n region = new RegionFields();\n const atlasRegion = new TextureAtlasRegion();\n\n atlasRegion.name = line;\n atlasRegion.page = page;\n let names: string[] = null;\n let values: number[][] = null;\n\n while (true) {\n const count = reader.readEntry(entry, (line = reader.readLine()));\n\n if (count == 0) break;\n const field: Function = regionFields[entry[0]];\n\n if (field) {\n field();\n } else {\n if (names == null) {\n names = [];\n values = [];\n }\n names.push(entry[0]);\n const entryValues: number[] = [];\n\n for (let i = 0; i < count; i++) {\n entryValues.push(parseInt(entry[i + 1]));\n }\n values.push(entryValues);\n }\n }\n if (region.originalWidth == 0 && region.originalHeight == 0) {\n region.originalWidth = region.width;\n region.originalHeight = region.height;\n }\n\n const resolution = page.baseTexture.resolution;\n\n region.x /= resolution;\n region.y /= resolution;\n region.width /= resolution;\n region.height /= resolution;\n region.originalWidth /= resolution;\n region.originalHeight /= resolution;\n region.offsetX /= resolution;\n region.offsetY /= resolution;\n\n const swapWH = region.rotate % 4 !== 0;\n const frame = new Rectangle(region.x, region.y, swapWH ? region.height : region.width, swapWH ? region.width : region.height);\n\n const orig = new Rectangle(0, 0, region.originalWidth, region.originalHeight);\n const trim = new Rectangle(region.offsetX, region.originalHeight - region.height - region.offsetY, region.width, region.height);\n\n atlasRegion.texture = new Texture(atlasRegion.page.baseTexture, frame, orig, trim, region.rotate);\n atlasRegion.index = region.index;\n atlasRegion.texture.updateUvs();\n\n this.regions.push(atlasRegion);\n }\n }\n };\n\n iterateParser();\n }\n\n findRegion(name: string): TextureAtlasRegion {\n for (let i = 0; i < this.regions.length; i++) {\n if (this.regions[i].name == name) {\n return this.regions[i];\n }\n }\n\n return null;\n }\n\n dispose() {\n for (let i = 0; i < this.pages.length; i++) {\n this.pages[i].baseTexture.dispose();\n }\n }\n}\n\n/**\n * @public\n */\nclass TextureAtlasReader {\n lines: Array<string>;\n index = 0;\n\n constructor(text: string) {\n this.lines = text.split(/\\r\\n|\\r|\\n/);\n }\n\n readLine(): string {\n if (this.index >= this.lines.length) {\n return null;\n }\n\n return this.lines[this.index++];\n }\n\n readEntry(entry: string[], line: string): number {\n if (line == null) return 0;\n line = line.trim();\n if (line.length == 0) return 0;\n\n const colon = line.indexOf(':');\n\n if (colon == -1) return 0;\n entry[0] = line.substr(0, colon).trim();\n for (let i = 1, lastMatch = colon + 1; ; i++) {\n const comma = line.indexOf(',', lastMatch);\n\n if (comma == -1) {\n entry[i] = line.substr(lastMatch).trim();\n\n return i;\n }\n entry[i] = line.substr(lastMatch, comma - lastMatch).trim();\n lastMatch = comma + 1;\n if (i == 4) return 4;\n }\n }\n}\n\n/**\n * @public\n */\nexport class TextureAtlasPage {\n name: string;\n minFilter: TextureFilter = TextureFilter.Nearest;\n magFilter: TextureFilter = TextureFilter.Nearest;\n uWrap: TextureWrap = TextureWrap.ClampToEdge;\n vWrap: TextureWrap = TextureWrap.ClampToEdge;\n baseTexture: BaseTexture;\n width: number;\n height: number;\n pma: boolean;\n\n public setFilters() {\n const tex = this.baseTexture;\n const filter = this.minFilter;\n\n if (filter == TextureFilter.Linear) {\n tex.scaleMode = SCALE_MODES.LINEAR;\n } else if (this.minFilter == TextureFilter.Nearest) {\n tex.scaleMode = SCALE_MODES.NEAREST;\n } else {\n tex.mipmap = MIPMAP_MODES.POW2;\n if (filter == TextureFilter.MipMapNearestNearest) {\n tex.scaleMode = SCALE_MODES.NEAREST;\n } else {\n tex.scaleMode = SCALE_MODES.LINEAR;\n }\n }\n }\n}\n\n/**\n * @public\n */\nexport class TextureAtlasRegion extends TextureRegion {\n page: TextureAtlasPage;\n name: string;\n index: number;\n}\n","import type { ISkeleton } from './ISkeleton';\n\n/**\n * @public\n */\n\nexport interface Map<T> {\n [key: string]: T;\n}\n\n/**\n * @public\n */\nexport interface StringMap<T> {\n [key: string]: T;\n}\n\n/**\n * @public\n */\nexport class IntSet {\n array = new Array<number>();\n\n add(value: number): boolean {\n const contains = this.contains(value);\n\n this.array[value | 0] = value | 0;\n\n return !contains;\n }\n\n contains(value: number) {\n return this.array[value | 0] != undefined;\n }\n\n remove(value: number) {\n this.array[value | 0] = undefined;\n }\n\n clear() {\n this.array.length = 0;\n }\n}\n\n/**\n * @public\n */\nexport class StringSet {\n entries: StringMap<boolean> = {};\n size = 0;\n\n add(value: string): boolean {\n const contains = this.entries[value];\n\n this.entries[value] = true;\n if (!contains) {\n this.size++;\n\n return true;\n }\n\n return false;\n }\n\n addAll(values: string[]): boolean {\n const oldSize = this.size;\n\n for (let i = 0, n = values.length; i < n; i++) {\n this.add(values[i]);\n }\n\n return oldSize != this.size;\n }\n\n contains(value: string) {\n return this.entries[value];\n }\n\n clear() {\n this.entries = {};\n this.size = 0;\n }\n}\n\n/**\n * @public\n */\nexport interface NumberArrayLike {\n readonly length: number;\n [n: number]: number;\n}\n\n/**\n * @public\n */\nexport interface Disposable {\n dispose(): void;\n}\n\n/**\n * @public\n */\nexport interface Restorable {\n restore(): void;\n}\n\n/**\n * @public\n */\nexport class Color {\n public static WHITE = new Color(1, 1, 1, 1);\n public static RED = new Color(1, 0, 0, 1);\n public static GREEN = new Color(0, 1, 0, 1);\n public static BLUE = new Color(0, 0, 1, 1);\n public static MAGENTA = new Color(1, 0, 1, 1);\n\n constructor(public r: number = 0, public g: number = 0, public b: number = 0, public a: number = 0) {}\n\n set(r: number, g: number, b: number, a: number) {\n this.r = r;\n this.g = g;\n this.b = b;\n this.a = a;\n\n return this.clamp();\n }\n\n setFromColor(c: Color) {\n this.r = c.r;\n this.g = c.g;\n this.b = c.b;\n this.a = c.a;\n\n return this;\n }\n\n setFromString(hex: string) {\n hex = hex.charAt(0) == '#' ? hex.substr(1) : hex;\n this.r = parseInt(hex.substr(0, 2), 16) / 255;\n this.g = parseInt(hex.substr(2, 2), 16) / 255;\n this.b = parseInt(hex.substr(4, 2), 16) / 255;\n this.a = hex.length != 8 ? 1 : parseInt(hex.substr(6, 2), 16) / 255;\n\n return this;\n }\n\n add(r: number, g: number, b: number, a: number) {\n this.r += r;\n this.g += g;\n this.b += b;\n this.a += a;\n\n return this.clamp();\n }\n\n clamp() {\n if (this.r < 0) this.r = 0;\n else if (this.r > 1) this.r = 1;\n\n if (this.g < 0) this.g = 0;\n else if (this.g > 1) this.g = 1;\n\n if (this.b < 0) this.b = 0;\n else if (this.b > 1) this.b = 1;\n\n if (this.a < 0) this.a = 0;\n else if (this.a > 1) this.a = 1;\n\n return this;\n }\n\n static rgba8888ToColor(color: Color, value: number) {\n color.r = ((value & 0xff000000) >>> 24) / 255;\n color.g = ((value & 0x00ff0000) >>> 16) / 255;\n color.b = ((value & 0x0000ff00) >>> 8) / 255;\n color.a = (value & 0x000000ff) / 255;\n }\n\n static rgb888ToColor(color: Color, value: number) {\n color.r = ((value & 0x00ff0000) >>> 16) / 255;\n color.g = ((value & 0x0000ff00) >>> 8) / 255;\n color.b = (value & 0x000000ff) / 255;\n }\n\n static fromString(hex: string): Color {\n return new Color().setFromString(hex);\n }\n}\n\n/**\n * @public\n */\nexport class MathUtils {\n static PI = 3.1415927;\n static PI2 = MathUtils.PI * 2;\n static radiansToDegrees = 180 / MathUtils.PI;\n static radDeg = MathUtils.radiansToDegrees;\n static degreesToRadians = MathUtils.PI / 180;\n static degRad = MathUtils.degreesToRadians;\n\n static clamp(value: number, min: number, max: number) {\n if (value < min) return min;\n if (value > max) return max;\n\n return value;\n }\n\n static cosDeg(degrees: number) {\n return Math.cos(degrees * MathUtils.degRad);\n }\n\n static sinDeg(degrees: number) {\n return Math.sin(degrees * MathUtils.degRad);\n }\n\n static signum(value: number): number {\n return Math.sign(value);\n }\n\n static toInt(x: number) {\n return x > 0 ? Math.floor(x) : Math.ceil(x);\n }\n\n static cbrt(x: number) {\n const y = Math.pow(Math.abs(x), 1 / 3);\n\n return x < 0 ? -y : y;\n }\n\n static randomTriangular(min: number, max: number): number {\n return MathUtils.randomTriangularWith(min, max, (min + max) * 0.5);\n }\n\n static randomTriangularWith(min: number, max: number, mode: number): number {\n const u = Math.random();\n const d = max - min;\n\n if (u <= (mode - min) / d) return min + Math.sqrt(u * d * (mode - min));\n\n return max - Math.sqrt((1 - u) * d * (max - mode));\n }\n\n static isPowerOfTwo(value: number) {\n return value && (value & (value - 1)) === 0;\n }\n}\n\n/**\n * @public\n */\nexport abstract class Interpolation {\n protected abstract applyInternal(a: number): number;\n apply(start: number, end: number, a: number): number {\n return start + (end - start) * this.applyInternal(a);\n }\n}\n\n/**\n * @public\n */\nexport class Pow extends Interpolation {\n protected power = 2;\n\n constructor(power: number) {\n super();\n this.power = power;\n }\n\n applyInternal(a: number): number {\n if (a <= 0.5) return Math.pow(a * 2, this.power) / 2;\n\n return Math.pow((a - 1) * 2, this.power) / (this.power % 2 == 0 ? -2 : 2) + 1;\n }\n}\n\n/**\n * @public\n */\nexport class PowOut extends Pow {\n applyInternal(a: number): number {\n return Math.pow(a - 1, this.power) * (this.power % 2 == 0 ? -1 : 1) + 1;\n }\n}\n\n/**\n * @public\n */\nexport class Utils {\n static SUPPORTS_TYPED_ARRAYS = typeof Float32Array !== 'undefined';\n\n static arrayCopy<T>(source: ArrayLike<T>, sourceStart: number, dest: ArrayLike<T>, destStart: number, numElements: number) {\n for (let i = sourceStart, j = destStart; i < sourceStart + numElements; i++, j++) {\n dest[j] = source[i];\n }\n }\n\n static arrayFill<T>(array: ArrayLike<T>, fromIndex: number, toIndex: number, value: T) {\n for (let i = fromIndex; i < toIndex; i++) {\n array[i] = value;\n }\n }\n\n static setArraySize<T>(array: Array<T>, size: number, value: any = 0): Array<T> {\n const oldSize = array.length;\n\n if (oldSize == size) return array;\n array.length = size;\n if (oldSize < size) {\n for (let i = oldSize; i < size; i++) array[i] = value;\n }\n\n return array;\n }\n\n static ensureArrayCapacity<T>(array: Array<T>, size: number, value: any = 0): Array<T> {\n if (array.length >= size) return array;\n\n return Utils.setArraySize(array, size, value);\n }\n\n static newArray<T>(size: number, defaultValue: T): Array<T> {\n const array = new Array<T>(size);\n\n for (let i = 0; i < size; i++) array[i] = defaultValue;\n\n return array;\n }\n\n static newFloatArray(size: number): NumberArrayLike {\n if (Utils.SUPPORTS_TYPED_ARRAYS) {\n return new Float32Array(size);\n }\n\n const array = new Array<number>(size);\n\n for (let i = 0; i < array.length; i++) array[i] = 0;\n\n return array;\n }\n\n static newShortArray(size: number): NumberArrayLike {\n if (Utils.SUPPORTS_TYPED_ARRAYS) {\n return new Int16Array(size);\n }\n\n const array = new Array<number>(size);\n\n for (let i = 0; i < array.length; i++) array[i] = 0;\n\n return array;\n }\n\n static toFloatArray(array: Array<number>) {\n return Utils.SUPPORTS_TYPED_ARRAYS ? new Float32Array(array) : array;\n }\n\n static toSinglePrecision(value: number) {\n return Utils.SUPPORTS_TYPED_ARRAYS ? Math.fround(value) : value;\n }\n\n // This function is used to fix WebKit 602 specific issue described at http://esotericsoftware.com/forum/iOS-10-disappearing-graphics-10109\n static webkit602BugfixHelper(alpha: number, blend: any) {}\n\n static contains<T>(array: Array<T>, element: T, identity = true) {\n for (let i = 0; i < array.length; i++) {\n if (array[i] == element) return true;\n }\n\n return false;\n }\n\n static enumValue(type: any, name: string) {\n return type[name[0].toUpperCase() + name.slice(1)];\n }\n}\n\n/**\n * @public\n */\nexport class DebugUtils {\n static logBones(skeleton: ISkeleton) {\n for (let i = 0; i < skeleton.bones.length; i++) {\n const bone = skeleton.bones[i];\n const mat = bone.matrix;\n\n console.log(`${bone.data.name}, ${mat.a}, ${mat.b}, ${mat.c}, ${mat.d}, ${mat.tx}, ${mat.ty}`);\n }\n }\n}\n\n/**\n * @public\n */\nexport class Pool<T> {\n private items = new Array<T>();\n private instantiator: () => T;\n\n constructor(instantiator: () => T) {\n this.instantiator = instantiator;\n }\n\n obtain() {\n return this.items.length > 0 ? this.items.pop() : this.instantiator();\n }\n\n free(item: T) {\n if ((item as any).reset) (item as any).reset();\n this.items.push(item);\n }\n\n freeAll(items: ArrayLike<T>) {\n for (let i = 0; i < items.length; i++) {\n this.free(items[i]);\n }\n }\n\n clear() {\n this.items.length = 0;\n }\n}\n\n/**\n * @public\n */\nexport class Vector2 {\n constructor(public x = 0, public y = 0) {}\n\n set(x: number, y: number): Vector2 {\n this.x = x;\n this.y = y;\n\n return this;\n }\n\n length() {\n const x = this.x;\n const y = this.y;\n\n return Math.sqrt(x * x + y * y);\n }\n\n normalize() {\n const len = this.length();\n\n if (len != 0) {\n this.x /= len;\n this.y /= len;\n }\n\n return this;\n }\n}\n\n/**\n * @public\n */\nexport class TimeKeeper {\n maxDelta = 0.064;\n framesPerSecond = 0;\n delta = 0;\n totalTime = 0;\n\n private lastTime = Date.now() / 1000;\n private frameCount = 0;\n private frameTime = 0;\n\n update() {\n const now = Date.now() / 1000;\n\n this.delta = now - this.lastTime;\n this.frameTime += this.delta;\n this.totalTime += this.delta;\n if (this.delta > this.maxDelta) this.delta = this.maxDelta;\n this.lastTime = now;\n\n this.frameCount++;\n if (this.frameTime > 1) {\n this.framesPerSecond = this.frameCount / this.frameTime;\n this.frameTime = 0;\n this.frameCount = 0;\n }\n }\n}\n\n/**\n * @public\n */\nexport interface ArrayLike<T> {\n length: number;\n [n: number]: T;\n}\n\n/**\n * @public\n */\nexport class WindowedMean {\n values: Array<number>;\n addedValues = 0;\n lastValue = 0;\n mean = 0;\n dirty = true;\n\n constructor(windowSize = 32) {\n this.values = new Array<number>(windowSize);\n }\n\n hasEnoughData() {\n return this.addedValues >= this.values.length;\n }\n\n addValue(value: number) {\n if (this.addedValues < this.values.length) this.addedValues++;\n this.values[this.lastValue++] = value;\n if (this.lastValue > this.values.length - 1) this.lastValue = 0;\n this.dirty = true;\n }\n\n getMean() {\n if (this.hasEnoughData()) {\n if (this.dirty) {\n let mean = 0;\n\n for (let i = 0; i < this.values.length; i++) {\n mean += this.values[i];\n }\n this.mean = mean / this.values.length;\n this.dirty = false;\n }\n\n return this.mean;\n }\n\n return 0;\n }\n}\n","import { AttachmentType } from './AttachmentType';\nimport type { ISkeleton, IVertexAttachment } from './ISkeleton';\nimport { NumberArrayLike, Pool, Utils } from './Utils';\n\n/** Collects each visible BoundingBoxAttachment and computes the world vertices for its polygon. The polygon vertices are\n * provided along with convenience methods for doing hit detection.\n * @public\n * */\nexport class SkeletonBoundsBase<BoundingBoxAttachment extends IVertexAttachment> {\n /** The left edge of the axis aligned bounding box. */\n minX = 0;\n\n /** The bottom edge of the axis aligned bounding box. */\n minY = 0;\n\n /** The right edge of the axis aligned bounding box. */\n maxX = 0;\n\n /** The top edge of the axis aligned bounding box. */\n maxY = 0;\n\n /** The visible bounding boxes. */\n boundingBoxes = new Array<BoundingBoxAttachment>();\n\n /** The world vertices for the bounding box polygons. */\n polygons = new Array<NumberArrayLike>();\n\n private polygonPool = new Pool<NumberArrayLike>(() => Utils.newFloatArray(16));\n\n /** Clears any previous polygons, finds all visible bounding box attachments, and computes the world vertices for each bounding\n * box's polygon.\n * @param updateAabb If true, the axis aligned bounding box containing all the polygons is computed. If false, the\n * SkeletonBounds AABB methods will always return true. */\n update(skeleton: ISkeleton, updateAabb: boolean) {\n if (!skeleton) throw new Error('skeleton cannot be null.');\n const boundingBoxes = this.boundingBoxes;\n const polygons = this.polygons;\n const polygonPool = this.polygonPool;\n const slots = skeleton.slots;\n const slotCount = slots.length;\n\n boundingBoxes.length = 0;\n polygonPool.freeAll(polygons);\n polygons.length = 0;\n\n for (let i = 0; i < slotCount; i++) {\n const slot = slots[i];\n\n if (!slot.bone.active) continue;\n const attachment = slot.getAttachment();\n\n if (attachment != null && attachment.type === AttachmentType.BoundingBox) {\n const boundingBox = attachment as BoundingBoxAttachment;\n\n boundingBoxes.push(boundingBox);\n\n let polygon = polygonPool.obtain() as NumberArrayLike;\n\n if (polygon.length != boundingBox.worldVerticesLength) {\n polygon = Utils.newFloatArray(boundingBox.worldVerticesLength);\n }\n polygons.push(polygon);\n