@rezless/vm 0.6.2 → 0.7.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 (104) hide show
  1. package/dist/builtins/base64.d.ts +5 -2
  2. package/dist/builtins/base64.d.ts.map +1 -1
  3. package/dist/builtins/base64.js +7 -5
  4. package/dist/builtins/base64.js.map +1 -1
  5. package/dist/builtins/detected.d.ts.map +1 -1
  6. package/dist/builtins/detected.js +1 -1
  7. package/dist/builtins/detected.js.map +1 -1
  8. package/dist/builtins/hash.d.ts +1 -0
  9. package/dist/builtins/hash.d.ts.map +1 -1
  10. package/dist/builtins/hash.js +12 -2
  11. package/dist/builtins/hash.js.map +1 -1
  12. package/dist/builtins/index.d.ts.map +1 -1
  13. package/dist/builtins/index.js +18 -7
  14. package/dist/builtins/index.js.map +1 -1
  15. package/dist/builtins/inventory.d.ts +7 -5
  16. package/dist/builtins/inventory.d.ts.map +1 -1
  17. package/dist/builtins/inventory.js +36 -14
  18. package/dist/builtins/inventory.js.map +1 -1
  19. package/dist/builtins/json.d.ts.map +1 -1
  20. package/dist/builtins/json.js +45 -34
  21. package/dist/builtins/json.js.map +1 -1
  22. package/dist/builtins/link-info.js +2 -2
  23. package/dist/builtins/link-info.js.map +1 -1
  24. package/dist/builtins/linksetdata.d.ts.map +1 -1
  25. package/dist/builtins/linksetdata.js +6 -9
  26. package/dist/builtins/linksetdata.js.map +1 -1
  27. package/dist/builtins/lists.d.ts +15 -0
  28. package/dist/builtins/lists.d.ts.map +1 -1
  29. package/dist/builtins/lists.js +118 -11
  30. package/dist/builtins/lists.js.map +1 -1
  31. package/dist/builtins/math.d.ts +2 -0
  32. package/dist/builtins/math.d.ts.map +1 -1
  33. package/dist/builtins/math.js +30 -5
  34. package/dist/builtins/math.js.map +1 -1
  35. package/dist/builtins/object.d.ts +5 -2
  36. package/dist/builtins/object.d.ts.map +1 -1
  37. package/dist/builtins/object.js +13 -5
  38. package/dist/builtins/object.js.map +1 -1
  39. package/dist/builtins/prim-accessors.d.ts +1 -0
  40. package/dist/builtins/prim-accessors.d.ts.map +1 -1
  41. package/dist/builtins/prim-accessors.js +51 -24
  42. package/dist/builtins/prim-accessors.js.map +1 -1
  43. package/dist/builtins/prim-params.d.ts.map +1 -1
  44. package/dist/builtins/prim-params.js +6 -2
  45. package/dist/builtins/prim-params.js.map +1 -1
  46. package/dist/builtins/script-control.d.ts +4 -3
  47. package/dist/builtins/script-control.d.ts.map +1 -1
  48. package/dist/builtins/script-control.js +22 -8
  49. package/dist/builtins/script-control.js.map +1 -1
  50. package/dist/builtins/strings.d.ts +6 -0
  51. package/dist/builtins/strings.d.ts.map +1 -1
  52. package/dist/builtins/strings.js +47 -6
  53. package/dist/builtins/strings.js.map +1 -1
  54. package/dist/builtins/time.d.ts +3 -5
  55. package/dist/builtins/time.d.ts.map +1 -1
  56. package/dist/builtins/time.js +4 -7
  57. package/dist/builtins/time.js.map +1 -1
  58. package/dist/clock.d.ts +16 -6
  59. package/dist/clock.d.ts.map +1 -1
  60. package/dist/clock.js +38 -11
  61. package/dist/clock.js.map +1 -1
  62. package/dist/dispatch.d.ts +3 -2
  63. package/dist/dispatch.d.ts.map +1 -1
  64. package/dist/dispatch.js +15 -10
  65. package/dist/dispatch.js.map +1 -1
  66. package/dist/interpreter.d.ts +5 -9
  67. package/dist/interpreter.d.ts.map +1 -1
  68. package/dist/interpreter.js +143 -70
  69. package/dist/interpreter.js.map +1 -1
  70. package/dist/linkset.d.ts +11 -3
  71. package/dist/linkset.d.ts.map +1 -1
  72. package/dist/linkset.js +32 -11
  73. package/dist/linkset.js.map +1 -1
  74. package/dist/memory.d.ts +5 -2
  75. package/dist/memory.d.ts.map +1 -1
  76. package/dist/memory.js +5 -2
  77. package/dist/memory.js.map +1 -1
  78. package/dist/prim-params.d.ts +27 -10
  79. package/dist/prim-params.d.ts.map +1 -1
  80. package/dist/prim-params.js +198 -122
  81. package/dist/prim-params.js.map +1 -1
  82. package/dist/prim.d.ts +1 -1
  83. package/dist/prim.d.ts.map +1 -1
  84. package/dist/prim.js +2 -2
  85. package/dist/prim.js.map +1 -1
  86. package/dist/runtime.d.ts +11 -3
  87. package/dist/runtime.d.ts.map +1 -1
  88. package/dist/script.d.ts +24 -10
  89. package/dist/script.d.ts.map +1 -1
  90. package/dist/script.js +118 -65
  91. package/dist/script.js.map +1 -1
  92. package/dist/typecheck.d.ts +3 -1
  93. package/dist/typecheck.d.ts.map +1 -1
  94. package/dist/typecheck.js +25 -5
  95. package/dist/typecheck.js.map +1 -1
  96. package/dist/values/coerce.d.ts +1 -1
  97. package/dist/values/coerce.d.ts.map +1 -1
  98. package/dist/values/coerce.js +10 -6
  99. package/dist/values/coerce.js.map +1 -1
  100. package/dist/values/format.d.ts +3 -1
  101. package/dist/values/format.d.ts.map +1 -1
  102. package/dist/values/format.js +10 -6
  103. package/dist/values/format.js.map +1 -1
  104. package/package.json +2 -2
