@forgeax/engine-math 0.1.23 → 0.1.24
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/README.md +6 -5
- package/dist/color.d.ts +12 -3
- package/dist/color.d.ts.map +1 -1
- package/dist/index.mjs +169 -9
- package/dist/index.mjs.map +1 -1
- package/dist/mat4.d.ts +6 -6
- package/dist/mat4.d.ts.map +1 -1
- package/package.json +1 -1
- package/src/color.ts +222 -14
- package/src/index.ts +1 -1
- package/src/mat4.ts +6 -6
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
|
|
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,
|
|
183
|
-
* // Guard: if (!(left < right &&
|
|
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,
|
|
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,
|
|
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,
|
|
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
|
*
|
package/dist/mat4.d.ts.map
CHANGED
|
@@ -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":"AAwCA,OAAO,KAAK,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,MAAM,SAAS,CAAC;AAG9E,YAAY,EAAE,IAAI,EAAE,QAAQ,EAAE,CAAC;AAE/B,qFAAqF;AACrF,wBAAgB,MAAM,IAAI,IAAI,CAE7B;AAED,gCAAgC;AAChC,wBAAgB,KAAK,CAAC,CAAC,EAAE,QAAQ,GAAG,IAAI,CAIvC;AAED,uCAAuC;AACvC,wBAAgB,QAAQ,CAAC,GAAG,EAAE,IAAI,GAAG,IAAI,CAkBxC;AAED,+FAA+F;AAC/F,wBAAgB,MAAM,CAAC,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,OAAO,SAAO,GAAG,OAAO,CAQxE;AAED;;;;GAIG;AACH,wBAAgB,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAkDlE;AAED,iFAAiF;AACjF,wBAAgB,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CA8BtD;AAED;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAuDnD;AAED;;GAEG;AACH,wBAAgB,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAqB/D;AAED;;GAEG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CA4CnE;AAED;;;;;;;;;;;GAWG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CA8DhF;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,MAAM,CAAC,GAAG,EAAE,IAAI,EAAE,GAAG,EAAE,QAAQ,EAAE,MAAM,EAAE,QAAQ,EAAE,EAAE,EAAE,QAAQ,GAAG,IAAI,CAsErF;AAED;;GAEG;AACH,wBAAgB,OAAO,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAuC9E;AAED;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,KAAK,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAkElF;AAED,yFAAyF;AACzF,wBAAgB,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAkCrD;AAED,kFAAkF;AAClF,wBAAgB,eAAe,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAkB5D;AAED,qEAAqE;AACrE,wBAAgB,WAAW,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAkBxD;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,IAAI,EAAE,QAAQ,EAAE,GAAG,EAAE,MAAM,GAAG,IAAI,CAgCzE;AAeD;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,WAAW,CACzB,GAAG,EAAE,IAAI,EACT,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV,IAAI,CA2BN;AAED;;;;;;;;;;;;;GAaG;AACH,wBAAgB,aAAa,CAC3B,GAAG,EAAE,IAAI,EACT,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV,IAAI,CA0BN;AAED;;;;;;;;;;;;;;;;;;;;;;;;;;;;;GA6BG;AACH,wBAAgB,mBAAmB,CACjC,GAAG,EAAE,IAAI,EACT,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV,IAAI,CA0BN;AAED;;;;;;;;;;GAUG;AACH,wBAAgB,YAAY,CAC1B,GAAG,EAAE,IAAI,EACT,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV,IAAI,CAqBN;AAED,uFAAuF;AACvF,wBAAgB,cAAc,CAC5B,GAAG,EAAE,IAAI,EACT,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV,IAAI,CAqBN;AAED;;;;;;;;;GASG;AACH,wBAAgB,oBAAoB,CAClC,GAAG,EAAE,IAAI,EACT,IAAI,EAAE,MAAM,EACZ,KAAK,EAAE,MAAM,EACb,GAAG,EAAE,MAAM,EACX,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV,IAAI,CAuBN;AAoBD;;;;;;;;;;;;;;;;;;GAkBG;AACH,wBAAgB,aAAa,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAkCvE;AAED;;;;;;;;;;;;;;;GAeG;AACH,eAAO,MAAM,cAAc,sBAAgB,CAAC;AAE5C;;;;;;;;;;;;;;;;;GAiBG;AACH,wBAAgB,kBAAkB,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAkB5E;AAqBD;;;GAGG;AACH,wBAAgB,cAAc,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAK3D;AAED;;;GAGG;AACH,wBAAgB,UAAU,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAKvD;AAED,4EAA4E;AAC5E,wBAAgB,KAAK,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAKlD;AAED,+EAA+E;AAC/E,wBAAgB,QAAQ,CAAC,GAAG,EAAE,IAAI,EAAE,CAAC,EAAE,QAAQ,GAAG,IAAI,CAKrD;AAgBD;;;;;;;;;;;;GAYG;AACH,wBAAgB,SAAS,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,KAAK,EAAE,QAAQ,GAAG,IAAI,CAE9E;AAcD;;;;;;;;;;;;;;;;;;;;;;;;;;GA0BG;AACH,wBAAgB,YAAY,CAAC,GAAG,EAAE,IAAI,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,EAAE,QAAQ,GAAG,IAAI,CAEpF;AAWD;;;;;;;;;;;;;;;GAeG;AACH,wBAAgB,eAAe,CAC7B,GAAG,EAAE,IAAI,EACT,GAAG,EAAE,QAAQ,EACb,MAAM,EAAE,QAAQ,EAChB,EAAE,EAAE,QAAQ,EACZ,WAAW,EAAE,MAAM,EACnB,MAAM,EAAE,MAAM,EACd,IAAI,EAAE,MAAM,EACZ,GAAG,EAAE,MAAM,GACV,IAAI,CAIN"}
|
package/package.json
CHANGED
package/src/color.ts
CHANGED
|
@@ -1,6 +1,6 @@
|
|
|
1
1
|
// color.ts — RGBA color namespace (M5 / T-032)
|
|
2
2
|
//
|
|
3
|
-
//
|
|
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);
|
|
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
|
-
// -
|
|
17
|
-
//
|
|
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.
|
|
124
|
-
* color.fromHex(out, '#ff800080'); // → (1, 0.
|
|
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
|
-
|
|
145
|
-
|
|
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)); // → '#
|
|
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
|
|
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,
|
|
927
|
-
* // Guard: if (!(left < right &&
|
|
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 {
|