diff --git a/CHANGELOG.md b/CHANGELOG.md index 7ac74c3a..e0b6a1cb 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -30,6 +30,7 @@ This project adheres to [Semantic Versioning](http://semver.org/). // equivalent to: Color.lerpLRGB(colorA, colorB, t); ``` +- Added `Color.fromFloatArray([0.0, 0.0, 0.0, 1.0])` and `Color.toFloatArray()` ### Fixed diff --git a/site/docs/04-graphics/04.1-color.mdx b/site/docs/04-graphics/04.1-color.mdx index 94c40213..e2aee189 100644 --- a/site/docs/04-graphics/04.1-color.mdx +++ b/site/docs/04-graphics/04.1-color.mdx @@ -37,6 +37,10 @@ ex.Color.fromHSL(h, s, l, a) // Hex, alpha optional ex.Color.fromHex('#000000') ex.Color.fromHex('#000000FF') +// Float array, alpha optional, components can be omitted (missed will be 0) +ex.Color.fromFloatArray([0.0]) +ex.Color.fromFloatArray([0.0, 0.0, 0.0]) +ex.Color.fromFloatArray([0.0, 0.0, 0.0, 1.0]) // String representation of a color with rgb as default // Options include rgb,hsl,hex diff --git a/src/engine/color.ts b/src/engine/color.ts index 7fb1e7bc..cec75eff 100644 --- a/src/engine/color.ts +++ b/src/engine/color.ts @@ -130,6 +130,17 @@ export class Color { return temp.toRGBA(); } + /** + * Creates a new instance of Color from array of float components. + * Missing components will be replaced with 0 for r, g, b, and 1 for a. + * @param array Array of [r, g, b, a] components + */ + public static fromFloatArray(array: number[]): Color { + const components = [array[0] ?? 0, array[1] ?? 0, array[2] ?? 0].map((c) => Math.round(c * 255)) as [number, number, number]; + + return new Color(...components, array[3]); + } + /** * Lightens the current color by a specified amount * @param factor The amount to lighten by [0-1] @@ -291,6 +302,21 @@ export class Color { return HSLColor.fromRGBA(this.r, this.g, this.b, this.a).toString(); } + /** + * Return float array representation of a color. + * @param precision + */ + public toFloatArray(precision?: number): [r: number, g: number, b: number, a: number] { + let components = [this.r / 255, this.g / 255, this.b / 255]; + + if (precision) { + components = components.map((c) => parseFloat(c.toFixed(precision))); + } + + components.push(this.a); + return components as [number, number, number, number]; + } + /** * Returns a CSS string representation of a color. */ diff --git a/src/engine/graphics/context/excalibur-graphics-context-webgl.ts b/src/engine/graphics/context/excalibur-graphics-context-webgl.ts index 0fc68998..3c5012af 100644 --- a/src/engine/graphics/context/excalibur-graphics-context-webgl.ts +++ b/src/engine/graphics/context/excalibur-graphics-context-webgl.ts @@ -342,7 +342,7 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { gl.viewport(0, 0, gl.canvas.width, gl.canvas.height); // Clear background - gl.clearColor(this.backgroundColor.r / 255, this.backgroundColor.g / 255, this.backgroundColor.b / 255, this.backgroundColor.a); + gl.clearColor(...this.backgroundColor.toFloatArray()); gl.clear(gl.COLOR_BUFFER_BIT); // Enable alpha blending @@ -734,7 +734,7 @@ export class ExcaliburGraphicsContextWebGL implements ExcaliburGraphicsContext { const gl = this.__gl; const currentTarget = this.multiSampleAntialiasing ? this._msaaTarget : this._renderTarget; currentTarget.use(); - gl.clearColor(this.backgroundColor.r / 255, this.backgroundColor.g / 255, this.backgroundColor.b / 255, this.backgroundColor.a); + gl.clearColor(...this.backgroundColor.toFloatArray()); // Clear the context with the newly set color. This is // the function call that actually does the drawing. gl.clear(gl.COLOR_BUFFER_BIT); diff --git a/src/engine/graphics/context/shader.ts b/src/engine/graphics/context/shader.ts index 00a22e50..76b7e480 100644 --- a/src/engine/graphics/context/shader.ts +++ b/src/engine/graphics/context/shader.ts @@ -762,7 +762,7 @@ export class Shader { * @param value */ setUniformFloatColor(name: string, value: Color): void { - this.setUniform('uniform4f', name, value.r / 255, value.g / 255, value.b / 255, value.a); + this.setUniform('uniform4f', name, ...value.toFloatArray()); } /** @@ -773,7 +773,7 @@ export class Shader { * @param value */ trySetUniformFloatColor(name: string, value: Color): boolean { - return this.trySetUniform('uniform4f', name, value.r / 255, value.g / 255, value.b / 255, value.a); + return this.trySetUniform('uniform4f', name, ...value.toFloatArray()); } /** diff --git a/src/spec/vitest/color-spec.ts b/src/spec/vitest/color-spec.ts index 406a395c..86d9fc65 100644 --- a/src/spec/vitest/color-spec.ts +++ b/src/spec/vitest/color-spec.ts @@ -94,6 +94,20 @@ describe('A color', () => { expect(color.a).toBe(0); }); + it('can be parsed from float array', () => { + color = ex.Color.fromFloatArray([1.0, 0.5, 1.0, 0.5]); + expect(color.r).toBe(255); + expect(color.g).toBe(128); + expect(color.b).toBe(255); + expect(color.a).toBe(0.5); + + color = ex.Color.fromFloatArray([1.0]); + expect(color.r).toBe(255); + expect(color.g).toBe(0); + expect(color.b).toBe(0); + expect(color.a).toBe(1.0); + }); + it('should have a default alpha of 255 if not specified', () => { color = ex.Color.fromHex('#000000'); expect(color.a).toBe(1); @@ -119,6 +133,24 @@ describe('A color', () => { expect(color.toHex()).toBe('#111111'); }); + it('generate valid float array representation', () => { + color = ex.Color.White; + let floatArray = color.toFloatArray(); + expect(floatArray[0]).toBe(1.0); + expect(floatArray[1]).toBe(1.0); + expect(floatArray[2]).toBe(1.0); + expect(floatArray[3]).toBe(1.0); + + color = ex.Color.Azure; + floatArray = color.toFloatArray(); + expect(floatArray[0]).toBe(0.0); + expect(floatArray[1]).toBe(0.4980392156862745); + expect(floatArray[2]).toBe(1.0); + expect(floatArray[3]).toBe(1.0); + + expect(color.toFloatArray(3)[1]).toBe(0.498); + }); + it('can be darkened', () => { color = ex.Color.White.clone(); color = color.darken();