@@ -1,6 +1,7 @@
1
- import { ALL_SIDES, CLICK_ACTION_TOUCH, PRIM_ALPHA_MODE, PRIM_ALPHA_MODE_NONE, PRIM_ALLOW_UNSIT, PRIM_BUMP_NONE, PRIM_BUMP_SHINY, PRIM_CAST_SHADOWS, PRIM_CLICK_ACTION, PRIM_COLOR, PRIM_DAMAGE, PRIM_DESC, PRIM_FLEXIBLE, PRIM_FULLBRIGHT, PRIM_GLOW, PRIM_GLTF_ALPHA_MODE_OPAQUE, PRIM_GLTF_BASE_COLOR, PRIM_GLTF_EMISSIVE, PRIM_GLTF_METALLIC_ROUGHNESS, PRIM_GLTF_NORMAL, PRIM_HEALTH, PRIM_HOLE_DEFAULT, PRIM_LINK_TARGET, PRIM_MATERIAL, PRIM_MATERIAL_WOOD, PRIM_NAME, PRIM_NORMAL, PRIM_OMEGA, PRIM_PHANTOM, PRIM_PHYSICS, PRIM_PHYSICS_SHAPE_PRIM, PRIM_PHYSICS_SHAPE_TYPE, PRIM_POINT_LIGHT, PRIM_POSITION, PRIM_POS_LOCAL, PRIM_PROJECTOR, PRIM_REFLECTION_PROBE, PRIM_RENDER_MATERIAL, PRIM_ROTATION, PRIM_ROT_LOCAL, PRIM_SCRIPTED_SIT_ONLY, PRIM_SCULPT_TYPE_SPHERE, PRIM_SHINY_NONE, PRIM_SIT_FLAGS, PRIM_SIT_TARGET, PRIM_SIZE, PRIM_SLICE, PRIM_SPECULAR, PRIM_TEMP_ON_REZ, PRIM_TEXGEN, PRIM_TEXGEN_DEFAULT, PRIM_TEXT, PRIM_TEXTURE, PRIM_TYPE, PRIM_TYPE_BOX, PRIM_TYPE_CYLINDER, PRIM_TYPE_PRISM, PRIM_TYPE_RING, PRIM_TYPE_SCULPT, PRIM_TYPE_SPHERE, PRIM_TYPE_TORUS, PRIM_TYPE_TUBE, STATUS_PHANTOM, STATUS_PHYSICS, TEXTURE_DEFAULT, } from './generated/constants.js';
1
+ import { CLICK_ACTION_TOUCH, OVERRIDE_GLTF_BASE_ALPHA, OVERRIDE_GLTF_BASE_ALPHA_MASK, OVERRIDE_GLTF_BASE_ALPHA_MODE, OVERRIDE_GLTF_BASE_COLOR_FACTOR, OVERRIDE_GLTF_BASE_DOUBLE_SIDED, OVERRIDE_GLTF_EMISSIVE_FACTOR, OVERRIDE_GLTF_METALLIC_FACTOR, OVERRIDE_GLTF_ROUGHNESS_FACTOR, PRIM_ALPHA_MODE, PRIM_ALPHA_MODE_BLEND, PRIM_ALPHA_MODE_MASK, PRIM_ALLOW_UNSIT, PRIM_BUMP_NONE, PRIM_BUMP_SHINY, PRIM_CAST_SHADOWS, PRIM_CLICK_ACTION, PRIM_COLOR, PRIM_DAMAGE, PRIM_DESC, PRIM_FLEXIBLE, PRIM_FULLBRIGHT, PRIM_GLOW, PRIM_GLTF_BASE_COLOR, PRIM_GLTF_EMISSIVE, PRIM_GLTF_METALLIC_ROUGHNESS, PRIM_GLTF_NORMAL, PRIM_HEALTH, PRIM_HOLE_DEFAULT, PRIM_LINK_TARGET, PRIM_MATERIAL, PRIM_MATERIAL_WOOD, PRIM_NAME, PRIM_NORMAL, PRIM_OMEGA, PRIM_PHANTOM, PRIM_PHYSICS, PRIM_PHYSICS_SHAPE_PRIM, PRIM_PHYSICS_SHAPE_TYPE, PRIM_POINT_LIGHT, PRIM_POSITION, PRIM_POS_LOCAL, PRIM_PROJECTOR, PRIM_REFLECTION_PROBE, PRIM_RENDER_MATERIAL, PRIM_ROTATION, PRIM_ROT_LOCAL, PRIM_SCRIPTED_SIT_ONLY, PRIM_SCULPT_TYPE_SPHERE, PRIM_SHINY_NONE, PRIM_SIT_FLAGS, PRIM_SIT_TARGET, PRIM_SIZE, PRIM_SLICE, PRIM_SPECULAR, PRIM_TEMP_ON_REZ, PRIM_TEXGEN, PRIM_TEXGEN_DEFAULT, PRIM_TEXT, PRIM_TEXTURE, PRIM_TYPE, PRIM_TYPE_BOX, PRIM_TYPE_CYLINDER, PRIM_TYPE_PRISM, PRIM_TYPE_RING, PRIM_TYPE_SCULPT, PRIM_TYPE_SPHERE, PRIM_TYPE_TORUS, PRIM_TYPE_TUBE, STATUS_PHANTOM, STATUS_PHYSICS, TEXTURE_DEFAULT, } from './generated/constants.js';
2
2
  import { NULL_KEY, isRotation, isVector } from './values/types.js';
