diff --git a/.gitignore b/.gitignore index 8bee854e..894abc1a 100644 --- a/.gitignore +++ b/.gitignore @@ -1,7 +1,7 @@ node_modules .DS_store .idea -docs +typedocs dist dist-cfg dist-vitest diff --git a/README.md b/README.md index da55a1ac..43e8e543 100644 --- a/README.md +++ b/README.md @@ -28,7 +28,7 @@ pnpm test # Run Visual Regression Tests pnpm test:visual -# Build API Documentation (builds into ./docs folder) +# Build API Documentation (builds into ./typedocs folder) pnpm typedoc # Launch Example Tests in dev mode (includes Build Renderer (watch mode)) diff --git a/docs/NodeDependencyGraph.md b/docs/NodeDependencyGraph.md new file mode 100644 index 00000000..421d5118 --- /dev/null +++ b/docs/NodeDependencyGraph.md @@ -0,0 +1,5 @@ +# Node Depenedency Graph + +The diagram below describes the reactive flow of inputs into cached calculated data elements that are updated during each `update()` of a Core Node. + +![Dependency graph describing by way of a water fall the input and output relationship of calculated (and cached) data elements of a node](./images/dependency-graph.png) diff --git a/docs/images/dependency-graph.png b/docs/images/dependency-graph.png new file mode 100644 index 00000000..99dd62cb Binary files /dev/null and b/docs/images/dependency-graph.png differ diff --git a/docs/images/dependency-graph.txt b/docs/images/dependency-graph.txt new file mode 100644 index 00000000..e82f8fbf --- /dev/null +++ b/docs/images/dependency-graph.txt @@ -0,0 +1,2 @@ +https://lucid.app/lucidchart/f5c48c94-6096-44c5-bca2-97fe2b581095/edit?viewport_loc=-1167%2C-285%2C4800%2C2244%2Cm8WTkWxK2plY&invitationId=inv_44218a81-5bb9-4436-a5e6-5697c146e164 +https://lucid.app/publicSegments/view/56aa76d3-d7c2-4020-a3bc-8638084cb22b/image.png diff --git a/src/core/CoreNode.ts b/src/core/CoreNode.ts index dbbccd27..a4d5d8d8 100644 --- a/src/core/CoreNode.ts +++ b/src/core/CoreNode.ts @@ -17,7 +17,7 @@ * limitations under the License. */ -import { assertTruthy } from '../utils.js'; +import { assertTruthy, mergeColorAlphaPremultiplied } from '../utils.js'; import type { ShaderMap } from './CoreShaderManager.js'; import type { ExtractProps, @@ -37,7 +37,11 @@ import type { NodeTextureLoadedPayload, } from '../common/CommonTypes.js'; import { EventEmitter } from '../common/EventEmitter.js'; -import { intersectRect, type Rect } from './lib/utils.js'; +import { + getNormalizedAlphaComponent, + intersectRect, + type Rect, +} from './lib/utils.js'; import { Matrix3d } from './lib/Matrix3d.js'; export interface CoreNodeProps { @@ -82,17 +86,82 @@ type ICoreNode = Omit< 'texture' | 'textureOptions' | 'shader' | 'shaderProps' >; +enum UpdateType { + /** + * Child updates + */ + Children = 1, + + /** + * Scale/Rotate transform update + */ + ScaleRotate = 2, + + /** + * Translate transform update (x/y/width/height/pivot/mount) + */ + Local = 4, + + /** + * Global transform update + */ + Global = 8, + + /** + * Clipping rect update + */ + Clipping = 16, + + /** + * Calculated ZIndex update + */ + CalculatedZIndex = 32, + + /** + * Z-Index Sorted Children update + */ + ZIndexSortedChildren = 64, + + /** + * Premultiplied Colors + */ + PremultipliedColors = 128, + + /** + * World Alpha + * + * @remarks + * World Alpha = Parent World Alpha * Alpha + */ + WorldAlpha = 256, + + /** + * None + */ + None = 0, + + /** + * All + */ + All = 511, +} + export class CoreNode extends EventEmitter implements ICoreNode { readonly children: CoreNode[] = []; protected props: Required; - public recalculationType = 0; - public hasUpdates = true; + public updateType = UpdateType.All; public globalTransform?: Matrix3d; public scaleRotateTransform?: Matrix3d; public localTransform?: Matrix3d; public clippingRect: Rect | null = null; private parentClippingRect: Rect | null = null; + public worldAlpha = 1; + public premultipliedColorTl = 0; + public premultipliedColorTr = 0; + public premultipliedColorBl = 0; + public premultipliedColorBr = 0; + public calcZIndex = 0; private isComplex = false; @@ -173,64 +242,38 @@ export class CoreNode extends EventEmitter implements ICoreNode { this.props.shaderProps = p; } - setHasUpdates(): void { - this.hasUpdates = true; - } - - setChildrenHasUpdates(): void { - this.children.forEach((child) => { - child.setRecalculationType(2); - }); - } - - setParentHasUpdates(): void { - if (!this.props.parent) { - return; - } - - this.props.parent.setRecalculationType(1); - } - /** * Change types types is used to determine the scope of the changes being applied - * 1 - alpha recalculation - * 2 - translate recalculation - * 4 - transform recalculation - * 8 - z-index recalculation + * + * @remarks + * See {@link UpdateType} for more information on each type * * @param type */ - setRecalculationType(type: number): void { - this.recalculationType |= type; - this.setHasUpdates(); - - // always forcing parent updates so the root will have an hasUpdates flag - this.setParentHasUpdates(); - - if (type & 4) { - this.setChildrenHasUpdates(); + setUpdateType(type: UpdateType): void { + this.updateType |= type; + + // If we're updating this node at all, we need to inform the parent + // (and all ancestors) that their children need updating as well + const parent = this.props.parent; + if (parent && !(parent.updateType & UpdateType.Children)) { + parent.setUpdateType(UpdateType.Children); } } sortChildren() { - this.children.sort((a, b) => a.zIndex - b.zIndex); + this.children.sort((a, b) => a.calcZIndex - b.calcZIndex); } updateScaleRotateTransform() { - this.setRecalculationType(4); - this.scaleRotateTransform = Matrix3d.rotate( this.props.rotation, this.scaleRotateTransform, ).scale(this.props.scaleX, this.props.scaleY); - - // do transformations when matrix is implemented - this.updateLocalTransform(); } updateLocalTransform() { assertTruthy(this.scaleRotateTransform); - this.setRecalculationType(2); const pivotTranslateX = this.props.pivotX * this.props.width; const pivotTranslateY = this.props.pivotY * this.props.height; const mountTranslateX = this.props.mountX * this.props.width; @@ -243,6 +286,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { ) .multiply(this.scaleRotateTransform) .translate(-pivotTranslateX, -pivotTranslateY); + this.setUpdateType(UpdateType.Global); } /** @@ -250,45 +294,135 @@ export class CoreNode extends EventEmitter implements ICoreNode { * @param delta */ update(delta: number, parentClippingRect: Rect | null = null): void { - assertTruthy(this.localTransform); - const parentGlobalTransform = this.parent?.globalTransform; - if (parentGlobalTransform) { - this.globalTransform = Matrix3d.copy( - parentGlobalTransform, - this.globalTransform, - ).multiply(this.localTransform); - } else { - this.globalTransform = Matrix3d.copy( - this.localTransform, - this.globalTransform, - ); + if (this.updateType & UpdateType.ScaleRotate) { + this.updateScaleRotateTransform(); + this.setUpdateType(UpdateType.Local); + } + + if (this.updateType & UpdateType.Local) { + this.updateLocalTransform(); + this.setUpdateType(UpdateType.Global); } - this.calculateClippingRect(parentClippingRect); + const parent = this.props.parent; + let childUpdateType = UpdateType.None; + if (this.updateType & UpdateType.Global) { + if (parent) { + assertTruthy(this.localTransform && parent.globalTransform); + this.globalTransform = Matrix3d.copy( + parent.globalTransform, + this.globalTransform, + ).multiply(this.localTransform); + this.setUpdateType(UpdateType.Clipping | UpdateType.Children); + childUpdateType |= UpdateType.Global; + } else { + assertTruthy(this.localTransform); + this.globalTransform = Matrix3d.copy( + this.localTransform, + this.globalTransform, + ); + this.setUpdateType(UpdateType.Clipping | UpdateType.Children); + childUpdateType |= UpdateType.Global; + } + } + + if (this.updateType & UpdateType.Clipping) { + this.calculateClippingRect(parentClippingRect); + this.setUpdateType(UpdateType.Children); + childUpdateType |= UpdateType.Clipping; + } - if (this.children.length) { + if (this.updateType & UpdateType.WorldAlpha) { + if (parent) { + this.worldAlpha = parent.worldAlpha * this.props.alpha; + } else { + this.worldAlpha = this.props.alpha; + } + this.setUpdateType(UpdateType.Children | UpdateType.PremultipliedColors); + childUpdateType |= UpdateType.WorldAlpha; + } + + if (this.updateType & UpdateType.PremultipliedColors) { + if (parent) { + this.premultipliedColorTl = mergeColorAlphaPremultiplied( + this.props.colorTl, + this.worldAlpha, + true, + ); + this.premultipliedColorTr = mergeColorAlphaPremultiplied( + this.props.colorTr, + this.worldAlpha, + true, + ); + this.premultipliedColorBl = mergeColorAlphaPremultiplied( + this.props.colorBl, + this.worldAlpha, + true, + ); + this.premultipliedColorBr = mergeColorAlphaPremultiplied( + this.props.colorBr, + this.worldAlpha, + true, + ); + } else { + this.premultipliedColorTl = mergeColorAlphaPremultiplied( + this.props.colorTl, + this.worldAlpha, + true, + ); + this.premultipliedColorTr = mergeColorAlphaPremultiplied( + this.props.colorTr, + this.worldAlpha, + true, + ); + this.premultipliedColorBl = mergeColorAlphaPremultiplied( + this.props.colorBl, + this.worldAlpha, + true, + ); + this.premultipliedColorBr = mergeColorAlphaPremultiplied( + this.props.colorBr, + this.worldAlpha, + true, + ); + } + this.setUpdateType(UpdateType.Children); + childUpdateType |= UpdateType.PremultipliedColors; + } + + // No need to update zIndex if there is no parent + if (parent && this.updateType & UpdateType.CalculatedZIndex) { + this.calculateZIndex(); + // Tell parent to re-sort children + parent.setUpdateType(UpdateType.ZIndexSortedChildren); + } + + if (this.updateType & UpdateType.Children && this.children.length) { this.children.forEach((child) => { + // Trigger the depenedent update types on the child + child.setUpdateType(childUpdateType); + // If child has no updates, skip + if (child.updateType === 0) { + return; + } child.update(delta, this.clippingRect); }); } - if (this.recalculationType & 8) { + // Sorting children MUST happen after children have been updated so + // that they have the oppotunity to update their calculated zIndex. + if (this.updateType & UpdateType.ZIndexSortedChildren) { // reorder z-index this.sortChildren(); } - // reset update flag - this.hasUpdates = false; - - // reset recalculation type - this.recalculationType = 0; + // reset update type + this.updateType = 0; } /** * This function calculates the clipping rectangle for a node. * - * If the node's globalTransform is not set, the function returns immediately. - * If the node's props do not require clipping and there is no parent clipping rectangle, the node's clipping rectangle is set to null. * If the parent clipping rectangle has not changed and the node's clipping rectangle is already set, the function returns immediately. * * The function then checks if the node is rotated. If the node requires clipping and is not rotated, a new clipping rectangle is created based on the node's global transform and dimensions. @@ -297,14 +431,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { * Finally, the node's parentClippingRect and clippingRect properties are updated. */ calculateClippingRect(parentClippingRect: Rect | null = null) { - if (!this.globalTransform) { - return; - } - - if (!this.props.clipping && !parentClippingRect) { - this.clippingRect = null; - return; - } + assertTruthy(this.globalTransform); if (this.parentClippingRect === parentClippingRect && this.clippingRect) { return; @@ -332,19 +459,28 @@ export class CoreNode extends EventEmitter implements ICoreNode { this.clippingRect = clippingRect; } + calculateZIndex(): void { + const props = this.props; + const z = props.zIndex || 0; + const p = props.parent?.zIndex || 0; + + let zIndex = z; + if (props.parent?.zIndexLocked) { + zIndex = z < p ? z : p; + } + this.calcZIndex = zIndex; + } + renderQuads(renderer: CoreRenderer): void { + const { width, height, texture, textureOptions, shader, shaderProps } = + this.props; const { - width, - height, - colorTl, - colorTr, - colorBl, - colorBr, - texture, - textureOptions, - shader, - shaderProps, - } = this.props; + premultipliedColorTl, + premultipliedColorTr, + premultipliedColorBl, + premultipliedColorBr, + } = this; + const { zIndex, worldAlpha, globalTransform: gt, clippingRect } = this; assertTruthy(gt); @@ -353,10 +489,10 @@ export class CoreNode extends EventEmitter implements ICoreNode { renderer.addQuad({ width, height, - colorTl, - colorTr, - colorBl, - colorBr, + colorTl: premultipliedColorTl, + colorTr: premultipliedColorTr, + colorBl: premultipliedColorBl, + colorBr: premultipliedColorBr, texture, textureOptions, zIndex, @@ -388,7 +524,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { set x(value: number) { if (this.props.x !== value) { this.props.x = value; - this.updateLocalTransform(); + this.setUpdateType(UpdateType.Local); } } @@ -410,7 +546,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { set y(value: number) { if (this.props.y !== value) { this.props.y = value; - this.updateLocalTransform(); + this.setUpdateType(UpdateType.Local); } } @@ -421,7 +557,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { set width(value: number) { if (this.props.width !== value) { this.props.width = value; - this.updateLocalTransform(); + this.setUpdateType(UpdateType.Local); } } @@ -432,7 +568,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { set height(value: number) { if (this.props.height !== value) { this.props.height = value; - this.updateLocalTransform(); + this.setUpdateType(UpdateType.Local); } } @@ -456,7 +592,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { set scaleX(value: number) { if (this.props.scaleX !== value) { this.props.scaleX = value; - this.updateScaleRotateTransform(); + this.setUpdateType(UpdateType.ScaleRotate); } } @@ -467,35 +603,21 @@ export class CoreNode extends EventEmitter implements ICoreNode { set scaleY(value: number) { if (this.props.scaleY !== value) { this.props.scaleY = value; - this.updateScaleRotateTransform(); + this.setUpdateType(UpdateType.ScaleRotate); } } - get worldScaleX(): number { - return ( - this.props.scaleX * (this.props.parent?.worldScaleX ?? 1) || - this.props.scaleX - ); - } - - get worldScaleY(): number { - return ( - this.props.scaleY * (this.props.parent?.worldScaleY ?? 1) || - this.props.scaleY - ); - } - get mount(): number { return this.props.mount; } set mount(value: number) { - // if (this.props.mountX !== value || this.props.mountY !== value) { - this.props.mountX = value; - this.props.mountY = value; - this.props.mount = value; - this.updateLocalTransform(); - // } + if (this.props.mountX !== value || this.props.mountY !== value) { + this.props.mountX = value; + this.props.mountY = value; + this.props.mount = value; + this.setUpdateType(UpdateType.Local); + } } get mountX(): number { @@ -503,8 +625,10 @@ export class CoreNode extends EventEmitter implements ICoreNode { } set mountX(value: number) { - this.props.mountX = value; - this.updateLocalTransform(); + if (this.props.mountX !== value) { + this.props.mountX = value; + this.setUpdateType(UpdateType.Local); + } } get mountY(): number { @@ -512,8 +636,10 @@ export class CoreNode extends EventEmitter implements ICoreNode { } set mountY(value: number) { - this.props.mountY = value; - this.updateLocalTransform(); + if (this.props.mountY !== value) { + this.props.mountY = value; + this.setUpdateType(UpdateType.Local); + } } get pivot(): number { @@ -524,7 +650,8 @@ export class CoreNode extends EventEmitter implements ICoreNode { if (this.props.pivotX !== value || this.props.pivotY !== value) { this.props.pivotX = value; this.props.pivotY = value; - this.updateLocalTransform(); + this.props.pivot = value; + this.setUpdateType(UpdateType.Local); } } @@ -533,8 +660,10 @@ export class CoreNode extends EventEmitter implements ICoreNode { } set pivotX(value: number) { - this.props.pivotX = value; - this.updateLocalTransform(); + if (this.props.pivotX !== value) { + this.props.pivotX = value; + this.setUpdateType(UpdateType.Local); + } } get pivotY(): number { @@ -542,8 +671,10 @@ export class CoreNode extends EventEmitter implements ICoreNode { } set pivotY(value: number) { - this.props.pivotY = value; - this.updateLocalTransform(); + if (this.props.pivotY !== value) { + this.props.pivotY = value; + this.setUpdateType(UpdateType.Local); + } } get rotation(): number { @@ -553,7 +684,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { set rotation(value: number) { if (this.props.rotation !== value) { this.props.rotation = value; - this.updateScaleRotateTransform(); + this.setUpdateType(UpdateType.ScaleRotate); } } @@ -563,14 +694,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { set alpha(value: number) { this.props.alpha = value; - this.setRecalculationType(1); - } - - get worldAlpha(): number { - const props = this.props; - const parent = props.parent; - - return props.alpha * (parent?.worldAlpha || 1); + this.setUpdateType(UpdateType.PremultipliedColors | UpdateType.WorldAlpha); } get clipping(): boolean { @@ -579,8 +703,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { set clipping(value: boolean) { this.props.clipping = value; - this.clippingRect = null; - this.setRecalculationType(4); + this.setUpdateType(UpdateType.Clipping); } get color(): number { @@ -601,7 +724,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { } this.props.color = value; - this.setRecalculationType(2); + this.setUpdateType(UpdateType.PremultipliedColors); } get colorTop(): number { @@ -614,7 +737,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { this.colorTr = value; } this.props.colorTop = value; - this.setRecalculationType(2); + this.setUpdateType(UpdateType.PremultipliedColors); } get colorBottom(): number { @@ -627,7 +750,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { this.colorBr = value; } this.props.colorBottom = value; - this.setRecalculationType(2); + this.setUpdateType(UpdateType.PremultipliedColors); } get colorLeft(): number { @@ -640,7 +763,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { this.colorBl = value; } this.props.colorLeft = value; - this.setRecalculationType(2); + this.setUpdateType(UpdateType.PremultipliedColors); } get colorRight(): number { @@ -653,7 +776,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { this.colorBr = value; } this.props.colorRight = value; - this.setRecalculationType(2); + this.setUpdateType(UpdateType.PremultipliedColors); } get colorTl(): number { @@ -662,7 +785,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { set colorTl(value: number) { this.props.colorTl = value; - this.setRecalculationType(2); + this.setUpdateType(UpdateType.PremultipliedColors); } get colorTr(): number { @@ -671,7 +794,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { set colorTr(value: number) { this.props.colorTr = value; - this.setRecalculationType(2); + this.setUpdateType(UpdateType.PremultipliedColors); } get colorBl(): number { @@ -680,7 +803,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { set colorBl(value: number) { this.props.colorBl = value; - this.setRecalculationType(2); + this.setUpdateType(UpdateType.PremultipliedColors); } get colorBr(): number { @@ -689,7 +812,7 @@ export class CoreNode extends EventEmitter implements ICoreNode { set colorBr(value: number) { this.props.colorBr = value; - this.setRecalculationType(2); + this.setUpdateType(UpdateType.PremultipliedColors); } // we're only interested in parent zIndex to test @@ -700,22 +823,22 @@ export class CoreNode extends EventEmitter implements ICoreNode { set zIndexLocked(value: number) { this.props.zIndexLocked = value; + this.setUpdateType(UpdateType.CalculatedZIndex | UpdateType.Children); + this.children.forEach((child) => { + child.setUpdateType(UpdateType.CalculatedZIndex); + }); } get zIndex(): number { - const props = this.props; - const z = props.zIndex || 0; - const p = props.parent?.zIndex || 0; - - if (props.parent?.zIndexLocked) { - return z < p ? z : p; - } - return z; + return this.props.zIndex; } set zIndex(value: number) { this.props.zIndex = value; - this.props.parent?.setRecalculationType(8); + this.setUpdateType(UpdateType.CalculatedZIndex | UpdateType.Children); + this.children.forEach((child) => { + child.setUpdateType(UpdateType.CalculatedZIndex); + }); } get parent(): CoreNode | null { @@ -738,8 +861,12 @@ export class CoreNode extends EventEmitter implements ICoreNode { } if (newParent) { newParent.children.push(this); - // force parent to recalculate z-index for its children - newParent.setRecalculationType(8); + // Since this node has a new parent, to be safe, have it do a full update. + this.setUpdateType(UpdateType.All); + // Tell parent that it's children need to be updated and sorted. + newParent.setUpdateType( + UpdateType.Children | UpdateType.ZIndexSortedChildren, + ); } this.updateScaleRotateTransform(); diff --git a/src/core/Stage.ts b/src/core/Stage.ts index 95d2a80d..8fae62e5 100644 --- a/src/core/Stage.ts +++ b/src/core/Stage.ts @@ -183,7 +183,7 @@ export class Stage extends EventEmitter { return false; } - return scene?.root?.hasUpdates; + return !!scene?.root?.updateType; } /** @@ -191,12 +191,11 @@ export class Stage extends EventEmitter { */ drawFrame() { const { renderer, scene } = this; - if (!scene?.root) { - return; - } - // reset and clear viewport - scene?.root?.update(this.deltaTime); + // Update tree if needed + if (scene.root.updateType !== 0) { + scene.root.update(this.deltaTime); + } // test if we need to update the scene renderer?.reset(); diff --git a/src/core/renderers/webgl/WebGlCoreRenderer.ts b/src/core/renderers/webgl/WebGlCoreRenderer.ts index 3217f95a..b1fe8c4d 100644 --- a/src/core/renderers/webgl/WebGlCoreRenderer.ts +++ b/src/core/renderers/webgl/WebGlCoreRenderer.ts @@ -325,11 +325,7 @@ export class WebGlCoreRenderer extends CoreRenderer { fQuadBuffer[bufferIdx++] = ty; // vertexY fQuadBuffer[bufferIdx++] = texCoordX1; // texCoordX fQuadBuffer[bufferIdx++] = texCoordY1; // texCoordY - uiQuadBuffer[bufferIdx++] = mergeColorAlphaPremultiplied( - colorTl, - alpha, - true, - ); // color + uiQuadBuffer[bufferIdx++] = colorTl; // color fQuadBuffer[bufferIdx++] = textureIdx; // texIndex // Upper-Right @@ -337,11 +333,7 @@ export class WebGlCoreRenderer extends CoreRenderer { fQuadBuffer[bufferIdx++] = ty + width * tc; fQuadBuffer[bufferIdx++] = texCoordX2; fQuadBuffer[bufferIdx++] = texCoordY1; - uiQuadBuffer[bufferIdx++] = mergeColorAlphaPremultiplied( - colorTr, - alpha, - true, - ); + uiQuadBuffer[bufferIdx++] = colorTr; fQuadBuffer[bufferIdx++] = textureIdx; // Lower-Left @@ -349,11 +341,7 @@ export class WebGlCoreRenderer extends CoreRenderer { fQuadBuffer[bufferIdx++] = ty + height * td; fQuadBuffer[bufferIdx++] = texCoordX1; fQuadBuffer[bufferIdx++] = texCoordY2; - uiQuadBuffer[bufferIdx++] = mergeColorAlphaPremultiplied( - colorBl, - alpha, - true, - ); + uiQuadBuffer[bufferIdx++] = colorBl; fQuadBuffer[bufferIdx++] = textureIdx; // Lower-Right @@ -361,11 +349,7 @@ export class WebGlCoreRenderer extends CoreRenderer { fQuadBuffer[bufferIdx++] = ty + width * tc + height * td; fQuadBuffer[bufferIdx++] = texCoordX2; fQuadBuffer[bufferIdx++] = texCoordY2; - uiQuadBuffer[bufferIdx++] = mergeColorAlphaPremultiplied( - colorBr, - alpha, - true, - ); + uiQuadBuffer[bufferIdx++] = colorBr; fQuadBuffer[bufferIdx++] = textureIdx; } else { // Calculate the right corner of the quad @@ -378,11 +362,7 @@ export class WebGlCoreRenderer extends CoreRenderer { fQuadBuffer[bufferIdx++] = ty; // vertexY fQuadBuffer[bufferIdx++] = texCoordX1; // texCoordX fQuadBuffer[bufferIdx++] = texCoordY1; // texCoordY - uiQuadBuffer[bufferIdx++] = mergeColorAlphaPremultiplied( - colorTl, - alpha, - true, - ); // color + uiQuadBuffer[bufferIdx++] = colorTl; // color fQuadBuffer[bufferIdx++] = textureIdx; // texIndex // Upper-Right @@ -390,11 +370,7 @@ export class WebGlCoreRenderer extends CoreRenderer { fQuadBuffer[bufferIdx++] = ty; fQuadBuffer[bufferIdx++] = texCoordX2; fQuadBuffer[bufferIdx++] = texCoordY1; - uiQuadBuffer[bufferIdx++] = mergeColorAlphaPremultiplied( - colorTr, - alpha, - true, - ); + uiQuadBuffer[bufferIdx++] = colorTr; fQuadBuffer[bufferIdx++] = textureIdx; // Lower-Left @@ -402,11 +378,7 @@ export class WebGlCoreRenderer extends CoreRenderer { fQuadBuffer[bufferIdx++] = rightCornerY; fQuadBuffer[bufferIdx++] = texCoordX1; fQuadBuffer[bufferIdx++] = texCoordY2; - uiQuadBuffer[bufferIdx++] = mergeColorAlphaPremultiplied( - colorBl, - alpha, - true, - ); + uiQuadBuffer[bufferIdx++] = colorBl; fQuadBuffer[bufferIdx++] = textureIdx; // Lower-Right @@ -414,11 +386,7 @@ export class WebGlCoreRenderer extends CoreRenderer { fQuadBuffer[bufferIdx++] = rightCornerY; fQuadBuffer[bufferIdx++] = texCoordX2; fQuadBuffer[bufferIdx++] = texCoordY2; - uiQuadBuffer[bufferIdx++] = mergeColorAlphaPremultiplied( - colorBr, - alpha, - true, - ); + uiQuadBuffer[bufferIdx++] = colorBr; fQuadBuffer[bufferIdx++] = textureIdx; } diff --git a/src/core/text-rendering/renderers/CanvasTextRenderer.ts b/src/core/text-rendering/renderers/CanvasTextRenderer.ts index 69445a2d..265b4caa 100644 --- a/src/core/text-rendering/renderers/CanvasTextRenderer.ts +++ b/src/core/text-rendering/renderers/CanvasTextRenderer.ts @@ -18,7 +18,7 @@ */ import { EventEmitter } from '../../../common/EventEmitter.js'; -import { assertTruthy } from '../../../utils.js'; +import { assertTruthy, mergeColorAlphaPremultiplied } from '../../../utils.js'; import type { Stage } from '../../Stage.js'; import type { Matrix3d } from '../../lib/Matrix3d.js'; import { @@ -114,7 +114,11 @@ export class CanvasTextRenderer extends TextRenderer { } else { this.canvas = document.createElement('canvas'); } - let context = this.canvas.getContext('2d'); + // eslint-disable-next-line @typescript-eslint/no-unnecessary-type-assertion + let context = this.canvas.getContext('2d') as + | OffscreenCanvasRenderingContext2D + | CanvasRenderingContext2D + | null; if (!context) { // A browser may appear to support OffscreenCanvas but not actually support the Canvas '2d' context // Here we try getting the context again after falling back to an HTMLCanvasElement. @@ -543,15 +547,15 @@ export class CanvasTextRenderer extends TextRenderer { // Color alpha of text is not properly rendered to the Canvas texture, so we // need to apply it here. const combinedAlpha = alpha * getNormalizedAlphaComponent(color); - + const quadColor = mergeColorAlphaPremultiplied(0xffffffff, combinedAlpha); if (canvasPages[0].valid) { this.stage.renderer.addQuad({ alpha: combinedAlpha, clippingRect, - colorBl: 0xffffffff, - colorBr: 0xffffffff, - colorTl: 0xffffffff, - colorTr: 0xffffffff, + colorBl: quadColor, + colorBr: quadColor, + colorTl: quadColor, + colorTr: quadColor, width: canvasPages[0].texture?.dimensions?.width || 0, height: canvasPages[0].texture?.dimensions?.height || 0, texture: canvasPages[0].texture!, @@ -571,10 +575,10 @@ export class CanvasTextRenderer extends TextRenderer { this.stage.renderer.addQuad({ alpha: combinedAlpha, clippingRect, - colorBl: 0xffffffff, - colorBr: 0xffffffff, - colorTl: 0xffffffff, - colorTr: 0xffffffff, + colorBl: quadColor, + colorBr: quadColor, + colorTl: quadColor, + colorTr: quadColor, width: canvasPages[1].texture?.dimensions?.width || 0, height: canvasPages[1].texture?.dimensions?.height || 0, texture: canvasPages[1].texture!, @@ -594,10 +598,10 @@ export class CanvasTextRenderer extends TextRenderer { this.stage.renderer.addQuad({ alpha: combinedAlpha, clippingRect, - colorBl: 0xffffffff, - colorBr: 0xffffffff, - colorTl: 0xffffffff, - colorTr: 0xffffffff, + colorBl: quadColor, + colorBr: quadColor, + colorTl: quadColor, + colorTr: quadColor, width: canvasPages[2].texture?.dimensions?.width || 0, height: canvasPages[2].texture?.dimensions?.height || 0, texture: canvasPages[2].texture!, diff --git a/typedoc.config.cjs b/typedoc.config.cjs index d83a6977..5303c633 100644 --- a/typedoc.config.cjs +++ b/typedoc.config.cjs @@ -6,5 +6,5 @@ module.exports = { './exports/core-api.ts', './exports/utils.ts', ], - out: 'docs', + out: 'typedocs', };