@flighthq/materials 0.1.0

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.
Files changed (60) hide show
  1. package/dist/classicMaterials.d.ts +5 -0
  2. package/dist/classicMaterials.d.ts.map +1 -0
  3. package/dist/classicMaterials.js +39 -0
  4. package/dist/classicMaterials.js.map +1 -0
  5. package/dist/color.d.ts +23 -0
  6. package/dist/color.d.ts.map +1 -0
  7. package/dist/color.js +291 -0
  8. package/dist/color.js.map +1 -0
  9. package/dist/colorTransform.d.ts +18 -0
  10. package/dist/colorTransform.d.ts.map +1 -0
  11. package/dist/colorTransform.js +119 -0
  12. package/dist/colorTransform.js.map +1 -0
  13. package/dist/colorTransformMaterial.d.ts +4 -0
  14. package/dist/colorTransformMaterial.d.ts.map +1 -0
  15. package/dist/colorTransformMaterial.js +18 -0
  16. package/dist/colorTransformMaterial.js.map +1 -0
  17. package/dist/index.d.ts +12 -0
  18. package/dist/index.d.ts.map +1 -0
  19. package/dist/index.js +12 -0
  20. package/dist/index.js.map +1 -0
  21. package/dist/material.d.ts +6 -0
  22. package/dist/material.d.ts.map +1 -0
  23. package/dist/material.js +73 -0
  24. package/dist/material.js.map +1 -0
  25. package/dist/materialPresets.d.ts +13 -0
  26. package/dist/materialPresets.d.ts.map +1 -0
  27. package/dist/materialPresets.js +118 -0
  28. package/dist/materialPresets.js.map +1 -0
  29. package/dist/materialValidation.d.ts +7 -0
  30. package/dist/materialValidation.d.ts.map +1 -0
  31. package/dist/materialValidation.js +52 -0
  32. package/dist/materialValidation.js.map +1 -0
  33. package/dist/pbrExtensionMaterials.d.ts +9 -0
  34. package/dist/pbrExtensionMaterials.d.ts.map +1 -0
  35. package/dist/pbrExtensionMaterials.js +93 -0
  36. package/dist/pbrExtensionMaterials.js.map +1 -0
  37. package/dist/pbrMaterials.d.ts +6 -0
  38. package/dist/pbrMaterials.d.ts.map +1 -0
  39. package/dist/pbrMaterials.js +134 -0
  40. package/dist/pbrMaterials.js.map +1 -0
  41. package/dist/surfaceMaterial.d.ts +7 -0
  42. package/dist/surfaceMaterial.d.ts.map +1 -0
  43. package/dist/surfaceMaterial.js +41 -0
  44. package/dist/surfaceMaterial.js.map +1 -0
  45. package/dist/unlitMaterials.d.ts +10 -0
  46. package/dist/unlitMaterials.d.ts.map +1 -0
  47. package/dist/unlitMaterials.js +66 -0
  48. package/dist/unlitMaterials.js.map +1 -0
  49. package/package.json +38 -0
  50. package/src/classicMaterials.test.ts +48 -0
  51. package/src/color.test.ts +383 -0
  52. package/src/colorTransform.test.ts +384 -0
  53. package/src/colorTransformMaterial.test.ts +26 -0
  54. package/src/material.test.ts +110 -0
  55. package/src/materialPresets.test.ts +125 -0
  56. package/src/materialValidation.test.ts +130 -0
  57. package/src/pbrExtensionMaterials.test.ts +123 -0
  58. package/src/pbrMaterials.test.ts +91 -0
  59. package/src/surfaceMaterial.test.ts +69 -0
  60. package/src/unlitMaterials.test.ts +127 -0