3
3
  import { listElementType } from './values/list-types.js';
4
+ import { InventoryType } from './inventory.js';
4
5
  import { truncateUtf8 } from './values/utf8.js';
5
6
  /**
6
7
  * Typed-literal aliases for the PRIM_TYPE_* constants. The
@@ -45,18 +46,109 @@ export function defaultFace() {
45
46
  fullBright: false,
46
47
  glow: 0,
47
48
  texgen: PRIM_TEXGEN_DEFAULT,
48
- renderMaterial: '',
49
- normal: defaultFaceMaterial(),
50
- specular: { ...defaultFaceMaterial(), color: one(), glossiness: 51, environment: 0 },
51
- alphaMode: { mode: PRIM_ALPHA_MODE_NONE, cutoff: 0 },
49
+ renderMaterial: NULL_KEY,
50
+ normal: { ...defaultFaceMaterial(), texture: NULL_KEY },
51
+ specular: { ...defaultFaceMaterial(), texture: NULL_KEY, color: one(), glossiness: 51, environment: 0 },
52
+ alphaMode: { mode: PRIM_ALPHA_MODE_BLEND, cutoff: 0 },
52
53
  gltf: {
53
- baseColor: { ...defaultFaceMaterial(), color: one(), alpha: 1, alphaMode: PRIM_GLTF_ALPHA_MODE_OPAQUE, alphaCutoff: 0.5, doubleSided: 0 },
54
+ baseColor: { ...defaultFaceMaterial(), color: '', alpha: '', alphaMode: '', alphaCutoff: '', doubleSided: '' },
54
55
  normal: defaultFaceMaterial(),
55
- metallicRoughness: { ...defaultFaceMaterial(), metallic: 1, roughness: 1 },
56
- emissive: { ...defaultFaceMaterial(), tint: zero() },
56
+ metallicRoughness: { ...defaultFaceMaterial(), metallic: '', roughness: '' },
57
+ emissive: { ...defaultFaceMaterial(), tint: '' },
57
58
  },
58
59
  };
59
60
  }
61
+ function clearGltfOverrides(g) {
62
+ g.baseColor = { ...g.baseColor, texture: '', color: '', alpha: '', alphaMode: '', alphaCutoff: '', doubleSided: '' };
63
+ g.normal = { ...g.normal, texture: '' };
64
+ g.metallicRoughness = { ...g.metallicRoughness, texture: '', metallic: '', roughness: '' };
65
+ g.emissive = { ...g.emissive, texture: '', tint: '' };
66
+ }
67
+ const UUID = /^[0-9a-f]{8}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{4}-[0-9a-f]{12}$/i;
68
+ const findItem = (prim, type, name) => prim.inventory.find((it) => it.type === type && it.name === name);
69
+ /** The key a render material argument names: "" (none), a key, or a material item's name; null if it names nothing. */
70
+ export function renderMaterialKey(prim, material) {
71
+ if (material === '')
72
+ return NULL_KEY;
73
+ if (UUID.test(material))
74
+ return material;
75
+ return findItem(prim, InventoryType.MATERIAL, material)?.key ?? null;
76
+ }
77
+ export function textureKey(prim, texture) {
78
+ if (texture === '')
79
+ return NULL_KEY;
80
+ if (UUID.test(texture))
81
+ return texture;
82
+ return findItem(prim, InventoryType.TEXTURE, texture)?.key ?? null;
83
+ }
84
+ const itemName = (prim, type, key) => key === NULL_KEY ? key : (prim.inventory.find((it) => it.type === type && it.key === key)?.name ?? key);
85
+ export const textureName = (prim, key) => itemName(prim, InventoryType.TEXTURE, key);
86
+ export const materialName = (prim, key) => itemName(prim, InventoryType.MATERIAL, key);
87
+ const OVERRIDE_TYPES = {
88
+ [OVERRIDE_GLTF_BASE_COLOR_FACTOR]: 'vector',
89
+ [OVERRIDE_GLTF_BASE_ALPHA]: 'float',
90
+ [OVERRIDE_GLTF_BASE_ALPHA_MODE]: 'integer',
91
+ [OVERRIDE_GLTF_BASE_ALPHA_MASK]: 'float',
92
+ [OVERRIDE_GLTF_BASE_DOUBLE_SIDED]: 'integer',
93
+ [OVERRIDE_GLTF_METALLIC_FACTOR]: 'float',
94
+ [OVERRIDE_GLTF_ROUGHNESS_FACTOR]: 'float',
95
+ [OVERRIDE_GLTF_EMISSIVE_FACTOR]: 'vector',
96
+ };
97
+ const overrideType = (code) => (typeof code === 'number' ? OVERRIDE_TYPES[code] : undefined);
98
+ /**
99
+ * Apply llSetLinkGLTFOverrides' [OVERRIDE_GLTF_*, value, ...] pairs. The base
100
+ * colour and alpha form one factor that "" leaves alone; "" clears the others.
101
+ * One wrong-typed value rejects the whole call; an unknown code is skipped.
102
+ */
103
+ export function setGltfOverrides(g, params) {
104
+ for (let i = 0; i + 1 < params.length; i += 2) {
105
+ const expected = overrideType(params[i]);
106
+ if (expected === undefined || params[i + 1] === '')
107
+ continue;
108
+ const given = givenType(params, i + 1);
109
+ if (given !== expected && !(expected === 'float' && given === 'integer'))
110
+ return;
111
+ }
112
+ const bc = g.baseColor;
113
+ for (let i = 0; i + 1 < params.length; i += 2) {
114
+ const v = params[i + 1];
115
+ const n = typeof v === 'number' ? v : '';
116
+ switch (params[i]) {
117
+ case OVERRIDE_GLTF_BASE_COLOR_FACTOR:
118
+ if (isVector(v)) {
119
+ bc.color = clampVec(asVec(v), 0, 1);
120
+ if (bc.alpha === '')
121
+ bc.alpha = 1;
122
+ }
123
+ break;
124
+ case OVERRIDE_GLTF_BASE_ALPHA:
125
+ if (typeof v === 'number') {
126
+ bc.alpha = clamp(v, 0, 1);
127
+ if (bc.color === '')
128
+ bc.color = one();
129
+ }
130
+ break;
131
+ case OVERRIDE_GLTF_BASE_ALPHA_MODE:
132
+ bc.alphaMode = n === '' ? '' : clamp(n, 0, 2);
133
+ break;
134
+ case OVERRIDE_GLTF_BASE_ALPHA_MASK:
135
+ bc.alphaCutoff = n === '' ? '' : clamp(n, 0, 1);
136
+ break;
137
+ case OVERRIDE_GLTF_BASE_DOUBLE_SIDED:
138
+ bc.doubleSided = n === '' ? '' : clamp(n, 0, 1);
139
+ break;
140
+ case OVERRIDE_GLTF_METALLIC_FACTOR:
141
+ g.metallicRoughness.metallic = n === '' ? '' : clamp(n, 0, 1);
142
+ break;
143
+ case OVERRIDE_GLTF_ROUGHNESS_FACTOR:
144
+ g.metallicRoughness.roughness = n === '' ? '' : clamp(n, 0, 1);
145
+ break;
146
+ case OVERRIDE_GLTF_EMISSIVE_FACTOR:
147
+ g.emissive.tint = isVector(v) ? clampVec(asVec(v), 0, 1) : '';
148
+ break;
149
+ }
150
+ }
151
+ }
60
152
  export function defaultShape() {
61
153
  return {
62
154
  kind: SHAPE_BOX,
@@ -74,7 +166,7 @@ export function defaultPrimParams() {
74
166
  rotation: identityRot(),
75
167
  size: { x: 0.5, y: 0.5, z: 0.5 },
76
168
  faces: Array.from({ length: 6 }, () => defaultFace()),
77
- pointLight: { enabled: false, color: one(), intensity: 1, radius: 10, falloff: 0.75 },
169
+ pointLight: { enabled: false, color: zero(), intensity: 0, radius: 0, falloff: 0 },
78
170
  glow: [0, 0, 0, 0, 0, 0],
79
171
  omega: { axis: zero(), spinrate: 0, gain: 0 },
80
172
  physicsShapeType: PRIM_PHYSICS_SHAPE_PRIM,
@@ -128,11 +220,11 @@ function normalizeRot(r) {
128
220
  return identityRot();
129
221
  return { x: r.x / mag, y: r.y / mag, z: r.z / mag, s: r.s / mag };
130
222
  }
131
- /** Face indexes a face argument names: every face for ALL_SIDES, none past the shape's face count. */
223
+ /** Face indexes a face argument names: every face for a negative face, none past the shape's face count. */
132
224
  export function faceIndices(p, face) {
133
- if (face === ALL_SIDES)
225
+ if (face < 0)
134
226
  return p.faces.map((_, i) => i);
135
- return face >= 0 && face < p.faces.length ? [face] : [];
227
+ return face < p.faces.length ? [face] : [];
136
228
  }
137
229
  const facesOf = (p, faceArg) => faceIndices(p, num(faceArg, 0) | 0);
138
230
  /**
@@ -214,20 +306,23 @@ export function writePrimParamSlots(param, rules, cursor) {
214
306
  * advances by `1 + consumed`). Returns `null` for unsupported / unknown
215
307
  * rule constants — the walker stops parsing the list, matching LSL.
216
308
  */
217
- export function writePrimParam(prim, param, rules, cursor) {
309
+ export function writePrimParam(prim, param, rules, cursor, caller = prim) {
218
310
  const p = prim.params;
219
311
  switch (param) {
220
312
  case PRIM_NAME:
221
- prim.name = truncateUtf8(str(rules[cursor]), 63);
313
+ prim.name = truncateUtf8(plainText(str(rules[cursor])), 63);
222
314
  return 1;
223
315
  case PRIM_DESC:
224
- prim.description = truncateUtf8(str(rules[cursor]), 127);
316
+ prim.description = truncateUtf8(plainText(str(rules[cursor])), 127);
225
317
  prim.appearance.description = prim.description;
226
318
  return 1;
227
319
  case PRIM_TEXT: {
228
320
  const text = truncateUtf8(str(rules[cursor]), 254);
229
321
  const color = asVec(rules[cursor + 1]);
230
322
  const alpha = num(rules[cursor + 2], 1);
323
+ const outOfRange = (v) => v < 0 || v > 1;
324
+ if (outOfRange(alpha) || outOfRange(color.x) || outOfRange(color.y) || outOfRange(color.z))
325
+ return 3;
231
326
  prim.appearance.text = { text, color, alpha };
232
327
  return 3;
233
328
  }
@@ -341,29 +436,33 @@ export function writePrimParam(prim, param, rules, cursor) {
341
436
  }
342
437
  case PRIM_TEXTURE: {
343
438
  const faces = facesOf(p, rules[cursor]);
344
- const tex = str(rules[cursor + 1]);
439
+ const tex = textureKey(caller, str(rules[cursor + 1])) ?? NULL_KEY;
345
440
  const repeats = asVec(rules[cursor + 2]);
346
441
  const offsets = asVec(rules[cursor + 3]);
347
442
  const rot = num(rules[cursor + 4]);
348
443
  for (const f of faces) {
349
444
  p.faces[f].texture = tex;
350
- p.faces[f].textureRepeats = { x: repeats.x, y: repeats.y, z: repeats.z };
351
- p.faces[f].textureOffsets = { x: offsets.x, y: offsets.y, z: offsets.z };
445
+ p.faces[f].textureRepeats = { x: repeats.x, y: repeats.y, z: 0 };
446
+ p.faces[f].textureOffsets = { x: offsets.x, y: offsets.y, z: 0 };
352
447
  p.faces[f].textureRotation = rot;
353
448
  }
354
449
  return 5;
355
450
  }
356
451
  case PRIM_RENDER_MATERIAL: {
357
452
  const faces = facesOf(p, rules[cursor]);
358
- const m = str(rules[cursor + 1]);
453
+ const m = renderMaterialKey(caller, str(rules[cursor + 1])) ?? NULL_KEY;
359
454
  for (const f of faces) {
360
455
  p.faces[f].renderMaterial = m;
456
+ clearGltfOverrides(p.faces[f].gltf);
361
457
  }
362
458
  return 2;
363
459
  }
364
460
  case PRIM_FULLBRIGHT: {
365
461
  const faces = facesOf(p, rules[cursor]);
366
- const v = num(rules[cursor + 1]) !== 0;
462
+ const raw = num(rules[cursor + 1]);
463
+ if (raw !== 0 && raw !== 1)
464
+ return 2;
465
+ const v = raw === 1;
367
466
  for (const f of faces) {
368
467
  p.faces[f].fullBright = v;
369
468
  }
@@ -382,6 +481,8 @@ export function writePrimParam(prim, param, rules, cursor) {
382
481
  const faces = facesOf(p, rules[cursor]);
383
482
  const shiny = num(rules[cursor + 1]) | 0;
384
483
  const bump = num(rules[cursor + 2]) | 0;
484
+ if (shiny < 0 || shiny > 3 || bump < 0 || bump > 17)
485
+ return 3;
385
486
  for (const f of faces) {
386
487
  p.faces[f].bumpShiny = { shiny, bump };
387
488
  }
@@ -390,6 +491,8 @@ export function writePrimParam(prim, param, rules, cursor) {
390
491
  case PRIM_TEXGEN: {
391
492
  const faces = facesOf(p, rules[cursor]);
392
493
  const mode = num(rules[cursor + 1]) | 0;
494
+ if (mode < 0 || mode > 1)
495
+ return 2;
393
496
  for (const f of faces) {
394
497
  p.faces[f].texgen = mode;
395
498
  }
@@ -398,12 +501,13 @@ export function writePrimParam(prim, param, rules, cursor) {
398
501
  case PRIM_NORMAL: {
399
502
  const faces = facesOf(p, rules[cursor]);
400
503
  const tex = str(rules[cursor + 1]);
401
- const repeats = asVec(rules[cursor + 2]);
402
- const offsets = asVec(rules[cursor + 3]);
403
- const rot = num(rules[cursor + 4]);
504
+ const key = textureKey(caller, tex) ?? NULL_KEY;
505
+ const repeats = tex === '' ? { x: 1, y: 1, z: 0 } : asVec(rules[cursor + 2]);
506
+ const offsets = tex === '' ? zero() : asVec(rules[cursor + 3]);
507
+ const rot = tex === '' ? 0 : num(rules[cursor + 4]);
404
508
  for (const f of faces) {
405
509
  p.faces[f].normal = {
406
- texture: tex,
510
+ texture: key,
407
511
  textureRepeats: { x: repeats.x, y: repeats.y, z: repeats.z },
408
512
  textureOffsets: { x: offsets.x, y: offsets.y, z: offsets.z },
409
513
  textureRotation: rot,
@@ -414,15 +518,17 @@ export function writePrimParam(prim, param, rules, cursor) {
414
518
  case PRIM_SPECULAR: {
415
519
  const faces = facesOf(p, rules[cursor]);
416
520
  const tex = str(rules[cursor + 1]);
417
- const repeats = asVec(rules[cursor + 2]);
418
- const offsets = asVec(rules[cursor + 3]);
419
- const rot = num(rules[cursor + 4]);
420
- const color = asVec(rules[cursor + 5]);
421
- const gloss = num(rules[cursor + 6]) | 0;
422
- const env = num(rules[cursor + 7]) | 0;
521
+ const key = textureKey(caller, tex) ?? NULL_KEY;
522
+ const reset = tex === '';
523
+ const repeats = reset ? { x: 1, y: 1, z: 0 } : asVec(rules[cursor + 2]);
524
+ const offsets = reset ? zero() : asVec(rules[cursor + 3]);
525
+ const rot = reset ? 0 : num(rules[cursor + 4]);
526
+ const color = reset ? one() : asVec(rules[cursor + 5]);
527
+ const gloss = reset ? 51 : clamp(num(rules[cursor + 6]) | 0, 0, 255);
528
+ const env = reset ? 0 : clamp(num(rules[cursor + 7]) | 0, 0, 255);
423
529
  for (const f of faces) {
424
530
  p.faces[f].specular = {
425
- texture: tex,
531
+ texture: key,
426
532
  textureRepeats: { x: repeats.x, y: repeats.y, z: repeats.z },
427
533
  textureOffsets: { x: offsets.x, y: offsets.y, z: offsets.z },
428
534
  // Second Life stores a negative rotation as 0 and the colour in 8 bits per channel
@@ -436,99 +542,66 @@ export function writePrimParam(prim, param, rules, cursor) {
436
542
  }
437
543
  case PRIM_ALPHA_MODE: {
438
544
  const faces = facesOf(p, rules[cursor]);
439
- const mode = num(rules[cursor + 1]) | 0;
440
- const cutoff = num(rules[cursor + 2]) | 0;
545
+ const mode = clamp(num(rules[cursor + 1]) | 0, 0, 3);
546
+ const cutoff = mode === PRIM_ALPHA_MODE_MASK ? clamp(num(rules[cursor + 2]) | 0, 0, 255) : 0;
441
547
  for (const f of faces) {
442
548
  p.faces[f].alphaMode = { mode, cutoff };
443
549
  }
444
550
  return 3;
445
551
  }
446
552
  case PRIM_GLTF_BASE_COLOR: {
447
- const faces = facesOf(p, rules[cursor]);
448
- const tex = str(rules[cursor + 1]);
449
- const repeats = asVec(rules[cursor + 2]);
450
- const offsets = asVec(rules[cursor + 3]);
451
- const rot = num(rules[cursor + 4]);
452
- const color = asVec(rules[cursor + 5]);
453
- const alpha = num(rules[cursor + 6], 1);
454
- const aMode = num(rules[cursor + 7]) | 0;
455
- const aCut = num(rules[cursor + 8]);
456
- const dbl = num(rules[cursor + 9]) | 0;
457
- for (const f of faces) {
458
- p.faces[f].gltf.baseColor = {
459
- texture: tex,
460
- textureRepeats: { x: repeats.x, y: repeats.y, z: repeats.z },
461
- textureOffsets: { x: offsets.x, y: offsets.y, z: offsets.z },
462
- textureRotation: rot,
463
- color: { x: color.x, y: color.y, z: color.z },
464
- alpha,
465
- alphaMode: aMode,
466
- alphaCutoff: aCut,
467
- doubleSided: dbl,
468
- };
469
- }
553
+ const color = rules[cursor + 5];
554
+ const alpha = rules[cursor + 6];
555
+ const factor = color !== '' || alpha !== '';
556
+ writeGltf(prim, caller, rules, cursor, 'baseColor', {
557
+ color: factor ? (color === '' ? one() : clampVec(asVec(color), 0, 1)) : '',
558
+ alpha: factor ? clamp(num(alpha, 1), 0, 1) : '',
559
+ alphaMode: valueOverride(rules[cursor + 7], (v) => clamp(num(v) | 0, 0, 2)),
560
+ alphaCutoff: valueOverride(rules[cursor + 8], (v) => clamp(num(v), 0, 1)),
561
+ doubleSided: valueOverride(rules[cursor + 9], (v) => clamp(num(v) | 0, 0, 1)),
562
+ });
470
563
  return 10;
471
564
  }
472
- case PRIM_GLTF_NORMAL: {
473
- const faces = facesOf(p, rules[cursor]);
474
- const tex = str(rules[cursor + 1]);
475
- const repeats = asVec(rules[cursor + 2]);
476
- const offsets = asVec(rules[cursor + 3]);
477
- const rot = num(rules[cursor + 4]);
478
- for (const f of faces) {
479
- p.faces[f].gltf.normal = {
480
- texture: tex,
481
- textureRepeats: { x: repeats.x, y: repeats.y, z: repeats.z },
482
- textureOffsets: { x: offsets.x, y: offsets.y, z: offsets.z },
483
- textureRotation: rot,
484
- };
485
- }
565
+ case PRIM_GLTF_NORMAL:
566
+ writeGltf(prim, caller, rules, cursor, 'normal', {});
486
567
  return 5;
487
- }
488
- case PRIM_GLTF_METALLIC_ROUGHNESS: {
489
- const faces = facesOf(p, rules[cursor]);
490
- const tex = str(rules[cursor + 1]);
491
- const repeats = asVec(rules[cursor + 2]);
492
- const offsets = asVec(rules[cursor + 3]);
493
- const rot = num(rules[cursor + 4]);
494
- const metallic = num(rules[cursor + 5]);
495
- const rough = num(rules[cursor + 6]);
496
- for (const f of faces) {
497
- p.faces[f].gltf.metallicRoughness = {
498
- texture: tex,
499
- textureRepeats: { x: repeats.x, y: repeats.y, z: repeats.z },
500
- textureOffsets: { x: offsets.x, y: offsets.y, z: offsets.z },
501
- textureRotation: rot,
502
- metallic,
503
- roughness: rough,
504
- };
505
- }
568
+ case PRIM_GLTF_METALLIC_ROUGHNESS:
569
+ writeGltf(prim, caller, rules, cursor, 'metallicRoughness', {
570
+ metallic: valueOverride(rules[cursor + 5], (v) => clamp(num(v), 0, 1)),
571
+ roughness: valueOverride(rules[cursor + 6], (v) => clamp(num(v), 0, 1)),
572
+ });
506
573
  return 7;
507
- }
508
- case PRIM_GLTF_EMISSIVE: {
509
- const faces = facesOf(p, rules[cursor]);
510
- const tex = str(rules[cursor + 1]);
511
- const repeats = asVec(rules[cursor + 2]);
512
- const offsets = asVec(rules[cursor + 3]);
513
- const rot = num(rules[cursor + 4]);
514
- const tint = asVec(rules[cursor + 5]);
515
- for (const f of faces) {
516
- p.faces[f].gltf.emissive = {
517
- texture: tex,
518
- textureRepeats: { x: repeats.x, y: repeats.y, z: repeats.z },
519
- textureOffsets: { x: offsets.x, y: offsets.y, z: offsets.z },
520
- textureRotation: rot,
521
- tint: { x: tint.x, y: tint.y, z: tint.z },
522
- };
523
- }
574
+ case PRIM_GLTF_EMISSIVE:
575
+ writeGltf(prim, caller, rules, cursor, 'emissive', { tint: valueOverride(rules[cursor + 5], (v) => clampVec(asVec(v), 0, 1)) });
524
576
  return 6;
525
- }
526
577
  case PRIM_LINK_TARGET:
527
578
  return null;
528
579
  default:
529
580
  return null;
530
581
  }
531
582
  }
583
+ const valueOverride = (v, read) => (v === '' ? '' : read(v));
584
+ /**
585
+ * Write one PRIM_GLTF_* rule. "" in a transform slot resets it; "" in a texture
586
+ * or value slot unsets that override. Faces without a render material keep
587
+ * only the transforms.
588
+ */
589
+ function writeGltf(prim, caller, rules, cursor, rule, values) {
590
+ const transform = {
591
+ textureRepeats: rules[cursor + 2] === '' ? { x: 1, y: 1, z: 0 } : asVec(rules[cursor + 2]),
592
+ textureOffsets: asVec(rules[cursor + 3]),
593
+ textureRotation: num(rules[cursor + 4]),
594
+ };
595
+ const name = str(rules[cursor + 1]);
596
+ const texture = name === '' ? '' : (textureKey(caller, name) ?? NULL_KEY);
597
+ for (const f of facesOf(prim.params, rules[cursor])) {
598
+ const face = prim.params.faces[f];
599
+ const overrides = face.renderMaterial === NULL_KEY ? {} : { texture, ...values };
600
+ face.gltf[rule] = { ...face.gltf[rule], ...transform, ...overrides };
601
+ }
602
+ }
603
+ /** Names and descriptions can't hold the separator characters; they become `?`. */
604
+ const plainText = (s) => s.replace(/[|\n]/g, '?');
532
605
  function writePrimType(p, rules, cursor) {
533
606
  const kind = (num(rules[cursor]) | 0);
534
607
  const old = p.shape;
@@ -606,7 +679,7 @@ export function readPrimParam(prim, param, rules, cursor) {
606
679
  return { values: [prim.description], consumed: 0 };
607
680
  case PRIM_TEXT: {
608
681
  const t = prim.appearance.text;
609
- return { values: t ? [t.text, t.color, t.alpha] : ['', zero(), 0], consumed: 0 };
682
+ return { values: t ? [t.text, t.color, t.alpha] : ['', zero(), 1], consumed: 0 };
610
683
  }
611
684
  case PRIM_POSITION:
612
685
  return { values: [prim.regionPosition()], consumed: 0 };
@@ -683,7 +756,7 @@ export function readPrimParam(prim, param, rules, cursor) {
683
756
  const out = [];
684
757
  for (const f of faces) {
685
758
  const fa = p.faces[f];
686
- out.push(fa.texture, fa.textureRepeats, fa.textureOffsets, fa.textureRotation);
759
+ out.push(textureName(prim, fa.texture), fa.textureRepeats, fa.textureOffsets, fa.textureRotation);
687
760
  }
688
761
  return { values: out, consumed: 1 };
689
762
  }
@@ -691,7 +764,7 @@ export function readPrimParam(prim, param, rules, cursor) {
691
764
  const faces = facesOf(p, rules[cursor]);
692
765
  const out = [];
693
766
  for (const f of faces) {
694
- out.push(p.faces[f].renderMaterial);
767
+ out.push(materialName(prim, p.faces[f].renderMaterial));
695
768
  }
696
769
  return { values: out, consumed: 1 };
697
770
  }
@@ -732,7 +805,7 @@ export function readPrimParam(prim, param, rules, cursor) {
732
805
  const out = [];
733
806
  for (const f of faces) {
734
807
  const n = p.faces[f].normal;
735
- out.push(n.texture, n.textureRepeats, n.textureOffsets, n.textureRotation);
808
+ out.push(textureName(prim, n.texture), n.textureRepeats, n.textureOffsets, n.textureRotation);
736
809
  }
737
810
  return { values: out, consumed: 1 };
738
811
  }
@@ -741,7 +814,7 @@ export function readPrimParam(prim, param, rules, cursor) {
741
814
  const out = [];
742
815
  for (const f of faces) {
743
816
  const s = p.faces[f].specular;
744
- out.push(s.texture, s.textureRepeats, s.textureOffsets, s.textureRotation, s.color, s.glossiness, s.environment);
817
+ out.push(textureName(prim, s.texture), s.textureRepeats, s.textureOffsets, s.textureRotation, s.color, s.glossiness, s.environment);
745
818
  }
746
819
  return { values: out, consumed: 1 };
747
820
  }
@@ -812,6 +885,7 @@ function readPrimType(p) {
812
885
  return [];
813
886
  }
814
887
  const MATERIAL_TYPES = ['string', 'vector', 'vector', 'float'];
888
+ const MAP_TYPES = ['key', 'vector', 'vector', 'float'];
815
889
  /**
816
890
  * LSL types of the values each PRIM_* rule reads. Per-face rules list one
817
891
  * face's values; the pattern repeats for each face.
@@ -851,13 +925,13 @@ const PRIM_PARAM_TYPES = {
851
925
  [PRIM_GLOW]: ['float'],
852
926
  [PRIM_BUMP_SHINY]: ['integer', 'integer'],
853
927
  [PRIM_TEXGEN]: ['integer'],
854
- [PRIM_NORMAL]: MATERIAL_TYPES,
855
- [PRIM_SPECULAR]: [...MATERIAL_TYPES, 'vector', 'integer', 'integer'],
928
+ [PRIM_NORMAL]: MAP_TYPES,
929
+ [PRIM_SPECULAR]: [...MAP_TYPES, 'vector', 'integer', 'integer'],
856
930
  [PRIM_ALPHA_MODE]: ['integer', 'integer'],
857
- [PRIM_GLTF_BASE_COLOR]: [...MATERIAL_TYPES, 'vector', 'float', 'integer', 'float', 'integer'],
858
- [PRIM_GLTF_NORMAL]: MATERIAL_TYPES,
859
- [PRIM_GLTF_METALLIC_ROUGHNESS]: [...MATERIAL_TYPES, 'float', 'float'],
860
- [PRIM_GLTF_EMISSIVE]: [...MATERIAL_TYPES, 'vector'],
931
+ [PRIM_GLTF_BASE_COLOR]: [...MAP_TYPES, 'vector', 'float', 'integer', 'float', 'integer'],
932
+ [PRIM_GLTF_NORMAL]: MAP_TYPES,
933
+ [PRIM_GLTF_METALLIC_ROUGHNESS]: [...MAP_TYPES, 'float', 'float'],
934
+ [PRIM_GLTF_EMISSIVE]: [...MAP_TYPES, 'vector'],
861
935
  };
862
936
  /** LSL types of PRIM_TYPE's values, which depend on the shape. */
863
937
  function primTypeTypes(kind) {
@@ -871,10 +945,12 @@ function primTypeTypes(kind) {
871
945
  }
872
946
  return [...common, 'vector', 'vector'];
873
947
  }
948
+ /** GLTF override rules take "" in any slot after the face to mean "no override", and read an unset one as "". */
949
+ const GLTF_RULES = new Set([PRIM_GLTF_BASE_COLOR, PRIM_GLTF_NORMAL, PRIM_GLTF_METALLIC_ROUGHNESS, PRIM_GLTF_EMISSIVE]);
874
950
  /** LSL types of the values `readPrimParam` returns for `param`. */
875
951
  export function primParamTypes(param, values) {
876
952
  const pattern = param === PRIM_TYPE ? primTypeTypes(values[0]) : (PRIM_PARAM_TYPES[param] ?? []);
877
- return values.map((_, i) => pattern[i % pattern.length]);
953
+ return values.map((v, i) => (GLTF_RULES.has(param) && v === '' ? 'string' : pattern[i % pattern.length]));
878
954
  }
879
955
  const PER_FACE_RULES = new Set([
880
956
  PRIM_COLOR, PRIM_TEXTURE, PRIM_RENDER_MATERIAL, PRIM_FULLBRIGHT, PRIM_GLOW, PRIM_BUMP_SHINY,
@@ -921,12 +997,12 @@ const formatG = (v) => String(Number(v.toPrecision(6)));
921
997
  * the rule can run, otherwise the error text that follows "rule #N" in the
922
998
  * simulator's message (e.g. " (PRIM_SIZE): arg #1 vector expected but string given").
923
999
  */
924
- /** GLTF override rules take "" in any slot after the face to mean "no override". */
925
- const GLTF_RULES = new Set([PRIM_GLTF_BASE_COLOR, PRIM_GLTF_NORMAL, PRIM_GLTF_METALLIC_ROUGHNESS, PRIM_GLTF_EMISSIVE]);
926
1000
  export function checkPrimRule(param, rules, cursor) {
927
1001
  const name = RULE_NAMES.get(param);
928
- if (name === undefined || writePrimParamSlots(param, rules, cursor) === null)
1002
+ if (name === undefined)
929
1003
  return ': unknown rule';
1004
+ if (writePrimParamSlots(param, rules, cursor) === null)
1005
+ return param === PRIM_TYPE ? null : ': unknown rule';
930
1006
  const pattern = param === PRIM_TYPE ? primTypeTypes(rules[cursor]) : PRIM_PARAM_TYPES[param];
931
1007
  const expected = PER_FACE_RULES.has(param) ? ['integer', ...pattern] : pattern;
932
1008
  for (let i = 0; i < expected.length; i++) {