@adatechnology/game-interactables-three 0.1.5 → 0.1.7

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/index.js CHANGED
@@ -697,6 +697,10 @@ function createCord(THREE, points, radius, material, radialSegments = 5) {
697
697
  return mesh;
698
698
  }
699
699
 
700
+ // src/mesh.ts
701
+ import { createShapeRecorder } from "@adatechnology/game-props3d";
702
+ import { colorChannels, generate } from "@adatechnology/game-shapes";
703
+
700
704
  // src/types.ts
701
705
  var MATERIAL_KEYS = ["plain", "metal", "wood", "glass", "fabric", "plant", "water", "emissive", "atlas_picture", "atlas_floor"];
702
706
 
@@ -1055,214 +1059,99 @@ function createDefaultMaterials(THREE, quality = "high") {
1055
1059
  atlas_floor: std({ roughness: 1 })
1056
1060
  };
1057
1061
  }
1058
- var IDENT_UV = { u0: 0, v0: 0, u1: 1, v1: 1 };
1062
+ var colorSpaces = /* @__PURE__ */ new WeakMap();
1063
+ function colorSpaceOf(THREE) {
1064
+ const known = colorSpaces.get(THREE.Color);
1065
+ if (known) return known;
1066
+ const space = detectColorSpace(THREE);
1067
+ colorSpaces.set(THREE.Color, space);
1068
+ return space;
1069
+ }
1070
+ function detectColorSpace(THREE) {
1071
+ const color = new THREE.Color();
1072
+ for (let c = 0; c < 256; c++) {
1073
+ color.setHex(c * 65793);
1074
+ if (color.r !== colorChannels(c * 65793, "linear")[0]) {
1075
+ if (color.r !== c / 255) throw new Error("createMeshApi: this three.js stores colours in a space game-shapes does not know");
1076
+ return "srgb";
1077
+ }
1078
+ }
1079
+ return "linear";
1080
+ }
1059
1081
  function createMeshApi(THREE, options = {}) {
1060
1082
  const quality = options.quality ?? "high";
1061
1083
  const shadows = options.shadows !== false;
1062
1084
  const materials = { ...createDefaultMaterials(THREE, quality), ...stripUndefined(options.materials) };
1063
1085
  if (options.doubleSidedPlanes) for (const k of MATERIAL_KEYS) materials[k].side = THREE.DoubleSide;
1064
- const root = { id: null, pivot: [0, 0, 0], ry: 0, acc: /* @__PURE__ */ new Map(), lights: [] };
1065
- const partTargets = /* @__PURE__ */ new Map();
1066
- const stack = [];
1067
- let current = root;
1068
1086
  let finished = false;
1069
- const templates = /* @__PURE__ */ new Map();
1070
- const template = (key, make) => {
1071
- let t = templates.get(key);
1072
- if (!t) {
1073
- const g = make();
1074
- t = {
1075
- pos: g.getAttribute("position").array,
1076
- nor: g.getAttribute("normal").array,
1077
- uv: g.getAttribute("uv").array,
1078
- idx: g.index ? g.index.array : Array.from({ length: g.getAttribute("position").count }, (_, i) => i)
1079
- };
1080
- templates.set(key, t);
1081
- }
1082
- return t;
1083
- };
1084
- const segs = quality === "high" ? { cyl: 12, sphereW: 12, sphereH: 8 } : { cyl: 8, sphereW: 8, sphereH: 6 };
1085
- const tBox = () => template("box", () => new THREE.BoxGeometry(1, 1, 1));
1086
- const tCyl = (shape) => {
1087
- switch (shape) {
1088
- case "cyl6":
1089
- return template("cyl6", () => new THREE.CylinderGeometry(1, 1, 1, 6));
1090
- case "cone":
1091
- return template("cone", () => new THREE.CylinderGeometry(0, 1, 1, segs.cyl));
1092
- case "sphere":
1093
- return template("sphere", () => new THREE.SphereGeometry(1, segs.sphereW, segs.sphereH));
1094
- default:
1095
- return template("cyl", () => new THREE.CylinderGeometry(1, 1, 1, segs.cyl));
1096
- }
1097
- };
1098
- const tPlane = () => template("plane", () => new THREE.PlaneGeometry(1, 1));
1099
- const tLeaf = () => {
1100
- let t = templates.get("leaf");
1101
- if (!t) {
1102
- const f = [0, 0, 0, -0.5, 0.4, 0, 0.5, 0.4, 0, 0, 1, 0];
1103
- const pos = [];
1104
- const nor = [];
1105
- const uv = [];
1106
- const idx = [];
1107
- for (const side of [1, -1]) {
1108
- const base = pos.length / 3;
1109
- for (let i = 0; i < 4; i++) {
1110
- pos.push(f[i * 3], f[i * 3 + 1], f[i * 3 + 2]);
1111
- nor.push(0, 0, side);
1112
- uv.push(f[i * 3] + 0.5, f[i * 3 + 1]);
1113
- }
1114
- if (side === 1) idx.push(base, base + 2, base + 1, base + 1, base + 2, base + 3);
1115
- else idx.push(base, base + 1, base + 2, base + 1, base + 3, base + 2);
1116
- }
1117
- t = { pos: new Float32Array(pos), nor: new Float32Array(nor), uv: new Float32Array(uv), idx };
1118
- templates.set("leaf", t);
1119
- }
1120
- return t;
1121
- };
1122
- const m4 = new THREE.Matrix4();
1123
- const mItem = new THREE.Matrix4();
1124
- const mTotal = new THREE.Matrix4();
1125
- const n3 = new THREE.Matrix3();
1126
- const v = new THREE.Vector3();
1127
- const quat = new THREE.Quaternion();
1128
- const euler = new THREE.Euler();
1129
- const scl = new THREE.Vector3();
1130
- const color = new THREE.Color();
1131
- const canonical = /* @__PURE__ */ new Map();
1087
+ const recorder = quality === "high" ? createShapeRecorder({ cylinderSegments: 12, sphereSegments: [12, 8] }) : createShapeRecorder({ cylinderSegments: 8, sphereSegments: [8, 6] });
1088
+ const materialAlias = {};
1132
1089
  for (const k of MATERIAL_KEYS) {
1133
1090
  const first = MATERIAL_KEYS.find((o) => materials[o] === materials[k]) ?? k;
1134
- canonical.set(k, first);
1091
+ if (first !== k) materialAlias[k] = first;
1135
1092
  }
1136
- const accOf = (requested) => {
1137
- const key = canonical.get(requested) ?? requested;
1138
- let a = current.acc.get(key);
1139
- if (!a) {
1140
- a = { pos: [], nor: [], col: [], uv: [], idx: [] };
1141
- current.acc.set(key, a);
1142
- }
1143
- return a;
1144
- };
1145
- const frame = (M) => {
1146
- mItem.makeRotationY(M?.ry ?? 0);
1147
- mItem.setPosition(M?.x ?? 0, 0, M?.z ?? 0);
1148
- return mItem;
1149
- };
1150
- const emit = (requested, M, local, t, hexColor, uv) => {
1151
- const key = requested in materials ? requested : "plain";
1152
- const a = accOf(key);
1153
- const total = mTotal.copy(frame(M)).multiply(local);
1154
- n3.getNormalMatrix(total);
1155
- const base = a.pos.length / 3;
1156
- color.setHex(hexColor);
1157
- const count = t.pos.length / 3;
1158
- const e = total.elements;
1159
- const nm = n3.elements;
1160
- const u = uv ?? IDENT_UV;
1161
- for (let i = 0; i < count; i++) {
1162
- const x = t.pos[i * 3];
1163
- const y = t.pos[i * 3 + 1];
1164
- const z = t.pos[i * 3 + 2];
1165
- a.pos.push(e[0] * x + e[4] * y + e[8] * z + e[12], e[1] * x + e[5] * y + e[9] * z + e[13], e[2] * x + e[6] * y + e[10] * z + e[14]);
1166
- const nx = t.nor[i * 3];
1167
- const ny = t.nor[i * 3 + 1];
1168
- const nz = t.nor[i * 3 + 2];
1169
- v.set(nm[0] * nx + nm[3] * ny + nm[6] * nz, nm[1] * nx + nm[4] * ny + nm[7] * nz, nm[2] * nx + nm[5] * ny + nm[8] * nz).normalize();
1170
- a.nor.push(v.x, v.y, v.z);
1171
- a.col.push(color.r, color.g, color.b);
1172
- a.uv.push(u.u0 + t.uv[i * 2] * (u.u1 - u.u0), u.v0 + t.uv[i * 2 + 1] * (u.v1 - u.v0));
1173
- }
1174
- for (let i = 0; i < t.idx.length; i++) a.idx.push(base + t.idx[i]);
1175
- };
1176
- const compose = (x, y, z, rx, ry, rz, sx, sy, sz) => {
1177
- euler.set(rx, ry, rz, "YXZ");
1178
- quat.setFromEuler(euler);
1179
- return m4.compose(v.set(x, y, z), quat, scl.set(sx, sy, sz));
1180
- };
1181
- const api = {
1182
- box(M, material, x, y, z, w, h, d, col, ry, rx, rz) {
1183
- emit(material, M, compose(x, y + h / 2, z, rx, ry, rz, w, h, d), tBox(), col);
1184
- },
1185
- cylinder(M, material, x, y, z, r, h, col, rx, rz, shape) {
1186
- const tilted = rx !== 0 || rz !== 0;
1187
- const cy = shape === "sphere" ? y + h / 2 : tilted ? y : y + h / 2;
1188
- const sy = shape === "sphere" ? h / 2 : h;
1189
- emit(material, M, compose(x, cy, z, rx, 0, rz, r, sy, r), tCyl(shape), col);
1190
- },
1191
- leaf(M, x, y, z, w, h, col, ry, rx) {
1192
- emit("plant", M, compose(x, y, z, rx, ry, 0, w, h, 1), tLeaf(), col);
1193
- },
1194
- plane(M, material, x, y, z, w, h, col, ry, rx, rz, uv) {
1195
- emit(material, M, compose(x, y, z, rx, ry, rz, w, h, 1), tPlane(), col, uv);
1196
- },
1197
- light(M, x, y, z, col, range) {
1198
- const fr = frame(M);
1199
- v.set(x, y, z).applyMatrix4(fr);
1200
- current.lights.push({ x: v.x, y: v.y, z: v.z, color: col, range });
1201
- },
1202
- part(id, pivot, ry, draw) {
1203
- let target = partTargets.get(id);
1204
- if (!target) {
1205
- target = { id, pivot: [pivot[0], pivot[1], pivot[2]], ry, acc: /* @__PURE__ */ new Map(), lights: [] };
1206
- partTargets.set(id, target);
1207
- }
1208
- stack.push(current);
1209
- current = target;
1210
- try {
1211
- draw();
1212
- } finally {
1213
- current = stack.pop() ?? root;
1214
- }
1215
- }
1216
- };
1217
- const build = (target, into, materialFor) => {
1218
- for (const [key, a] of target.acc) {
1219
- if (!a.idx.length) continue;
1093
+ const lightsOut = [];
1094
+ const build = (batches, points, id, into, materialFor) => {
1095
+ for (const batch of batches) {
1220
1096
  const g = new THREE.BufferGeometry();
1221
- g.setAttribute("position", new THREE.Float32BufferAttribute(a.pos, 3));
1222
- g.setAttribute("normal", new THREE.Float32BufferAttribute(a.nor, 3));
1223
- g.setAttribute("color", new THREE.Float32BufferAttribute(a.col, 3));
1224
- g.setAttribute("uv", new THREE.Float32BufferAttribute(a.uv, 2));
1225
- g.setIndex(a.pos.length / 3 > 65535 ? new THREE.Uint32BufferAttribute(a.idx, 1) : new THREE.Uint16BufferAttribute(a.idx, 1));
1097
+ g.setAttribute("position", new THREE.BufferAttribute(batch.positions, 3));
1098
+ g.setAttribute("normal", new THREE.BufferAttribute(batch.normals, 3));
1099
+ g.setAttribute("color", new THREE.BufferAttribute(batch.colors, 3));
1100
+ g.setAttribute("uv", new THREE.BufferAttribute(batch.uvs, 2));
1101
+ g.setIndex(new THREE.BufferAttribute(batch.indices, 1));
1226
1102
  g.computeBoundingSphere();
1227
1103
  g.computeBoundingBox();
1228
- const mesh = new THREE.Mesh(g, materialFor?.(key, target.id) ?? materials[key]);
1229
- mesh.name = target.id === null ? `static:${key}` : `part:${target.id}`;
1230
- if (target.id !== null) mesh.userData.part = target.id;
1104
+ const key = batch.material;
1105
+ const mesh = new THREE.Mesh(g, materialFor?.(key, id) ?? materials[key]);
1106
+ mesh.name = id === null ? `static:${key}` : `part:${id}`;
1107
+ if (id !== null) mesh.userData.part = id;
1231
1108
  mesh.userData.material = key;
1232
1109
  mesh.castShadow = shadows;
1233
1110
  mesh.receiveShadow = shadows;
1234
1111
  into.add(mesh);
1235
1112
  }
1236
- for (const l of target.lights) {
1113
+ for (const point of points) {
1114
+ const l = point.data;
1237
1115
  const light = new THREE.PointLight(l.color, 1, l.range);
1238
- light.position.set(l.x, l.y, l.z);
1116
+ light.position.set(point.position[0], point.position[1], point.position[2]);
1239
1117
  into.add(light);
1240
1118
  lightsOut.push(light);
1241
1119
  }
1242
1120
  };
1243
- const lightsOut = [];
1244
1121
  return {
1245
- api,
1122
+ api: recorder.api,
1246
1123
  materials,
1247
1124
  finish(spec, finishOptions) {
1248
1125
  const materialFor = finishOptions?.materialFor ?? options.materialFor;
1249
1126
  if (finished) throw new Error("createMeshApi: finish() was already called");
1250
1127
  finished = true;
1128
+ const buffers = generate(recorder.schema(), {
1129
+ colors: colorSpaceOf(THREE),
1130
+ materials: MATERIAL_KEYS,
1131
+ fallbackMaterial: "plain",
1132
+ materialAlias,
1133
+ validate: false,
1134
+ key: false
1135
+ });
1136
+ const batches = buffers.levels[0]?.batches ?? [];
1137
+ const of = (id) => ({ batches: batches.filter((b) => b.part === id), points: buffers.points.filter((p) => p.part === id) });
1251
1138
  const group = new THREE.Group();
1252
1139
  group.name = "interactable";
1253
1140
  const parts = {};
1254
- build(root, group, materialFor);
1255
- for (const [id, target] of partTargets) {
1141
+ const body = of(null);
1142
+ build(body.batches, body.points, null, group, materialFor);
1143
+ for (const part of recorder.parts) {
1256
1144
  const g = new THREE.Group();
1257
- g.name = `part:${id}`;
1258
- g.userData.part = id;
1259
- g.position.set(target.pivot[0], target.pivot[1], target.pivot[2]);
1260
- g.rotation.y = target.ry;
1145
+ g.name = `part:${part.id}`;
1146
+ g.userData.part = part.id;
1147
+ g.position.set(part.pivot[0], part.pivot[1], part.pivot[2]);
1148
+ g.rotation.y = part.ry;
1261
1149
  g.updateMatrix();
1262
- build(target, g, materialFor);
1150
+ const drawn = of(part.id);
1151
+ build(drawn.batches, drawn.points, part.id, g, materialFor);
1263
1152
  rememberRest(g);
1264
1153
  group.add(g);
1265
- parts[id] = g;
1154
+ parts[part.id] = g;
1266
1155
  }
1267
1156
  if (spec && (options.mergeParts ?? quality === "game")) mergeParts(THREE, group, parts, spec, { swayAmplitude: options.swayAmplitude });
1268
1157
  if (spec) tagDestruction(group, parts, spec);