@@ -0,0 +1,384 @@
1
+ import {
2
+ cloneColorTransform,
3
+ concatColorTransform,
4
+ copyColorTransform,
5
+ copyColorTransformToArrays,
6
+ createColorTransform,
7
+ equalsColorTransform,
8
+ equalsColorTransformMultipliers,
9
+ equalsColorTransformOffsets,
10
+ getColorTransformOffsetRgb,
11
+ getColorTransformOffsetRgba,
12
+ invertColorTransform,
13
+ isIdentityColorTransform,
14
+ setColorTransform,
15
+ setColorTransformIdentity,
16
+ setColorTransformOffsetRgb,
17
+ setColorTransformOffsetRgba,
18
+ } from '@flighthq/materials';
19
+
20
+ describe('cloneColorTransform', () => {
21
+ it('returns a new object with identical values', () => {
22
+ const ct = createColorTransform({ redMultiplier: 0.5, greenOffset: 64 });
23
+ const cloned = cloneColorTransform(ct);
24
+ expect(cloned).not.toBe(ct);
25
+ expect(cloned.redMultiplier).toBe(0.5);
26
+ expect(cloned.greenOffset).toBe(64);
27
+ });
28
+
29
+ it('does not share references', () => {
30
+ const ct = createColorTransform({ redOffset: 10 });
31
+ const c = cloneColorTransform(ct);
32
+ c.redOffset = 99;
33
+ expect(ct.redOffset).toBe(10);
34
+ });
35
+ });
36
+
37
+ describe('concatColorTransform', () => {
38
+ it('composes two setColorTransformIdentity transforms into setColorTransformIdentity', () => {
39
+ const a = createColorTransform();
40
+ const b = createColorTransform();
41
+ const out = createColorTransform({ redMultiplier: 0, greenMultiplier: 0, blueMultiplier: 0, alphaMultiplier: 0 });
42
+ concatColorTransform(out, a, b);
43
+ expect(out.redMultiplier).toBe(1);
44
+ expect(out.greenMultiplier).toBe(1);
45
+ expect(out.blueMultiplier).toBe(1);
46
+ expect(out.alphaMultiplier).toBe(1);
47
+ expect(out.redOffset).toBe(0);
48
+ expect(out.greenOffset).toBe(0);
49
+ expect(out.blueOffset).toBe(0);
50
+ expect(out.alphaOffset).toBe(0);
51
+ });
52
+
53
+ it('multiplies multipliers', () => {
54
+ const a = createColorTransform({ redMultiplier: 2, greenMultiplier: 0.5 });
55
+ const b = createColorTransform({ redMultiplier: 3, greenMultiplier: 4 });
56
+ const out = createColorTransform();
57
+ concatColorTransform(out, a, b);
58
+ expect(out.redMultiplier).toBe(6);
59
+ expect(out.greenMultiplier).toBe(2);
60
+ });
61
+
62
+ it('combines offsets: out.offset = source.multiplier * other.offset + source.offset', () => {
63
+ const source = createColorTransform({ redMultiplier: 2, redOffset: 10 });
64
+ const other = createColorTransform({ redOffset: 5 });
65
+ const out = createColorTransform();
66
+ concatColorTransform(out, source, other);
67
+ expect(out.redOffset).toBe(2 * 5 + 10);
68
+ });
69
+
70
+ it('can write result into one of the inputs', () => {
71
+ const a = createColorTransform({ redMultiplier: 2, redOffset: 10 });
72
+ const b = createColorTransform({ redMultiplier: 3, redOffset: 5 });
73
+ const out = createColorTransform();
74
+ concatColorTransform(out, a, b);
75
+ const expectedMultiplier = 6;
76
+ const expectedOffset = 2 * 5 + 10;
77
+ expect(out.redMultiplier).toBe(expectedMultiplier);
78
+ expect(out.redOffset).toBe(expectedOffset);
79
+ });
80
+ });
81
+
82
+ describe('copyColorTransform', () => {
83
+ it('copies all fields from source to out', () => {
84
+ const source = createColorTransform({ redMultiplier: 0.5, greenOffset: 128, alphaMultiplier: 0.8, blueOffset: 64 });
85
+ const out = createColorTransform();
86
+ copyColorTransform(out, source);
87
+ expect(out.redMultiplier).toBe(0.5);
88
+ expect(out.greenMultiplier).toBe(1);
89
+ expect(out.blueMultiplier).toBe(1);
90
+ expect(out.alphaMultiplier).toBe(0.8);
91
+ expect(out.redOffset).toBe(0);
92
+ expect(out.greenOffset).toBe(128);
93
+ expect(out.blueOffset).toBe(64);
94
+ expect(out.alphaOffset).toBe(0);
95
+ });
96
+
97
+ it('does not share references between out and source', () => {
98
+ const source = createColorTransform({ redOffset: 50 });
99
+ const out = createColorTransform();
100
+ copyColorTransform(out, source);
101
+ out.redOffset = 99;
102
+ expect(source.redOffset).toBe(50);
103
+ });
104
+ });
105
+
106
+ describe('copyColorTransformToArrays', () => {
107
+ it('writes multipliers and offsets into parallel arrays', () => {
108
+ const ct = createColorTransform({
109
+ redMultiplier: 0.5,
110
+ greenMultiplier: 0.25,
111
+ blueMultiplier: 2,
112
+ alphaMultiplier: 0.8,
113
+ });
114
+ setColorTransform(ct, 0.5, 0.25, 2, 0.8, 10, 20, 30, 40);
115
+ const multipliers: number[] = [];
116
+ const offsets: number[] = [];
117
+ copyColorTransformToArrays(multipliers, offsets, ct);
118
+ expect(multipliers).toEqual([0.5, 0.25, 2, 0.8]);
119
+ expect(offsets).toEqual([10, 20, 30, 40]);
120
+ });
121
+
122
+ it('writes into existing arrays without creating new ones', () => {
123
+ const ct = createColorTransform();
124
+ const multipliers = [9, 9, 9, 9];
125
+ const offsets = [9, 9, 9, 9];
126
+ copyColorTransformToArrays(multipliers, offsets, ct);
127
+ expect(multipliers).toEqual([1, 1, 1, 1]);
128
+ expect(offsets).toEqual([0, 0, 0, 0]);
129
+ });
130
+ });
131
+
132
+ describe('createColorTransform', () => {
133
+ it('initializes multipliers to 1 and offsets to 0 by default', () => {
134
+ const ct = createColorTransform();
135
+ expect(ct.redMultiplier).toBe(1);
136
+ expect(ct.greenMultiplier).toBe(1);
137
+ expect(ct.blueMultiplier).toBe(1);
138
+ expect(ct.alphaMultiplier).toBe(1);
139
+ expect(ct.redOffset).toBe(0);
140
+ expect(ct.greenOffset).toBe(0);
141
+ expect(ct.blueOffset).toBe(0);
142
+ expect(ct.alphaOffset).toBe(0);
143
+ });
144
+
145
+ it('applies partial overrides', () => {
146
+ const ct = createColorTransform({ redMultiplier: 0.5, blueOffset: 128 });
147
+ expect(ct.redMultiplier).toBe(0.5);
148
+ expect(ct.greenMultiplier).toBe(1);
149
+ expect(ct.blueOffset).toBe(128);
150
+ expect(ct.alphaOffset).toBe(0);
151
+ });
152
+ });
153
+
154
+ describe('equalsColorTransform', () => {
155
+ it('returns true for two setColorTransformIdentity transforms', () => {
156
+ expect(equalsColorTransform(createColorTransform(), createColorTransform())).toBe(true);
157
+ });
158
+
159
+ it('returns false when any field differs', () => {
160
+ expect(equalsColorTransform(createColorTransform({ redMultiplier: 0.5 }), createColorTransform())).toBe(false);
161
+ expect(equalsColorTransform(createColorTransform({ redOffset: 1 }), createColorTransform())).toBe(false);
162
+ expect(equalsColorTransform(createColorTransform({ alphaMultiplier: 0 }), createColorTransform())).toBe(false);
163
+ expect(equalsColorTransform(createColorTransform({ alphaOffset: 1 }), createColorTransform())).toBe(false);
164
+ });
165
+
166
+ it('returns true for matching non-setColorTransformIdentity transforms', () => {
167
+ const a = createColorTransform({ redMultiplier: 0.5, greenOffset: 128 });
168
+ const b = createColorTransform({ redMultiplier: 0.5, greenOffset: 128 });
169
+ expect(equalsColorTransform(a, b)).toBe(true);
170
+ });
171
+ });
172
+
173
+ describe('equalsColorTransformMultipliers', () => {
174
+ it('returns true when all multipliers match', () => {
175
+ const a = createColorTransform({ redMultiplier: 0.5, greenMultiplier: 0.25 });
176
+ const b = createColorTransform({ redMultiplier: 0.5, greenMultiplier: 0.25 });
177
+ expect(equalsColorTransformMultipliers(a, b)).toBe(true);
178
+ });
179
+
180
+ it('returns false when any multiplier differs', () => {
181
+ expect(equalsColorTransformMultipliers(createColorTransform({ redMultiplier: 0.5 }), createColorTransform())).toBe(
182
+ false,
183
+ );
184
+ expect(
185
+ equalsColorTransformMultipliers(createColorTransform({ alphaMultiplier: 0.5 }), createColorTransform()),
186
+ ).toBe(false);
187
+ });
188
+
189
+ it('ignores alpha when compareAlpha is false', () => {
190
+ const a = createColorTransform({ alphaMultiplier: 0.5 });
191
+ const b = createColorTransform({ alphaMultiplier: 1 });
192
+ expect(equalsColorTransformMultipliers(a, b, false)).toBe(true);
193
+ });
194
+
195
+ it('still compares RGB when compareAlpha is false', () => {
196
+ const a = createColorTransform({ redMultiplier: 0.5 });
197
+ const b = createColorTransform({ redMultiplier: 1 });
198
+ expect(equalsColorTransformMultipliers(a, b, false)).toBe(false);
199
+ });
200
+ });
201
+
202
+ describe('equalsColorTransformOffsets', () => {
203
+ it('returns true when all offsets match', () => {
204
+ const a = createColorTransform({ redOffset: 64, greenOffset: 128 });
205
+ const b = createColorTransform({ redOffset: 64, greenOffset: 128 });
206
+ expect(equalsColorTransformOffsets(a, b)).toBe(true);
207
+ });
208
+
209
+ it('returns false when any offset differs', () => {
210
+ expect(equalsColorTransformOffsets(createColorTransform({ redOffset: 1 }), createColorTransform())).toBe(false);
211
+ expect(equalsColorTransformOffsets(createColorTransform({ alphaOffset: 1 }), createColorTransform())).toBe(false);
212
+ });
213
+
214
+ it('ignores alpha when compareAlpha is false', () => {
215
+ const a = createColorTransform({ alphaOffset: 50 });
216
+ const b = createColorTransform({ alphaOffset: 0 });
217
+ expect(equalsColorTransformOffsets(a, b, false)).toBe(true);
218
+ });
219
+
220
+ it('still compares RGB when compareAlpha is false', () => {
221
+ const a = createColorTransform({ redOffset: 50 });
222
+ const b = createColorTransform({ redOffset: 0 });
223
+ expect(equalsColorTransformOffsets(a, b, false)).toBe(false);
224
+ });
225
+ });
226
+
227
+ describe('getColorTransformOffsetRgb', () => {
228
+ it('packs red, green, blue offsets into a single integer', () => {
229
+ const ct = createColorTransform({ redOffset: 0xff, greenOffset: 0x80, blueOffset: 0x10 });
230
+ const packed = getColorTransformOffsetRgb(ct);
231
+ expect((packed >> 16) & 0xff).toBe(0xff);
232
+ expect((packed >> 8) & 0xff).toBe(0x80);
233
+ expect(packed & 0xff).toBe(0x10);
234
+ });
235
+
236
+ it('returns 0 when all offsets are 0', () => {
237
+ expect(getColorTransformOffsetRgb(createColorTransform())).toBe(0);
238
+ });
239
+ });
240
+
241
+ describe('getColorTransformOffsetRgba', () => {
242
+ it('packs red, green, blue, alpha offsets into a single integer', () => {
243
+ const ct = createColorTransform({ redOffset: 0x10, greenOffset: 0x20, blueOffset: 0x30, alphaOffset: 0x40 });
244
+ const packed = getColorTransformOffsetRgba(ct);
245
+ expect((packed >> 24) & 0xff).toBe(0x10);
246
+ expect((packed >> 16) & 0xff).toBe(0x20);
247
+ expect((packed >> 8) & 0xff).toBe(0x30);
248
+ expect(packed & 0xff).toBe(0x40);
249
+ });
250
+
251
+ it('returns 0 when all offsets are 0', () => {
252
+ expect(getColorTransformOffsetRgba(createColorTransform())).toBe(0);
253
+ });
254
+ });
255
+
256
+ describe('invertColorTransform', () => {
257
+ it('reciprocates multipliers', () => {
258
+ const source = createColorTransform({
259
+ redMultiplier: 2,
260
+ greenMultiplier: 4,
261
+ blueMultiplier: 0.5,
262
+ alphaMultiplier: 0.25,
263
+ });
264
+ const out = createColorTransform();
265
+ invertColorTransform(out, source);
266
+ expect(out.redMultiplier).toBe(0.5);
267
+ expect(out.greenMultiplier).toBe(0.25);
268
+ expect(out.blueMultiplier).toBe(2);
269
+ expect(out.alphaMultiplier).toBe(4);
270
+ });
271
+
272
+ it('negates offsets', () => {
273
+ const source = createColorTransform({ redOffset: 64, greenOffset: -32, blueOffset: 128, alphaOffset: -10 });
274
+ const out = createColorTransform();
275
+ invertColorTransform(out, source);
276
+ expect(out.redOffset).toBe(-64);
277
+ expect(out.greenOffset).toBe(32);
278
+ expect(out.blueOffset).toBe(-128);
279
+ expect(out.alphaOffset).toBe(10);
280
+ });
281
+
282
+ it('uses 1 when multiplier is 0 to avoid division by zero', () => {
283
+ const source = createColorTransform({
284
+ redMultiplier: 0,
285
+ greenMultiplier: 0,
286
+ blueMultiplier: 0,
287
+ alphaMultiplier: 0,
288
+ });
289
+ const out = createColorTransform();
290
+ invertColorTransform(out, source);
291
+ expect(out.redMultiplier).toBe(1);
292
+ expect(out.greenMultiplier).toBe(1);
293
+ expect(out.blueMultiplier).toBe(1);
294
+ expect(out.alphaMultiplier).toBe(1);
295
+ });
296
+ });
297
+
298
+ describe('isIdentityColorTransform', () => {
299
+ it('returns true for a default transform', () => {
300
+ expect(isIdentityColorTransform(createColorTransform())).toBe(true);
301
+ });
302
+
303
+ it('returns false when any multiplier differs from 1', () => {
304
+ expect(isIdentityColorTransform(createColorTransform({ redMultiplier: 0.5 }))).toBe(false);
305
+ });
306
+
307
+ it('returns false when any offset is non-zero', () => {
308
+ expect(isIdentityColorTransform(createColorTransform({ greenOffset: 1 }))).toBe(false);
309
+ });
310
+
311
+ it('returns true when alphaMultiplier differs but compareAlphaMultiplier is false', () => {
312
+ expect(isIdentityColorTransform(createColorTransform({ alphaMultiplier: 0 }), false)).toBe(true);
313
+ });
314
+ });
315
+
316
+ describe('setColorTransform', () => {
317
+ it('sets all eight fields', () => {
318
+ const ct = createColorTransform();
319
+ setColorTransform(ct, 0.1, 0.2, 0.3, 0.4, 10, 20, 30, 40);
320
+ expect(ct.redMultiplier).toBe(0.1);
321
+ expect(ct.greenMultiplier).toBe(0.2);
322
+ expect(ct.blueMultiplier).toBe(0.3);
323
+ expect(ct.alphaMultiplier).toBe(0.4);
324
+ expect(ct.redOffset).toBe(10);
325
+ expect(ct.greenOffset).toBe(20);
326
+ expect(ct.blueOffset).toBe(30);
327
+ expect(ct.alphaOffset).toBe(40);
328
+ });
329
+ });
330
+
331
+ describe('setColorTransformIdentity', () => {
332
+ it('resets multipliers to 1 and offsets to 0', () => {
333
+ const ct = createColorTransform({ redMultiplier: 0.5, greenOffset: 128, alphaMultiplier: 0, blueOffset: 64 });
334
+ setColorTransformIdentity(ct);
335
+ expect(ct.redMultiplier).toBe(1);
336
+ expect(ct.greenMultiplier).toBe(1);
337
+ expect(ct.blueMultiplier).toBe(1);
338
+ expect(ct.alphaMultiplier).toBe(1);
339
+ expect(ct.redOffset).toBe(0);
340
+ expect(ct.greenOffset).toBe(0);
341
+ expect(ct.blueOffset).toBe(0);
342
+ expect(ct.alphaOffset).toBe(0);
343
+ });
344
+ });
345
+
346
+ describe('setColorTransformOffsetRgb', () => {
347
+ it('unpacks red, green, blue from a packed integer', () => {
348
+ const ct = createColorTransform();
349
+ setColorTransformOffsetRgb(ct, (0xab << 16) | (0xcd << 8) | 0xef);
350
+ expect(ct.redOffset).toBe(0xab);
351
+ expect(ct.greenOffset).toBe(0xcd);
352
+ expect(ct.blueOffset).toBe(0xef);
353
+ expect(ct.alphaOffset).toBe(0);
354
+ });
355
+
356
+ it('zeroes RGB multipliers and keeps alphaMultiplier at 1', () => {
357
+ const ct = createColorTransform();
358
+ setColorTransformOffsetRgb(ct, 0xffffff);
359
+ expect(ct.redMultiplier).toBe(0);
360
+ expect(ct.greenMultiplier).toBe(0);
361
+ expect(ct.blueMultiplier).toBe(0);
362
+ expect(ct.alphaMultiplier).toBe(1);
363
+ });
364
+ });
365
+
366
+ describe('setColorTransformOffsetRgba', () => {
367
+ it('unpacks red, green, blue, alpha from a packed integer', () => {
368
+ const ct = createColorTransform();
369
+ setColorTransformOffsetRgba(ct, (0x10 << 24) | (0x20 << 16) | (0x30 << 8) | 0x40);
370
+ expect(ct.redOffset).toBe(0x10);
371
+ expect(ct.greenOffset).toBe(0x20);
372
+ expect(ct.blueOffset).toBe(0x30);
373
+ expect(ct.alphaOffset).toBe(0x40);
374
+ });
375
+
376
+ it('zeroes all multipliers including alpha', () => {
377
+ const ct = createColorTransform();
378
+ setColorTransformOffsetRgba(ct, 0xffffffff);
379
+ expect(ct.redMultiplier).toBe(0);
380
+ expect(ct.greenMultiplier).toBe(0);
381
+ expect(ct.blueMultiplier).toBe(0);
382
+ expect(ct.alphaMultiplier).toBe(0);
383
+ });
384
+ });
@@ -0,0 +1,26 @@
1
+ import { ColorTransformMaterialKind, UniformColorTransformMaterialKind } from '@flighthq/types';
2
+
3
+ import { createColorTransform } from './colorTransform';
4
+ import { createColorTransformMaterial, createUniformColorTransformMaterial } from './colorTransformMaterial';
5
+
6
+ describe('createColorTransformMaterial', () => {
7
+ it('creates a material with the per-instance color transform kind and no value', () => {
8
+ const material = createColorTransformMaterial();
9
+ expect(material.kind).toBe(ColorTransformMaterialKind);
10
+ expect((material as { colorTransform?: unknown }).colorTransform).toBeUndefined();
11
+ });
12
+ });
13
+
14
+ describe('createUniformColorTransformMaterial', () => {
15
+ it('defaults to an identity color transform', () => {
16
+ const material = createUniformColorTransformMaterial();
17
+ expect(material.kind).toBe(UniformColorTransformMaterialKind);
18
+ expect(material.colorTransform.redMultiplier).toBe(1);
19
+ });
20
+
21
+ it('carries the provided color transform', () => {
22
+ const ct = createColorTransform({ greenMultiplier: 0.5 });
23
+ const material = createUniformColorTransformMaterial(ct);
24
+ expect(material.colorTransform).toBe(ct);
25
+ });
26
+ });
@@ -0,0 +1,110 @@
1
+ import type { StandardPbrMaterial, UniformColorTransformMaterial } from '@flighthq/types';
2
+ import {
3
+ ColorTransformMaterialKind,
4
+ StandardPbrMaterialKind,
5
+ UniformColorTransformMaterialKind,
6
+ } from '@flighthq/types';
7
+
8
+ import { createColorTransform } from './colorTransform';
9
+ import { createColorTransformMaterial, createUniformColorTransformMaterial } from './colorTransformMaterial';
10
+ import { cloneMaterial, copyMaterial, createMaterial, equalsMaterial } from './material';
11
+ import { createStandardPbrMaterial } from './pbrMaterials';
12
+
13
+ const TestMaterialKind = 'TestMaterial';
14
+
15
+ describe('cloneMaterial', () => {
16
+ it('returns a new object with the same kind', () => {
17
+ const original = createMaterial(TestMaterialKind);
18
+ const clone = cloneMaterial(original);
19
+ expect(clone).not.toBe(original);
20
+ expect(clone.kind).toBe(TestMaterialKind);
21
+ });
22
+ it('copies scalar fields from a standard PBR material', () => {
23
+ const original = createStandardPbrMaterial({ metallic: 0.5, roughness: 0.25 });
24
+ const clone = cloneMaterial(original);
25
+ const clonePbr = clone as StandardPbrMaterial;
26
+ expect(clonePbr.metallic).toBe(0.5);
27
+ expect(clonePbr.roughness).toBe(0.25);
28
+ expect(clone.kind).toBe(StandardPbrMaterialKind);
29
+ });
30
+ it('deep-clones the colorTransform of a UniformColorTransformMaterial', () => {
31
+ const ct = createColorTransform({ redMultiplier: 0.5 });
32
+ const original = createUniformColorTransformMaterial(ct);
33
+ const clone = cloneMaterial(original) as UniformColorTransformMaterial;
34
+ expect(clone.colorTransform).not.toBe(original.colorTransform);
35
+ expect(clone.colorTransform.redMultiplier).toBe(0.5);
36
+ // Mutating the clone does not affect the original.
37
+ clone.colorTransform.redMultiplier = 1;
38
+ expect(original.colorTransform.redMultiplier).toBe(0.5);
39
+ });
40
+ it('produces a clone that is structurally equal to the original', () => {
41
+ const original = createStandardPbrMaterial({ metallic: 0.7 });
42
+ const clone = cloneMaterial(original);
43
+ expect(equalsMaterial(original, clone)).toBe(true);
44
+ });
45
+ });
46
+
47
+ describe('copyMaterial', () => {
48
+ it('copies scalar fields from source to out', () => {
49
+ const source = createStandardPbrMaterial({ metallic: 0.3, roughness: 0.8 });
50
+ const out = createStandardPbrMaterial();
51
+ copyMaterial(out, source);
52
+ const outPbr = out as StandardPbrMaterial;
53
+ expect(outPbr.metallic).toBe(0.3);
54
+ expect(outPbr.roughness).toBe(0.8);
55
+ });
56
+ it('is a no-op when out === source (alias-safe)', () => {
57
+ const material = createStandardPbrMaterial({ metallic: 0.5 });
58
+ copyMaterial(material, material);
59
+ expect(material.metallic).toBe(0.5);
60
+ });
61
+ it('deep-copies the colorTransform sub-entity', () => {
62
+ const ct = createColorTransform({ redMultiplier: 0.4 });
63
+ const source = createUniformColorTransformMaterial(ct);
64
+ const out = createUniformColorTransformMaterial();
65
+ copyMaterial(out, source);
66
+ expect(out.colorTransform).not.toBe(source.colorTransform);
67
+ expect(out.colorTransform.redMultiplier).toBe(0.4);
68
+ });
69
+ });
70
+
71
+ describe('createMaterial', () => {
72
+ it('creates a material carrying the given kind', () => {
73
+ const material = createMaterial(TestMaterialKind);
74
+ expect(material.kind).toBe(TestMaterialKind);
75
+ });
76
+ });
77
+
78
+ describe('equalsMaterial', () => {
79
+ it('is true for the same reference', () => {
80
+ const material = createMaterial(TestMaterialKind);
81
+ expect(equalsMaterial(material, material)).toBe(true);
82
+ });
83
+
84
+ it('is false for different kinds', () => {
85
+ expect(equalsMaterial(createColorTransformMaterial(), createUniformColorTransformMaterial())).toBe(false);
86
+ });
87
+
88
+ it('compares the color transform of uniform color transform materials', () => {
89
+ const a = createUniformColorTransformMaterial(createColorTransform({ redMultiplier: 0.5 }));
90
+ const b = createUniformColorTransformMaterial(createColorTransform({ redMultiplier: 0.5 }));
91
+ const c = createUniformColorTransformMaterial(createColorTransform({ redMultiplier: 0.25 }));
92
+ expect(a.kind).toBe(UniformColorTransformMaterialKind);
93
+ expect(equalsMaterial(a, b)).toBe(true);
94
+ expect(equalsMaterial(a, c)).toBe(false);
95
+ });
96
+
97
+ it('compares scalar fields of standard PBR materials structurally', () => {
98
+ const a = createStandardPbrMaterial({ metallic: 0.5, roughness: 0.25 });
99
+ const b = createStandardPbrMaterial({ metallic: 0.5, roughness: 0.25 });
100
+ const c = createStandardPbrMaterial({ metallic: 0.5, roughness: 0.5 });
101
+ expect(a.kind).toBe(StandardPbrMaterialKind);
102
+ expect(equalsMaterial(a, b)).toBe(true);
103
+ expect(equalsMaterial(a, c)).toBe(false);
104
+ });
105
+
106
+ it('treats same-kind materials without comparable fields as equal', () => {
107
+ expect(equalsMaterial(createColorTransformMaterial(), createColorTransformMaterial())).toBe(true);
108
+ expect(createColorTransformMaterial().kind).toBe(ColorTransformMaterialKind);
109
+ });
110
+ });
@@ -0,0 +1,125 @@
1
+ import { StandardPbrMaterialKind, TransmissionVolumePbrMaterialKind } from '@flighthq/types';
2
+
3
+ import {
4
+ createAluminumStandardPbrMaterial,
5
+ createCarbonStandardPbrMaterial,
6
+ createGlassTransmissionVolumePbrMaterial,
7
+ createGoldStandardPbrMaterial,
8
+ createIronStandardPbrMaterial,
9
+ createMarbleStandardPbrMaterial,
10
+ createPlasticStandardPbrMaterial,
11
+ createRubberStandardPbrMaterial,
12
+ createSilverStandardPbrMaterial,
13
+ createSkinStandardPbrMaterial,
14
+ createWoodStandardPbrMaterial,
15
+ } from './materialPresets';
16
+
17
+ describe('createAluminumStandardPbrMaterial', () => {
18
+ it('creates a fully metallic, moderately rough material', () => {
19
+ const m = createAluminumStandardPbrMaterial();
20
+ expect(m.kind).toBe(StandardPbrMaterialKind);
21
+ expect(m.metallic).toBe(1);
22
+ expect(m.roughness).toBeCloseTo(0.35);
23
+ });
24
+ it('applies overrides', () => {
25
+ const m = createAluminumStandardPbrMaterial({ roughness: 0.6 });
26
+ expect(m.roughness).toBe(0.6);
27
+ });
28
+ });
29
+
30
+ describe('createCarbonStandardPbrMaterial', () => {
31
+ it('creates a dielectric, very rough dark material', () => {
32
+ const m = createCarbonStandardPbrMaterial();
33
+ expect(m.kind).toBe(StandardPbrMaterialKind);
34
+ expect(m.metallic).toBe(0);
35
+ expect(m.roughness).toBeCloseTo(0.95);
36
+ });
37
+ });
38
+
39
+ describe('createGlassTransmissionVolumePbrMaterial', () => {
40
+ it('creates a fully-transmissive material with IOR 1.5', () => {
41
+ const m = createGlassTransmissionVolumePbrMaterial();
42
+ expect(m.kind).toBe(TransmissionVolumePbrMaterialKind);
43
+ expect(m.transmission).toBe(1);
44
+ expect(m.ior).toBe(1.5);
45
+ });
46
+ it('applies overrides', () => {
47
+ const m = createGlassTransmissionVolumePbrMaterial({ ior: 1.7 });
48
+ expect(m.ior).toBe(1.7);
49
+ });
50
+ });
51
+
52
+ describe('createGoldStandardPbrMaterial', () => {
53
+ it('creates a fully metallic material with gold base color', () => {
54
+ const m = createGoldStandardPbrMaterial();
55
+ expect(m.kind).toBe(StandardPbrMaterialKind);
56
+ expect(m.metallic).toBe(1);
57
+ expect(m.baseColor).toBe(0xffd700ff);
58
+ expect(m.roughness).toBeCloseTo(0.25);
59
+ });
60
+ });
61
+
62
+ describe('createIronStandardPbrMaterial', () => {
63
+ it('creates a fully metallic, moderately rough dark material', () => {
64
+ const m = createIronStandardPbrMaterial();
65
+ expect(m.kind).toBe(StandardPbrMaterialKind);
66
+ expect(m.metallic).toBe(1);
67
+ expect(m.roughness).toBeCloseTo(0.7);
68
+ });
69
+ });
70
+
71
+ describe('createMarbleStandardPbrMaterial', () => {
72
+ it('creates a very smooth dielectric material', () => {
73
+ const m = createMarbleStandardPbrMaterial();
74
+ expect(m.kind).toBe(StandardPbrMaterialKind);
75
+ expect(m.metallic).toBe(0);
76
+ expect(m.roughness).toBeCloseTo(0.05);
77
+ });
78
+ });
79
+
80
+ describe('createPlasticStandardPbrMaterial', () => {
81
+ it('creates a glossy dielectric material with white base color', () => {
82
+ const m = createPlasticStandardPbrMaterial();
83
+ expect(m.kind).toBe(StandardPbrMaterialKind);
84
+ expect(m.metallic).toBe(0);
85
+ expect(m.roughness).toBeCloseTo(0.05);
86
+ expect(m.baseColor).toBe(0xffffffff);
87
+ });
88
+ });
89
+
90
+ describe('createRubberStandardPbrMaterial', () => {
91
+ it('creates a very rough dielectric material', () => {
92
+ const m = createRubberStandardPbrMaterial();
93
+ expect(m.kind).toBe(StandardPbrMaterialKind);
94
+ expect(m.metallic).toBe(0);
95
+ expect(m.roughness).toBeCloseTo(0.9);
96
+ });
97
+ });
98
+
99
+ describe('createSilverStandardPbrMaterial', () => {
100
+ it('creates a highly polished metallic material', () => {
101
+ const m = createSilverStandardPbrMaterial();
102
+ expect(m.kind).toBe(StandardPbrMaterialKind);
103
+ expect(m.metallic).toBe(1);
104
+ expect(m.roughness).toBeCloseTo(0.1);
105
+ expect(m.baseColor).toBe(0xc0c0c0ff);
106
+ });
107
+ });
108
+
109
+ describe('createSkinStandardPbrMaterial', () => {
110
+ it('creates a warm-toned dielectric material with moderate roughness', () => {
111
+ const m = createSkinStandardPbrMaterial();
112
+ expect(m.kind).toBe(StandardPbrMaterialKind);
113
+ expect(m.metallic).toBe(0);
114
+ expect(m.roughness).toBeCloseTo(0.4);
115
+ });
116
+ });
117
+
118
+ describe('createWoodStandardPbrMaterial', () => {
119
+ it('creates a rough dielectric wood material', () => {
120
+ const m = createWoodStandardPbrMaterial();
121
+ expect(m.kind).toBe(StandardPbrMaterialKind);
122
+ expect(m.metallic).toBe(0);
123
+ expect(m.roughness).toBeCloseTo(0.8);
124
+ });
125
+ });