diff --git a/src/index.d.ts b/src/index.d.ts index 183070d..2f1e987 100644 --- a/src/index.d.ts +++ b/src/index.d.ts @@ -5,5 +5,5 @@ */ export * from "./types"; -// eslint-disable-next-line @typescript-eslint/no-explicit-any -export declare const Primitives: any; +import * as Primitives from "./primitives"; +export { Primitives }; diff --git a/src/primitives/types/types.js b/src/primitives/types/types.js index a0f5647..6ac518f 100644 --- a/src/primitives/types/types.js +++ b/src/primitives/types/types.js @@ -588,7 +588,7 @@ const _Types = { value = [value]; } - if (value instanceof Vector2) { + if (value?.isVector2) { target.copy(value); } else if (value instanceof Array) { value = value.concat(defaults.slice(value.length)); @@ -655,7 +655,7 @@ const _Types = { value = [value]; } - if (value instanceof Vector3) { + if (value?.isVector3) { target.copy(value); } else if (value instanceof Array) { value = value.concat(defaults.slice(value.length)); @@ -731,7 +731,7 @@ const _Types = { value = [value]; } - if (value instanceof Vector4) { + if (value?.isVector4) { target.copy(value); } else if (value instanceof Array) { value = value.concat(defaults.slice(value.length)); @@ -826,7 +826,7 @@ const _Types = { return m; }, validate(value, target, invalid) { - if (value instanceof Matrix3) { + if (value?.isMatrix3) { target.copy(value); } else if (value instanceof Array) { value = value.concat(defaults.slice(value.length)); @@ -951,7 +951,7 @@ const _Types = { return m; }, validate(value, target, invalid) { - if (value instanceof Matrix4) { + if (value?.isMatrix4) { target.copy(value); } else if (value instanceof Array) { value = value.concat(defaults.slice(value.length)); @@ -987,7 +987,7 @@ const _Types = { return new Quaternion(); }, validate(value, target, invalid) { - if (value instanceof Quaternion) { + if (value?.isQuaternion) { target.copy(value); } else { target = vec4.validate(value, target, invalid); @@ -1046,7 +1046,7 @@ const _Types = { value = new Color(value); } - if (value instanceof Color) { + if (value?.isColor) { target.copy(value); } else if (value instanceof Array) { value = value.concat(defaults.slice(value.length)); diff --git a/src/primitives/types/types_typed.ts b/src/primitives/types/types_typed.ts index e44826e..b8c5c35 100644 --- a/src/primitives/types/types_typed.ts +++ b/src/primitives/types/types_typed.ts @@ -7,7 +7,17 @@ * specifying types that are only consumed in our source code, but no good for * specifying types that should be included in the output. */ +import type { + Color, + Matrix3, + Matrix4, + Quaternion, + Vector2, + Vector3, + Vector4, +} from "three"; import type { MathboxNode, MathboxSelection } from "../../types"; + import { Types as TypesUntyped } from "./types"; type OnInvalid = () => void; @@ -61,6 +71,19 @@ export type Alignments = "left" | "middle" | "right" | number; */ export type TransitionStates = "enter" | "visible" | "exiit" | number; +/** + * A representation of a color. Can be: + * - a string, which is parsed by THREE.Color + * - a THREE.Color instance + * - a number, which is interpreted as a hex value + * - an array of numbers, which is interpreted as an RGB value + */ +type ColorDescription = string | Color | number | number[]; + +type Vec2Like = number | number[] | Vector2; +type Vec3Like = number | number[] | Vector3; +type Vec4Like = number | number[] | Vector4; + export type TypeGenerators = { // Helpers nullable(type: Type): Type; @@ -121,18 +144,60 @@ export type TypeGenerators = { object: any; timestamp: any; - vec2: any; - ivec2: any; - vec3: any; - ivec3: any; - vec4: any; - ivec4: any; + vec2(x?: number, y?: number): Type; + ivec2(x?: number, y?: number): Type; + vec3(x?: number, y?: number, z?: number): Type; + ivec3(x?: number, y?: number, z?: number): Type; + vec4(x?: number, y?: number, z?: number, w?: number): Type; + ivec4( + x?: number, + y?: number, + z?: number, + w?: number + ): Type; - mat3: any; - mat4: any; + mat3( + n11?: number, + n12?: number, + n13?: number, + n21?: number, + n22?: number, + n23?: number, + n31?: number, + n32?: number, + n33?: number + ): Type; + mat4( + n11?: number, + n12?: number, + n13?: number, + n14?: number, + n21?: number, + n22?: number, + n23?: number, + n24?: number, + n31?: number, + n32?: number, + n33?: number, + n34?: number, + n41?: number, + n42?: number, + n43?: number, + n44?: number + ): Type; - quat: any; - color: any; + quat( + x?: number, + y?: number, + z?: number, + w?: number + ): Type; + color( + r?: number, + g?: number, + b?: number, + a?: number + ): Type; transpose(order?: string | Axes[]): Type, number[]>; swizzle( diff --git a/test/primitives/types/types.spec.ts b/test/primitives/types/types.spec.ts index ca61af0..154fe40 100644 --- a/test/primitives/types/types.spec.ts +++ b/test/primitives/types/types.spec.ts @@ -1,5 +1,6 @@ import * as MathBox from "../../../src"; import type { Axes, AxesWithZero } from "../../../src/types"; +import { Color, Matrix3, Matrix4, Quaternion, Vector2, Vector3, Vector4 } from "three" const { Types } = MathBox.Primitives.Types; @@ -213,4 +214,240 @@ describe("primitives.types.types", function () { } expect(invalid).toHaveBeenCalledTimes(4); }); -}); + + it("validates color", () => { + const color = Types.color(); + const value = color.make(); + const target = new Color() + const onInvalid = jasmine.createSpy(); + + // Default color is 0.5, 0.5, 0.5 + expect(value).toEqual(new Color(0.5, 0.5, 0.5)); + + + // string colors work + expect( + color.validate("red", target, onInvalid) + ).toEqual(new Color(1, 0, 0)); + expect(onInvalid).not.toHaveBeenCalled(); + + // ThreeJS Colors work + expect( + color.validate(new Color(0.1, 0.2, 0.3), target, onInvalid) + ).toEqual(new Color(0.1, 0.2, 0.3)); + expect(onInvalid).not.toHaveBeenCalled(); + + // Array colors are RGB + expect( + color.validate([0.3,0.4,0.5], target, onInvalid) + ).toEqual(new Color(0.3,0.4,0.5)); + expect(onInvalid).not.toHaveBeenCalled(); + + // Numbers are interpreted as hex + expect( + color.validate(parseInt("00FFFF", 16), target, onInvalid) + ).toEqual(new Color(0,1,1)); + expect(onInvalid).not.toHaveBeenCalled(); + + // @ts-expect-error Null is invalid; should call invalid + color.validate(null, target, onInvalid) + expect(onInvalid).toHaveBeenCalled(); + onInvalid.calls.reset() + }) + + it("validates vec2", () => { + const vec2 = Types.vec2(2, 8); + const value = vec2.make(); + const target = new Vector2() + const onInvalid = jasmine.createSpy(); + + expect(value).toEqual(new Vector2(2, 8)); + + + expect( + vec2.validate(1, target, onInvalid) + ).toEqual(new Vector2(1, 8)); + expect(onInvalid).not.toHaveBeenCalled(); + + expect( + vec2.validate([10, 20], target, onInvalid) + ).toEqual(new Vector2(10, 20)); + expect(onInvalid).not.toHaveBeenCalled(); + + expect( + vec2.validate(new Vector2(30, 40), target, onInvalid) + ).toEqual(new Vector2(30, 40)); + expect(onInvalid).not.toHaveBeenCalled(); + + // @ts-expect-error Null is invalid; should call invalid + vec2.validate(null, target, onInvalid) + expect(onInvalid).toHaveBeenCalled(); + onInvalid.calls.reset() + }) + + it("validates vec3", () => { + const vec3 = Types.vec3(2, 8, 16); + const value = vec3.make(); + const target = new Vector3() + const onInvalid = jasmine.createSpy(); + + expect(value).toEqual(new Vector3(2, 8, 16)); + + + expect( + vec3.validate(1, target, onInvalid) + ).toEqual(new Vector3(1, 8, 16)); + expect(onInvalid).not.toHaveBeenCalled(); + + expect( + vec3.validate([10, 20, 30], target, onInvalid) + ).toEqual(new Vector3(10, 20, 30)); + expect(onInvalid).not.toHaveBeenCalled(); + + expect( + vec3.validate(new Vector3(30, 40, 50), target, onInvalid) + ).toEqual(new Vector3(30, 40, 50)); + expect(onInvalid).not.toHaveBeenCalled(); + + // @ts-expect-error Null is invalid; should call invalid + vec3.validate(null, target, onInvalid) + expect(onInvalid).toHaveBeenCalled(); + onInvalid.calls.reset() + }) + + it("validates vec4", () => { + const vec4 = Types.vec4(2, 8, 16, 32); + const value = vec4.make(); + const target = new Vector4() + const onInvalid = jasmine.createSpy(); + + expect(value).toEqual(new Vector4(2, 8, 16, 32)); + + + expect( + vec4.validate(1, target, onInvalid) + ).toEqual(new Vector4(1, 8, 16, 32)); + expect(onInvalid).not.toHaveBeenCalled(); + + expect( + vec4.validate([10, 20, 30, 40], target, onInvalid) + ).toEqual(new Vector4(10, 20, 30, 40)); + expect(onInvalid).not.toHaveBeenCalled(); + + expect( + vec4.validate(new Vector4(30, 40, 50, 60), target, onInvalid) + ).toEqual(new Vector4(30, 40, 50, 60)); + expect(onInvalid).not.toHaveBeenCalled(); + + // @ts-expect-error Null is invalid; should call invalid + vec4.validate(null, target, onInvalid) + expect(onInvalid).toHaveBeenCalled(); + onInvalid.calls.reset() + }) + + it("validates mat3", () => { + const mat3 = Types.mat3(10, 20, 30); + const value = mat3.make(); + const target = new Matrix3() + const onInvalid = jasmine.createSpy(); + + const expected1 = new Matrix3() + expected1.set(10, 20, 30, 0, 1, 0, 0, 0, 1) + expect(value).toEqual(expected1); + + + const expected2 = new Matrix3() + expected2.set(6,5,4,3,2,1,0,0,1) + expect( + mat3.validate([6, 5, 4, 3 ,2 ,1], target, onInvalid) + ).toEqual(expected2); + expect(onInvalid).not.toHaveBeenCalled(); + + const input3 = new Matrix3() + input3.set(10, 20, 30, 40, 50, 60, 70, 80, 90) + const expected3 = new Matrix3() + expected3.set(10, 20, 30, 40, 50, 60, 70, 80, 90) + expect( + mat3.validate(input3, target, onInvalid) + ).toEqual(expected3); + expect(onInvalid).not.toHaveBeenCalled(); + + + // @ts-expect-error Numbers are invalid; should call invalid + mat3.validate(123, target, onInvalid) + expect(onInvalid).toHaveBeenCalled(); + onInvalid.calls.reset() + }) + + it("validates mat4", () => { + const mat4 = Types.mat4(10, 20, 30, 40); + const value = mat4.make(); + const target = new Matrix4() + const onInvalid = jasmine.createSpy(); + + const expected1 = new Matrix4() + expected1.set(10, 20, 30, 40, 0, 1, 0, 0, 0,0,1, 0, 0,0,0, 1) + expect(value).toEqual(expected1); + + + const expected2 = new Matrix4() + expected2.set(8, 7, 6, 5, 4, 3, 2, 1, 0, 0, 1, 0, 0, 0, 0, 1) + expect( + mat4.validate([8, 7, 6, 5, 4, 3, 2, 1], target, onInvalid) + ).toEqual(expected2); + expect(onInvalid).not.toHaveBeenCalled(); + + const input3 = new Matrix4() + input3.set(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) + const expected3 = new Matrix4() + expected3.set(1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16) + expect( + mat4.validate(input3, target, onInvalid) + ).toEqual(expected3); + expect(onInvalid).not.toHaveBeenCalled(); + + + // @ts-expect-error Numbers are invalid; should call invalid + mat4.validate(123, target, onInvalid) + expect(onInvalid).toHaveBeenCalled(); + onInvalid.calls.reset() + }) + + it("validates quaternions", () => { + const quat = Types.quat(2, 8, 16); + const value = quat.make(); + const target = new Quaternion() + const onInvalid = jasmine.createSpy(); + + + /** + * This seems like a bug: quat.make() should be using + * the default values provided to Types.quat. + * + * In practice, this isn't really observable because Mathbox never provides + * arguments to Types.quat(...) + */ + expect(value).toEqual(new Quaternion(0, 0, 0, 1)); + + + expect( + quat.validate(10, target, onInvalid) + ).toEqual(new Quaternion(10, 8, 16, 1).normalize()); + expect(onInvalid).not.toHaveBeenCalled(); + + expect( + quat.validate([10, 20 ], target, onInvalid) + ).toEqual(new Quaternion(10, 20, 16, 1).normalize()); + expect(onInvalid).not.toHaveBeenCalled(); + + expect( + quat.validate(new Vector4(10, 20), target, onInvalid) + ).toEqual(new Quaternion(10, 20, 0, 1).normalize()); + expect(onInvalid).not.toHaveBeenCalled(); + + expect( + quat.validate(new Quaternion(30, 40, 50, 60), target, onInvalid) + ).toEqual(new Quaternion(30, 40, 50, 60).normalize()); + expect(onInvalid).not.toHaveBeenCalled(); + }) +}); \ No newline at end of file