@forgeax/engine-math 0.1.23 → 0.1.25

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/dist/mat4.d.ts CHANGED
@@ -175,17 +175,17 @@ export declare function perspectiveReverseZ(out: Mat4, fovYRadians: number, aspe
175
175
  /**
176
176
  * out = orthographic projection (**WebGPU [0, 1] NDC** short name, D-3). Returns out.
177
177
  *
178
- * @degrade near >= far or left >= right or bottom >= top → numerically undefined but does not throw.
178
+ * @degrade near >= far or left >= right or top <= bottom → numerically undefined but does not throw.
179
179
  *
180
180
  * @example
181
181
  * ```ts
182
- * mat4.orthographic(out, -10, 10, -10, 10, 0.1, 100);
183
- * // Guard: if (!(left < right && bottom < top && near < far)) handleInvalid();
182
+ * mat4.orthographic(out, -10, 10, 10, -10, 0.1, 100);
183
+ * // Guard: if (!(left < right && top > bottom && near < far)) handleInvalid();
184
184
  * ```
185
185
  */
186
- export declare function orthographic(out: Mat4, left: number, right: number, bottom: number, top: number, near: number, far: number): Mat4;
186
+ export declare function orthographic(out: Mat4, left: number, right: number, top: number, bottom: number, near: number, far: number): Mat4;
187
187
  /** out = orthographic projection (**WebGL/OpenGL [-1, 1] NDC**, *NO*). Returns out. */
188
- export declare function orthographicNO(out: Mat4, left: number, right: number, bottom: number, top: number, near: number, far: number): Mat4;
188
+ export declare function orthographicNO(out: Mat4, left: number, right: number, top: number, bottom: number, near: number, far: number): Mat4;
189
189
  /**
190
190
  * out = reversed-Z orthographic projection (**WebGPU [0, 1] NDC, near→1 far→0**;
191
191
  * D-P3 self-extension).
@@ -196,7 +196,7 @@ export declare function orthographicNO(out: Mat4, left: number, right: number, b
196
196
  * Note: this function is an @forgeax/engine-math self-extension; wgpu-matrix / gl-matrix have no matching
197
197
  * name (see plan-strategy D-P3 + README quick-ref table footnote).
198
198
  */
199
- export declare function orthographicReverseZ(out: Mat4, left: number, right: number, bottom: number, top: number, near: number, far: number): Mat4;
199
+ export declare function orthographicReverseZ(out: Mat4, left: number, right: number, top: number, bottom: number, near: number, far: number): Mat4;
200
200
  /**
201
201
  * out = M * (v.x, v.y, v.z, 1) (with perspective divide by w'). Returns out.
202
202
  *
@@ -1 +1 @@
1
- {"version":3,"file":"mat4.d.ts","sourceRoot":"","sources":["../src/mat4.ts"],"names":[],"mappings":"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"}
1
+ {"version":3,"file":"mat4.d.ts","sourceRoot":"","sources":["../src/mat4.ts"],"names":[],"mappings":"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"}
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@forgeax/engine-math",
3
- "version": "0.1.23",
3
+ "version": "0.1.25",
4
4
  "private": false,
5
5
  "type": "module",
6
6
  "license": "Apache-2.0",
package/src/color.ts CHANGED
@@ -1,6 +1,6 @@
1
1
  // color.ts — RGBA color namespace (M5 / T-032)
2
2
  //
3
- // 6-function surface: create / clone / srgbToLinear / linearToSrgb / fromHex / toHex
3
+ // 7-function surface: create / clone / srgbToLinear / linearToSrgb / fromHex / fromCss / toHex
4
4
  //
5
5
  // Design anchors:
6
6
  // - branded Float32Array length 4 [r, g, b, a]; `as Color` casts are funneled inside factories (D-P15)
@@ -8,13 +8,16 @@
8
8
  // - srgbToLinear: cutoff 0.04045 / linear segment v/12.92 / power segment ((v+0.055)/1.055)^2.4
9
9
  // - linearToSrgb: cutoff 0.0031308 / linear segment 12.92*v / power segment 1.055*v^(1/2.4) - 0.055
10
10
  // - negatives / NaN returned verbatim (HDR-friendly + IEEE-754 NaN propagation)
11
- // - fromHex (D-P7): only `#RRGGBB` (7 chars) and `#RRGGBBAA` (9 chars); illegal inputs silently fall
11
+ // - fromHex (D-P7): only `#RRGGBB` (7 chars) and `#RRGGBBAA` (9 chars); RGB channels are decoded
12
+ // from sRGB to linear; illegal inputs silently fall
12
13
  // back to (0, 0, 0, 1) without throwing (D-P12 degenerate family / AC-06)
13
14
  // - #RGB / #RGBA short forms are not supported (stricter than bevy_color::Srgba::hex; less ambiguous
14
15
  // in practice; wiki/sources/2026-05-05-bevy-0-19-math-transform-color §Srgba::hex mentions short
15
16
  // forms but D-P7 actively tightens the contract)
16
- // - toHex: alpha=1 `#rrggbb`; alpha<1 `#rrggbbaa`; components are clamped to [0, 1] then
17
- // multiplied by 255 and rounded; output is lowercase, symmetric with fromHex's tolerant input
17
+ // - fromCss: deterministic CSS color boundary for full hex plus common rgb()/rgba()/hsl()/hsla()
18
+ // and named colors; output is linear and illegal inputs use the same black fallback
19
+ // - toHex: alpha=1 → `#rrggbb`; alpha<1 → `#rrggbbaa`; linear components are encoded to sRGB,
20
+ // clamped to [0, 1], multiplied by 255 and rounded; output is lowercase
18
21
  //
19
22
  // Related: requirements §Surface color lower bound 6 + AC-06 silent fall-back (never raises);
20
23
  // plan-strategy D-P7 / D-P12 / §appendix A degenerate registry #14-#16;
@@ -120,8 +123,8 @@ const HEX_PATTERN = /^#([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA-F]{2})([0-9a-fA
120
123
  *
121
124
  * @example
122
125
  * ```ts
123
- * color.fromHex(out, '#ff8000'); // → (1, 0.502, 0, 1)
124
- * color.fromHex(out, '#ff800080'); // → (1, 0.502, 0, 0.502)
126
+ * color.fromHex(out, '#ff8000'); // → (1, ~0.216, 0, 1)
127
+ * color.fromHex(out, '#ff800080'); // → (1, ~0.216, 0, 0.502)
125
128
  * color.fromHex(out, '#fff'); // → (0, 0, 0, 1) short form unsupported → silent fall-back
126
129
  * ```
127
130
  */
@@ -134,17 +137,222 @@ export function fromHex(out: Color, hex: string): Color {
134
137
  out[3] = 1;
135
138
  return out;
136
139
  }
137
- out[0] = Number.parseInt(match[1] as string, 16) / 255;
138
- out[1] = Number.parseInt(match[2] as string, 16) / 255;
139
- out[2] = Number.parseInt(match[3] as string, 16) / 255;
140
+ out[0] = srgbChannelToLinear(Number.parseInt(match[1] as string, 16) / 255);
141
+ out[1] = srgbChannelToLinear(Number.parseInt(match[2] as string, 16) / 255);
142
+ out[2] = srgbChannelToLinear(Number.parseInt(match[3] as string, 16) / 255);
140
143
  out[3] = match[4] !== undefined ? Number.parseInt(match[4], 16) / 255 : 1;
141
144
  return out;
142
145
  }
