diff --git a/src/Renderer.ts b/src/Renderer.ts index 076e11b..7b116db 100644 --- a/src/Renderer.ts +++ b/src/Renderer.ts @@ -504,13 +504,13 @@ export default class Renderer { sprite: Sprite | Stage, options: RenderSpriteOptions ): void { - const spriteScale = "size" in sprite ? sprite.size / 100 : 1; + const drawable = this._getDrawable(sprite); this._renderSkin( this._getSkin(sprite.costume), options.drawMode, - this._getDrawable(sprite).getMatrix(), - spriteScale, + drawable.getMatrix(), + drawable.getSpriteScale(), sprite.effects, options.effectMask, options.colorMask, diff --git a/src/renderer/Drawable.ts b/src/renderer/Drawable.ts index de28a10..e1be5bd 100644 --- a/src/renderer/Drawable.ts +++ b/src/renderer/Drawable.ts @@ -88,10 +88,14 @@ export default class Drawable { private _tightBoundingBox: Rectangle; private _convexHullMatrixDiff: SpriteTransformDiff; + private _warnedBadSize: boolean; + public constructor(renderer: Renderer, sprite: Sprite | Stage) { this._renderer = renderer; this._sprite = sprite; + this._warnedBadSize = false; + // Transformation matrix for the sprite. this._matrix = Matrix.create(); // Track when the sprite's transform changes so we can recalculate the @@ -345,7 +349,7 @@ export default class Drawable { } } - const spriteScale = spr.size / 100; + const spriteScale = this.getSpriteScale(); Matrix.scale(m, m, spriteScale, spriteScale); } @@ -379,4 +383,35 @@ export default class Drawable { return this._matrix; } + + public getSpriteScale(): number { + if (this._sprite instanceof Stage) { + return 1; + } else { + const { size } = this._sprite; + if (typeof size !== "number") { + this._warnBadSize(typeof size, "0%"); + return 0; + } else if (isNaN(size)) { + this._warnBadSize("NaN", "0%"); + return 0; + } else if (size === -Infinity) { + this._warnBadSize("negative Infinity", "0%"); + return 0; + } else if (size < 0) { + this._warnBadSize("less than zero", "0%"); + return 0; + } else { + return size / 100; + } + } + } + + private _warnBadSize(description: string, treating: string): void { + if (!this._warnedBadSize) { + const { name } = this._sprite.constructor; + console.warn(`Expected a number, sprite ${name} size is ${description}. Treating as ${treating}.`); + this._warnedBadSize = true; + } + } } diff --git a/src/renderer/VectorSkin.ts b/src/renderer/VectorSkin.ts index 065b403..748e518 100644 --- a/src/renderer/VectorSkin.ts +++ b/src/renderer/VectorSkin.ts @@ -84,15 +84,17 @@ export default class VectorSkin extends Skin { // TODO: handle proper subpixel positioning when SVG viewbox has non-integer coordinates // This will require rethinking costume + project loading probably - private _createMipmap(mipLevel: number): void { + private _createMipmap(mipLevel: number): WebGLTexture | null { // Instead of uploading the image to WebGL as a texture, render the image to a canvas and upload the canvas. const ctx = this._drawSvgToCanvas(mipLevel); - this._mipmaps.set( - mipLevel, - // Use linear (i.e. smooth) texture filtering for vectors - // If the image is 0x0, we return null. Check for that. - ctx === null ? null : this._makeTexture(ctx.canvas, this.gl.LINEAR) - ); + + // If the image is 0x0, we return null. Check for that. + if (ctx === null) { + return null; + } + + // Use linear (i.e. smooth) texture filtering for vectors. + return this._makeTexture(ctx.canvas, this.gl.LINEAR); } public getTexture(scale: number): WebGLTexture | null { @@ -106,9 +108,19 @@ export default class VectorSkin extends Skin { // This means that one texture pixel will always be between 0.5x and 1x the size of one rendered pixel, // but never bigger than one rendered pixel--this prevents blurriness from blowing up the texture too much. const mipLevel = VectorSkin.mipLevelForScale(scale); - if (!this._mipmaps.has(mipLevel)) this._createMipmap(mipLevel); + if (!this._mipmaps.has(mipLevel)) { + this._mipmaps.set(mipLevel, this._createMipmap(mipLevel)); + } - return this._mipmaps.get(mipLevel) ?? null; + if (this._mipmaps.has(mipLevel)) { + // TODO: Awkward `as` due to microsoft/typescript#13086 + // See: https://github.com/leopard-js/leopard/pull/199#discussion_r1669416720 + return this._mipmaps.get(mipLevel) as WebGLTexture | null; + } else { + const mipmap = this._createMipmap(mipLevel); + this._mipmaps.set(mipLevel, mipmap); + return mipmap; + } } public destroy(): void {