143
146
 
144
- /** Per-component [0, 1] clamp + ×255 + round + 2-digit hex (lowercase). */
145
- function toHexByte(v: number): string {
147
+ const CSS_HEX_PATTERN = /^#([0-9a-f]{3,4}|[0-9a-f]{6}|[0-9a-f]{8})$/i;
148
+ const CSS_RGB_PATTERN = /^rgba?\(([^()]*)\)$/i;
149
+ const CSS_HSL_PATTERN = /^hsla?\(([^()]*)\)$/i;
150
+ const CSS_DECIMAL_PATTERN = /^[+-]?(?:\d+(?:\.\d*)?|\.\d+)$/;
151
+
152
+ const CSS_NAMED_COLORS: Readonly<Record<string, string>> = {
153
+ black: '#000000',
154
+ blue: '#0000ff',
155
+ cyan: '#00ffff',
156
+ fuchsia: '#ff00ff',
157
+ gray: '#808080',
158
+ green: '#008000',
159
+ grey: '#808080',
160
+ lime: '#00ff00',
161
+ magenta: '#ff00ff',
162
+ maroon: '#800000',
163
+ navy: '#000080',
164
+ olive: '#808000',
165
+ orange: '#ffa500',
166
+ purple: '#800080',
167
+ red: '#ff0000',
168
+ silver: '#c0c0c0',
169
+ teal: '#008080',
170
+ transparent: '#00000000',
171
+ white: '#ffffff',
172
+ yellow: '#ffff00',
173
+ };
174
+
175
+ function clamp01(v: number): number {
176
+ return Math.min(1, Math.max(0, v));
177
+ }
178
+
179
+ function parseCssChannel(token: string): number | undefined {
180
+ const trimmed = token.trim();
181
+ if (trimmed.endsWith('%')) {
182
+ const raw = trimmed.slice(0, -1).trim();
183
+ if (!CSS_DECIMAL_PATTERN.test(raw)) return undefined;
184
+ const value = Number(raw);
185
+ return Number.isFinite(value) ? clamp01(value / 100) : undefined;
186
+ }
187
+ if (!CSS_DECIMAL_PATTERN.test(trimmed)) return undefined;
188
+ const value = Number(trimmed);
189
+ return Number.isFinite(value) ? clamp01(value / 255) : undefined;
190
+ }
191
+
192
+ function parseCssAlpha(token: string): number | undefined {
193
+ const trimmed = token.trim();
194
+ if (trimmed.endsWith('%')) {
195
+ const raw = trimmed.slice(0, -1).trim();
196
+ if (!CSS_DECIMAL_PATTERN.test(raw)) return undefined;
197
+ const value = Number(raw);
198
+ return Number.isFinite(value) ? clamp01(value / 100) : undefined;
199
+ }
200
+ if (!CSS_DECIMAL_PATTERN.test(trimmed)) return undefined;
201
+ const value = Number(trimmed);
202
+ return Number.isFinite(value) ? clamp01(value) : undefined;
203
+ }
204
+
205
+ function parseCssHue(token: string): number | undefined {
206
+ const match = /^([+-]?(?:\d+\.?\d*|\.\d+))(deg|grad|rad|turn)?$/i.exec(token.trim());
207
+ if (match === null) return undefined;
208
+ const raw = Number(match[1] as string);
209
+ if (!Number.isFinite(raw)) return undefined;
210
+ const unit = (match[2] ?? 'deg').toLowerCase();
211
+ const degrees =
212
+ unit === 'grad'
213
+ ? raw * 0.9
214
+ : unit === 'rad'
215
+ ? (raw * 180) / Math.PI
216
+ : unit === 'turn'
217
+ ? raw * 360
218
+ : raw;
219
+ return ((degrees % 360) + 360) % 360;
220
+ }
221
+
222
+ function parseCssPercentage(token: string): number | undefined {
223
+ const trimmed = token.trim();
224
+ if (!trimmed.endsWith('%')) return undefined;
225
+ const raw = trimmed.slice(0, -1).trim();
226
+ if (!CSS_DECIMAL_PATTERN.test(raw)) return undefined;
227
+ const value = Number(raw);
228
+ return Number.isFinite(value) ? clamp01(value / 100) : undefined;
229
+ }
230
+
231
+ function splitCssColorArgs(
232
+ body: string,
233
+ ): { channels: [string, string, string]; alpha?: string } | undefined {
234
+ const source = body.trim();
235
+ if (source.length === 0) return undefined;
236
+ if (source.includes(',')) {
237
+ if (source.includes('/')) return undefined;
238
+ const parts = source.split(',').map((part) => part.trim());
239
+ if (parts.length !== 3 && parts.length !== 4) return undefined;
240
+ if (parts.some((part) => part.length === 0)) return undefined;
241
+ return {
242
+ channels: [parts[0] as string, parts[1] as string, parts[2] as string],
243
+ ...(parts.length === 4 ? { alpha: parts[3] as string } : {}),
244
+ };
245
+ }
246
+ const tokens = source.replaceAll('/', ' / ').split(/\s+/);
247
+ const slash = tokens.indexOf('/');
248
+ if (slash === -1) {
249
+ if (tokens.length !== 3) return undefined;
250
+ return { channels: [tokens[0] as string, tokens[1] as string, tokens[2] as string] };
251
+ }
252
+ if (slash !== 3 || tokens.length !== 5 || tokens[4] === undefined) return undefined;
253
+ return {
254
+ channels: [tokens[0] as string, tokens[1] as string, tokens[2] as string],
255
+ alpha: tokens[4],
256
+ };
257
+ }
258
+
259
+ function hslToSrgb(hue: number, saturation: number, lightness: number): [number, number, number] {
260
+ const chroma = (1 - Math.abs(2 * lightness - 1)) * saturation;
261
+ const h = hue / 60;
262
+ const x = chroma * (1 - Math.abs((h % 2) - 1));
263
+ const [r1, g1, b1] =
264
+ h < 1
265
+ ? [chroma, x, 0]
266
+ : h < 2
267
+ ? [x, chroma, 0]
268
+ : h < 3
269
+ ? [0, chroma, x]
270
+ : h < 4
271
+ ? [0, x, chroma]
272
+ : h < 5
273
+ ? [x, 0, chroma]
274
+ : [chroma, 0, x];
275
+ const m = lightness - chroma / 2;
276
+ return [r1 + m, g1 + m, b1 + m];
277
+ }
278
+
279
+ function cssFallback(out: Color): Color {
280
+ out[0] = 0;
281
+ out[1] = 0;
282
+ out[2] = 0;
283
+ out[3] = 1;
284
+ return out;
285
+ }
286
+
287
+ /**
288
+ * Write a Color from a deterministic CSS color string.
289
+ *
290
+ * Supports full and short hex, common named colors, rgb()/rgba() channel
291
+ * numbers or percentages, and hsl()/hsla() with an optional alpha. RGB
292
+ * channels are decoded to linear-sRGB; alpha is passed through unchanged.
293
+ * Unsupported or malformed input silently falls back to opaque black.
294
+ */
295
+ export function fromCss(out: Color, css: string): Color {
296
+ if (typeof css !== 'string') return cssFallback(out);
297
+ const source = css.trim().toLowerCase();
298
+ const named = CSS_NAMED_COLORS[source];
299
+ if (named !== undefined) return fromHex(out, named);
300
+ const fullHex = CSS_HEX_PATTERN.exec(source);
301
+ if (fullHex !== null) {
302
+ const digits = fullHex[1] as string;
303
+ if (digits.length === 3 || digits.length === 4) {
304
+ const expanded = [...digits].map((channel) => `${channel}${channel}`).join('');
305
+ return fromHex(out, `#${expanded}`);
306
+ }
307
+ return fromHex(out, source);
308
+ }
309
+
310
+ const rgb = CSS_RGB_PATTERN.exec(source);
311
+ if (rgb !== null) {
312
+ const args = splitCssColorArgs(rgb[1] as string);
313
+ if (args === undefined) return cssFallback(out);
314
+ const r = parseCssChannel(args.channels[0]);
315
+ const g = parseCssChannel(args.channels[1]);
316
+ const b = parseCssChannel(args.channels[2]);
317
+ const alpha = args.alpha === undefined ? 1 : parseCssAlpha(args.alpha);
318
+ if (r === undefined || g === undefined || b === undefined || alpha === undefined)
319
+ return cssFallback(out);
320
+ out[0] = srgbChannelToLinear(r);
321
+ out[1] = srgbChannelToLinear(g);
322
+ out[2] = srgbChannelToLinear(b);
323
+ out[3] = alpha;
324
+ return out;
325
+ }
326
+
327
+ const hsl = CSS_HSL_PATTERN.exec(source);
328
+ if (hsl !== null) {
329
+ const args = splitCssColorArgs(hsl[1] as string);
330
+ if (args === undefined) return cssFallback(out);
331
+ const hue = parseCssHue(args.channels[0]);
332
+ const saturation = parseCssPercentage(args.channels[1]);
333
+ const lightness = parseCssPercentage(args.channels[2]);
334
+ const alpha = args.alpha === undefined ? 1 : parseCssAlpha(args.alpha);
335
+ if (
336
+ hue === undefined ||
337
+ saturation === undefined ||
338
+ lightness === undefined ||
339
+ alpha === undefined
340
+ )
341
+ return cssFallback(out);
342
+ const [r, g, b] = hslToSrgb(hue, saturation, lightness);
343
+ out[0] = srgbChannelToLinear(r);
344
+ out[1] = srgbChannelToLinear(g);
345
+ out[2] = srgbChannelToLinear(b);
346
+ out[3] = alpha;
347
+ return out;
348
+ }
349
+ return cssFallback(out);
350
+ }
351
+
352
+ /** Encode one linear component to sRGB, then clamp + ×255 + round. */
353
+ function toHexByte(v: number, encodeLinear = true): string {
146
354
  if (Number.isNaN(v)) return '00';
147
- const clamped = Math.max(0, Math.min(1, v));
355
+ const clamped = Math.max(0, Math.min(1, encodeLinear ? linearChannelToSrgb(v) : v));
148
356
  const byte = Math.round(clamped * 255);
149
357
  return byte.toString(16).padStart(2, '0');
150
358
  }
@@ -159,7 +367,7 @@ function toHexByte(v: number): string {
159
367
  *
160
368
  * @example
161
369
  * ```ts
162
- * color.toHex(color.create(2, -0.1, 0.5, 1)); // → '#ff0080' (HDR/negative clamp)
370
+ * color.toHex(color.create(2, -0.1, 0.5, 1)); // → '#ff00bc' (linear HDR/negative clamp)
163
371
  * color.toHex(color.create(1, 0, 0, 0.5)); // → '#ff000080'
164
372
  * color.toHex(color.create(NaN, 0, 0, NaN)); // → '#000000' (NaN→'00', alpha=NaN takes short form)
165
373
  * ```
@@ -172,6 +380,6 @@ export function toHex(c: ColorLike): string {
172
380
  if (aValue >= 1 || Number.isNaN(aValue)) {
173
381
  return `#${r}${g}${b}`;
174
382
  }
175
- const a = toHexByte(aValue);
383
+ const a = toHexByte(aValue, false);
176
384
  return `#${r}${g}${b}${a}`;
177
385
  }
package/src/index.ts CHANGED
@@ -41,7 +41,7 @@ export type {
41
41
  // - vector family vec2 / vec3 / vec4
42
42
  // - matrix family mat3 / mat4
43
43
  // - rotation quat / euler
44
- // - color (sRGB↔linear + hex parse/format)
44
+ // - color (sRGB↔linear + hex/CSS parse/format)
45
45
  // - easing (scalar S-curve time remaps)
46
46
  // - noise (Perlin noise, growable home for further variants)
47
47
 
package/src/mat4.ts CHANGED
@@ -919,20 +919,20 @@ export function perspectiveReverseZ(
919
919
  /**
920
920
  * out = orthographic projection (**WebGPU [0, 1] NDC** short name, D-3). Returns out.
921
921
  *
922
- * @degrade near >= far or left >= right or bottom >= top → numerically undefined but does not throw.
922
+ * @degrade near >= far or left >= right or top <= bottom → numerically undefined but does not throw.
923
923
  *
924
924
  * @example
925
925
  * ```ts
926
- * mat4.orthographic(out, -10, 10, -10, 10, 0.1, 100);
927
- * // Guard: if (!(left < right && bottom < top && near < far)) handleInvalid();
926
+ * mat4.orthographic(out, -10, 10, 10, -10, 0.1, 100);
927
+ * // Guard: if (!(left < right && top > bottom && near < far)) handleInvalid();
928
928
  * ```
929
929
  */
930
930
  export function orthographic(
931
931
  out: Mat4,
932
932
  left: number,
933
933
  right: number,
934
- bottom: number,
935
934
  top: number,
935
+ bottom: number,
936
936
  near: number,
937
937
  far: number,
938
938
  ): Mat4 {
@@ -963,8 +963,8 @@ export function orthographicNO(
963
963
  out: Mat4,
964
964
  left: number,
965
965
  right: number,
966
- bottom: number,
967
966
  top: number,
967
+ bottom: number,
968
968
  near: number,
969
969
  far: number,
970
970
  ): Mat4 {
@@ -1004,8 +1004,8 @@ export function orthographicReverseZ(
1004
1004
  out: Mat4,
1005
1005
  left: number,
1006
1006
  right: number,
1007
- bottom: number,
1008
1007
  top: number,
1008
+ bottom: number,
1009
1009
  near: number,
1010
1010
  far: number,
1011
1011
  ): Mat4 {