@adatechnology/game-interactables-three 0.1.6 → 0.1.8
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 +3 -1
- package/dist/index.d.ts +3 -0
- package/dist/index.global.js +2337 -225
- package/dist/index.global.js.map +1 -1
- package/dist/index.js +62 -173
- package/dist/index.js.map +1 -1
- package/package.json +6 -4
package/dist/index.global.js
CHANGED
|
@@ -376,15 +376,15 @@ void main() {
|
|
|
376
376
|
const uv = new Float32Array(n * 2);
|
|
377
377
|
geo.setAttribute("uv", new THREE.BufferAttribute(uv, 2).setUsage(THREE.DynamicDrawUsage));
|
|
378
378
|
geo.setIndex(streamIndex);
|
|
379
|
-
const
|
|
380
|
-
|
|
381
|
-
|
|
382
|
-
|
|
383
|
-
group.add(
|
|
384
|
-
streams.push(
|
|
385
|
-
}
|
|
386
|
-
const writeStream = (
|
|
387
|
-
const geo =
|
|
379
|
+
const mesh2 = new THREE.Mesh(geo, streamMat);
|
|
380
|
+
mesh2.frustumCulled = false;
|
|
381
|
+
mesh2.visible = false;
|
|
382
|
+
mesh2.name = `effects:stream${i}`;
|
|
383
|
+
group.add(mesh2);
|
|
384
|
+
streams.push(mesh2);
|
|
385
|
+
}
|
|
386
|
+
const writeStream = (mesh2, k) => {
|
|
387
|
+
const geo = mesh2.geometry;
|
|
388
388
|
const pos = geo.getAttribute("position").array;
|
|
389
389
|
const col = geo.getAttribute("color").array;
|
|
390
390
|
const uv = geo.getAttribute("uv").array;
|
|
@@ -435,7 +435,7 @@ void main() {
|
|
|
435
435
|
geo.getAttribute("position").needsUpdate = true;
|
|
436
436
|
geo.getAttribute("color").needsUpdate = true;
|
|
437
437
|
geo.getAttribute("uv").needsUpdate = true;
|
|
438
|
-
|
|
438
|
+
mesh2.visible = true;
|
|
439
439
|
};
|
|
440
440
|
const CONES = 16;
|
|
441
441
|
const maxFlames = Math.max(0, Math.floor(options.maxFlames ?? 16));
|
|
@@ -733,10 +733,2237 @@ void main() {
|
|
|
733
733
|
if (points.length < 2) throw new Error("createCord needs at least two points");
|
|
734
734
|
const curve = new THREE.CatmullRomCurve3(points.map((p) => new THREE.Vector3(p[0], p[1], p[2])), false, "centripetal");
|
|
735
735
|
const segments = Math.max(4, (points.length - 1) * 4);
|
|
736
|
-
const
|
|
737
|
-
|
|
738
|
-
|
|
739
|
-
return
|
|
736
|
+
const mesh2 = new THREE.Mesh(new THREE.TubeGeometry(curve, segments, radius, radialSegments, false), material);
|
|
737
|
+
mesh2.name = "cord";
|
|
738
|
+
mesh2.castShadow = true;
|
|
739
|
+
return mesh2;
|
|
740
|
+
}
|
|
741
|
+
|
|
742
|
+
// ../game-shapes/dist/chunk-R4RY23RH.js
|
|
743
|
+
var __defProp2 = Object.defineProperty;
|
|
744
|
+
var __defNormalProp = (obj, key, value) => key in obj ? __defProp2(obj, key, { enumerable: true, configurable: true, writable: true, value }) : obj[key] = value;
|
|
745
|
+
var __publicField = (obj, key, value) => __defNormalProp(obj, typeof key !== "symbol" ? key + "" : key, value);
|
|
746
|
+
function colorHex(color2, fallback = 16777215) {
|
|
747
|
+
if (color2 === void 0) return fallback;
|
|
748
|
+
if (typeof color2 === "number") return Math.floor(color2);
|
|
749
|
+
const match = /^#([0-9a-fA-F]{6})$/.exec(color2);
|
|
750
|
+
if (!match) throw new Error(`game-shapes: colour "${color2}" is not 0xRRGGBB or #rrggbb`);
|
|
751
|
+
return parseInt(match[1], 16);
|
|
752
|
+
}
|
|
753
|
+
var srgbToLinear = (c) => c < 0.04045 ? c * 0.0773993808 : Math.pow(c * 0.9478672986 + 0.0521327014, 2.4);
|
|
754
|
+
function colorChannels(hex, space) {
|
|
755
|
+
const r = (hex >> 16 & 255) / 255;
|
|
756
|
+
const g = (hex >> 8 & 255) / 255;
|
|
757
|
+
const b = (hex & 255) / 255;
|
|
758
|
+
return space === "linear" ? [srgbToLinear(r), srgbToLinear(g), srgbToLinear(b)] : [r, g, b];
|
|
759
|
+
}
|
|
760
|
+
function canonicalJson(value) {
|
|
761
|
+
if (value === null) return "null";
|
|
762
|
+
if (typeof value === "number") return Object.is(value, -0) ? "-0" : Number.isFinite(value) ? String(value) : JSON.stringify(String(value));
|
|
763
|
+
if (typeof value === "string") return JSON.stringify(value);
|
|
764
|
+
if (typeof value === "boolean") return value ? "true" : "false";
|
|
765
|
+
if (typeof value === "function") throw new Error("game-shapes: a function cannot be part of a shape (register it as a generator)");
|
|
766
|
+
if (ArrayBuffer.isView(value)) return canonicalJson(Array.from(value));
|
|
767
|
+
if (Array.isArray(value)) return `[${value.map((item) => item === void 0 ? "null" : canonicalJson(item)).join(",")}]`;
|
|
768
|
+
if (typeof value === "object") {
|
|
769
|
+
const record = value;
|
|
770
|
+
const keys = Object.keys(record).filter((key) => record[key] !== void 0).sort();
|
|
771
|
+
return `{${keys.map((key) => `${JSON.stringify(key)}:${canonicalJson(record[key])}`).join(",")}}`;
|
|
772
|
+
}
|
|
773
|
+
return "null";
|
|
774
|
+
}
|
|
775
|
+
var lane = (text, offset, prime) => {
|
|
776
|
+
let hash = offset;
|
|
777
|
+
for (let index = 0; index < text.length; index++) {
|
|
778
|
+
hash ^= text.charCodeAt(index);
|
|
779
|
+
hash = Math.imul(hash, prime);
|
|
780
|
+
}
|
|
781
|
+
return (hash >>> 0).toString(16).padStart(8, "0");
|
|
782
|
+
};
|
|
783
|
+
var hash64 = (text) => lane(text, 2166136261, 16777619) + lane(text, 84696351, 1540483477);
|
|
784
|
+
var hashValue = (value) => hash64(canonicalJson(value));
|
|
785
|
+
var identity4 = () => [1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1, 0, 0, 0, 0, 1];
|
|
786
|
+
function quaternionFromEuler(x, y, z, order = "XYZ") {
|
|
787
|
+
const cos = Math.cos;
|
|
788
|
+
const sin = Math.sin;
|
|
789
|
+
const c1 = cos(x / 2);
|
|
790
|
+
const c2 = cos(y / 2);
|
|
791
|
+
const c3 = cos(z / 2);
|
|
792
|
+
const s1 = sin(x / 2);
|
|
793
|
+
const s2 = sin(y / 2);
|
|
794
|
+
const s3 = sin(z / 2);
|
|
795
|
+
switch (order) {
|
|
796
|
+
case "XYZ":
|
|
797
|
+
return [s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3];
|
|
798
|
+
case "YXZ":
|
|
799
|
+
return [s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3];
|
|
800
|
+
case "ZXY":
|
|
801
|
+
return [s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3];
|
|
802
|
+
case "ZYX":
|
|
803
|
+
return [s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3];
|
|
804
|
+
case "YZX":
|
|
805
|
+
return [s1 * c2 * c3 + c1 * s2 * s3, c1 * s2 * c3 + s1 * c2 * s3, c1 * c2 * s3 - s1 * s2 * c3, c1 * c2 * c3 - s1 * s2 * s3];
|
|
806
|
+
case "XZY":
|
|
807
|
+
return [s1 * c2 * c3 - c1 * s2 * s3, c1 * s2 * c3 - s1 * c2 * s3, c1 * c2 * s3 + s1 * s2 * c3, c1 * c2 * c3 + s1 * s2 * s3];
|
|
808
|
+
default:
|
|
809
|
+
throw new Error(`game-shapes: unknown rotation order "${String(order)}"`);
|
|
810
|
+
}
|
|
811
|
+
}
|
|
812
|
+
function compose(px, py, pz, q, sx, sy, sz) {
|
|
813
|
+
const [x, y, z, w] = q;
|
|
814
|
+
const x2 = x + x;
|
|
815
|
+
const y2 = y + y;
|
|
816
|
+
const z2 = z + z;
|
|
817
|
+
const xx = x * x2;
|
|
818
|
+
const xy = x * y2;
|
|
819
|
+
const xz = x * z2;
|
|
820
|
+
const yy = y * y2;
|
|
821
|
+
const yz = y * z2;
|
|
822
|
+
const zz = z * z2;
|
|
823
|
+
const wx = w * x2;
|
|
824
|
+
const wy = w * y2;
|
|
825
|
+
const wz = w * z2;
|
|
826
|
+
return [
|
|
827
|
+
(1 - (yy + zz)) * sx,
|
|
828
|
+
(xy + wz) * sx,
|
|
829
|
+
(xz - wy) * sx,
|
|
830
|
+
0,
|
|
831
|
+
(xy - wz) * sy,
|
|
832
|
+
(1 - (xx + zz)) * sy,
|
|
833
|
+
(yz + wx) * sy,
|
|
834
|
+
0,
|
|
835
|
+
(xz + wy) * sz,
|
|
836
|
+
(yz - wx) * sz,
|
|
837
|
+
(1 - (xx + yy)) * sz,
|
|
838
|
+
0,
|
|
839
|
+
px,
|
|
840
|
+
py,
|
|
841
|
+
pz,
|
|
842
|
+
1
|
|
843
|
+
];
|
|
844
|
+
}
|
|
845
|
+
function headingMatrix(theta, x = 0, y = 0, z = 0) {
|
|
846
|
+
const c = Math.cos(theta);
|
|
847
|
+
const s = Math.sin(theta);
|
|
848
|
+
return [c, 0, -s, 0, 0, 1, 0, 0, s, 0, c, 0, x, y, z, 1];
|
|
849
|
+
}
|
|
850
|
+
function multiply(ae, be) {
|
|
851
|
+
const a11 = ae[0], a12 = ae[4], a13 = ae[8], a14 = ae[12];
|
|
852
|
+
const a21 = ae[1], a22 = ae[5], a23 = ae[9], a24 = ae[13];
|
|
853
|
+
const a31 = ae[2], a32 = ae[6], a33 = ae[10], a34 = ae[14];
|
|
854
|
+
const a41 = ae[3], a42 = ae[7], a43 = ae[11], a44 = ae[15];
|
|
855
|
+
const b11 = be[0], b12 = be[4], b13 = be[8], b14 = be[12];
|
|
856
|
+
const b21 = be[1], b22 = be[5], b23 = be[9], b24 = be[13];
|
|
857
|
+
const b31 = be[2], b32 = be[6], b33 = be[10], b34 = be[14];
|
|
858
|
+
const b41 = be[3], b42 = be[7], b43 = be[11], b44 = be[15];
|
|
859
|
+
return [
|
|
860
|
+
a11 * b11 + a12 * b21 + a13 * b31 + a14 * b41,
|
|
861
|
+
a21 * b11 + a22 * b21 + a23 * b31 + a24 * b41,
|
|
862
|
+
a31 * b11 + a32 * b21 + a33 * b31 + a34 * b41,
|
|
863
|
+
a41 * b11 + a42 * b21 + a43 * b31 + a44 * b41,
|
|
864
|
+
a11 * b12 + a12 * b22 + a13 * b32 + a14 * b42,
|
|
865
|
+
a21 * b12 + a22 * b22 + a23 * b32 + a24 * b42,
|
|
866
|
+
a31 * b12 + a32 * b22 + a33 * b32 + a34 * b42,
|
|
867
|
+
a41 * b12 + a42 * b22 + a43 * b32 + a44 * b42,
|
|
868
|
+
a11 * b13 + a12 * b23 + a13 * b33 + a14 * b43,
|
|
869
|
+
a21 * b13 + a22 * b23 + a23 * b33 + a24 * b43,
|
|
870
|
+
a31 * b13 + a32 * b23 + a33 * b33 + a34 * b43,
|
|
871
|
+
a41 * b13 + a42 * b23 + a43 * b33 + a44 * b43,
|
|
872
|
+
a11 * b14 + a12 * b24 + a13 * b34 + a14 * b44,
|
|
873
|
+
a21 * b14 + a22 * b24 + a23 * b34 + a24 * b44,
|
|
874
|
+
a31 * b14 + a32 * b24 + a33 * b34 + a34 * b44,
|
|
875
|
+
a41 * b14 + a42 * b24 + a43 * b34 + a44 * b44
|
|
876
|
+
];
|
|
877
|
+
}
|
|
878
|
+
function normalMatrix(me) {
|
|
879
|
+
const n11 = me[0], n21 = me[1], n31 = me[2];
|
|
880
|
+
const n12 = me[4], n22 = me[5], n32 = me[6];
|
|
881
|
+
const n13 = me[8], n23 = me[9], n33 = me[10];
|
|
882
|
+
const t11 = n33 * n22 - n32 * n23;
|
|
883
|
+
const t12 = n32 * n13 - n33 * n12;
|
|
884
|
+
const t13 = n23 * n12 - n22 * n13;
|
|
885
|
+
const det = n11 * t11 + n21 * t12 + n31 * t13;
|
|
886
|
+
if (det === 0) return [0, 0, 0, 0, 0, 0, 0, 0, 0];
|
|
887
|
+
const detInv = 1 / det;
|
|
888
|
+
const te = [
|
|
889
|
+
t11 * detInv,
|
|
890
|
+
(n31 * n23 - n33 * n21) * detInv,
|
|
891
|
+
(n32 * n21 - n31 * n22) * detInv,
|
|
892
|
+
t12 * detInv,
|
|
893
|
+
(n33 * n11 - n31 * n13) * detInv,
|
|
894
|
+
(n31 * n12 - n32 * n11) * detInv,
|
|
895
|
+
t13 * detInv,
|
|
896
|
+
(n21 * n13 - n23 * n11) * detInv,
|
|
897
|
+
(n22 * n11 - n21 * n12) * detInv
|
|
898
|
+
];
|
|
899
|
+
let tmp = te[1];
|
|
900
|
+
te[1] = te[3];
|
|
901
|
+
te[3] = tmp;
|
|
902
|
+
tmp = te[2];
|
|
903
|
+
te[2] = te[6];
|
|
904
|
+
te[6] = tmp;
|
|
905
|
+
tmp = te[5];
|
|
906
|
+
te[5] = te[7];
|
|
907
|
+
te[7] = tmp;
|
|
908
|
+
return te;
|
|
909
|
+
}
|
|
910
|
+
var isAffine = (e) => e[3] === 0 && e[7] === 0 && e[11] === 0 && e[15] === 1;
|
|
911
|
+
function transformPoint(e, x, y, z) {
|
|
912
|
+
const w = 1 / (e[3] * x + e[7] * y + e[11] * z + e[15]);
|
|
913
|
+
return [(e[0] * x + e[4] * y + e[8] * z + e[12]) * w, (e[1] * x + e[5] * y + e[9] * z + e[13]) * w, (e[2] * x + e[6] * y + e[10] * z + e[14]) * w];
|
|
914
|
+
}
|
|
915
|
+
var toVec3 = (scale) => scale === void 0 ? [1, 1, 1] : typeof scale === "number" ? [scale, scale, scale] : scale;
|
|
916
|
+
function matrixOf(t) {
|
|
917
|
+
if (t.matrix !== void 0) return [...t.matrix];
|
|
918
|
+
if (t.heading !== void 0) {
|
|
919
|
+
const p2 = t.position ?? [0, 0, 0];
|
|
920
|
+
return headingMatrix(t.heading, p2[0], p2[1], p2[2]);
|
|
921
|
+
}
|
|
922
|
+
if (t.position === void 0 && t.rotation === void 0 && t.scale === void 0) return null;
|
|
923
|
+
const p = t.position ?? [0, 0, 0];
|
|
924
|
+
const r = t.rotation ?? [0, 0, 0];
|
|
925
|
+
const s = toVec3(t.scale);
|
|
926
|
+
return compose(p[0], p[1], p[2], quaternionFromEuler(r[0], r[1], r[2], t.rotationOrder ?? "XYZ"), s[0], s[1], s[2]);
|
|
927
|
+
}
|
|
928
|
+
var asMat4 = (m) => m ? [...m] : identity4();
|
|
929
|
+
function needsUint32(array) {
|
|
930
|
+
for (let i = array.length - 1; i >= 0; --i) if (array[i] >= 65535) return true;
|
|
931
|
+
return false;
|
|
932
|
+
}
|
|
933
|
+
var indexArray = (indices) => needsUint32(indices) ? new Uint32Array(indices) : new Uint16Array(indices);
|
|
934
|
+
function pack(type, parameters, a) {
|
|
935
|
+
return {
|
|
936
|
+
type,
|
|
937
|
+
parameters,
|
|
938
|
+
positions: new Float32Array(a.vertices),
|
|
939
|
+
normals: new Float32Array(a.normals),
|
|
940
|
+
uvs: new Float32Array(a.uvs),
|
|
941
|
+
...a.indices ? { indices: indexArray(a.indices) } : {},
|
|
942
|
+
groups: a.groups
|
|
943
|
+
};
|
|
944
|
+
}
|
|
945
|
+
var normalize3 = (x, y, z) => {
|
|
946
|
+
const length = Math.sqrt(x * x + y * y + z * z);
|
|
947
|
+
const s = 1 / (length || 1);
|
|
948
|
+
return [x * s, y * s, z * s];
|
|
949
|
+
};
|
|
950
|
+
function box(width = 1, height = 1, depth = 1, widthSegments = 1, heightSegments = 1, depthSegments = 1) {
|
|
951
|
+
const parameters = { width, height, depth, widthSegments, heightSegments, depthSegments };
|
|
952
|
+
widthSegments = Math.floor(widthSegments);
|
|
953
|
+
heightSegments = Math.floor(heightSegments);
|
|
954
|
+
depthSegments = Math.floor(depthSegments);
|
|
955
|
+
const indices = [];
|
|
956
|
+
const vertices = [];
|
|
957
|
+
const normals = [];
|
|
958
|
+
const uvs = [];
|
|
959
|
+
const groups = [];
|
|
960
|
+
let numberOfVertices = 0;
|
|
961
|
+
let groupStart = 0;
|
|
962
|
+
const buildPlane = (u, v, w, udir, vdir, width2, height2, depth2, gridX, gridY, materialIndex) => {
|
|
963
|
+
const segmentWidth = width2 / gridX;
|
|
964
|
+
const segmentHeight = height2 / gridY;
|
|
965
|
+
const widthHalf = width2 / 2;
|
|
966
|
+
const heightHalf = height2 / 2;
|
|
967
|
+
const depthHalf = depth2 / 2;
|
|
968
|
+
const gridX1 = gridX + 1;
|
|
969
|
+
const gridY1 = gridY + 1;
|
|
970
|
+
let vertexCounter = 0;
|
|
971
|
+
let groupCount = 0;
|
|
972
|
+
const vector = { x: 0, y: 0, z: 0 };
|
|
973
|
+
for (let iy = 0; iy < gridY1; iy++) {
|
|
974
|
+
const y = iy * segmentHeight - heightHalf;
|
|
975
|
+
for (let ix = 0; ix < gridX1; ix++) {
|
|
976
|
+
const x = ix * segmentWidth - widthHalf;
|
|
977
|
+
vector[u] = x * udir;
|
|
978
|
+
vector[v] = y * vdir;
|
|
979
|
+
vector[w] = depthHalf;
|
|
980
|
+
vertices.push(vector.x, vector.y, vector.z);
|
|
981
|
+
vector[u] = 0;
|
|
982
|
+
vector[v] = 0;
|
|
983
|
+
vector[w] = depth2 > 0 ? 1 : -1;
|
|
984
|
+
normals.push(vector.x, vector.y, vector.z);
|
|
985
|
+
uvs.push(ix / gridX);
|
|
986
|
+
uvs.push(1 - iy / gridY);
|
|
987
|
+
vertexCounter += 1;
|
|
988
|
+
}
|
|
989
|
+
}
|
|
990
|
+
for (let iy = 0; iy < gridY; iy++) {
|
|
991
|
+
for (let ix = 0; ix < gridX; ix++) {
|
|
992
|
+
const a = numberOfVertices + ix + gridX1 * iy;
|
|
993
|
+
const b = numberOfVertices + ix + gridX1 * (iy + 1);
|
|
994
|
+
const c = numberOfVertices + (ix + 1) + gridX1 * (iy + 1);
|
|
995
|
+
const d = numberOfVertices + (ix + 1) + gridX1 * iy;
|
|
996
|
+
indices.push(a, b, d);
|
|
997
|
+
indices.push(b, c, d);
|
|
998
|
+
groupCount += 6;
|
|
999
|
+
}
|
|
1000
|
+
}
|
|
1001
|
+
groups.push({ start: groupStart, count: groupCount, materialIndex });
|
|
1002
|
+
groupStart += groupCount;
|
|
1003
|
+
numberOfVertices += vertexCounter;
|
|
1004
|
+
};
|
|
1005
|
+
buildPlane("z", "y", "x", -1, -1, depth, height, width, depthSegments, heightSegments, 0);
|
|
1006
|
+
buildPlane("z", "y", "x", 1, -1, depth, height, -width, depthSegments, heightSegments, 1);
|
|
1007
|
+
buildPlane("x", "z", "y", 1, 1, width, depth, height, widthSegments, depthSegments, 2);
|
|
1008
|
+
buildPlane("x", "z", "y", 1, -1, width, depth, -height, widthSegments, depthSegments, 3);
|
|
1009
|
+
buildPlane("x", "y", "z", 1, -1, width, height, depth, widthSegments, heightSegments, 4);
|
|
1010
|
+
buildPlane("x", "y", "z", -1, -1, width, height, -depth, widthSegments, heightSegments, 5);
|
|
1011
|
+
return pack("BoxGeometry", parameters, { vertices, normals, uvs, indices, groups });
|
|
1012
|
+
}
|
|
1013
|
+
function cylinderArrays(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength) {
|
|
1014
|
+
radialSegments = Math.floor(radialSegments);
|
|
1015
|
+
heightSegments = Math.floor(heightSegments);
|
|
1016
|
+
const indices = [];
|
|
1017
|
+
const vertices = [];
|
|
1018
|
+
const normals = [];
|
|
1019
|
+
const uvs = [];
|
|
1020
|
+
const groups = [];
|
|
1021
|
+
let index = 0;
|
|
1022
|
+
const indexArray2 = [];
|
|
1023
|
+
const halfHeight = height / 2;
|
|
1024
|
+
let groupStart = 0;
|
|
1025
|
+
const generateTorso = () => {
|
|
1026
|
+
let groupCount = 0;
|
|
1027
|
+
const slope = (radiusBottom - radiusTop) / height;
|
|
1028
|
+
for (let y = 0; y <= heightSegments; y++) {
|
|
1029
|
+
const indexRow = [];
|
|
1030
|
+
const v = y / heightSegments;
|
|
1031
|
+
const radius = v * (radiusBottom - radiusTop) + radiusTop;
|
|
1032
|
+
for (let x = 0; x <= radialSegments; x++) {
|
|
1033
|
+
const u = x / radialSegments;
|
|
1034
|
+
const theta = u * thetaLength + thetaStart;
|
|
1035
|
+
const sinTheta = Math.sin(theta);
|
|
1036
|
+
const cosTheta = Math.cos(theta);
|
|
1037
|
+
vertices.push(radius * sinTheta, -v * height + halfHeight, radius * cosTheta);
|
|
1038
|
+
const n = normalize3(sinTheta, slope, cosTheta);
|
|
1039
|
+
normals.push(n[0], n[1], n[2]);
|
|
1040
|
+
uvs.push(u, 1 - v);
|
|
1041
|
+
indexRow.push(index++);
|
|
1042
|
+
}
|
|
1043
|
+
indexArray2.push(indexRow);
|
|
1044
|
+
}
|
|
1045
|
+
for (let x = 0; x < radialSegments; x++) {
|
|
1046
|
+
for (let y = 0; y < heightSegments; y++) {
|
|
1047
|
+
const a = indexArray2[y][x];
|
|
1048
|
+
const b = indexArray2[y + 1][x];
|
|
1049
|
+
const c = indexArray2[y + 1][x + 1];
|
|
1050
|
+
const d = indexArray2[y][x + 1];
|
|
1051
|
+
indices.push(a, b, d);
|
|
1052
|
+
indices.push(b, c, d);
|
|
1053
|
+
groupCount += 6;
|
|
1054
|
+
}
|
|
1055
|
+
}
|
|
1056
|
+
groups.push({ start: groupStart, count: groupCount, materialIndex: 0 });
|
|
1057
|
+
groupStart += groupCount;
|
|
1058
|
+
};
|
|
1059
|
+
const generateCap = (top) => {
|
|
1060
|
+
const centerIndexStart = index;
|
|
1061
|
+
let groupCount = 0;
|
|
1062
|
+
const radius = top === true ? radiusTop : radiusBottom;
|
|
1063
|
+
const sign2 = top === true ? 1 : -1;
|
|
1064
|
+
for (let x = 1; x <= radialSegments; x++) {
|
|
1065
|
+
vertices.push(0, halfHeight * sign2, 0);
|
|
1066
|
+
normals.push(0, sign2, 0);
|
|
1067
|
+
uvs.push(0.5, 0.5);
|
|
1068
|
+
index++;
|
|
1069
|
+
}
|
|
1070
|
+
const centerIndexEnd = index;
|
|
1071
|
+
for (let x = 0; x <= radialSegments; x++) {
|
|
1072
|
+
const u = x / radialSegments;
|
|
1073
|
+
const theta = u * thetaLength + thetaStart;
|
|
1074
|
+
const cosTheta = Math.cos(theta);
|
|
1075
|
+
const sinTheta = Math.sin(theta);
|
|
1076
|
+
vertices.push(radius * sinTheta, halfHeight * sign2, radius * cosTheta);
|
|
1077
|
+
normals.push(0, sign2, 0);
|
|
1078
|
+
uvs.push(cosTheta * 0.5 + 0.5, sinTheta * 0.5 * sign2 + 0.5);
|
|
1079
|
+
index++;
|
|
1080
|
+
}
|
|
1081
|
+
for (let x = 0; x < radialSegments; x++) {
|
|
1082
|
+
const c = centerIndexStart + x;
|
|
1083
|
+
const i = centerIndexEnd + x;
|
|
1084
|
+
if (top === true) indices.push(i, i + 1, c);
|
|
1085
|
+
else indices.push(i + 1, i, c);
|
|
1086
|
+
groupCount += 3;
|
|
1087
|
+
}
|
|
1088
|
+
groups.push({ start: groupStart, count: groupCount, materialIndex: top === true ? 1 : 2 });
|
|
1089
|
+
groupStart += groupCount;
|
|
1090
|
+
};
|
|
1091
|
+
generateTorso();
|
|
1092
|
+
if (openEnded === false) {
|
|
1093
|
+
if (radiusTop > 0) generateCap(true);
|
|
1094
|
+
if (radiusBottom > 0) generateCap(false);
|
|
1095
|
+
}
|
|
1096
|
+
return { vertices, normals, uvs, indices, groups };
|
|
1097
|
+
}
|
|
1098
|
+
function cylinder(radiusTop = 1, radiusBottom = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2) {
|
|
1099
|
+
const parameters = { radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength };
|
|
1100
|
+
return pack("CylinderGeometry", parameters, cylinderArrays(radiusTop, radiusBottom, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength));
|
|
1101
|
+
}
|
|
1102
|
+
function cone(radius = 1, height = 1, radialSegments = 32, heightSegments = 1, openEnded = false, thetaStart = 0, thetaLength = Math.PI * 2) {
|
|
1103
|
+
const parameters = { radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength };
|
|
1104
|
+
return pack("ConeGeometry", parameters, cylinderArrays(0, radius, height, radialSegments, heightSegments, openEnded, thetaStart, thetaLength));
|
|
1105
|
+
}
|
|
1106
|
+
function sphere(radius = 1, widthSegments = 32, heightSegments = 16, phiStart = 0, phiLength = Math.PI * 2, thetaStart = 0, thetaLength = Math.PI) {
|
|
1107
|
+
const parameters = { radius, widthSegments, heightSegments, phiStart, phiLength, thetaStart, thetaLength };
|
|
1108
|
+
widthSegments = Math.max(3, Math.floor(widthSegments));
|
|
1109
|
+
heightSegments = Math.max(2, Math.floor(heightSegments));
|
|
1110
|
+
const thetaEnd = Math.min(thetaStart + thetaLength, Math.PI);
|
|
1111
|
+
let index = 0;
|
|
1112
|
+
const grid = [];
|
|
1113
|
+
const indices = [];
|
|
1114
|
+
const vertices = [];
|
|
1115
|
+
const normals = [];
|
|
1116
|
+
const uvs = [];
|
|
1117
|
+
for (let iy = 0; iy <= heightSegments; iy++) {
|
|
1118
|
+
const verticesRow = [];
|
|
1119
|
+
const v = iy / heightSegments;
|
|
1120
|
+
let uOffset = 0;
|
|
1121
|
+
if (iy === 0 && thetaStart === 0) uOffset = 0.5 / widthSegments;
|
|
1122
|
+
else if (iy === heightSegments && thetaEnd === Math.PI) uOffset = -0.5 / widthSegments;
|
|
1123
|
+
for (let ix = 0; ix <= widthSegments; ix++) {
|
|
1124
|
+
const u = ix / widthSegments;
|
|
1125
|
+
const x = -radius * Math.cos(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength);
|
|
1126
|
+
const y = radius * Math.cos(thetaStart + v * thetaLength);
|
|
1127
|
+
const z = radius * Math.sin(phiStart + u * phiLength) * Math.sin(thetaStart + v * thetaLength);
|
|
1128
|
+
vertices.push(x, y, z);
|
|
1129
|
+
const n = normalize3(x, y, z);
|
|
1130
|
+
normals.push(n[0], n[1], n[2]);
|
|
1131
|
+
uvs.push(u + uOffset, 1 - v);
|
|
1132
|
+
verticesRow.push(index++);
|
|
1133
|
+
}
|
|
1134
|
+
grid.push(verticesRow);
|
|
1135
|
+
}
|
|
1136
|
+
for (let iy = 0; iy < heightSegments; iy++) {
|
|
1137
|
+
for (let ix = 0; ix < widthSegments; ix++) {
|
|
1138
|
+
const a = grid[iy][ix + 1];
|
|
1139
|
+
const b = grid[iy][ix];
|
|
1140
|
+
const c = grid[iy + 1][ix];
|
|
1141
|
+
const d = grid[iy + 1][ix + 1];
|
|
1142
|
+
if (iy !== 0 || thetaStart > 0) indices.push(a, b, d);
|
|
1143
|
+
if (iy !== heightSegments - 1 || thetaEnd < Math.PI) indices.push(b, c, d);
|
|
1144
|
+
}
|
|
1145
|
+
}
|
|
1146
|
+
return pack("SphereGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
|
|
1147
|
+
}
|
|
1148
|
+
function plane(width = 1, height = 1, widthSegments = 1, heightSegments = 1) {
|
|
1149
|
+
const parameters = { width, height, widthSegments, heightSegments };
|
|
1150
|
+
const widthHalf = width / 2;
|
|
1151
|
+
const heightHalf = height / 2;
|
|
1152
|
+
const gridX = Math.floor(widthSegments);
|
|
1153
|
+
const gridY = Math.floor(heightSegments);
|
|
1154
|
+
const gridX1 = gridX + 1;
|
|
1155
|
+
const gridY1 = gridY + 1;
|
|
1156
|
+
const segmentWidth = width / gridX;
|
|
1157
|
+
const segmentHeight = height / gridY;
|
|
1158
|
+
const indices = [];
|
|
1159
|
+
const vertices = [];
|
|
1160
|
+
const normals = [];
|
|
1161
|
+
const uvs = [];
|
|
1162
|
+
for (let iy = 0; iy < gridY1; iy++) {
|
|
1163
|
+
const y = iy * segmentHeight - heightHalf;
|
|
1164
|
+
for (let ix = 0; ix < gridX1; ix++) {
|
|
1165
|
+
const x = ix * segmentWidth - widthHalf;
|
|
1166
|
+
vertices.push(x, -y, 0);
|
|
1167
|
+
normals.push(0, 0, 1);
|
|
1168
|
+
uvs.push(ix / gridX);
|
|
1169
|
+
uvs.push(1 - iy / gridY);
|
|
1170
|
+
}
|
|
1171
|
+
}
|
|
1172
|
+
for (let iy = 0; iy < gridY; iy++) {
|
|
1173
|
+
for (let ix = 0; ix < gridX; ix++) {
|
|
1174
|
+
const a = ix + gridX1 * iy;
|
|
1175
|
+
const b = ix + gridX1 * (iy + 1);
|
|
1176
|
+
const c = ix + 1 + gridX1 * (iy + 1);
|
|
1177
|
+
const d = ix + 1 + gridX1 * iy;
|
|
1178
|
+
indices.push(a, b, d);
|
|
1179
|
+
indices.push(b, c, d);
|
|
1180
|
+
}
|
|
1181
|
+
}
|
|
1182
|
+
return pack("PlaneGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
|
|
1183
|
+
}
|
|
1184
|
+
function circle(radius = 1, segments = 32, thetaStart = 0, thetaLength = Math.PI * 2) {
|
|
1185
|
+
const parameters = { radius, segments, thetaStart, thetaLength };
|
|
1186
|
+
segments = Math.max(3, segments);
|
|
1187
|
+
const indices = [];
|
|
1188
|
+
const vertices = [];
|
|
1189
|
+
const normals = [];
|
|
1190
|
+
const uvs = [];
|
|
1191
|
+
vertices.push(0, 0, 0);
|
|
1192
|
+
normals.push(0, 0, 1);
|
|
1193
|
+
uvs.push(0.5, 0.5);
|
|
1194
|
+
for (let s = 0, i = 3; s <= segments; s++, i += 3) {
|
|
1195
|
+
const segment = thetaStart + s / segments * thetaLength;
|
|
1196
|
+
vertices.push(radius * Math.cos(segment), radius * Math.sin(segment), 0);
|
|
1197
|
+
normals.push(0, 0, 1);
|
|
1198
|
+
uvs.push((vertices[i] / radius + 1) / 2, (vertices[i + 1] / radius + 1) / 2);
|
|
1199
|
+
}
|
|
1200
|
+
for (let i = 1; i <= segments; i++) indices.push(i, i + 1, 0);
|
|
1201
|
+
return pack("CircleGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
|
|
1202
|
+
}
|
|
1203
|
+
function ring(innerRadius = 0.5, outerRadius = 1, thetaSegments = 32, phiSegments = 1, thetaStart = 0, thetaLength = Math.PI * 2) {
|
|
1204
|
+
const parameters = { innerRadius, outerRadius, thetaSegments, phiSegments, thetaStart, thetaLength };
|
|
1205
|
+
thetaSegments = Math.max(3, thetaSegments);
|
|
1206
|
+
phiSegments = Math.max(1, phiSegments);
|
|
1207
|
+
const indices = [];
|
|
1208
|
+
const vertices = [];
|
|
1209
|
+
const normals = [];
|
|
1210
|
+
const uvs = [];
|
|
1211
|
+
let radius = innerRadius;
|
|
1212
|
+
const radiusStep = (outerRadius - innerRadius) / phiSegments;
|
|
1213
|
+
for (let j = 0; j <= phiSegments; j++) {
|
|
1214
|
+
for (let i = 0; i <= thetaSegments; i++) {
|
|
1215
|
+
const segment = thetaStart + i / thetaSegments * thetaLength;
|
|
1216
|
+
const x = radius * Math.cos(segment);
|
|
1217
|
+
const y = radius * Math.sin(segment);
|
|
1218
|
+
vertices.push(x, y, 0);
|
|
1219
|
+
normals.push(0, 0, 1);
|
|
1220
|
+
uvs.push((x / outerRadius + 1) / 2, (y / outerRadius + 1) / 2);
|
|
1221
|
+
}
|
|
1222
|
+
radius += radiusStep;
|
|
1223
|
+
}
|
|
1224
|
+
for (let j = 0; j < phiSegments; j++) {
|
|
1225
|
+
const thetaSegmentLevel = j * (thetaSegments + 1);
|
|
1226
|
+
for (let i = 0; i < thetaSegments; i++) {
|
|
1227
|
+
const segment = i + thetaSegmentLevel;
|
|
1228
|
+
const a = segment;
|
|
1229
|
+
const b = segment + thetaSegments + 1;
|
|
1230
|
+
const c = segment + thetaSegments + 2;
|
|
1231
|
+
const d = segment + 1;
|
|
1232
|
+
indices.push(a, b, d);
|
|
1233
|
+
indices.push(b, c, d);
|
|
1234
|
+
}
|
|
1235
|
+
}
|
|
1236
|
+
return pack("RingGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
|
|
1237
|
+
}
|
|
1238
|
+
function lathe(points, segments = 12, phiStart = 0, phiLength = Math.PI * 2) {
|
|
1239
|
+
const parameters = { points: points.map(([x, y]) => ({ x, y })), segments, phiStart, phiLength };
|
|
1240
|
+
segments = Math.floor(segments);
|
|
1241
|
+
phiLength = Math.max(0, Math.min(Math.PI * 2, phiLength));
|
|
1242
|
+
const indices = [];
|
|
1243
|
+
const vertices = [];
|
|
1244
|
+
const uvs = [];
|
|
1245
|
+
const initNormals = [];
|
|
1246
|
+
const normals = [];
|
|
1247
|
+
const inverseSegments = 1 / segments;
|
|
1248
|
+
const normal = { x: 0, y: 0, z: 0 };
|
|
1249
|
+
const curNormal = { x: 0, y: 0, z: 0 };
|
|
1250
|
+
const prevNormal = { x: 0, y: 0, z: 0 };
|
|
1251
|
+
let dx = 0;
|
|
1252
|
+
let dy = 0;
|
|
1253
|
+
const last = points.length - 1;
|
|
1254
|
+
const px = (j) => points[j][0];
|
|
1255
|
+
const py = (j) => points[j][1];
|
|
1256
|
+
const setNormalized = () => {
|
|
1257
|
+
const n = normalize3(normal.x, normal.y, normal.z);
|
|
1258
|
+
normal.x = n[0];
|
|
1259
|
+
normal.y = n[1];
|
|
1260
|
+
normal.z = n[2];
|
|
1261
|
+
};
|
|
1262
|
+
for (let j = 0; j <= last; j++) {
|
|
1263
|
+
if (j === 0) {
|
|
1264
|
+
dx = px(j + 1) - px(j);
|
|
1265
|
+
dy = py(j + 1) - py(j);
|
|
1266
|
+
normal.x = dy * 1;
|
|
1267
|
+
normal.y = -dx;
|
|
1268
|
+
normal.z = dy * 0;
|
|
1269
|
+
prevNormal.x = normal.x;
|
|
1270
|
+
prevNormal.y = normal.y;
|
|
1271
|
+
prevNormal.z = normal.z;
|
|
1272
|
+
setNormalized();
|
|
1273
|
+
initNormals.push(normal.x, normal.y, normal.z);
|
|
1274
|
+
} else if (j === last) {
|
|
1275
|
+
initNormals.push(prevNormal.x, prevNormal.y, prevNormal.z);
|
|
1276
|
+
} else {
|
|
1277
|
+
dx = px(j + 1) - px(j);
|
|
1278
|
+
dy = py(j + 1) - py(j);
|
|
1279
|
+
normal.x = dy * 1;
|
|
1280
|
+
normal.y = -dx;
|
|
1281
|
+
normal.z = dy * 0;
|
|
1282
|
+
curNormal.x = normal.x;
|
|
1283
|
+
curNormal.y = normal.y;
|
|
1284
|
+
curNormal.z = normal.z;
|
|
1285
|
+
normal.x += prevNormal.x;
|
|
1286
|
+
normal.y += prevNormal.y;
|
|
1287
|
+
normal.z += prevNormal.z;
|
|
1288
|
+
setNormalized();
|
|
1289
|
+
initNormals.push(normal.x, normal.y, normal.z);
|
|
1290
|
+
prevNormal.x = curNormal.x;
|
|
1291
|
+
prevNormal.y = curNormal.y;
|
|
1292
|
+
prevNormal.z = curNormal.z;
|
|
1293
|
+
}
|
|
1294
|
+
}
|
|
1295
|
+
for (let i = 0; i <= segments; i++) {
|
|
1296
|
+
const phi = phiStart + i * inverseSegments * phiLength;
|
|
1297
|
+
const sin = Math.sin(phi);
|
|
1298
|
+
const cos = Math.cos(phi);
|
|
1299
|
+
for (let j = 0; j <= last; j++) {
|
|
1300
|
+
vertices.push(px(j) * sin, py(j), px(j) * cos);
|
|
1301
|
+
uvs.push(i / segments, j / (points.length - 1));
|
|
1302
|
+
const x = initNormals[3 * j + 0] * sin;
|
|
1303
|
+
const y = initNormals[3 * j + 1];
|
|
1304
|
+
const z = initNormals[3 * j + 0] * cos;
|
|
1305
|
+
normals.push(x, y, z);
|
|
1306
|
+
}
|
|
1307
|
+
}
|
|
1308
|
+
for (let i = 0; i < segments; i++) {
|
|
1309
|
+
for (let j = 0; j < points.length - 1; j++) {
|
|
1310
|
+
const base = j + i * points.length;
|
|
1311
|
+
const a = base;
|
|
1312
|
+
const b = base + points.length;
|
|
1313
|
+
const c = base + points.length + 1;
|
|
1314
|
+
const d = base + 1;
|
|
1315
|
+
indices.push(a, b, d);
|
|
1316
|
+
indices.push(c, d, b);
|
|
1317
|
+
}
|
|
1318
|
+
}
|
|
1319
|
+
return pack("LatheGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
|
|
1320
|
+
}
|
|
1321
|
+
function torus(radius = 1, tube = 0.4, radialSegments = 12, tubularSegments = 48, arc = Math.PI * 2) {
|
|
1322
|
+
const parameters = { radius, tube, radialSegments, tubularSegments, arc };
|
|
1323
|
+
radialSegments = Math.floor(radialSegments);
|
|
1324
|
+
tubularSegments = Math.floor(tubularSegments);
|
|
1325
|
+
const indices = [];
|
|
1326
|
+
const vertices = [];
|
|
1327
|
+
const normals = [];
|
|
1328
|
+
const uvs = [];
|
|
1329
|
+
for (let j = 0; j <= radialSegments; j++) {
|
|
1330
|
+
for (let i = 0; i <= tubularSegments; i++) {
|
|
1331
|
+
const u = i / tubularSegments * arc;
|
|
1332
|
+
const v = j / radialSegments * Math.PI * 2;
|
|
1333
|
+
const x = (radius + tube * Math.cos(v)) * Math.cos(u);
|
|
1334
|
+
const y = (radius + tube * Math.cos(v)) * Math.sin(u);
|
|
1335
|
+
const z = tube * Math.sin(v);
|
|
1336
|
+
vertices.push(x, y, z);
|
|
1337
|
+
const cx = radius * Math.cos(u);
|
|
1338
|
+
const cy = radius * Math.sin(u);
|
|
1339
|
+
const n = normalize3(x - cx, y - cy, z - 0);
|
|
1340
|
+
normals.push(n[0], n[1], n[2]);
|
|
1341
|
+
uvs.push(i / tubularSegments);
|
|
1342
|
+
uvs.push(j / radialSegments);
|
|
1343
|
+
}
|
|
1344
|
+
}
|
|
1345
|
+
for (let j = 1; j <= radialSegments; j++) {
|
|
1346
|
+
for (let i = 1; i <= tubularSegments; i++) {
|
|
1347
|
+
const a = (tubularSegments + 1) * j + i - 1;
|
|
1348
|
+
const b = (tubularSegments + 1) * (j - 1) + i - 1;
|
|
1349
|
+
const c = (tubularSegments + 1) * (j - 1) + i;
|
|
1350
|
+
const d = (tubularSegments + 1) * j + i;
|
|
1351
|
+
indices.push(a, b, d);
|
|
1352
|
+
indices.push(b, c, d);
|
|
1353
|
+
}
|
|
1354
|
+
}
|
|
1355
|
+
return pack("TorusGeometry", parameters, { vertices, normals, uvs, indices, groups: [] });
|
|
1356
|
+
}
|
|
1357
|
+
function difference(size, subtract) {
|
|
1358
|
+
const [w, h, d] = size;
|
|
1359
|
+
const hw = w / 2;
|
|
1360
|
+
const hh = h / 2;
|
|
1361
|
+
const hd = d / 2;
|
|
1362
|
+
const holes = subtract.map((hole) => ({ x0: Math.max(-hw, Math.min(hole.min[0], hole.max[0])), x1: Math.min(hw, Math.max(hole.min[0], hole.max[0])), y0: Math.max(-hh, Math.min(hole.min[1], hole.max[1])), y1: Math.min(hh, Math.max(hole.min[1], hole.max[1])) })).filter((hole) => hole.x1 > hole.x0 && hole.y1 > hole.y0);
|
|
1363
|
+
const cuts = (lo, hi, values) => [.../* @__PURE__ */ new Set([lo, hi, ...values.filter((v) => v > lo && v < hi)])].sort((a, b) => a - b);
|
|
1364
|
+
const xs = cuts(-hw, hw, holes.flatMap((hole) => [hole.x0, hole.x1]));
|
|
1365
|
+
const ys = cuts(-hh, hh, holes.flatMap((hole) => [hole.y0, hole.y1]));
|
|
1366
|
+
const nx = xs.length - 1;
|
|
1367
|
+
const ny = ys.length - 1;
|
|
1368
|
+
const solid = (i, j) => {
|
|
1369
|
+
if (i < 0 || j < 0 || i >= nx || j >= ny) return false;
|
|
1370
|
+
const cx = (xs[i] + xs[i + 1]) / 2;
|
|
1371
|
+
const cy = (ys[j] + ys[j + 1]) / 2;
|
|
1372
|
+
return !holes.some((hole) => cx > hole.x0 && cx < hole.x1 && cy > hole.y0 && cy < hole.y1);
|
|
1373
|
+
};
|
|
1374
|
+
const positions = [];
|
|
1375
|
+
const normals = [];
|
|
1376
|
+
const uvs = [];
|
|
1377
|
+
const indices = [];
|
|
1378
|
+
const quad = (corners, normal, uv) => {
|
|
1379
|
+
const base = positions.length / 3;
|
|
1380
|
+
for (let k = 0; k < 4; k++) {
|
|
1381
|
+
positions.push(...corners[k]);
|
|
1382
|
+
normals.push(...normal);
|
|
1383
|
+
uvs.push(...uv[k]);
|
|
1384
|
+
}
|
|
1385
|
+
indices.push(base, base + 1, base + 2, base, base + 2, base + 3);
|
|
1386
|
+
};
|
|
1387
|
+
const fu = (x) => (x + hw) / w;
|
|
1388
|
+
const fv = (y) => (y + hh) / h;
|
|
1389
|
+
for (let j = 0; j < ny; j++) {
|
|
1390
|
+
for (let i = 0; i < nx; i++) {
|
|
1391
|
+
if (!solid(i, j)) continue;
|
|
1392
|
+
const x0 = xs[i];
|
|
1393
|
+
const x1 = xs[i + 1];
|
|
1394
|
+
const y0 = ys[j];
|
|
1395
|
+
const y1 = ys[j + 1];
|
|
1396
|
+
quad([[x0, y0, hd], [x1, y0, hd], [x1, y1, hd], [x0, y1, hd]], [0, 0, 1], [[fu(x0), fv(y0)], [fu(x1), fv(y0)], [fu(x1), fv(y1)], [fu(x0), fv(y1)]]);
|
|
1397
|
+
quad([[x1, y0, -hd], [x0, y0, -hd], [x0, y1, -hd], [x1, y1, -hd]], [0, 0, -1], [[1 - fu(x1), fv(y0)], [1 - fu(x0), fv(y0)], [1 - fu(x0), fv(y1)], [1 - fu(x1), fv(y1)]]);
|
|
1398
|
+
const zu = (z) => (z + hd) / d;
|
|
1399
|
+
if (!solid(i - 1, j)) quad([[x0, y0, -hd], [x0, y0, hd], [x0, y1, hd], [x0, y1, -hd]], [-1, 0, 0], [[zu(-hd), fv(y0)], [zu(hd), fv(y0)], [zu(hd), fv(y1)], [zu(-hd), fv(y1)]]);
|
|
1400
|
+
if (!solid(i + 1, j)) quad([[x1, y0, hd], [x1, y0, -hd], [x1, y1, -hd], [x1, y1, hd]], [1, 0, 0], [[zu(hd), fv(y0)], [zu(-hd), fv(y0)], [zu(-hd), fv(y1)], [zu(hd), fv(y1)]]);
|
|
1401
|
+
if (!solid(i, j - 1)) quad([[x0, y0, -hd], [x1, y0, -hd], [x1, y0, hd], [x0, y0, hd]], [0, -1, 0], [[fu(x0), zu(-hd)], [fu(x1), zu(-hd)], [fu(x1), zu(hd)], [fu(x0), zu(hd)]]);
|
|
1402
|
+
if (!solid(i, j + 1)) quad([[x0, y1, hd], [x1, y1, hd], [x1, y1, -hd], [x0, y1, -hd]], [0, 1, 0], [[fu(x0), zu(hd)], [fu(x1), zu(hd)], [fu(x1), zu(-hd)], [fu(x0), zu(-hd)]]);
|
|
1403
|
+
}
|
|
1404
|
+
}
|
|
1405
|
+
return {
|
|
1406
|
+
type: "BufferGeometry",
|
|
1407
|
+
parameters: { size: [w, h, d], subtract: holes.map((hole) => ({ min: [hole.x0, hole.y0], max: [hole.x1, hole.y1] })) },
|
|
1408
|
+
positions: new Float32Array(positions),
|
|
1409
|
+
normals: new Float32Array(normals),
|
|
1410
|
+
uvs: new Float32Array(uvs),
|
|
1411
|
+
indices: indexArray(indices),
|
|
1412
|
+
groups: []
|
|
1413
|
+
};
|
|
1414
|
+
}
|
|
1415
|
+
var Earcut = {
|
|
1416
|
+
triangulate: function(data, holeIndices, dim = 2) {
|
|
1417
|
+
const hasHoles = holeIndices && holeIndices.length;
|
|
1418
|
+
const outerLen = hasHoles ? holeIndices[0] * dim : data.length;
|
|
1419
|
+
let outerNode = linkedList(data, 0, outerLen, dim, true);
|
|
1420
|
+
const triangles = [];
|
|
1421
|
+
if (!outerNode || outerNode.next === outerNode.prev) return triangles;
|
|
1422
|
+
let minX, minY, maxX, maxY, x, y, invSize;
|
|
1423
|
+
if (hasHoles) outerNode = eliminateHoles(data, holeIndices, outerNode, dim);
|
|
1424
|
+
if (data.length > 80 * dim) {
|
|
1425
|
+
minX = maxX = data[0];
|
|
1426
|
+
minY = maxY = data[1];
|
|
1427
|
+
for (let i = dim; i < outerLen; i += dim) {
|
|
1428
|
+
x = data[i];
|
|
1429
|
+
y = data[i + 1];
|
|
1430
|
+
if (x < minX) minX = x;
|
|
1431
|
+
if (y < minY) minY = y;
|
|
1432
|
+
if (x > maxX) maxX = x;
|
|
1433
|
+
if (y > maxY) maxY = y;
|
|
1434
|
+
}
|
|
1435
|
+
invSize = Math.max(maxX - minX, maxY - minY);
|
|
1436
|
+
invSize = invSize !== 0 ? 32767 / invSize : 0;
|
|
1437
|
+
}
|
|
1438
|
+
earcutLinked(outerNode, triangles, dim, minX, minY, invSize, 0);
|
|
1439
|
+
return triangles;
|
|
1440
|
+
}
|
|
1441
|
+
};
|
|
1442
|
+
function linkedList(data, start, end, dim, clockwise) {
|
|
1443
|
+
let i, last;
|
|
1444
|
+
if (clockwise === signedArea(data, start, end, dim) > 0) {
|
|
1445
|
+
for (i = start; i < end; i += dim) last = insertNode(i, data[i], data[i + 1], last);
|
|
1446
|
+
} else {
|
|
1447
|
+
for (i = end - dim; i >= start; i -= dim) last = insertNode(i, data[i], data[i + 1], last);
|
|
1448
|
+
}
|
|
1449
|
+
if (last && equals(last, last.next)) {
|
|
1450
|
+
removeNode(last);
|
|
1451
|
+
last = last.next;
|
|
1452
|
+
}
|
|
1453
|
+
return last;
|
|
1454
|
+
}
|
|
1455
|
+
function filterPoints(start, end) {
|
|
1456
|
+
if (!start) return start;
|
|
1457
|
+
if (!end) end = start;
|
|
1458
|
+
let p = start, again;
|
|
1459
|
+
do {
|
|
1460
|
+
again = false;
|
|
1461
|
+
if (!p.steiner && (equals(p, p.next) || area(p.prev, p, p.next) === 0)) {
|
|
1462
|
+
removeNode(p);
|
|
1463
|
+
p = end = p.prev;
|
|
1464
|
+
if (p === p.next) break;
|
|
1465
|
+
again = true;
|
|
1466
|
+
} else {
|
|
1467
|
+
p = p.next;
|
|
1468
|
+
}
|
|
1469
|
+
} while (again || p !== end);
|
|
1470
|
+
return end;
|
|
1471
|
+
}
|
|
1472
|
+
function earcutLinked(ear, triangles, dim, minX, minY, invSize, pass) {
|
|
1473
|
+
if (!ear) return;
|
|
1474
|
+
if (!pass && invSize) indexCurve(ear, minX, minY, invSize);
|
|
1475
|
+
let stop = ear, prev, next;
|
|
1476
|
+
while (ear.prev !== ear.next) {
|
|
1477
|
+
prev = ear.prev;
|
|
1478
|
+
next = ear.next;
|
|
1479
|
+
if (invSize ? isEarHashed(ear, minX, minY, invSize) : isEar(ear)) {
|
|
1480
|
+
triangles.push(prev.i / dim | 0);
|
|
1481
|
+
triangles.push(ear.i / dim | 0);
|
|
1482
|
+
triangles.push(next.i / dim | 0);
|
|
1483
|
+
removeNode(ear);
|
|
1484
|
+
ear = next.next;
|
|
1485
|
+
stop = next.next;
|
|
1486
|
+
continue;
|
|
1487
|
+
}
|
|
1488
|
+
ear = next;
|
|
1489
|
+
if (ear === stop) {
|
|
1490
|
+
if (!pass) {
|
|
1491
|
+
earcutLinked(filterPoints(ear), triangles, dim, minX, minY, invSize, 1);
|
|
1492
|
+
} else if (pass === 1) {
|
|
1493
|
+
ear = cureLocalIntersections(filterPoints(ear), triangles, dim);
|
|
1494
|
+
earcutLinked(ear, triangles, dim, minX, minY, invSize, 2);
|
|
1495
|
+
} else if (pass === 2) {
|
|
1496
|
+
splitEarcut(ear, triangles, dim, minX, minY, invSize);
|
|
1497
|
+
}
|
|
1498
|
+
break;
|
|
1499
|
+
}
|
|
1500
|
+
}
|
|
1501
|
+
}
|
|
1502
|
+
function isEar(ear) {
|
|
1503
|
+
const a = ear.prev, b = ear, c = ear.next;
|
|
1504
|
+
if (area(a, b, c) >= 0) return false;
|
|
1505
|
+
const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
|
|
1506
|
+
const x0 = ax < bx ? ax < cx ? ax : cx : bx < cx ? bx : cx, y0 = ay < by ? ay < cy ? ay : cy : by < cy ? by : cy, x1 = ax > bx ? ax > cx ? ax : cx : bx > cx ? bx : cx, y1 = ay > by ? ay > cy ? ay : cy : by > cy ? by : cy;
|
|
1507
|
+
let p = c.next;
|
|
1508
|
+
while (p !== a) {
|
|
1509
|
+
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
1510
|
+
p = p.next;
|
|
1511
|
+
}
|
|
1512
|
+
return true;
|
|
1513
|
+
}
|
|
1514
|
+
function isEarHashed(ear, minX, minY, invSize) {
|
|
1515
|
+
const a = ear.prev, b = ear, c = ear.next;
|
|
1516
|
+
if (area(a, b, c) >= 0) return false;
|
|
1517
|
+
const ax = a.x, bx = b.x, cx = c.x, ay = a.y, by = b.y, cy = c.y;
|
|
1518
|
+
const x0 = ax < bx ? ax < cx ? ax : cx : bx < cx ? bx : cx, y0 = ay < by ? ay < cy ? ay : cy : by < cy ? by : cy, x1 = ax > bx ? ax > cx ? ax : cx : bx > cx ? bx : cx, y1 = ay > by ? ay > cy ? ay : cy : by > cy ? by : cy;
|
|
1519
|
+
const minZ = zOrder(x0, y0, minX, minY, invSize), maxZ = zOrder(x1, y1, minX, minY, invSize);
|
|
1520
|
+
let p = ear.prevZ, n = ear.nextZ;
|
|
1521
|
+
while (p && p.z >= minZ && n && n.z <= maxZ) {
|
|
1522
|
+
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
1523
|
+
p = p.prevZ;
|
|
1524
|
+
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
|
|
1525
|
+
n = n.nextZ;
|
|
1526
|
+
}
|
|
1527
|
+
while (p && p.z >= minZ) {
|
|
1528
|
+
if (p.x >= x0 && p.x <= x1 && p.y >= y0 && p.y <= y1 && p !== a && p !== c && pointInTriangle(ax, ay, bx, by, cx, cy, p.x, p.y) && area(p.prev, p, p.next) >= 0) return false;
|
|
1529
|
+
p = p.prevZ;
|
|
1530
|
+
}
|
|
1531
|
+
while (n && n.z <= maxZ) {
|
|
1532
|
+
if (n.x >= x0 && n.x <= x1 && n.y >= y0 && n.y <= y1 && n !== a && n !== c && pointInTriangle(ax, ay, bx, by, cx, cy, n.x, n.y) && area(n.prev, n, n.next) >= 0) return false;
|
|
1533
|
+
n = n.nextZ;
|
|
1534
|
+
}
|
|
1535
|
+
return true;
|
|
1536
|
+
}
|
|
1537
|
+
function cureLocalIntersections(start, triangles, dim) {
|
|
1538
|
+
let p = start;
|
|
1539
|
+
do {
|
|
1540
|
+
const a = p.prev, b = p.next.next;
|
|
1541
|
+
if (!equals(a, b) && intersects(a, p, p.next, b) && locallyInside(a, b) && locallyInside(b, a)) {
|
|
1542
|
+
triangles.push(a.i / dim | 0);
|
|
1543
|
+
triangles.push(p.i / dim | 0);
|
|
1544
|
+
triangles.push(b.i / dim | 0);
|
|
1545
|
+
removeNode(p);
|
|
1546
|
+
removeNode(p.next);
|
|
1547
|
+
p = start = b;
|
|
1548
|
+
}
|
|
1549
|
+
p = p.next;
|
|
1550
|
+
} while (p !== start);
|
|
1551
|
+
return filterPoints(p);
|
|
1552
|
+
}
|
|
1553
|
+
function splitEarcut(start, triangles, dim, minX, minY, invSize) {
|
|
1554
|
+
let a = start;
|
|
1555
|
+
do {
|
|
1556
|
+
let b = a.next.next;
|
|
1557
|
+
while (b !== a.prev) {
|
|
1558
|
+
if (a.i !== b.i && isValidDiagonal(a, b)) {
|
|
1559
|
+
let c = splitPolygon(a, b);
|
|
1560
|
+
a = filterPoints(a, a.next);
|
|
1561
|
+
c = filterPoints(c, c.next);
|
|
1562
|
+
earcutLinked(a, triangles, dim, minX, minY, invSize, 0);
|
|
1563
|
+
earcutLinked(c, triangles, dim, minX, minY, invSize, 0);
|
|
1564
|
+
return;
|
|
1565
|
+
}
|
|
1566
|
+
b = b.next;
|
|
1567
|
+
}
|
|
1568
|
+
a = a.next;
|
|
1569
|
+
} while (a !== start);
|
|
1570
|
+
}
|
|
1571
|
+
function eliminateHoles(data, holeIndices, outerNode, dim) {
|
|
1572
|
+
const queue = [];
|
|
1573
|
+
let i, len, start, end, list2;
|
|
1574
|
+
for (i = 0, len = holeIndices.length; i < len; i++) {
|
|
1575
|
+
start = holeIndices[i] * dim;
|
|
1576
|
+
end = i < len - 1 ? holeIndices[i + 1] * dim : data.length;
|
|
1577
|
+
list2 = linkedList(data, start, end, dim, false);
|
|
1578
|
+
if (list2 === list2.next) list2.steiner = true;
|
|
1579
|
+
queue.push(getLeftmost(list2));
|
|
1580
|
+
}
|
|
1581
|
+
queue.sort(compareX);
|
|
1582
|
+
for (i = 0; i < queue.length; i++) {
|
|
1583
|
+
outerNode = eliminateHole(queue[i], outerNode);
|
|
1584
|
+
}
|
|
1585
|
+
return outerNode;
|
|
1586
|
+
}
|
|
1587
|
+
function compareX(a, b) {
|
|
1588
|
+
return a.x - b.x;
|
|
1589
|
+
}
|
|
1590
|
+
function eliminateHole(hole, outerNode) {
|
|
1591
|
+
const bridge = findHoleBridge(hole, outerNode);
|
|
1592
|
+
if (!bridge) {
|
|
1593
|
+
return outerNode;
|
|
1594
|
+
}
|
|
1595
|
+
const bridgeReverse = splitPolygon(bridge, hole);
|
|
1596
|
+
filterPoints(bridgeReverse, bridgeReverse.next);
|
|
1597
|
+
return filterPoints(bridge, bridge.next);
|
|
1598
|
+
}
|
|
1599
|
+
function findHoleBridge(hole, outerNode) {
|
|
1600
|
+
let p = outerNode, qx = -Infinity, m;
|
|
1601
|
+
const hx = hole.x, hy = hole.y;
|
|
1602
|
+
do {
|
|
1603
|
+
if (hy <= p.y && hy >= p.next.y && p.next.y !== p.y) {
|
|
1604
|
+
const x = p.x + (hy - p.y) * (p.next.x - p.x) / (p.next.y - p.y);
|
|
1605
|
+
if (x <= hx && x > qx) {
|
|
1606
|
+
qx = x;
|
|
1607
|
+
m = p.x < p.next.x ? p : p.next;
|
|
1608
|
+
if (x === hx) return m;
|
|
1609
|
+
}
|
|
1610
|
+
}
|
|
1611
|
+
p = p.next;
|
|
1612
|
+
} while (p !== outerNode);
|
|
1613
|
+
if (!m) return null;
|
|
1614
|
+
const stop = m, mx = m.x, my = m.y;
|
|
1615
|
+
let tanMin = Infinity, tan;
|
|
1616
|
+
p = m;
|
|
1617
|
+
do {
|
|
1618
|
+
if (hx >= p.x && p.x >= mx && hx !== p.x && pointInTriangle(hy < my ? hx : qx, hy, mx, my, hy < my ? qx : hx, hy, p.x, p.y)) {
|
|
1619
|
+
tan = Math.abs(hy - p.y) / (hx - p.x);
|
|
1620
|
+
if (locallyInside(p, hole) && (tan < tanMin || tan === tanMin && (p.x > m.x || p.x === m.x && sectorContainsSector(m, p)))) {
|
|
1621
|
+
m = p;
|
|
1622
|
+
tanMin = tan;
|
|
1623
|
+
}
|
|
1624
|
+
}
|
|
1625
|
+
p = p.next;
|
|
1626
|
+
} while (p !== stop);
|
|
1627
|
+
return m;
|
|
1628
|
+
}
|
|
1629
|
+
function sectorContainsSector(m, p) {
|
|
1630
|
+
return area(m.prev, m, p.prev) < 0 && area(p.next, m, m.next) < 0;
|
|
1631
|
+
}
|
|
1632
|
+
function indexCurve(start, minX, minY, invSize) {
|
|
1633
|
+
let p = start;
|
|
1634
|
+
do {
|
|
1635
|
+
if (p.z === 0) p.z = zOrder(p.x, p.y, minX, minY, invSize);
|
|
1636
|
+
p.prevZ = p.prev;
|
|
1637
|
+
p.nextZ = p.next;
|
|
1638
|
+
p = p.next;
|
|
1639
|
+
} while (p !== start);
|
|
1640
|
+
p.prevZ.nextZ = null;
|
|
1641
|
+
p.prevZ = null;
|
|
1642
|
+
sortLinked(p);
|
|
1643
|
+
}
|
|
1644
|
+
function sortLinked(list2) {
|
|
1645
|
+
let i, p, q, e, tail, numMerges, pSize, qSize, inSize = 1;
|
|
1646
|
+
do {
|
|
1647
|
+
p = list2;
|
|
1648
|
+
list2 = null;
|
|
1649
|
+
tail = null;
|
|
1650
|
+
numMerges = 0;
|
|
1651
|
+
while (p) {
|
|
1652
|
+
numMerges++;
|
|
1653
|
+
q = p;
|
|
1654
|
+
pSize = 0;
|
|
1655
|
+
for (i = 0; i < inSize; i++) {
|
|
1656
|
+
pSize++;
|
|
1657
|
+
q = q.nextZ;
|
|
1658
|
+
if (!q) break;
|
|
1659
|
+
}
|
|
1660
|
+
qSize = inSize;
|
|
1661
|
+
while (pSize > 0 || qSize > 0 && q) {
|
|
1662
|
+
if (pSize !== 0 && (qSize === 0 || !q || p.z <= q.z)) {
|
|
1663
|
+
e = p;
|
|
1664
|
+
p = p.nextZ;
|
|
1665
|
+
pSize--;
|
|
1666
|
+
} else {
|
|
1667
|
+
e = q;
|
|
1668
|
+
q = q.nextZ;
|
|
1669
|
+
qSize--;
|
|
1670
|
+
}
|
|
1671
|
+
if (tail) tail.nextZ = e;
|
|
1672
|
+
else list2 = e;
|
|
1673
|
+
e.prevZ = tail;
|
|
1674
|
+
tail = e;
|
|
1675
|
+
}
|
|
1676
|
+
p = q;
|
|
1677
|
+
}
|
|
1678
|
+
tail.nextZ = null;
|
|
1679
|
+
inSize *= 2;
|
|
1680
|
+
} while (numMerges > 1);
|
|
1681
|
+
return list2;
|
|
1682
|
+
}
|
|
1683
|
+
function zOrder(x, y, minX, minY, invSize) {
|
|
1684
|
+
x = (x - minX) * invSize | 0;
|
|
1685
|
+
y = (y - minY) * invSize | 0;
|
|
1686
|
+
x = (x | x << 8) & 16711935;
|
|
1687
|
+
x = (x | x << 4) & 252645135;
|
|
1688
|
+
x = (x | x << 2) & 858993459;
|
|
1689
|
+
x = (x | x << 1) & 1431655765;
|
|
1690
|
+
y = (y | y << 8) & 16711935;
|
|
1691
|
+
y = (y | y << 4) & 252645135;
|
|
1692
|
+
y = (y | y << 2) & 858993459;
|
|
1693
|
+
y = (y | y << 1) & 1431655765;
|
|
1694
|
+
return x | y << 1;
|
|
1695
|
+
}
|
|
1696
|
+
function getLeftmost(start) {
|
|
1697
|
+
let p = start, leftmost = start;
|
|
1698
|
+
do {
|
|
1699
|
+
if (p.x < leftmost.x || p.x === leftmost.x && p.y < leftmost.y) leftmost = p;
|
|
1700
|
+
p = p.next;
|
|
1701
|
+
} while (p !== start);
|
|
1702
|
+
return leftmost;
|
|
1703
|
+
}
|
|
1704
|
+
function pointInTriangle(ax, ay, bx, by, cx, cy, px, py) {
|
|
1705
|
+
return (cx - px) * (ay - py) >= (ax - px) * (cy - py) && (ax - px) * (by - py) >= (bx - px) * (ay - py) && (bx - px) * (cy - py) >= (cx - px) * (by - py);
|
|
1706
|
+
}
|
|
1707
|
+
function isValidDiagonal(a, b) {
|
|
1708
|
+
return a.next.i !== b.i && a.prev.i !== b.i && !intersectsPolygon(a, b) && // dones't intersect other edges
|
|
1709
|
+
(locallyInside(a, b) && locallyInside(b, a) && middleInside(a, b) && // locally visible
|
|
1710
|
+
(area(a.prev, a, b.prev) || area(a, b.prev, b)) || // does not create opposite-facing sectors
|
|
1711
|
+
equals(a, b) && area(a.prev, a, a.next) > 0 && area(b.prev, b, b.next) > 0);
|
|
1712
|
+
}
|
|
1713
|
+
function area(p, q, r) {
|
|
1714
|
+
return (q.y - p.y) * (r.x - q.x) - (q.x - p.x) * (r.y - q.y);
|
|
1715
|
+
}
|
|
1716
|
+
function equals(p1, p2) {
|
|
1717
|
+
return p1.x === p2.x && p1.y === p2.y;
|
|
1718
|
+
}
|
|
1719
|
+
function intersects(p1, q1, p2, q2) {
|
|
1720
|
+
const o1 = sign(area(p1, q1, p2));
|
|
1721
|
+
const o2 = sign(area(p1, q1, q2));
|
|
1722
|
+
const o3 = sign(area(p2, q2, p1));
|
|
1723
|
+
const o4 = sign(area(p2, q2, q1));
|
|
1724
|
+
if (o1 !== o2 && o3 !== o4) return true;
|
|
1725
|
+
if (o1 === 0 && onSegment(p1, p2, q1)) return true;
|
|
1726
|
+
if (o2 === 0 && onSegment(p1, q2, q1)) return true;
|
|
1727
|
+
if (o3 === 0 && onSegment(p2, p1, q2)) return true;
|
|
1728
|
+
if (o4 === 0 && onSegment(p2, q1, q2)) return true;
|
|
1729
|
+
return false;
|
|
1730
|
+
}
|
|
1731
|
+
function onSegment(p, q, r) {
|
|
1732
|
+
return q.x <= Math.max(p.x, r.x) && q.x >= Math.min(p.x, r.x) && q.y <= Math.max(p.y, r.y) && q.y >= Math.min(p.y, r.y);
|
|
1733
|
+
}
|
|
1734
|
+
function sign(num2) {
|
|
1735
|
+
return num2 > 0 ? 1 : num2 < 0 ? -1 : 0;
|
|
1736
|
+
}
|
|
1737
|
+
function intersectsPolygon(a, b) {
|
|
1738
|
+
let p = a;
|
|
1739
|
+
do {
|
|
1740
|
+
if (p.i !== a.i && p.next.i !== a.i && p.i !== b.i && p.next.i !== b.i && intersects(p, p.next, a, b)) return true;
|
|
1741
|
+
p = p.next;
|
|
1742
|
+
} while (p !== a);
|
|
1743
|
+
return false;
|
|
1744
|
+
}
|
|
1745
|
+
function locallyInside(a, b) {
|
|
1746
|
+
return area(a.prev, a, a.next) < 0 ? area(a, b, a.next) >= 0 && area(a, a.prev, b) >= 0 : area(a, b, a.prev) < 0 || area(a, a.next, b) < 0;
|
|
1747
|
+
}
|
|
1748
|
+
function middleInside(a, b) {
|
|
1749
|
+
let p = a, inside = false;
|
|
1750
|
+
const px = (a.x + b.x) / 2, py = (a.y + b.y) / 2;
|
|
1751
|
+
do {
|
|
1752
|
+
if (p.y > py !== p.next.y > py && p.next.y !== p.y && px < (p.next.x - p.x) * (py - p.y) / (p.next.y - p.y) + p.x)
|
|
1753
|
+
inside = !inside;
|
|
1754
|
+
p = p.next;
|
|
1755
|
+
} while (p !== a);
|
|
1756
|
+
return inside;
|
|
1757
|
+
}
|
|
1758
|
+
function splitPolygon(a, b) {
|
|
1759
|
+
const a2 = new Node(a.i, a.x, a.y), b2 = new Node(b.i, b.x, b.y), an = a.next, bp = b.prev;
|
|
1760
|
+
a.next = b;
|
|
1761
|
+
b.prev = a;
|
|
1762
|
+
a2.next = an;
|
|
1763
|
+
an.prev = a2;
|
|
1764
|
+
b2.next = a2;
|
|
1765
|
+
a2.prev = b2;
|
|
1766
|
+
bp.next = b2;
|
|
1767
|
+
b2.prev = bp;
|
|
1768
|
+
return b2;
|
|
1769
|
+
}
|
|
1770
|
+
function insertNode(i, x, y, last) {
|
|
1771
|
+
const p = new Node(i, x, y);
|
|
1772
|
+
if (!last) {
|
|
1773
|
+
p.prev = p;
|
|
1774
|
+
p.next = p;
|
|
1775
|
+
} else {
|
|
1776
|
+
p.next = last.next;
|
|
1777
|
+
p.prev = last;
|
|
1778
|
+
last.next.prev = p;
|
|
1779
|
+
last.next = p;
|
|
1780
|
+
}
|
|
1781
|
+
return p;
|
|
1782
|
+
}
|
|
1783
|
+
function removeNode(p) {
|
|
1784
|
+
p.next.prev = p.prev;
|
|
1785
|
+
p.prev.next = p.next;
|
|
1786
|
+
if (p.prevZ) p.prevZ.nextZ = p.nextZ;
|
|
1787
|
+
if (p.nextZ) p.nextZ.prevZ = p.prevZ;
|
|
1788
|
+
}
|
|
1789
|
+
function Node(i, x, y) {
|
|
1790
|
+
this.i = i;
|
|
1791
|
+
this.x = x;
|
|
1792
|
+
this.y = y;
|
|
1793
|
+
this.prev = null;
|
|
1794
|
+
this.next = null;
|
|
1795
|
+
this.z = 0;
|
|
1796
|
+
this.prevZ = null;
|
|
1797
|
+
this.nextZ = null;
|
|
1798
|
+
this.steiner = false;
|
|
1799
|
+
}
|
|
1800
|
+
function signedArea(data, start, end, dim) {
|
|
1801
|
+
let sum = 0;
|
|
1802
|
+
for (let i = start, j = end - dim; i < end; i += dim) {
|
|
1803
|
+
sum += (data[j] - data[i]) * (data[i + 1] + data[j + 1]);
|
|
1804
|
+
j = i;
|
|
1805
|
+
}
|
|
1806
|
+
return sum;
|
|
1807
|
+
}
|
|
1808
|
+
var equals2 = (a, b) => a.x === b.x && a.y === b.y;
|
|
1809
|
+
var linePoint = (v1, v2, t) => t === 1 ? { x: v2.x, y: v2.y } : { x: (v2.x - v1.x) * t + v1.x, y: (v2.y - v1.y) * t + v1.y };
|
|
1810
|
+
function pathPoints(points) {
|
|
1811
|
+
const out = [];
|
|
1812
|
+
let last;
|
|
1813
|
+
for (let i = 1; i < points.length; i++) {
|
|
1814
|
+
const v1 = { x: points[i - 1][0], y: points[i - 1][1] };
|
|
1815
|
+
const v2 = { x: points[i][0], y: points[i][1] };
|
|
1816
|
+
for (let d = 0; d <= 1; d++) {
|
|
1817
|
+
const point = linePoint(v1, v2, d / 1);
|
|
1818
|
+
if (last && equals2(last, point)) continue;
|
|
1819
|
+
out.push(point);
|
|
1820
|
+
last = point;
|
|
1821
|
+
}
|
|
1822
|
+
}
|
|
1823
|
+
return out;
|
|
1824
|
+
}
|
|
1825
|
+
function area2(contour) {
|
|
1826
|
+
const n = contour.length;
|
|
1827
|
+
let a = 0;
|
|
1828
|
+
for (let p = n - 1, q = 0; q < n; p = q++) a += contour[p].x * contour[q].y - contour[q].x * contour[p].y;
|
|
1829
|
+
return a * 0.5;
|
|
1830
|
+
}
|
|
1831
|
+
var isClockWise = (pts) => area2(pts) < 0;
|
|
1832
|
+
function removeDupEndPts(points) {
|
|
1833
|
+
const l = points.length;
|
|
1834
|
+
if (l > 2 && equals2(points[l - 1], points[0])) points.pop();
|
|
1835
|
+
}
|
|
1836
|
+
function triangulateShape(contour, holes) {
|
|
1837
|
+
const vertices = [];
|
|
1838
|
+
const holeIndices = [];
|
|
1839
|
+
const faces = [];
|
|
1840
|
+
removeDupEndPts(contour);
|
|
1841
|
+
for (const p of contour) vertices.push(p.x, p.y);
|
|
1842
|
+
let holeIndex = contour.length;
|
|
1843
|
+
holes.forEach(removeDupEndPts);
|
|
1844
|
+
for (let i = 0; i < holes.length; i++) {
|
|
1845
|
+
holeIndices.push(holeIndex);
|
|
1846
|
+
holeIndex += holes[i].length;
|
|
1847
|
+
for (const p of holes[i]) vertices.push(p.x, p.y);
|
|
1848
|
+
}
|
|
1849
|
+
const triangles = Earcut.triangulate(vertices, holeIndices);
|
|
1850
|
+
for (let i = 0; i < triangles.length; i += 3) faces.push(triangles.slice(i, i + 3));
|
|
1851
|
+
return faces;
|
|
1852
|
+
}
|
|
1853
|
+
function computeVertexNormals(positions, indices) {
|
|
1854
|
+
const normals = new Float32Array(positions.length);
|
|
1855
|
+
const get = (array, i) => [array[i * 3], array[i * 3 + 1], array[i * 3 + 2]];
|
|
1856
|
+
const face = (vA, vB, vC) => {
|
|
1857
|
+
const pA = get(positions, vA);
|
|
1858
|
+
const pB = get(positions, vB);
|
|
1859
|
+
const pC = get(positions, vC);
|
|
1860
|
+
const cb = [pC[0] - pB[0], pC[1] - pB[1], pC[2] - pB[2]];
|
|
1861
|
+
const ab = [pA[0] - pB[0], pA[1] - pB[1], pA[2] - pB[2]];
|
|
1862
|
+
return [cb[1] * ab[2] - cb[2] * ab[1], cb[2] * ab[0] - cb[0] * ab[2], cb[0] * ab[1] - cb[1] * ab[0]];
|
|
1863
|
+
};
|
|
1864
|
+
const set = (i, x, y, z) => {
|
|
1865
|
+
normals[i * 3] = x;
|
|
1866
|
+
normals[i * 3 + 1] = y;
|
|
1867
|
+
normals[i * 3 + 2] = z;
|
|
1868
|
+
};
|
|
1869
|
+
if (indices) {
|
|
1870
|
+
for (let i = 0; i < indices.length; i += 3) {
|
|
1871
|
+
const vA = indices[i];
|
|
1872
|
+
const vB = indices[i + 1];
|
|
1873
|
+
const vC = indices[i + 2];
|
|
1874
|
+
const cb = face(vA, vB, vC);
|
|
1875
|
+
for (const v of [vA, vB, vC]) {
|
|
1876
|
+
const n = get(normals, v);
|
|
1877
|
+
set(v, n[0] + cb[0], n[1] + cb[1], n[2] + cb[2]);
|
|
1878
|
+
}
|
|
1879
|
+
}
|
|
1880
|
+
} else {
|
|
1881
|
+
for (let i = 0; i < positions.length / 3; i += 3) {
|
|
1882
|
+
const cb = face(i, i + 1, i + 2);
|
|
1883
|
+
set(i, cb[0], cb[1], cb[2]);
|
|
1884
|
+
set(i + 1, cb[0], cb[1], cb[2]);
|
|
1885
|
+
set(i + 2, cb[0], cb[1], cb[2]);
|
|
1886
|
+
}
|
|
1887
|
+
}
|
|
1888
|
+
for (let i = 0; i < normals.length / 3; i++) {
|
|
1889
|
+
const [x, y, z] = get(normals, i);
|
|
1890
|
+
const s = 1 / (Math.sqrt(x * x + y * y + z * z) || 1);
|
|
1891
|
+
set(i, x * s, y * s, z * s);
|
|
1892
|
+
}
|
|
1893
|
+
return normals;
|
|
1894
|
+
}
|
|
1895
|
+
function extrude(outline, depth = 1, steps = 1, bevel = void 0) {
|
|
1896
|
+
const parameters = { shape: outline, depth, steps, bevel: bevel ?? {} };
|
|
1897
|
+
const verticesArray = [];
|
|
1898
|
+
const uvArray = [];
|
|
1899
|
+
const groups = [];
|
|
1900
|
+
const placeholder = [];
|
|
1901
|
+
const bevelEnabled = bevel !== false;
|
|
1902
|
+
const spec = bevel === false || bevel === void 0 ? {} : bevel;
|
|
1903
|
+
let bevelThickness = spec.thickness !== void 0 ? spec.thickness : 0.2;
|
|
1904
|
+
let bevelSize = spec.size !== void 0 ? spec.size : bevelThickness - 0.1;
|
|
1905
|
+
let bevelOffset = spec.offset !== void 0 ? spec.offset : 0;
|
|
1906
|
+
let bevelSegments = spec.segments !== void 0 ? spec.segments : 3;
|
|
1907
|
+
if (!bevelEnabled) {
|
|
1908
|
+
bevelSegments = 0;
|
|
1909
|
+
bevelThickness = 0;
|
|
1910
|
+
bevelSize = 0;
|
|
1911
|
+
bevelOffset = 0;
|
|
1912
|
+
}
|
|
1913
|
+
let vertices = pathPoints(outline.outer);
|
|
1914
|
+
const holes = (outline.holes ?? []).map(pathPoints);
|
|
1915
|
+
const reverse = !isClockWise(vertices);
|
|
1916
|
+
if (reverse) {
|
|
1917
|
+
vertices = vertices.reverse();
|
|
1918
|
+
for (let h = 0; h < holes.length; h++) {
|
|
1919
|
+
const ahole = holes[h];
|
|
1920
|
+
if (isClockWise(ahole)) holes[h] = ahole.reverse();
|
|
1921
|
+
}
|
|
1922
|
+
}
|
|
1923
|
+
const faces = triangulateShape(vertices, holes);
|
|
1924
|
+
const contour = vertices;
|
|
1925
|
+
for (const ahole of holes) vertices = vertices.concat(ahole);
|
|
1926
|
+
const scalePt2 = (pt, vec, size) => ({ x: pt.x + vec.x * size, y: pt.y + vec.y * size });
|
|
1927
|
+
const vlen = vertices.length;
|
|
1928
|
+
const flen = faces.length;
|
|
1929
|
+
const getBevelVec = (inPt, inPrev, inNext) => {
|
|
1930
|
+
let vTransX;
|
|
1931
|
+
let vTransY;
|
|
1932
|
+
let shrinkBy;
|
|
1933
|
+
const vPrevX = inPt.x - inPrev.x;
|
|
1934
|
+
const vPrevY = inPt.y - inPrev.y;
|
|
1935
|
+
const vNextX = inNext.x - inPt.x;
|
|
1936
|
+
const vNextY = inNext.y - inPt.y;
|
|
1937
|
+
const vPrevLensq = vPrevX * vPrevX + vPrevY * vPrevY;
|
|
1938
|
+
const collinear0 = vPrevX * vNextY - vPrevY * vNextX;
|
|
1939
|
+
if (Math.abs(collinear0) > Number.EPSILON) {
|
|
1940
|
+
const vPrevLen = Math.sqrt(vPrevLensq);
|
|
1941
|
+
const vNextLen = Math.sqrt(vNextX * vNextX + vNextY * vNextY);
|
|
1942
|
+
const ptPrevShiftX = inPrev.x - vPrevY / vPrevLen;
|
|
1943
|
+
const ptPrevShiftY = inPrev.y + vPrevX / vPrevLen;
|
|
1944
|
+
const ptNextShiftX = inNext.x - vNextY / vNextLen;
|
|
1945
|
+
const ptNextShiftY = inNext.y + vNextX / vNextLen;
|
|
1946
|
+
const sf = ((ptNextShiftX - ptPrevShiftX) * vNextY - (ptNextShiftY - ptPrevShiftY) * vNextX) / (vPrevX * vNextY - vPrevY * vNextX);
|
|
1947
|
+
vTransX = ptPrevShiftX + vPrevX * sf - inPt.x;
|
|
1948
|
+
vTransY = ptPrevShiftY + vPrevY * sf - inPt.y;
|
|
1949
|
+
const vTransLensq = vTransX * vTransX + vTransY * vTransY;
|
|
1950
|
+
if (vTransLensq <= 2) return { x: vTransX, y: vTransY };
|
|
1951
|
+
shrinkBy = Math.sqrt(vTransLensq / 2);
|
|
1952
|
+
} else {
|
|
1953
|
+
let directionEq = false;
|
|
1954
|
+
if (vPrevX > Number.EPSILON) {
|
|
1955
|
+
if (vNextX > Number.EPSILON) directionEq = true;
|
|
1956
|
+
} else if (vPrevX < -Number.EPSILON) {
|
|
1957
|
+
if (vNextX < -Number.EPSILON) directionEq = true;
|
|
1958
|
+
} else if (Math.sign(vPrevY) === Math.sign(vNextY)) {
|
|
1959
|
+
directionEq = true;
|
|
1960
|
+
}
|
|
1961
|
+
if (directionEq) {
|
|
1962
|
+
vTransX = -vPrevY;
|
|
1963
|
+
vTransY = vPrevX;
|
|
1964
|
+
shrinkBy = Math.sqrt(vPrevLensq);
|
|
1965
|
+
} else {
|
|
1966
|
+
vTransX = vPrevX;
|
|
1967
|
+
vTransY = vPrevY;
|
|
1968
|
+
shrinkBy = Math.sqrt(vPrevLensq / 2);
|
|
1969
|
+
}
|
|
1970
|
+
}
|
|
1971
|
+
return { x: vTransX / shrinkBy, y: vTransY / shrinkBy };
|
|
1972
|
+
};
|
|
1973
|
+
const movements = (ring2) => {
|
|
1974
|
+
const out = [];
|
|
1975
|
+
for (let i = 0, il = ring2.length, j = il - 1, k = i + 1; i < il; i++, j++, k++) {
|
|
1976
|
+
if (j === il) j = 0;
|
|
1977
|
+
if (k === il) k = 0;
|
|
1978
|
+
out[i] = getBevelVec(ring2[i], ring2[j], ring2[k]);
|
|
1979
|
+
}
|
|
1980
|
+
return out;
|
|
1981
|
+
};
|
|
1982
|
+
const contourMovements = movements(contour);
|
|
1983
|
+
const holesMovements = [];
|
|
1984
|
+
let verticesMovements = contourMovements.concat();
|
|
1985
|
+
for (const ahole of holes) {
|
|
1986
|
+
const oneHoleMovements = movements(ahole);
|
|
1987
|
+
holesMovements.push(oneHoleMovements);
|
|
1988
|
+
verticesMovements = verticesMovements.concat(oneHoleMovements);
|
|
1989
|
+
}
|
|
1990
|
+
const v = (x, y, z) => {
|
|
1991
|
+
placeholder.push(x);
|
|
1992
|
+
placeholder.push(y);
|
|
1993
|
+
placeholder.push(z);
|
|
1994
|
+
};
|
|
1995
|
+
const bevelLayer = (b, zOf) => {
|
|
1996
|
+
const t = b / bevelSegments;
|
|
1997
|
+
const z = bevelThickness * Math.cos(t * Math.PI / 2);
|
|
1998
|
+
const bs2 = bevelSize * Math.sin(t * Math.PI / 2) + bevelOffset;
|
|
1999
|
+
for (let i = 0; i < contour.length; i++) {
|
|
2000
|
+
const vert = scalePt2(contour[i], contourMovements[i], bs2);
|
|
2001
|
+
v(vert.x, vert.y, zOf(z));
|
|
2002
|
+
}
|
|
2003
|
+
for (let h = 0; h < holes.length; h++) {
|
|
2004
|
+
const ahole = holes[h];
|
|
2005
|
+
const oneHoleMovements = holesMovements[h];
|
|
2006
|
+
for (let i = 0; i < ahole.length; i++) {
|
|
2007
|
+
const vert = scalePt2(ahole[i], oneHoleMovements[i], bs2);
|
|
2008
|
+
v(vert.x, vert.y, zOf(z));
|
|
2009
|
+
}
|
|
2010
|
+
}
|
|
2011
|
+
};
|
|
2012
|
+
for (let b = 0; b < bevelSegments; b++) bevelLayer(b, (z) => -z);
|
|
2013
|
+
const bs = bevelSize + bevelOffset;
|
|
2014
|
+
for (let i = 0; i < vlen; i++) {
|
|
2015
|
+
const vert = bevelEnabled ? scalePt2(vertices[i], verticesMovements[i], bs) : vertices[i];
|
|
2016
|
+
v(vert.x, vert.y, 0);
|
|
2017
|
+
}
|
|
2018
|
+
for (let s = 1; s <= steps; s++) {
|
|
2019
|
+
for (let i = 0; i < vlen; i++) {
|
|
2020
|
+
const vert = bevelEnabled ? scalePt2(vertices[i], verticesMovements[i], bs) : vertices[i];
|
|
2021
|
+
v(vert.x, vert.y, depth / steps * s);
|
|
2022
|
+
}
|
|
2023
|
+
}
|
|
2024
|
+
for (let b = bevelSegments - 1; b >= 0; b--) bevelLayer(b, (z) => depth + z);
|
|
2025
|
+
const addVertex = (index) => {
|
|
2026
|
+
verticesArray.push(placeholder[index * 3 + 0]);
|
|
2027
|
+
verticesArray.push(placeholder[index * 3 + 1]);
|
|
2028
|
+
verticesArray.push(placeholder[index * 3 + 2]);
|
|
2029
|
+
};
|
|
2030
|
+
const addUV = (x, y) => {
|
|
2031
|
+
uvArray.push(x);
|
|
2032
|
+
uvArray.push(y);
|
|
2033
|
+
};
|
|
2034
|
+
const at = (index, axis) => verticesArray[index * 3 + axis];
|
|
2035
|
+
const f3 = (a, b, c) => {
|
|
2036
|
+
addVertex(a);
|
|
2037
|
+
addVertex(b);
|
|
2038
|
+
addVertex(c);
|
|
2039
|
+
const nextIndex = verticesArray.length / 3;
|
|
2040
|
+
for (const index of [nextIndex - 3, nextIndex - 2, nextIndex - 1]) addUV(at(index, 0), at(index, 1));
|
|
2041
|
+
};
|
|
2042
|
+
const f4 = (a, b, c, d) => {
|
|
2043
|
+
addVertex(a);
|
|
2044
|
+
addVertex(b);
|
|
2045
|
+
addVertex(d);
|
|
2046
|
+
addVertex(b);
|
|
2047
|
+
addVertex(c);
|
|
2048
|
+
addVertex(d);
|
|
2049
|
+
const nextIndex = verticesArray.length / 3;
|
|
2050
|
+
const iA = nextIndex - 6;
|
|
2051
|
+
const iB = nextIndex - 3;
|
|
2052
|
+
const iC = nextIndex - 2;
|
|
2053
|
+
const iD = nextIndex - 1;
|
|
2054
|
+
const byX = Math.abs(at(iA, 1) - at(iB, 1)) < Math.abs(at(iA, 0) - at(iB, 0));
|
|
2055
|
+
const uv = (index) => [byX ? at(index, 0) : at(index, 1), 1 - at(index, 2)];
|
|
2056
|
+
const uvs = [uv(iA), uv(iB), uv(iC), uv(iD)];
|
|
2057
|
+
for (const k of [0, 1, 3, 1, 2, 3]) addUV(...uvs[k]);
|
|
2058
|
+
};
|
|
2059
|
+
let start = verticesArray.length / 3;
|
|
2060
|
+
if (bevelEnabled) {
|
|
2061
|
+
let offset = 0;
|
|
2062
|
+
for (let i = 0; i < flen; i++) {
|
|
2063
|
+
const face = faces[i];
|
|
2064
|
+
f3(face[2] + offset, face[1] + offset, face[0] + offset);
|
|
2065
|
+
}
|
|
2066
|
+
const layer = steps + bevelSegments * 2;
|
|
2067
|
+
offset = vlen * layer;
|
|
2068
|
+
for (let i = 0; i < flen; i++) {
|
|
2069
|
+
const face = faces[i];
|
|
2070
|
+
f3(face[0] + offset, face[1] + offset, face[2] + offset);
|
|
2071
|
+
}
|
|
2072
|
+
} else {
|
|
2073
|
+
for (let i = 0; i < flen; i++) {
|
|
2074
|
+
const face = faces[i];
|
|
2075
|
+
f3(face[2], face[1], face[0]);
|
|
2076
|
+
}
|
|
2077
|
+
for (let i = 0; i < flen; i++) {
|
|
2078
|
+
const face = faces[i];
|
|
2079
|
+
f3(face[0] + vlen * steps, face[1] + vlen * steps, face[2] + vlen * steps);
|
|
2080
|
+
}
|
|
2081
|
+
}
|
|
2082
|
+
groups.push({ start, count: verticesArray.length / 3 - start, materialIndex: 0 });
|
|
2083
|
+
start = verticesArray.length / 3;
|
|
2084
|
+
const sidewalls = (ring2, layeroffset2) => {
|
|
2085
|
+
let i = ring2.length;
|
|
2086
|
+
while (--i >= 0) {
|
|
2087
|
+
const j = i;
|
|
2088
|
+
let k = i - 1;
|
|
2089
|
+
if (k < 0) k = ring2.length - 1;
|
|
2090
|
+
for (let s = 0, sl = steps + bevelSegments * 2; s < sl; s++) {
|
|
2091
|
+
const slen1 = vlen * s;
|
|
2092
|
+
const slen2 = vlen * (s + 1);
|
|
2093
|
+
f4(layeroffset2 + j + slen1, layeroffset2 + k + slen1, layeroffset2 + k + slen2, layeroffset2 + j + slen2);
|
|
2094
|
+
}
|
|
2095
|
+
}
|
|
2096
|
+
};
|
|
2097
|
+
let layeroffset = 0;
|
|
2098
|
+
sidewalls(contour, layeroffset);
|
|
2099
|
+
layeroffset += contour.length;
|
|
2100
|
+
for (const ahole of holes) {
|
|
2101
|
+
sidewalls(ahole, layeroffset);
|
|
2102
|
+
layeroffset += ahole.length;
|
|
2103
|
+
}
|
|
2104
|
+
groups.push({ start, count: verticesArray.length / 3 - start, materialIndex: 1 });
|
|
2105
|
+
const positions = new Float32Array(verticesArray);
|
|
2106
|
+
return { type: "ExtrudeGeometry", parameters, positions, normals: computeVertexNormals(positions), uvs: new Float32Array(uvArray), groups };
|
|
2107
|
+
}
|
|
2108
|
+
var PARAMETER_KEYS = {
|
|
2109
|
+
box: ["size", "segments"],
|
|
2110
|
+
cylinder: ["radiusTop", "radiusBottom", "height", "radialSegments", "heightSegments", "openEnded", "thetaStart", "thetaLength"],
|
|
2111
|
+
cone: ["radius", "height", "radialSegments", "heightSegments", "openEnded", "thetaStart", "thetaLength"],
|
|
2112
|
+
sphere: ["radius", "widthSegments", "heightSegments", "phiStart", "phiLength", "thetaStart", "thetaLength"],
|
|
2113
|
+
plane: ["width", "height", "widthSegments", "heightSegments"],
|
|
2114
|
+
circle: ["radius", "segments", "thetaStart", "thetaLength"],
|
|
2115
|
+
ring: ["innerRadius", "outerRadius", "thetaSegments", "phiSegments", "thetaStart", "thetaLength"],
|
|
2116
|
+
lathe: ["points", "segments", "phiStart", "phiLength"],
|
|
2117
|
+
torus: ["radius", "tube", "radialSegments", "tubularSegments", "arc"],
|
|
2118
|
+
extrude: ["shape", "depth", "steps", "bevel"],
|
|
2119
|
+
difference: ["size", "subtract"],
|
|
2120
|
+
mesh: ["positions", "normals", "uvs", "indices"]
|
|
2121
|
+
};
|
|
2122
|
+
var SEGMENT_KEYS = {
|
|
2123
|
+
cylinder: ["radialSegments"],
|
|
2124
|
+
cone: ["radialSegments"],
|
|
2125
|
+
sphere: ["widthSegments", "heightSegments"],
|
|
2126
|
+
circle: ["segments"],
|
|
2127
|
+
ring: ["thetaSegments"],
|
|
2128
|
+
lathe: ["segments"],
|
|
2129
|
+
torus: ["radialSegments", "tubularSegments"]
|
|
2130
|
+
};
|
|
2131
|
+
var SEGMENT_DEFAULTS = { radialSegments: 32, widthSegments: 32, heightSegments: 16, segments: 12, thetaSegments: 32, tubularSegments: 48 };
|
|
2132
|
+
var segmentDefault = (kind, key) => kind === "circle" && key === "segments" ? 32 : kind === "torus" && key === "radialSegments" ? 12 : SEGMENT_DEFAULTS[key];
|
|
2133
|
+
function primitiveParameters(node, options = {}) {
|
|
2134
|
+
const out = {};
|
|
2135
|
+
const source = node;
|
|
2136
|
+
for (const key of PARAMETER_KEYS[node.kind]) if (source[key] !== void 0) out[key] = source[key];
|
|
2137
|
+
const detail = options.detail ?? 1;
|
|
2138
|
+
if (detail !== 1) {
|
|
2139
|
+
const floor = options.minSegments ?? 3;
|
|
2140
|
+
for (const key of SEGMENT_KEYS[node.kind] ?? []) {
|
|
2141
|
+
const given = out[key] ?? segmentDefault(node.kind, key);
|
|
2142
|
+
out[key] = Math.max(floor, Math.round(given * detail));
|
|
2143
|
+
}
|
|
2144
|
+
}
|
|
2145
|
+
return out;
|
|
2146
|
+
}
|
|
2147
|
+
function mesh(p) {
|
|
2148
|
+
const positions = new Float32Array(p.positions);
|
|
2149
|
+
const indices = p.indices ? indexArray(p.indices) : void 0;
|
|
2150
|
+
return {
|
|
2151
|
+
type: "BufferGeometry",
|
|
2152
|
+
parameters: {},
|
|
2153
|
+
positions,
|
|
2154
|
+
normals: p.normals ? new Float32Array(p.normals) : computeVertexNormals(positions, indices),
|
|
2155
|
+
uvs: p.uvs ? new Float32Array(p.uvs) : new Float32Array(positions.length / 3 * 2),
|
|
2156
|
+
...indices ? { indices } : {},
|
|
2157
|
+
groups: []
|
|
2158
|
+
};
|
|
2159
|
+
}
|
|
2160
|
+
function make(kind, p) {
|
|
2161
|
+
switch (kind) {
|
|
2162
|
+
case "box": {
|
|
2163
|
+
const size = p.size ?? [1, 1, 1];
|
|
2164
|
+
const segments = p.segments ?? [1, 1, 1];
|
|
2165
|
+
return box(size[0], size[1], size[2], segments[0], segments[1], segments[2]);
|
|
2166
|
+
}
|
|
2167
|
+
case "cylinder":
|
|
2168
|
+
return cylinder(p.radiusTop, p.radiusBottom, p.height, p.radialSegments, p.heightSegments, p.openEnded, p.thetaStart, p.thetaLength);
|
|
2169
|
+
case "cone":
|
|
2170
|
+
return cone(p.radius, p.height, p.radialSegments, p.heightSegments, p.openEnded, p.thetaStart, p.thetaLength);
|
|
2171
|
+
case "sphere":
|
|
2172
|
+
return sphere(p.radius, p.widthSegments, p.heightSegments, p.phiStart, p.phiLength, p.thetaStart, p.thetaLength);
|
|
2173
|
+
case "plane":
|
|
2174
|
+
return plane(p.width, p.height, p.widthSegments, p.heightSegments);
|
|
2175
|
+
case "circle":
|
|
2176
|
+
return circle(p.radius, p.segments, p.thetaStart, p.thetaLength);
|
|
2177
|
+
case "ring":
|
|
2178
|
+
return ring(p.innerRadius, p.outerRadius, p.thetaSegments, p.phiSegments, p.thetaStart, p.thetaLength);
|
|
2179
|
+
case "lathe":
|
|
2180
|
+
return lathe(p.points, p.segments, p.phiStart, p.phiLength);
|
|
2181
|
+
case "torus":
|
|
2182
|
+
return torus(p.radius, p.tube, p.radialSegments, p.tubularSegments, p.arc);
|
|
2183
|
+
case "extrude":
|
|
2184
|
+
return extrude(p.shape, p.depth, p.steps, p.bevel);
|
|
2185
|
+
case "difference":
|
|
2186
|
+
return difference(p.size, p.subtract ?? []);
|
|
2187
|
+
case "mesh":
|
|
2188
|
+
return mesh(p);
|
|
2189
|
+
default:
|
|
2190
|
+
throw new Error(`game-shapes: unknown primitive kind "${String(kind)}"`);
|
|
2191
|
+
}
|
|
2192
|
+
}
|
|
2193
|
+
var templates = /* @__PURE__ */ new Map();
|
|
2194
|
+
var templateLimit = 4096;
|
|
2195
|
+
var stats = { hits: 0, misses: 0 };
|
|
2196
|
+
function buildPrimitive(node, options = {}) {
|
|
2197
|
+
const parameters = primitiveParameters(node, options);
|
|
2198
|
+
if (node.kind === "mesh" || templateLimit === 0) return make(node.kind, parameters);
|
|
2199
|
+
const key = `${node.kind}:${canonicalJson(parameters)}`;
|
|
2200
|
+
const cached = templates.get(key);
|
|
2201
|
+
if (cached) {
|
|
2202
|
+
stats.hits++;
|
|
2203
|
+
templates.delete(key);
|
|
2204
|
+
templates.set(key, cached);
|
|
2205
|
+
return cached;
|
|
2206
|
+
}
|
|
2207
|
+
stats.misses++;
|
|
2208
|
+
const built = make(node.kind, parameters);
|
|
2209
|
+
templates.set(key, built);
|
|
2210
|
+
if (templates.size > templateLimit) templates.delete(templates.keys().next().value);
|
|
2211
|
+
return built;
|
|
2212
|
+
}
|
|
2213
|
+
function mulberry32(seed) {
|
|
2214
|
+
let state = seed >>> 0;
|
|
2215
|
+
return () => {
|
|
2216
|
+
state = state + 1831565813 >>> 0;
|
|
2217
|
+
let t = state;
|
|
2218
|
+
t = Math.imul(t ^ t >>> 15, t | 1);
|
|
2219
|
+
t ^= t + Math.imul(t ^ t >>> 7, t | 61);
|
|
2220
|
+
return ((t ^ t >>> 14) >>> 0) / 4294967296;
|
|
2221
|
+
};
|
|
2222
|
+
}
|
|
2223
|
+
var streamFor = (seed, generator, path) => mulberry32(parseInt(hash64(`${String(seed)}\0${generator}\0${path}`).slice(0, 8), 16));
|
|
2224
|
+
var generators = /* @__PURE__ */ new Map();
|
|
2225
|
+
var getGenerator = (name) => generators.get(name);
|
|
2226
|
+
var num = (v) => typeof v === "number" && Number.isFinite(v) ? null : "expected a finite number";
|
|
2227
|
+
var bool = (v) => typeof v === "boolean" ? null : "expected a boolean";
|
|
2228
|
+
var str = (v) => typeof v === "string" ? null : "expected a string";
|
|
2229
|
+
var int = (min) => (v) => typeof v === "number" && Number.isInteger(v) && v >= min ? null : `expected an integer >= ${min}`;
|
|
2230
|
+
var range = (lo, hi, loOpen = false) => (v) => typeof v === "number" && (loOpen ? v > lo : v >= lo) && v <= hi ? null : `expected a number in ${loOpen ? "(" : "["}${lo}, ${hi}]`;
|
|
2231
|
+
var tuple = (n, item = num) => (v) => {
|
|
2232
|
+
if (!Array.isArray(v) || v.length !== n) return `expected an array of ${n}`;
|
|
2233
|
+
for (const x of v) if (item(x)) return `expected an array of ${n} numbers`;
|
|
2234
|
+
return null;
|
|
2235
|
+
};
|
|
2236
|
+
var list = (item, min = 0) => (v) => {
|
|
2237
|
+
if (!Array.isArray(v)) return "expected an array";
|
|
2238
|
+
if (v.length < min) return `expected at least ${min} items`;
|
|
2239
|
+
for (let i = 0; i < v.length; i++) {
|
|
2240
|
+
const e = item(v[i]);
|
|
2241
|
+
if (e) return `[${i}]: ${e}`;
|
|
2242
|
+
}
|
|
2243
|
+
return null;
|
|
2244
|
+
};
|
|
2245
|
+
var color = (v) => typeof v === "number" && Number.isInteger(v) && v >= 0 && v <= 16777215 || typeof v === "string" && /^#[0-9a-fA-F]{6}$/.test(v) ? null : 'expected 0xRRGGBB or "#rrggbb"';
|
|
2246
|
+
var oneOf = (values) => (v) => values.includes(v) ? null : `expected one of ${values.map((x) => JSON.stringify(x)).join(", ")}`;
|
|
2247
|
+
var object = (fields, required = []) => (v) => {
|
|
2248
|
+
if (!v || typeof v !== "object" || Array.isArray(v)) return "expected an object";
|
|
2249
|
+
const record = v;
|
|
2250
|
+
for (const key of required) if (record[key] === void 0) return `${key}: required`;
|
|
2251
|
+
for (const key of Object.keys(record)) {
|
|
2252
|
+
if (record[key] === void 0) continue;
|
|
2253
|
+
const check = fields[key];
|
|
2254
|
+
if (!check) return `${key}: unknown field`;
|
|
2255
|
+
const e = check(record[key]);
|
|
2256
|
+
if (e) return `${key}: ${e}`;
|
|
2257
|
+
}
|
|
2258
|
+
return null;
|
|
2259
|
+
};
|
|
2260
|
+
var ORDERS = ["XYZ", "YXZ", "ZXY", "ZYX", "YZX", "XZY"];
|
|
2261
|
+
var TRANSFORM = {
|
|
2262
|
+
position: tuple(3),
|
|
2263
|
+
rotation: tuple(3),
|
|
2264
|
+
rotationOrder: oneOf(ORDERS),
|
|
2265
|
+
scale: (v) => typeof v === "number" ? num(v) : tuple(3)(v),
|
|
2266
|
+
heading: num,
|
|
2267
|
+
matrix: tuple(16)
|
|
2268
|
+
};
|
|
2269
|
+
var UV = object({ u0: num, v0: num, u1: num, v1: num }, ["u0", "v0", "u1", "v1"]);
|
|
2270
|
+
var INSTANCE = object({ ...TRANSFORM, color });
|
|
2271
|
+
var COMMON = {
|
|
2272
|
+
...TRANSFORM,
|
|
2273
|
+
kind: str,
|
|
2274
|
+
name: str,
|
|
2275
|
+
part: str,
|
|
2276
|
+
material: str,
|
|
2277
|
+
color,
|
|
2278
|
+
uv: UV,
|
|
2279
|
+
instances: list(INSTANCE),
|
|
2280
|
+
minDetail: range(0, 1, true),
|
|
2281
|
+
tags: list(str)
|
|
2282
|
+
};
|
|
2283
|
+
var vec2 = tuple(2);
|
|
2284
|
+
var BEVEL = (v) => v === false ? null : object({ thickness: num, size: num, offset: num, segments: int(1) })(v);
|
|
2285
|
+
var KINDS = {
|
|
2286
|
+
group: { fields: {} },
|
|
2287
|
+
box: { fields: { size: tuple(3), segments: tuple(3, int(1)) } },
|
|
2288
|
+
cylinder: { fields: { radiusTop: num, radiusBottom: num, height: num, radialSegments: int(1), heightSegments: int(1), openEnded: bool, thetaStart: num, thetaLength: num } },
|
|
2289
|
+
cone: { fields: { radius: num, height: num, radialSegments: int(1), heightSegments: int(1), openEnded: bool, thetaStart: num, thetaLength: num } },
|
|
2290
|
+
sphere: { fields: { radius: num, widthSegments: int(3), heightSegments: int(2), phiStart: num, phiLength: num, thetaStart: num, thetaLength: num } },
|
|
2291
|
+
plane: { fields: { width: num, height: num, widthSegments: int(1), heightSegments: int(1) } },
|
|
2292
|
+
circle: { fields: { radius: num, segments: int(3), thetaStart: num, thetaLength: num } },
|
|
2293
|
+
ring: { fields: { innerRadius: num, outerRadius: num, thetaSegments: int(3), phiSegments: int(1), thetaStart: num, thetaLength: num } },
|
|
2294
|
+
lathe: { fields: { points: list(vec2, 2), segments: int(1), phiStart: num, phiLength: num }, required: ["points"] },
|
|
2295
|
+
torus: { fields: { radius: num, tube: num, radialSegments: int(1), tubularSegments: int(1), arc: num } },
|
|
2296
|
+
extrude: {
|
|
2297
|
+
fields: { shape: object({ outer: list(vec2, 3), holes: list(list(vec2, 3)) }, ["outer"]), depth: num, steps: int(1), bevel: BEVEL },
|
|
2298
|
+
required: ["shape"]
|
|
2299
|
+
},
|
|
2300
|
+
difference: { fields: { size: tuple(3), subtract: list(object({ min: vec2, max: vec2 }, ["min", "max"])) }, required: ["size", "subtract"] },
|
|
2301
|
+
mesh: {
|
|
2302
|
+
fields: {
|
|
2303
|
+
positions: (v) => list(num)(v) ?? (v.length % 3 === 0 ? null : "expected a multiple of 3 numbers"),
|
|
2304
|
+
normals: list(num),
|
|
2305
|
+
uvs: list(num),
|
|
2306
|
+
indices: list(int(0))
|
|
2307
|
+
},
|
|
2308
|
+
required: ["positions"]
|
|
2309
|
+
},
|
|
2310
|
+
point: { fields: { data: () => null } },
|
|
2311
|
+
custom: { fields: { generator: str, params: () => null }, required: ["generator"] }
|
|
2312
|
+
};
|
|
2313
|
+
function validateNode(node, path, errors) {
|
|
2314
|
+
if (!node || typeof node !== "object" || Array.isArray(node)) {
|
|
2315
|
+
errors.push(`${path}: expected a node object`);
|
|
2316
|
+
return;
|
|
2317
|
+
}
|
|
2318
|
+
const record = node;
|
|
2319
|
+
const spec = typeof record.kind === "string" ? KINDS[record.kind] : void 0;
|
|
2320
|
+
if (!spec) {
|
|
2321
|
+
errors.push(`${path}.kind: expected one of ${Object.keys(KINDS).join(", ")}`);
|
|
2322
|
+
return;
|
|
2323
|
+
}
|
|
2324
|
+
for (const key of spec.required ?? []) if (record[key] === void 0) errors.push(`${path}.${key}: required`);
|
|
2325
|
+
for (const key of Object.keys(record)) {
|
|
2326
|
+
if (record[key] === void 0 || key === "children") continue;
|
|
2327
|
+
const check = spec.fields[key] ?? COMMON[key];
|
|
2328
|
+
if (!check) {
|
|
2329
|
+
errors.push(`${path}.${key}: unknown field for a ${String(record.kind)}`);
|
|
2330
|
+
continue;
|
|
2331
|
+
}
|
|
2332
|
+
const e = check(record[key]);
|
|
2333
|
+
if (e) errors.push(`${path}.${key}: ${e}`);
|
|
2334
|
+
}
|
|
2335
|
+
const exclusive = ["matrix", "heading"].filter((key) => record[key] !== void 0);
|
|
2336
|
+
if (record.matrix !== void 0 && (record.position !== void 0 || record.rotation !== void 0 || record.scale !== void 0 || record.heading !== void 0))
|
|
2337
|
+
errors.push(`${path}.matrix: cannot be combined with position, rotation, scale or heading`);
|
|
2338
|
+
else if (exclusive.includes("heading") && (record.rotation !== void 0 || record.scale !== void 0)) errors.push(`${path}.heading: cannot be combined with rotation or scale`);
|
|
2339
|
+
if (record.children !== void 0) {
|
|
2340
|
+
if (record.kind !== "group") errors.push(`${path}.children: only a group has children`);
|
|
2341
|
+
else if (!Array.isArray(record.children)) errors.push(`${path}.children: expected an array`);
|
|
2342
|
+
else record.children.forEach((child, index) => validateNode(child, `${path}.children[${index}]`, errors));
|
|
2343
|
+
}
|
|
2344
|
+
}
|
|
2345
|
+
function validateShape(schema) {
|
|
2346
|
+
const errors = [];
|
|
2347
|
+
if (!schema || typeof schema !== "object" || Array.isArray(schema)) return ["expected a shape object"];
|
|
2348
|
+
const record = schema;
|
|
2349
|
+
for (const key of Object.keys(record)) if (!["version", "name", "materials", "root", "lod", "cullDistance"].includes(key) && record[key] !== void 0) errors.push(`${key}: unknown field`);
|
|
2350
|
+
if (record.version !== 1) errors.push("version: expected 1");
|
|
2351
|
+
if (record.name !== void 0 && typeof record.name !== "string") errors.push("name: expected a string");
|
|
2352
|
+
if (record.materials !== void 0) {
|
|
2353
|
+
if (!record.materials || typeof record.materials !== "object" || Array.isArray(record.materials)) errors.push("materials: expected an object");
|
|
2354
|
+
else {
|
|
2355
|
+
const material = object({
|
|
2356
|
+
color,
|
|
2357
|
+
roughness: range(0, 1),
|
|
2358
|
+
metalness: range(0, 1),
|
|
2359
|
+
opacity: range(0, 1),
|
|
2360
|
+
transparent: bool,
|
|
2361
|
+
emissive: color,
|
|
2362
|
+
emissiveIntensity: (v) => typeof v === "number" && v >= 0 ? null : "expected a number >= 0",
|
|
2363
|
+
side: oneOf(["front", "back", "double"]),
|
|
2364
|
+
flatShading: bool,
|
|
2365
|
+
vertexColors: bool,
|
|
2366
|
+
depthWrite: bool
|
|
2367
|
+
});
|
|
2368
|
+
for (const [id, hint] of Object.entries(record.materials)) {
|
|
2369
|
+
const e = material(hint);
|
|
2370
|
+
if (e) errors.push(`materials.${id}: ${e}`);
|
|
2371
|
+
}
|
|
2372
|
+
}
|
|
2373
|
+
}
|
|
2374
|
+
if (record.lod !== void 0) {
|
|
2375
|
+
const e = list(object({ distance: (v) => typeof v === "number" && v >= 0 ? null : "expected a number >= 0", detail: range(0, 1, true), minSize: (v) => typeof v === "number" && v >= 0 ? null : "expected a number >= 0", minSegments: int(3) }, ["distance", "detail"]))(record.lod);
|
|
2376
|
+
if (e) errors.push(`lod${e.startsWith("[") ? "" : ": "}${e}`);
|
|
2377
|
+
}
|
|
2378
|
+
if (record.cullDistance !== void 0 && !(typeof record.cullDistance === "number" && record.cullDistance > 0)) errors.push("cullDistance: expected a number > 0");
|
|
2379
|
+
if (record.root === void 0) errors.push("root: required");
|
|
2380
|
+
else validateNode(record.root, "root", errors);
|
|
2381
|
+
return errors;
|
|
2382
|
+
}
|
|
2383
|
+
function assertValidShape(schema) {
|
|
2384
|
+
const errors = validateShape(schema);
|
|
2385
|
+
if (errors.length) throw new Error(`game-shapes: invalid shape
|
|
2386
|
+
${errors.slice(0, 20).join("\n ")}${errors.length > 20 ? `
|
|
2387
|
+
... ${errors.length - 20} more` : ""}`);
|
|
2388
|
+
}
|
|
2389
|
+
var PRIMITIVE_KINDS = /* @__PURE__ */ new Set(["box", "cylinder", "cone", "sphere", "plane", "circle", "ring", "lathe", "torus", "extrude", "difference", "mesh"]);
|
|
2390
|
+
var isPrimitiveNode = (node) => PRIMITIVE_KINDS.has(node.kind);
|
|
2391
|
+
function keyOptions(options = {}) {
|
|
2392
|
+
return {
|
|
2393
|
+
seed: options.seed ?? 0,
|
|
2394
|
+
colors: options.colors ?? "linear",
|
|
2395
|
+
indexed: options.indexed ?? true,
|
|
2396
|
+
materials: options.materials,
|
|
2397
|
+
fallbackMaterial: options.fallbackMaterial,
|
|
2398
|
+
materialAlias: options.materialAlias,
|
|
2399
|
+
instancing: options.instancing,
|
|
2400
|
+
levels: options.levels
|
|
2401
|
+
};
|
|
2402
|
+
}
|
|
2403
|
+
var shapeKey = (schema, options = {}) => hashValue({ schema, options: keyOptions(options) });
|
|
2404
|
+
var IDENTITY_UV = { u0: 0, v0: 0, u1: 1, v1: 1 };
|
|
2405
|
+
var FloatList = class {
|
|
2406
|
+
constructor(capacity = 256) {
|
|
2407
|
+
__publicField(this, "array");
|
|
2408
|
+
__publicField(this, "length", 0);
|
|
2409
|
+
this.array = new Float32Array(capacity);
|
|
2410
|
+
}
|
|
2411
|
+
reserve(extra) {
|
|
2412
|
+
if (this.length + extra <= this.array.length) return;
|
|
2413
|
+
let capacity = this.array.length * 2;
|
|
2414
|
+
while (capacity < this.length + extra) capacity *= 2;
|
|
2415
|
+
const next = new Float32Array(capacity);
|
|
2416
|
+
next.set(this.array.subarray(0, this.length));
|
|
2417
|
+
this.array = next;
|
|
2418
|
+
}
|
|
2419
|
+
done() {
|
|
2420
|
+
return this.array.slice(0, this.length);
|
|
2421
|
+
}
|
|
2422
|
+
};
|
|
2423
|
+
var IndexList = class {
|
|
2424
|
+
constructor(capacity = 256) {
|
|
2425
|
+
__publicField(this, "array");
|
|
2426
|
+
__publicField(this, "length", 0);
|
|
2427
|
+
this.array = new Uint32Array(capacity);
|
|
2428
|
+
}
|
|
2429
|
+
reserve(extra) {
|
|
2430
|
+
if (this.length + extra <= this.array.length) return;
|
|
2431
|
+
let capacity = this.array.length * 2;
|
|
2432
|
+
while (capacity < this.length + extra) capacity *= 2;
|
|
2433
|
+
const next = new Uint32Array(capacity);
|
|
2434
|
+
next.set(this.array.subarray(0, this.length));
|
|
2435
|
+
this.array = next;
|
|
2436
|
+
}
|
|
2437
|
+
};
|
|
2438
|
+
var Accumulator = class {
|
|
2439
|
+
constructor(material, part) {
|
|
2440
|
+
__publicField(this, "material", material);
|
|
2441
|
+
__publicField(this, "part", part);
|
|
2442
|
+
__publicField(this, "positions", new FloatList());
|
|
2443
|
+
__publicField(this, "normals", new FloatList());
|
|
2444
|
+
__publicField(this, "uvs", new FloatList(170));
|
|
2445
|
+
__publicField(this, "colors", new FloatList());
|
|
2446
|
+
__publicField(this, "indices", new IndexList());
|
|
2447
|
+
__publicField(this, "ranges", []);
|
|
2448
|
+
__publicField(this, "vertices", 0);
|
|
2449
|
+
}
|
|
2450
|
+
};
|
|
2451
|
+
function bake(acc, t, world, rgb, u, indexed) {
|
|
2452
|
+
const e = world;
|
|
2453
|
+
const nm = normalMatrix(world);
|
|
2454
|
+
const affine = isAffine(world);
|
|
2455
|
+
const source = t.positions;
|
|
2456
|
+
const count = source.length / 3;
|
|
2457
|
+
const order = indexed ? null : t.indices ?? null;
|
|
2458
|
+
const emitted = order ? order.length : count;
|
|
2459
|
+
acc.positions.reserve(emitted * 3);
|
|
2460
|
+
acc.normals.reserve(emitted * 3);
|
|
2461
|
+
acc.uvs.reserve(emitted * 2);
|
|
2462
|
+
if (rgb) acc.colors.reserve(emitted * 3);
|
|
2463
|
+
const P = acc.positions.array;
|
|
2464
|
+
const N = acc.normals.array;
|
|
2465
|
+
const U = acc.uvs.array;
|
|
2466
|
+
const C = acc.colors.array;
|
|
2467
|
+
let pi = acc.positions.length;
|
|
2468
|
+
let ni = acc.normals.length;
|
|
2469
|
+
let ui = acc.uvs.length;
|
|
2470
|
+
let ci = acc.colors.length;
|
|
2471
|
+
const du = u.u1 - u.u0;
|
|
2472
|
+
const dv = u.v1 - u.v0;
|
|
2473
|
+
const tn = t.normals;
|
|
2474
|
+
const tu = t.uvs;
|
|
2475
|
+
for (let k = 0; k < emitted; k++) {
|
|
2476
|
+
const i = order ? order[k] : k;
|
|
2477
|
+
const x = source[i * 3];
|
|
2478
|
+
const y = source[i * 3 + 1];
|
|
2479
|
+
const z = source[i * 3 + 2];
|
|
2480
|
+
if (affine) {
|
|
2481
|
+
P[pi++] = e[0] * x + e[4] * y + e[8] * z + e[12];
|
|
2482
|
+
P[pi++] = e[1] * x + e[5] * y + e[9] * z + e[13];
|
|
2483
|
+
P[pi++] = e[2] * x + e[6] * y + e[10] * z + e[14];
|
|
2484
|
+
} else {
|
|
2485
|
+
const p = transformPoint(e, x, y, z);
|
|
2486
|
+
P[pi++] = p[0];
|
|
2487
|
+
P[pi++] = p[1];
|
|
2488
|
+
P[pi++] = p[2];
|
|
2489
|
+
}
|
|
2490
|
+
const nx = tn[i * 3];
|
|
2491
|
+
const ny = tn[i * 3 + 1];
|
|
2492
|
+
const nz = tn[i * 3 + 2];
|
|
2493
|
+
const vx = nm[0] * nx + nm[3] * ny + nm[6] * nz;
|
|
2494
|
+
const vy = nm[1] * nx + nm[4] * ny + nm[7] * nz;
|
|
2495
|
+
const vz = nm[2] * nx + nm[5] * ny + nm[8] * nz;
|
|
2496
|
+
const s = 1 / (Math.sqrt(vx * vx + vy * vy + vz * vz) || 1);
|
|
2497
|
+
N[ni++] = vx * s;
|
|
2498
|
+
N[ni++] = vy * s;
|
|
2499
|
+
N[ni++] = vz * s;
|
|
2500
|
+
U[ui++] = u.u0 + tu[i * 2] * du;
|
|
2501
|
+
U[ui++] = u.v0 + tu[i * 2 + 1] * dv;
|
|
2502
|
+
if (rgb) {
|
|
2503
|
+
C[ci++] = rgb[0];
|
|
2504
|
+
C[ci++] = rgb[1];
|
|
2505
|
+
C[ci++] = rgb[2];
|
|
2506
|
+
}
|
|
2507
|
+
}
|
|
2508
|
+
acc.positions.length = pi;
|
|
2509
|
+
acc.normals.length = ni;
|
|
2510
|
+
acc.uvs.length = ui;
|
|
2511
|
+
acc.colors.length = ci;
|
|
2512
|
+
if (indexed) {
|
|
2513
|
+
const base = acc.vertices;
|
|
2514
|
+
const idx = t.indices;
|
|
2515
|
+
const n = idx ? idx.length : count;
|
|
2516
|
+
acc.indices.reserve(n);
|
|
2517
|
+
const I = acc.indices.array;
|
|
2518
|
+
let ii = acc.indices.length;
|
|
2519
|
+
for (let k = 0; k < n; k++) I[ii++] = base + (idx ? idx[k] : k);
|
|
2520
|
+
acc.indices.length = ii;
|
|
2521
|
+
}
|
|
2522
|
+
acc.vertices += emitted;
|
|
2523
|
+
}
|
|
2524
|
+
function boundsOf(positions) {
|
|
2525
|
+
const min = [Infinity, Infinity, Infinity];
|
|
2526
|
+
const max = [-Infinity, -Infinity, -Infinity];
|
|
2527
|
+
for (let i = 0; i < positions.length; i += 3) {
|
|
2528
|
+
for (let axis = 0; axis < 3; axis++) {
|
|
2529
|
+
const v = positions[i + axis];
|
|
2530
|
+
if (v < min[axis]) min[axis] = v;
|
|
2531
|
+
if (v > max[axis]) max[axis] = v;
|
|
2532
|
+
}
|
|
2533
|
+
}
|
|
2534
|
+
return { min, max };
|
|
2535
|
+
}
|
|
2536
|
+
function finish(acc, indexed, withColors) {
|
|
2537
|
+
const positions = acc.positions.done();
|
|
2538
|
+
const indices = indexed ? acc.vertices > 65535 ? acc.indices.array.slice(0, acc.indices.length) : Uint16Array.from(acc.indices.array.subarray(0, acc.indices.length)) : void 0;
|
|
2539
|
+
return {
|
|
2540
|
+
material: acc.material,
|
|
2541
|
+
part: acc.part,
|
|
2542
|
+
positions,
|
|
2543
|
+
normals: acc.normals.done(),
|
|
2544
|
+
uvs: acc.uvs.done(),
|
|
2545
|
+
...withColors ? { colors: acc.colors.done() } : {},
|
|
2546
|
+
...indices ? { indices } : {},
|
|
2547
|
+
ranges: acc.ranges,
|
|
2548
|
+
bounds: boundsOf(positions)
|
|
2549
|
+
};
|
|
2550
|
+
}
|
|
2551
|
+
var radiusOf = (t, world) => {
|
|
2552
|
+
let r = 0;
|
|
2553
|
+
const p = t.positions;
|
|
2554
|
+
for (let i = 0; i < p.length; i += 3) r = Math.max(r, Math.hypot(p[i], p[i + 1], p[i + 2]));
|
|
2555
|
+
const sx = Math.hypot(world[0], world[1], world[2]);
|
|
2556
|
+
const sy = Math.hypot(world[4], world[5], world[6]);
|
|
2557
|
+
const sz = Math.hypot(world[8], world[9], world[10]);
|
|
2558
|
+
return r * Math.max(sx, sy, sz);
|
|
2559
|
+
};
|
|
2560
|
+
function expandCustom(node, settings, detail, path) {
|
|
2561
|
+
const generator = settings.generators?.[node.generator] ?? getGenerator(node.generator);
|
|
2562
|
+
if (!generator) throw new Error(`game-shapes: no generator "${node.generator}" (register it with registerGenerator, in the worker too) at ${path}`);
|
|
2563
|
+
const generated = generator(node.params, { random: streamFor(settings.seed, node.generator, path), seed: settings.seed, detail, path });
|
|
2564
|
+
const { kind: _kind, generator: _generator, params: _params, ...common } = node;
|
|
2565
|
+
return { ...common, kind: "group", children: [generated] };
|
|
2566
|
+
}
|
|
2567
|
+
var leafKey = (node, material, color2, uv, level) => `${node.kind}|${canonicalJson(primitiveParameters(node, level))}|${material}|${color2}|${uv.u0},${uv.v0},${uv.u1},${uv.v1}`;
|
|
2568
|
+
function runLevel(schema, settings, level) {
|
|
2569
|
+
const bakeParts = level.index > 0;
|
|
2570
|
+
const root = { part: null, accs: /* @__PURE__ */ new Map() };
|
|
2571
|
+
const targets = [root];
|
|
2572
|
+
const partTargets = /* @__PURE__ */ new Map();
|
|
2573
|
+
const parts = [];
|
|
2574
|
+
const points = [];
|
|
2575
|
+
const instanced = [];
|
|
2576
|
+
let primitives = 0;
|
|
2577
|
+
const space = settings.colors === "none" ? null : settings.colors;
|
|
2578
|
+
const channels = /* @__PURE__ */ new Map();
|
|
2579
|
+
const rgbOf = (hex) => {
|
|
2580
|
+
if (!space) return null;
|
|
2581
|
+
let c = channels.get(hex);
|
|
2582
|
+
if (!c) {
|
|
2583
|
+
c = colorChannels(hex, space);
|
|
2584
|
+
channels.set(hex, c);
|
|
2585
|
+
}
|
|
2586
|
+
return c;
|
|
2587
|
+
};
|
|
2588
|
+
const resolveMaterial = (id) => {
|
|
2589
|
+
let out = id;
|
|
2590
|
+
if (settings.materials && !settings.materials.has(out)) out = settings.fallback ?? out;
|
|
2591
|
+
return settings.alias?.[out] ?? out;
|
|
2592
|
+
};
|
|
2593
|
+
const accOf = (target, material) => {
|
|
2594
|
+
let acc = target.accs.get(material);
|
|
2595
|
+
if (!acc) {
|
|
2596
|
+
acc = new Accumulator(material, target.part);
|
|
2597
|
+
target.accs.set(material, acc);
|
|
2598
|
+
}
|
|
2599
|
+
return acc;
|
|
2600
|
+
};
|
|
2601
|
+
const autoCounts = /* @__PURE__ */ new Map();
|
|
2602
|
+
const autoGroups = /* @__PURE__ */ new Map();
|
|
2603
|
+
const count = (node, inherited, path) => {
|
|
2604
|
+
if (node.minDetail !== void 0 && level.detail < node.minDetail) return;
|
|
2605
|
+
if (node.kind === "custom") return count(expandCustom(node, settings, level.detail, path), inherited, path);
|
|
2606
|
+
const next = {
|
|
2607
|
+
material: node.material !== void 0 ? resolveMaterial(node.material) : inherited.material,
|
|
2608
|
+
color: node.color !== void 0 ? colorHex(node.color) : inherited.color,
|
|
2609
|
+
uv: node.uv ?? inherited.uv,
|
|
2610
|
+
inPart: inherited.inPart || node.part !== void 0 && !bakeParts
|
|
2611
|
+
};
|
|
2612
|
+
if (node.instances) return;
|
|
2613
|
+
if (node.kind === "group") {
|
|
2614
|
+
node.children?.forEach((child, index) => count(child, next, `${path}/${index}`));
|
|
2615
|
+
return;
|
|
2616
|
+
}
|
|
2617
|
+
if (isPrimitiveNode(node) && node.name === void 0 && !next.inPart) {
|
|
2618
|
+
const key = leafKey(node, next.material, next.color, next.uv, level);
|
|
2619
|
+
autoCounts.set(key, (autoCounts.get(key) ?? 0) + 1);
|
|
2620
|
+
}
|
|
2621
|
+
};
|
|
2622
|
+
const rootMaterial = resolveMaterial("default");
|
|
2623
|
+
if (settings.autoMin !== void 0) count(schema.root, { material: rootMaterial, color: 16777215, uv: IDENTITY_UV, inPart: false }, "root");
|
|
2624
|
+
const visit = (node, parent, inherited, path) => {
|
|
2625
|
+
if (node.minDetail !== void 0 && level.detail < node.minDetail) return;
|
|
2626
|
+
if (node.kind === "custom") return visit(expandCustom(node, settings, level.detail, path), parent, inherited, path);
|
|
2627
|
+
let target = inherited.target;
|
|
2628
|
+
let parentWorld = parent;
|
|
2629
|
+
if (node.part !== void 0) {
|
|
2630
|
+
if (bakeParts) {
|
|
2631
|
+
parentWorld = null;
|
|
2632
|
+
} else {
|
|
2633
|
+
let partTarget = partTargets.get(node.part);
|
|
2634
|
+
if (!partTarget) {
|
|
2635
|
+
partTarget = { part: node.part, accs: /* @__PURE__ */ new Map() };
|
|
2636
|
+
partTargets.set(node.part, partTarget);
|
|
2637
|
+
targets.push(partTarget);
|
|
2638
|
+
const { position, rotation, rotationOrder, scale, heading, matrix } = node;
|
|
2639
|
+
parts.push({
|
|
2640
|
+
id: node.part,
|
|
2641
|
+
...position ? { position } : {},
|
|
2642
|
+
...rotation ? { rotation } : {},
|
|
2643
|
+
...rotationOrder ? { rotationOrder } : {},
|
|
2644
|
+
...scale !== void 0 ? { scale } : {},
|
|
2645
|
+
...heading !== void 0 ? { heading } : {},
|
|
2646
|
+
...matrix ? { matrix } : { matrix: asMat4(matrixOf(node)) }
|
|
2647
|
+
});
|
|
2648
|
+
}
|
|
2649
|
+
target = partTarget;
|
|
2650
|
+
}
|
|
2651
|
+
}
|
|
2652
|
+
const isPart = node.part !== void 0 && !bakeParts;
|
|
2653
|
+
const local = isPart ? null : matrixOf(node);
|
|
2654
|
+
const world = isPart ? null : local ? parentWorld ? multiply(parentWorld, local) : local : parentWorld;
|
|
2655
|
+
const next = {
|
|
2656
|
+
material: node.material !== void 0 ? resolveMaterial(node.material) : inherited.material,
|
|
2657
|
+
color: node.color !== void 0 ? colorHex(node.color) : inherited.color,
|
|
2658
|
+
uv: node.uv ?? inherited.uv,
|
|
2659
|
+
target
|
|
2660
|
+
};
|
|
2661
|
+
if (node.instances) {
|
|
2662
|
+
drawInstanced(node, parentWorld, next, path);
|
|
2663
|
+
return;
|
|
2664
|
+
}
|
|
2665
|
+
const snapshot = node.name !== void 0 ? new Map([...target.accs].map(([m, a]) => [m, { v: a.vertices, i: a.indices.length }])) : null;
|
|
2666
|
+
if (node.kind === "group") {
|
|
2667
|
+
node.children?.forEach((child, index) => visit(child, world, next, `${path}/${index}`));
|
|
2668
|
+
} else if (node.kind === "point") {
|
|
2669
|
+
const p = node.position ?? [0, 0, 0];
|
|
2670
|
+
points.push({
|
|
2671
|
+
...node.name !== void 0 ? { name: node.name } : {},
|
|
2672
|
+
part: target.part,
|
|
2673
|
+
position: parent ? transformPoint(parent, p[0], p[1], p[2]) : [p[0], p[1], p[2]],
|
|
2674
|
+
...node.data !== void 0 ? { data: node.data } : {},
|
|
2675
|
+
...node.tags ? { tags: node.tags } : {}
|
|
2676
|
+
});
|
|
2677
|
+
} else if (isPrimitiveNode(node)) {
|
|
2678
|
+
const template = buildPrimitive(node, level);
|
|
2679
|
+
const m = world ?? identity4();
|
|
2680
|
+
if (level.minSize !== void 0 && radiusOf(template, m) < level.minSize) return;
|
|
2681
|
+
primitives++;
|
|
2682
|
+
if (settings.autoMin !== void 0 && node.name === void 0 && target.part === null) {
|
|
2683
|
+
const key = leafKey(node, next.material, next.color, next.uv, level);
|
|
2684
|
+
if ((autoCounts.get(key) ?? 0) >= settings.autoMin) {
|
|
2685
|
+
let group = autoGroups.get(key);
|
|
2686
|
+
if (!group) {
|
|
2687
|
+
group = { node, material: next.material, color: next.color, part: null, matrices: [] };
|
|
2688
|
+
autoGroups.set(key, group);
|
|
2689
|
+
}
|
|
2690
|
+
group.matrices.push(...m);
|
|
2691
|
+
return;
|
|
2692
|
+
}
|
|
2693
|
+
}
|
|
2694
|
+
bake(accOf(target, next.material), template, m, rgbOf(next.color), next.uv, settings.indexed);
|
|
2695
|
+
}
|
|
2696
|
+
if (snapshot) {
|
|
2697
|
+
for (const [material, acc] of target.accs) {
|
|
2698
|
+
const before = snapshot.get(material) ?? { v: 0, i: 0 };
|
|
2699
|
+
if (acc.vertices === before.v) continue;
|
|
2700
|
+
acc.ranges.push({
|
|
2701
|
+
name: node.name,
|
|
2702
|
+
...node.tags ? { tags: node.tags } : {},
|
|
2703
|
+
vertexStart: before.v,
|
|
2704
|
+
vertexCount: acc.vertices - before.v,
|
|
2705
|
+
indexStart: before.i,
|
|
2706
|
+
indexCount: acc.indices.length - before.i
|
|
2707
|
+
});
|
|
2708
|
+
}
|
|
2709
|
+
}
|
|
2710
|
+
};
|
|
2711
|
+
const drawInstanced = (node, parentWorld, inherited, path) => {
|
|
2712
|
+
const collector = { part: inherited.target.part, accs: /* @__PURE__ */ new Map() };
|
|
2713
|
+
const { instances, ...single } = node;
|
|
2714
|
+
const before = parts.length;
|
|
2715
|
+
visit(single, null, { ...inherited, target: collector }, path);
|
|
2716
|
+
if (parts.length !== before) throw new Error(`game-shapes: an instanced node cannot hold parts (${path})`);
|
|
2717
|
+
const matrices = new Float32Array(instances.length * 16);
|
|
2718
|
+
const hasColors = instances.some((instance) => instance.color !== void 0);
|
|
2719
|
+
const colors = hasColors ? new Float32Array(instances.length * 3) : void 0;
|
|
2720
|
+
instances.forEach((instance, index) => {
|
|
2721
|
+
const local = matrixOf(instance);
|
|
2722
|
+
const m = local ? parentWorld ? multiply(parentWorld, local) : local : parentWorld ?? identity4();
|
|
2723
|
+
matrices.set(m, index * 16);
|
|
2724
|
+
if (colors) colors.set(colorChannels(colorHex(instance.color), space ?? "srgb"), index * 3);
|
|
2725
|
+
});
|
|
2726
|
+
for (const acc of collector.accs.values()) {
|
|
2727
|
+
const batch = finish(acc, settings.indexed, false);
|
|
2728
|
+
instanced.push({
|
|
2729
|
+
material: acc.material,
|
|
2730
|
+
part: collector.part,
|
|
2731
|
+
...node.name !== void 0 ? { name: node.name } : {},
|
|
2732
|
+
geometry: { type: "BufferGeometry", parameters: {}, positions: batch.positions, normals: batch.normals, uvs: batch.uvs, ...batch.indices ? { indices: batch.indices } : {}, groups: [] },
|
|
2733
|
+
matrices: matrices.slice(),
|
|
2734
|
+
...colors ? { colors: colors.slice() } : {},
|
|
2735
|
+
count: instances.length
|
|
2736
|
+
});
|
|
2737
|
+
}
|
|
2738
|
+
};
|
|
2739
|
+
visit(schema.root, null, { material: rootMaterial, color: 16777215, uv: IDENTITY_UV, target: root }, "root");
|
|
2740
|
+
for (const group of autoGroups.values()) {
|
|
2741
|
+
const template = buildPrimitive(group.node, level);
|
|
2742
|
+
const n = group.matrices.length / 16;
|
|
2743
|
+
const rgb = colorChannels(group.color, space ?? "srgb");
|
|
2744
|
+
const colors = new Float32Array(n * 3);
|
|
2745
|
+
for (let i = 0; i < n; i++) colors.set(rgb, i * 3);
|
|
2746
|
+
instanced.push({
|
|
2747
|
+
material: group.material,
|
|
2748
|
+
part: null,
|
|
2749
|
+
geometry: { type: template.type, parameters: template.parameters, positions: template.positions.slice(), normals: template.normals.slice(), uvs: template.uvs.slice(), ...template.indices ? { indices: template.indices.slice() } : {}, groups: template.groups.map((g) => ({ ...g })) },
|
|
2750
|
+
matrices: new Float32Array(group.matrices),
|
|
2751
|
+
...space ? { colors } : {},
|
|
2752
|
+
count: n
|
|
2753
|
+
});
|
|
2754
|
+
}
|
|
2755
|
+
const batches = [];
|
|
2756
|
+
for (const target of targets) for (const acc of target.accs.values()) if (acc.vertices > 0 && (!settings.indexed || acc.indices.length > 0)) batches.push(finish(acc, settings.indexed, space !== null));
|
|
2757
|
+
return { level: { distance: level.distance, detail: level.detail, batches, instanced }, parts, points, primitives };
|
|
2758
|
+
}
|
|
2759
|
+
function generate(schema, options = {}) {
|
|
2760
|
+
if (options.validate !== false) assertValidShape(schema);
|
|
2761
|
+
const settings = {
|
|
2762
|
+
colors: options.colors ?? "linear",
|
|
2763
|
+
indexed: options.indexed ?? true,
|
|
2764
|
+
...options.materials ? { materials: new Set(options.materials) } : {},
|
|
2765
|
+
...options.fallbackMaterial !== void 0 ? { fallback: options.fallbackMaterial } : {},
|
|
2766
|
+
...options.materialAlias ? { alias: options.materialAlias } : {},
|
|
2767
|
+
seed: options.seed ?? 0,
|
|
2768
|
+
...options.generators ? { generators: options.generators } : {},
|
|
2769
|
+
...options.instancing ? { autoMin: Math.max(2, options.instancing.minCount) } : {}
|
|
2770
|
+
};
|
|
2771
|
+
const specs = [{ distance: 0, detail: 1, index: 0 }, ...(schema.lod ?? []).map((rule, index) => ({ ...rule, index: index + 1 }))];
|
|
2772
|
+
const wanted = options.levels ? specs.filter((spec) => options.levels.includes(spec.index)) : specs;
|
|
2773
|
+
let parts = [];
|
|
2774
|
+
let points = [];
|
|
2775
|
+
let primitives = 0;
|
|
2776
|
+
const levels = [];
|
|
2777
|
+
for (const spec of wanted) {
|
|
2778
|
+
const result = runLevel(schema, settings, spec);
|
|
2779
|
+
if (spec.index === 0 || levels.length === 0) {
|
|
2780
|
+
parts = result.parts;
|
|
2781
|
+
points = result.points;
|
|
2782
|
+
primitives = result.primitives;
|
|
2783
|
+
}
|
|
2784
|
+
levels.push(result.level);
|
|
2785
|
+
}
|
|
2786
|
+
const first = levels[0];
|
|
2787
|
+
const vertices = first ? first.batches.reduce((sum, batch) => sum + batch.positions.length / 3, 0) : 0;
|
|
2788
|
+
const triangles = first ? first.batches.reduce((sum, batch) => sum + (batch.indices ? batch.indices.length : batch.positions.length / 3) / 3, 0) + first.instanced.reduce((sum, batch) => sum + (batch.geometry.indices ? batch.geometry.indices.length : batch.geometry.positions.length / 3) / 3 * batch.count, 0) : 0;
|
|
2789
|
+
return {
|
|
2790
|
+
key: options.key === false ? "" : options.key ?? shapeKey(schema, options),
|
|
2791
|
+
...schema.name !== void 0 ? { name: schema.name } : {},
|
|
2792
|
+
levels,
|
|
2793
|
+
parts,
|
|
2794
|
+
points,
|
|
2795
|
+
...schema.materials ? { materials: schema.materials } : {},
|
|
2796
|
+
...schema.cullDistance !== void 0 ? { cullDistance: schema.cullDistance } : {},
|
|
2797
|
+
stats: { vertices, triangles, batches: first ? first.batches.length : 0, instances: first ? first.instanced.reduce((sum, batch) => sum + batch.count, 0) : 0, primitives }
|
|
2798
|
+
};
|
|
2799
|
+
}
|
|
2800
|
+
|
|
2801
|
+
// ../game-props3d/dist/index.js
|
|
2802
|
+
var T = (id, interior, w, d, h, y, solid, group) => ({ id, interior, w, d, h, y, solid, group });
|
|
2803
|
+
var TYPE_DEFS = [
|
|
2804
|
+
T("ceiling_light", true, 0.4, 0.4, 0.07, 0, false, "ceiling"),
|
|
2805
|
+
T("pendant", true, 0.34, 0.34, 1, 0, false, "ceiling"),
|
|
2806
|
+
T("track_spot", true, 1.2, 0.1, 0.2, 0, false, "ceiling"),
|
|
2807
|
+
T("chandelier", true, 0.7, 0.7, 0.7, 0, false, "ceiling"),
|
|
2808
|
+
T("wall_ac", true, 0.9, 0.2, 0.28, 2.15, false, "wall"),
|
|
2809
|
+
T("picture_small", true, 0.35, 0.03, 0.45, 1.3, false, "wall"),
|
|
2810
|
+
T("picture_large", true, 1, 0.04, 0.7, 1.2, false, "wall"),
|
|
2811
|
+
T("mirror", true, 0.6, 0.03, 1.2, 0.9, false, "wall"),
|
|
2812
|
+
T("curtain", true, 1.6, 0.12, 2.3, 0.1, false, "wall"),
|
|
2813
|
+
T("blinds", true, 1.2, 0.08, 1.3, 1, false, "wall"),
|
|
2814
|
+
T("flat_tv", true, 1.2, 0.06, 0.7, 1, false, "wall"),
|
|
2815
|
+
T("floor_plant", true, 0.5, 0.5, 1.1, 0, true, "plants"),
|
|
2816
|
+
T("table_plant", true, 0.2, 0.2, 0.3, 0, false, "plants"),
|
|
2817
|
+
T("hanging_plant", true, 0.5, 0.5, 1, 0, false, "plants"),
|
|
2818
|
+
T("tv_stand", true, 1.6, 0.42, 1.05, 0, true, "furniture"),
|
|
2819
|
+
T("breakfast_bar", true, 2, 1.6, 0.95, 0, true, "furniture"),
|
|
2820
|
+
T("sofa_l", true, 2.8, 1.7, 0.85, 0, true, "furniture"),
|
|
2821
|
+
T("double_bed_modern", true, 1.6, 2.1, 1.2, 0, true, "furniture"),
|
|
2822
|
+
T("home_office", true, 1.4, 0.75, 1.2, 0, true, "furniture"),
|
|
2823
|
+
T("closet", true, 2.2, 0.6, 2.3, 0, true, "furniture"),
|
|
2824
|
+
T("vanity", true, 0.6, 0.45, 1.5, 0, true, "furniture"),
|
|
2825
|
+
T("ac_condenser", false, 0.8, 0.3, 0.55, 0.5, true, "yard"),
|
|
2826
|
+
T("lawn", false, 4, 4, 0.02, 0, false, "floor"),
|
|
2827
|
+
T("tile_floor", false, 4, 3, 0.03, 0, false, "floor"),
|
|
2828
|
+
T("concrete", false, 4, 4, 0.03, 0, false, "floor"),
|
|
2829
|
+
T("deck_floor", false, 4, 3, 0.04, 0, false, "floor"),
|
|
2830
|
+
T("dirt", false, 4, 4, 0.02, 0, false, "floor"),
|
|
2831
|
+
T("gourmet_counter", false, 4.2, 3, 2.9, 0, true, "yard"),
|
|
2832
|
+
T("sauna", false, 2, 1.8, 2.1, 0, true, "yard"),
|
|
2833
|
+
T("soaking_tub", false, 1.3, 1.3, 0.8, 0, true, "yard"),
|
|
2834
|
+
T("pool", false, 4.5, 2.8, 0.1, 0, true, "yard"),
|
|
2835
|
+
T("deck", false, 3.5, 3, 0.22, 0, true, "yard"),
|
|
2836
|
+
T("sun_lounger", false, 0.62, 1.9, 0.5, 0, true, "yard"),
|
|
2837
|
+
T("garden_bed", false, 2, 1, 0.9, 0, true, "yard"),
|
|
2838
|
+
T("fruit_tree", false, 2.4, 2.4, 3.2, 0, true, "yard"),
|
|
2839
|
+
T("palm_tree", false, 2, 2, 4.5, 0, true, "yard"),
|
|
2840
|
+
T("pergola", false, 3.5, 3, 2.4, 0, true, "yard"),
|
|
2841
|
+
T("slide", false, 0.8, 2.6, 1.5, 0, true, "yard"),
|
|
2842
|
+
T("swing", false, 2.2, 1.7, 2.2, 0, true, "yard"),
|
|
2843
|
+
T("bbq_simple", false, 0.9, 0.6, 1, 0, true, "yard"),
|
|
2844
|
+
T("garage_car", false, 1.8, 4.2, 1.45, 0, true, "yard"),
|
|
2845
|
+
T("window_boards", true, 1.2, 0.06, 1.15, 1, false, "house_state"),
|
|
2846
|
+
T("barricade", true, 1.5, 0.7, 1.8, 0, true, "house_state"),
|
|
2847
|
+
T("sandbags", false, 1.6, 0.5, 0.6, 0, true, "house_state"),
|
|
2848
|
+
T("barbed_wire", false, 4, 0.2, 0.6, 0, false, "house_state"),
|
|
2849
|
+
T("razor_wire", false, 4, 0.6, 0.6, 0, false, "house_state"),
|
|
2850
|
+
T("water_bottle_crates", true, 0.9, 0.6, 0.65, 0, true, "house_state"),
|
|
2851
|
+
T("canned_food", true, 0.4, 0.3, 0.3, 0, false, "house_state"),
|
|
2852
|
+
T("water_jug", true, 0.3, 0.3, 0.5, 0, true, "house_state"),
|
|
2853
|
+
T("floor_mattress", true, 0.9, 1.9, 0.3, 0, false, "house_state"),
|
|
2854
|
+
T("lantern", true, 0.16, 0.16, 0.3, 0, false, "house_state"),
|
|
2855
|
+
T("candles", true, 0.25, 0.25, 0.2, 0, false, "house_state"),
|
|
2856
|
+
T("open_suitcases", true, 1.5, 0.7, 0.6, 0, false, "house_state"),
|
|
2857
|
+
T("clothes_on_floor", true, 1.2, 1, 0.05, 0, false, "house_state"),
|
|
2858
|
+
T("fallen_chair", true, 0.5, 0.5, 0.5, 0, false, "house_state"),
|
|
2859
|
+
T("blood_stains", true, 1.2, 1.2, 0.01, 0, false, "house_state"),
|
|
2860
|
+
T("open_door", true, 0.9, 0.15, 2.15, 0, false, "house_state")
|
|
2861
|
+
];
|
|
2862
|
+
var DEFS = Object.fromEntries(TYPE_DEFS.map((t) => [t.id, t]));
|
|
2863
|
+
var LEAF = (() => {
|
|
2864
|
+
const f = [0, 0, 0, -0.5, 0.4, 0, 0.5, 0.4, 0, 0, 1, 0];
|
|
2865
|
+
const positions = [];
|
|
2866
|
+
const normals = [];
|
|
2867
|
+
const uvs = [];
|
|
2868
|
+
const indices = [];
|
|
2869
|
+
for (const side of [1, -1]) {
|
|
2870
|
+
const base = positions.length / 3;
|
|
2871
|
+
for (let i = 0; i < 4; i++) {
|
|
2872
|
+
positions.push(f[i * 3], f[i * 3 + 1], f[i * 3 + 2]);
|
|
2873
|
+
normals.push(0, 0, side);
|
|
2874
|
+
uvs.push(f[i * 3] + 0.5, f[i * 3 + 1]);
|
|
2875
|
+
}
|
|
2876
|
+
if (side === 1) indices.push(base, base + 2, base + 1, base + 1, base + 2, base + 3);
|
|
2877
|
+
else indices.push(base, base + 1, base + 2, base + 1, base + 3, base + 2);
|
|
2878
|
+
}
|
|
2879
|
+
return { positions, normals, uvs, indices };
|
|
2880
|
+
})();
|
|
2881
|
+
function createShapeRecorder(options = {}) {
|
|
2882
|
+
const cyl = options.cylinderSegments ?? 12;
|
|
2883
|
+
const hex2 = options.hexSegments ?? 6;
|
|
2884
|
+
const [sphereW, sphereH] = options.sphereSegments ?? [12, 8];
|
|
2885
|
+
const root = [];
|
|
2886
|
+
const parts = [];
|
|
2887
|
+
const partChildren = /* @__PURE__ */ new Map();
|
|
2888
|
+
let current = root;
|
|
2889
|
+
let lastFrame = null;
|
|
2890
|
+
const into = (M) => {
|
|
2891
|
+
const x = M?.x ?? 0;
|
|
2892
|
+
const z = M?.z ?? 0;
|
|
2893
|
+
const ry = M?.ry ?? 0;
|
|
2894
|
+
const last = lastFrame;
|
|
2895
|
+
if (last && last.list === current && current[current.length - 1] === last.group && Object.is(last.x, x) && Object.is(last.z, z) && Object.is(last.ry, ry)) return last.group.children;
|
|
2896
|
+
const group = { kind: "group", position: [x, 0, z], heading: ry, children: [] };
|
|
2897
|
+
current.push(group);
|
|
2898
|
+
lastFrame = { list: current, group, x, z, ry };
|
|
2899
|
+
return group.children;
|
|
2900
|
+
};
|
|
2901
|
+
const api = {
|
|
2902
|
+
box(M, material, x, y, z, w, h, d, color2, ry, rx, rz) {
|
|
2903
|
+
into(M).push({ kind: "box", material, color: color2, position: [x, y + h / 2, z], rotation: [rx, ry, rz], rotationOrder: "YXZ", scale: [w, h, d] });
|
|
2904
|
+
},
|
|
2905
|
+
cylinder(M, material, x, y, z, r, h, color2, rx, rz, shape) {
|
|
2906
|
+
const tilted = rx !== 0 || rz !== 0;
|
|
2907
|
+
const cy = shape === "sphere" ? y + h / 2 : tilted ? y : y + h / 2;
|
|
2908
|
+
const sy = shape === "sphere" ? h / 2 : h;
|
|
2909
|
+
const place = { material, color: color2, position: [x, cy, z], rotation: [rx, 0, rz], rotationOrder: "YXZ", scale: [r, sy, r] };
|
|
2910
|
+
const list2 = into(M);
|
|
2911
|
+
switch (shape) {
|
|
2912
|
+
case "cyl6":
|
|
2913
|
+
list2.push({ kind: "cylinder", radiusTop: 1, radiusBottom: 1, height: 1, radialSegments: hex2, ...place });
|
|
2914
|
+
break;
|
|
2915
|
+
case "cone":
|
|
2916
|
+
list2.push({ kind: "cylinder", radiusTop: 0, radiusBottom: 1, height: 1, radialSegments: cyl, ...place });
|
|
2917
|
+
break;
|
|
2918
|
+
case "sphere":
|
|
2919
|
+
list2.push({ kind: "sphere", radius: 1, widthSegments: sphereW, heightSegments: sphereH, ...place });
|
|
2920
|
+
break;
|
|
2921
|
+
default:
|
|
2922
|
+
list2.push({ kind: "cylinder", radiusTop: 1, radiusBottom: 1, height: 1, radialSegments: cyl, ...place });
|
|
2923
|
+
}
|
|
2924
|
+
},
|
|
2925
|
+
leaf(M, x, y, z, w, h, color2, ry, rx) {
|
|
2926
|
+
into(M).push({ kind: "mesh", ...LEAF, material: "plant", color: color2, position: [x, y, z], rotation: [rx, ry, 0], rotationOrder: "YXZ", scale: [w, h, 1] });
|
|
2927
|
+
},
|
|
2928
|
+
plane(M, material, x, y, z, w, h, color2, ry, rx, rz, uv) {
|
|
2929
|
+
into(M).push({
|
|
2930
|
+
kind: "plane",
|
|
2931
|
+
width: 1,
|
|
2932
|
+
height: 1,
|
|
2933
|
+
material,
|
|
2934
|
+
color: color2,
|
|
2935
|
+
position: [x, y, z],
|
|
2936
|
+
rotation: [rx, ry, rz],
|
|
2937
|
+
rotationOrder: "YXZ",
|
|
2938
|
+
scale: [w, h, 1],
|
|
2939
|
+
...uv ? { uv: { u0: uv.u0, v0: uv.v0, u1: uv.u1, v1: uv.v1 } } : {}
|
|
2940
|
+
});
|
|
2941
|
+
},
|
|
2942
|
+
light(M, x, y, z, color2, range2, kind) {
|
|
2943
|
+
into(M).push({ kind: "point", position: [x, y, z], data: { light: true, color: color2, range: range2, kind } });
|
|
2944
|
+
},
|
|
2945
|
+
part(id, pivot, ry, draw) {
|
|
2946
|
+
let children = partChildren.get(id);
|
|
2947
|
+
if (!children) {
|
|
2948
|
+
children = [];
|
|
2949
|
+
partChildren.set(id, children);
|
|
2950
|
+
parts.push({ id, pivot: [pivot[0], pivot[1], pivot[2]], ry });
|
|
2951
|
+
current.push({ kind: "group", part: id, position: [pivot[0], pivot[1], pivot[2]], rotation: [0, ry, 0], children });
|
|
2952
|
+
}
|
|
2953
|
+
const outer = current;
|
|
2954
|
+
current = children;
|
|
2955
|
+
try {
|
|
2956
|
+
draw();
|
|
2957
|
+
} finally {
|
|
2958
|
+
current = outer;
|
|
2959
|
+
}
|
|
2960
|
+
}
|
|
2961
|
+
};
|
|
2962
|
+
return {
|
|
2963
|
+
api,
|
|
2964
|
+
parts,
|
|
2965
|
+
schema: () => ({ version: 1, ...options.name !== void 0 ? { name: options.name } : {}, root: { kind: "group", children: root } })
|
|
2966
|
+
};
|
|
740
2967
|
}
|
|
741
2968
|
|
|
742
2969
|
// src/types.ts
|
|
@@ -954,27 +3181,27 @@ vec3 batchRotate(vec4 q, vec3 v) { return v + 2.0 * cross(q.xyz, cross(q.xyz, v)
|
|
|
954
3181
|
const map = /* @__PURE__ */ new Map();
|
|
955
3182
|
for (const g of groups) {
|
|
956
3183
|
g.updateMatrix();
|
|
957
|
-
for (const
|
|
958
|
-
const key = `${String(
|
|
3184
|
+
for (const mesh2 of meshesOf(g)) {
|
|
3185
|
+
const key = `${String(mesh2.userData.material)}|${mesh2.material.uuid}`;
|
|
959
3186
|
let e = map.get(key);
|
|
960
3187
|
if (!e) {
|
|
961
|
-
e = { base:
|
|
3188
|
+
e = { base: mesh2.material, pieces: [], mesh: mesh2 };
|
|
962
3189
|
map.set(key, e);
|
|
963
3190
|
}
|
|
964
|
-
e.pieces.push({ geometry:
|
|
3191
|
+
e.pieces.push({ geometry: mesh2.geometry, matrix: g.matrix, extra: extra(g, mesh2.geometry.getAttribute("position").count) });
|
|
965
3192
|
}
|
|
966
3193
|
}
|
|
967
3194
|
return map;
|
|
968
3195
|
};
|
|
969
3196
|
const finishMesh = (name, key, geometry, template, set, group) => {
|
|
970
|
-
const
|
|
971
|
-
|
|
972
|
-
|
|
973
|
-
|
|
974
|
-
|
|
975
|
-
|
|
976
|
-
|
|
977
|
-
group.add(
|
|
3197
|
+
const mesh2 = new THREE.Mesh(geometry, set.material);
|
|
3198
|
+
mesh2.name = `${name}:${key}`;
|
|
3199
|
+
mesh2.userData.material = key;
|
|
3200
|
+
mesh2.castShadow = template.castShadow;
|
|
3201
|
+
mesh2.receiveShadow = template.receiveShadow;
|
|
3202
|
+
mesh2.customDepthMaterial = set.depth;
|
|
3203
|
+
mesh2.customDistanceMaterial = set.distance;
|
|
3204
|
+
group.add(mesh2);
|
|
978
3205
|
};
|
|
979
3206
|
const cloths = (spec.soft ?? []).filter((s) => s.kind === "cloth" && parts[s.part ?? s.id] && !skip.has(s.part ?? s.id));
|
|
980
3207
|
const clothPart = (s) => s.part ?? s.id;
|
|
@@ -1043,8 +3270,8 @@ uniform vec2 swayAxz[${n}];
|
|
|
1043
3270
|
for (const p of spec.parts ?? []) {
|
|
1044
3271
|
if (!p.group || !parts[p.id] || skip.has(p.id)) continue;
|
|
1045
3272
|
const sig = `${p.group}|${JSON.stringify(p.motion)}`;
|
|
1046
|
-
const
|
|
1047
|
-
if (
|
|
3273
|
+
const list2 = bySignature.get(sig);
|
|
3274
|
+
if (list2) list2.push(p.id);
|
|
1048
3275
|
else bySignature.set(sig, [p.id]);
|
|
1049
3276
|
}
|
|
1050
3277
|
for (const [sig, ids] of bySignature) {
|
|
@@ -1122,214 +3349,99 @@ ${QROT}${shader.vertexShader}`;
|
|
|
1122
3349
|
atlas_floor: std({ roughness: 1 })
|
|
1123
3350
|
};
|
|
1124
3351
|
}
|
|
1125
|
-
var
|
|
3352
|
+
var colorSpaces = /* @__PURE__ */ new WeakMap();
|
|
3353
|
+
function colorSpaceOf(THREE) {
|
|
3354
|
+
const known = colorSpaces.get(THREE.Color);
|
|
3355
|
+
if (known) return known;
|
|
3356
|
+
const space = detectColorSpace(THREE);
|
|
3357
|
+
colorSpaces.set(THREE.Color, space);
|
|
3358
|
+
return space;
|
|
3359
|
+
}
|
|
3360
|
+
function detectColorSpace(THREE) {
|
|
3361
|
+
const color2 = new THREE.Color();
|
|
3362
|
+
for (let c = 0; c < 256; c++) {
|
|
3363
|
+
color2.setHex(c * 65793);
|
|
3364
|
+
if (color2.r !== colorChannels(c * 65793, "linear")[0]) {
|
|
3365
|
+
if (color2.r !== c / 255) throw new Error("createMeshApi: this three.js stores colours in a space game-shapes does not know");
|
|
3366
|
+
return "srgb";
|
|
3367
|
+
}
|
|
3368
|
+
}
|
|
3369
|
+
return "linear";
|
|
3370
|
+
}
|
|
1126
3371
|
function createMeshApi(THREE, options = {}) {
|
|
1127
3372
|
const quality = options.quality ?? "high";
|
|
1128
3373
|
const shadows = options.shadows !== false;
|
|
1129
3374
|
const materials = { ...createDefaultMaterials(THREE, quality), ...stripUndefined(options.materials) };
|
|
1130
3375
|
if (options.doubleSidedPlanes) for (const k of MATERIAL_KEYS) materials[k].side = THREE.DoubleSide;
|
|
1131
|
-
const root = { id: null, pivot: [0, 0, 0], ry: 0, acc: /* @__PURE__ */ new Map(), lights: [] };
|
|
1132
|
-
const partTargets = /* @__PURE__ */ new Map();
|
|
1133
|
-
const stack = [];
|
|
1134
|
-
let current = root;
|
|
1135
3376
|
let finished = false;
|
|
1136
|
-
const
|
|
1137
|
-
const
|
|
1138
|
-
let t = templates.get(key);
|
|
1139
|
-
if (!t) {
|
|
1140
|
-
const g = make();
|
|
1141
|
-
t = {
|
|
1142
|
-
pos: g.getAttribute("position").array,
|
|
1143
|
-
nor: g.getAttribute("normal").array,
|
|
1144
|
-
uv: g.getAttribute("uv").array,
|
|
1145
|
-
idx: g.index ? g.index.array : Array.from({ length: g.getAttribute("position").count }, (_, i) => i)
|
|
1146
|
-
};
|
|
1147
|
-
templates.set(key, t);
|
|
1148
|
-
}
|
|
1149
|
-
return t;
|
|
1150
|
-
};
|
|
1151
|
-
const segs = quality === "high" ? { cyl: 12, sphereW: 12, sphereH: 8 } : { cyl: 8, sphereW: 8, sphereH: 6 };
|
|
1152
|
-
const tBox = () => template("box", () => new THREE.BoxGeometry(1, 1, 1));
|
|
1153
|
-
const tCyl = (shape) => {
|
|
1154
|
-
switch (shape) {
|
|
1155
|
-
case "cyl6":
|
|
1156
|
-
return template("cyl6", () => new THREE.CylinderGeometry(1, 1, 1, 6));
|
|
1157
|
-
case "cone":
|
|
1158
|
-
return template("cone", () => new THREE.CylinderGeometry(0, 1, 1, segs.cyl));
|
|
1159
|
-
case "sphere":
|
|
1160
|
-
return template("sphere", () => new THREE.SphereGeometry(1, segs.sphereW, segs.sphereH));
|
|
1161
|
-
default:
|
|
1162
|
-
return template("cyl", () => new THREE.CylinderGeometry(1, 1, 1, segs.cyl));
|
|
1163
|
-
}
|
|
1164
|
-
};
|
|
1165
|
-
const tPlane = () => template("plane", () => new THREE.PlaneGeometry(1, 1));
|
|
1166
|
-
const tLeaf = () => {
|
|
1167
|
-
let t = templates.get("leaf");
|
|
1168
|
-
if (!t) {
|
|
1169
|
-
const f = [0, 0, 0, -0.5, 0.4, 0, 0.5, 0.4, 0, 0, 1, 0];
|
|
1170
|
-
const pos = [];
|
|
1171
|
-
const nor = [];
|
|
1172
|
-
const uv = [];
|
|
1173
|
-
const idx = [];
|
|
1174
|
-
for (const side of [1, -1]) {
|
|
1175
|
-
const base = pos.length / 3;
|
|
1176
|
-
for (let i = 0; i < 4; i++) {
|
|
1177
|
-
pos.push(f[i * 3], f[i * 3 + 1], f[i * 3 + 2]);
|
|
1178
|
-
nor.push(0, 0, side);
|
|
1179
|
-
uv.push(f[i * 3] + 0.5, f[i * 3 + 1]);
|
|
1180
|
-
}
|
|
1181
|
-
if (side === 1) idx.push(base, base + 2, base + 1, base + 1, base + 2, base + 3);
|
|
1182
|
-
else idx.push(base, base + 1, base + 2, base + 1, base + 3, base + 2);
|
|
1183
|
-
}
|
|
1184
|
-
t = { pos: new Float32Array(pos), nor: new Float32Array(nor), uv: new Float32Array(uv), idx };
|
|
1185
|
-
templates.set("leaf", t);
|
|
1186
|
-
}
|
|
1187
|
-
return t;
|
|
1188
|
-
};
|
|
1189
|
-
const m4 = new THREE.Matrix4();
|
|
1190
|
-
const mItem = new THREE.Matrix4();
|
|
1191
|
-
const mTotal = new THREE.Matrix4();
|
|
1192
|
-
const n3 = new THREE.Matrix3();
|
|
1193
|
-
const v = new THREE.Vector3();
|
|
1194
|
-
const quat = new THREE.Quaternion();
|
|
1195
|
-
const euler = new THREE.Euler();
|
|
1196
|
-
const scl = new THREE.Vector3();
|
|
1197
|
-
const color = new THREE.Color();
|
|
1198
|
-
const canonical = /* @__PURE__ */ new Map();
|
|
3377
|
+
const recorder = quality === "high" ? createShapeRecorder({ cylinderSegments: 12, sphereSegments: [12, 8] }) : createShapeRecorder({ cylinderSegments: 8, sphereSegments: [8, 6] });
|
|
3378
|
+
const materialAlias = {};
|
|
1199
3379
|
for (const k of MATERIAL_KEYS) {
|
|
1200
3380
|
const first = MATERIAL_KEYS.find((o) => materials[o] === materials[k]) ?? k;
|
|
1201
|
-
|
|
1202
|
-
}
|
|
1203
|
-
const
|
|
1204
|
-
|
|
1205
|
-
|
|
1206
|
-
if (!a) {
|
|
1207
|
-
a = { pos: [], nor: [], col: [], uv: [], idx: [] };
|
|
1208
|
-
current.acc.set(key, a);
|
|
1209
|
-
}
|
|
1210
|
-
return a;
|
|
1211
|
-
};
|
|
1212
|
-
const frame = (M) => {
|
|
1213
|
-
mItem.makeRotationY(M?.ry ?? 0);
|
|
1214
|
-
mItem.setPosition(M?.x ?? 0, 0, M?.z ?? 0);
|
|
1215
|
-
return mItem;
|
|
1216
|
-
};
|
|
1217
|
-
const emit = (requested, M, local, t, hexColor, uv) => {
|
|
1218
|
-
const key = requested in materials ? requested : "plain";
|
|
1219
|
-
const a = accOf(key);
|
|
1220
|
-
const total = mTotal.copy(frame(M)).multiply(local);
|
|
1221
|
-
n3.getNormalMatrix(total);
|
|
1222
|
-
const base = a.pos.length / 3;
|
|
1223
|
-
color.setHex(hexColor);
|
|
1224
|
-
const count = t.pos.length / 3;
|
|
1225
|
-
const e = total.elements;
|
|
1226
|
-
const nm = n3.elements;
|
|
1227
|
-
const u = uv ?? IDENT_UV;
|
|
1228
|
-
for (let i = 0; i < count; i++) {
|
|
1229
|
-
const x = t.pos[i * 3];
|
|
1230
|
-
const y = t.pos[i * 3 + 1];
|
|
1231
|
-
const z = t.pos[i * 3 + 2];
|
|
1232
|
-
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]);
|
|
1233
|
-
const nx = t.nor[i * 3];
|
|
1234
|
-
const ny = t.nor[i * 3 + 1];
|
|
1235
|
-
const nz = t.nor[i * 3 + 2];
|
|
1236
|
-
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();
|
|
1237
|
-
a.nor.push(v.x, v.y, v.z);
|
|
1238
|
-
a.col.push(color.r, color.g, color.b);
|
|
1239
|
-
a.uv.push(u.u0 + t.uv[i * 2] * (u.u1 - u.u0), u.v0 + t.uv[i * 2 + 1] * (u.v1 - u.v0));
|
|
1240
|
-
}
|
|
1241
|
-
for (let i = 0; i < t.idx.length; i++) a.idx.push(base + t.idx[i]);
|
|
1242
|
-
};
|
|
1243
|
-
const compose = (x, y, z, rx, ry, rz, sx, sy, sz) => {
|
|
1244
|
-
euler.set(rx, ry, rz, "YXZ");
|
|
1245
|
-
quat.setFromEuler(euler);
|
|
1246
|
-
return m4.compose(v.set(x, y, z), quat, scl.set(sx, sy, sz));
|
|
1247
|
-
};
|
|
1248
|
-
const api = {
|
|
1249
|
-
box(M, material, x, y, z, w, h, d, col, ry, rx, rz) {
|
|
1250
|
-
emit(material, M, compose(x, y + h / 2, z, rx, ry, rz, w, h, d), tBox(), col);
|
|
1251
|
-
},
|
|
1252
|
-
cylinder(M, material, x, y, z, r, h, col, rx, rz, shape) {
|
|
1253
|
-
const tilted = rx !== 0 || rz !== 0;
|
|
1254
|
-
const cy = shape === "sphere" ? y + h / 2 : tilted ? y : y + h / 2;
|
|
1255
|
-
const sy = shape === "sphere" ? h / 2 : h;
|
|
1256
|
-
emit(material, M, compose(x, cy, z, rx, 0, rz, r, sy, r), tCyl(shape), col);
|
|
1257
|
-
},
|
|
1258
|
-
leaf(M, x, y, z, w, h, col, ry, rx) {
|
|
1259
|
-
emit("plant", M, compose(x, y, z, rx, ry, 0, w, h, 1), tLeaf(), col);
|
|
1260
|
-
},
|
|
1261
|
-
plane(M, material, x, y, z, w, h, col, ry, rx, rz, uv) {
|
|
1262
|
-
emit(material, M, compose(x, y, z, rx, ry, rz, w, h, 1), tPlane(), col, uv);
|
|
1263
|
-
},
|
|
1264
|
-
light(M, x, y, z, col, range) {
|
|
1265
|
-
const fr = frame(M);
|
|
1266
|
-
v.set(x, y, z).applyMatrix4(fr);
|
|
1267
|
-
current.lights.push({ x: v.x, y: v.y, z: v.z, color: col, range });
|
|
1268
|
-
},
|
|
1269
|
-
part(id, pivot, ry, draw) {
|
|
1270
|
-
let target = partTargets.get(id);
|
|
1271
|
-
if (!target) {
|
|
1272
|
-
target = { id, pivot: [pivot[0], pivot[1], pivot[2]], ry, acc: /* @__PURE__ */ new Map(), lights: [] };
|
|
1273
|
-
partTargets.set(id, target);
|
|
1274
|
-
}
|
|
1275
|
-
stack.push(current);
|
|
1276
|
-
current = target;
|
|
1277
|
-
try {
|
|
1278
|
-
draw();
|
|
1279
|
-
} finally {
|
|
1280
|
-
current = stack.pop() ?? root;
|
|
1281
|
-
}
|
|
1282
|
-
}
|
|
1283
|
-
};
|
|
1284
|
-
const build = (target, into, materialFor) => {
|
|
1285
|
-
for (const [key, a] of target.acc) {
|
|
1286
|
-
if (!a.idx.length) continue;
|
|
3381
|
+
if (first !== k) materialAlias[k] = first;
|
|
3382
|
+
}
|
|
3383
|
+
const lightsOut = [];
|
|
3384
|
+
const build = (batches, points, id, into, materialFor) => {
|
|
3385
|
+
for (const batch of batches) {
|
|
1287
3386
|
const g = new THREE.BufferGeometry();
|
|
1288
|
-
g.setAttribute("position", new THREE.
|
|
1289
|
-
g.setAttribute("normal", new THREE.
|
|
1290
|
-
g.setAttribute("color", new THREE.
|
|
1291
|
-
g.setAttribute("uv", new THREE.
|
|
1292
|
-
g.setIndex(
|
|
3387
|
+
g.setAttribute("position", new THREE.BufferAttribute(batch.positions, 3));
|
|
3388
|
+
g.setAttribute("normal", new THREE.BufferAttribute(batch.normals, 3));
|
|
3389
|
+
g.setAttribute("color", new THREE.BufferAttribute(batch.colors, 3));
|
|
3390
|
+
g.setAttribute("uv", new THREE.BufferAttribute(batch.uvs, 2));
|
|
3391
|
+
g.setIndex(new THREE.BufferAttribute(batch.indices, 1));
|
|
1293
3392
|
g.computeBoundingSphere();
|
|
1294
3393
|
g.computeBoundingBox();
|
|
1295
|
-
const
|
|
1296
|
-
|
|
1297
|
-
|
|
1298
|
-
|
|
1299
|
-
|
|
1300
|
-
|
|
1301
|
-
|
|
1302
|
-
|
|
1303
|
-
|
|
3394
|
+
const key = batch.material;
|
|
3395
|
+
const mesh2 = new THREE.Mesh(g, materialFor?.(key, id) ?? materials[key]);
|
|
3396
|
+
mesh2.name = id === null ? `static:${key}` : `part:${id}`;
|
|
3397
|
+
if (id !== null) mesh2.userData.part = id;
|
|
3398
|
+
mesh2.userData.material = key;
|
|
3399
|
+
mesh2.castShadow = shadows;
|
|
3400
|
+
mesh2.receiveShadow = shadows;
|
|
3401
|
+
into.add(mesh2);
|
|
3402
|
+
}
|
|
3403
|
+
for (const point of points) {
|
|
3404
|
+
const l = point.data;
|
|
1304
3405
|
const light = new THREE.PointLight(l.color, 1, l.range);
|
|
1305
|
-
light.position.set(
|
|
3406
|
+
light.position.set(point.position[0], point.position[1], point.position[2]);
|
|
1306
3407
|
into.add(light);
|
|
1307
3408
|
lightsOut.push(light);
|
|
1308
3409
|
}
|
|
1309
3410
|
};
|
|
1310
|
-
const lightsOut = [];
|
|
1311
3411
|
return {
|
|
1312
|
-
api,
|
|
3412
|
+
api: recorder.api,
|
|
1313
3413
|
materials,
|
|
1314
3414
|
finish(spec, finishOptions) {
|
|
1315
3415
|
const materialFor = finishOptions?.materialFor ?? options.materialFor;
|
|
1316
3416
|
if (finished) throw new Error("createMeshApi: finish() was already called");
|
|
1317
3417
|
finished = true;
|
|
3418
|
+
const buffers = generate(recorder.schema(), {
|
|
3419
|
+
colors: colorSpaceOf(THREE),
|
|
3420
|
+
materials: MATERIAL_KEYS,
|
|
3421
|
+
fallbackMaterial: "plain",
|
|
3422
|
+
materialAlias,
|
|
3423
|
+
validate: false,
|
|
3424
|
+
key: false
|
|
3425
|
+
});
|
|
3426
|
+
const batches = buffers.levels[0]?.batches ?? [];
|
|
3427
|
+
const of = (id) => ({ batches: batches.filter((b) => b.part === id), points: buffers.points.filter((p) => p.part === id) });
|
|
1318
3428
|
const group = new THREE.Group();
|
|
1319
3429
|
group.name = "interactable";
|
|
1320
3430
|
const parts = {};
|
|
1321
|
-
|
|
1322
|
-
|
|
3431
|
+
const body = of(null);
|
|
3432
|
+
build(body.batches, body.points, null, group, materialFor);
|
|
3433
|
+
for (const part of recorder.parts) {
|
|
1323
3434
|
const g = new THREE.Group();
|
|
1324
|
-
g.name = `part:${id}`;
|
|
1325
|
-
g.userData.part = id;
|
|
1326
|
-
g.position.set(
|
|
1327
|
-
g.rotation.y =
|
|
3435
|
+
g.name = `part:${part.id}`;
|
|
3436
|
+
g.userData.part = part.id;
|
|
3437
|
+
g.position.set(part.pivot[0], part.pivot[1], part.pivot[2]);
|
|
3438
|
+
g.rotation.y = part.ry;
|
|
1328
3439
|
g.updateMatrix();
|
|
1329
|
-
|
|
3440
|
+
const drawn = of(part.id);
|
|
3441
|
+
build(drawn.batches, drawn.points, part.id, g, materialFor);
|
|
1330
3442
|
rememberRest(g);
|
|
1331
3443
|
group.add(g);
|
|
1332
|
-
parts[id] = g;
|
|
3444
|
+
parts[part.id] = g;
|
|
1333
3445
|
}
|
|
1334
3446
|
if (spec && (options.mergeParts ?? quality === "game")) mergeParts(THREE, group, parts, spec, { swayAmplitude: options.swayAmplitude });
|
|
1335
3447
|
if (spec) tagDestruction(group, parts, spec);
|
|
@@ -1418,16 +3530,16 @@ ${QROT}${shader.vertexShader}`;
|
|
|
1418
3530
|
const scl = new THREE.Vector3();
|
|
1419
3531
|
const n = new THREE.Vector3();
|
|
1420
3532
|
const toEye = new THREE.Vector3();
|
|
1421
|
-
const
|
|
3533
|
+
const box2 = new THREE.Box3();
|
|
1422
3534
|
const size = new THREE.Vector3();
|
|
1423
3535
|
const facing = new THREE.Vector3();
|
|
1424
3536
|
const flip = new THREE.Quaternion(0, 1, 0, 0);
|
|
1425
3537
|
return {
|
|
1426
3538
|
envMaterial,
|
|
1427
|
-
register(id,
|
|
3539
|
+
register(id, mesh2, normal) {
|
|
1428
3540
|
const previous = entries.get(id);
|
|
1429
|
-
entries.set(id, { mesh, normal: normal ?? null, original: previous ? previous.original :
|
|
1430
|
-
|
|
3541
|
+
entries.set(id, { mesh: mesh2, normal: normal ?? null, original: previous ? previous.original : mesh2.material });
|
|
3542
|
+
mesh2.material = envMaterial;
|
|
1431
3543
|
},
|
|
1432
3544
|
unregister(id) {
|
|
1433
3545
|
const e = entries.get(id);
|
|
@@ -1460,8 +3572,8 @@ ${QROT}${shader.vertexShader}`;
|
|
|
1460
3572
|
e.mesh.updateWorldMatrix(true, false);
|
|
1461
3573
|
e.mesh.matrixWorld.decompose(pos, quat, scl);
|
|
1462
3574
|
if (!e.mesh.geometry.boundingBox) e.mesh.geometry.computeBoundingBox();
|
|
1463
|
-
|
|
1464
|
-
|
|
3575
|
+
box2.copy(e.mesh.geometry.boundingBox);
|
|
3576
|
+
box2.getSize(size);
|
|
1465
3577
|
worldNormal(e, n);
|
|
1466
3578
|
s.reflector.position.copy(pos).addScaledVector(n, offset);
|
|
1467
3579
|
s.reflector.quaternion.copy(quat);
|
|
@@ -1672,19 +3784,19 @@ uniform float swayAmplitude;
|
|
|
1672
3784
|
material.userData.distanceMaterial = distance;
|
|
1673
3785
|
return material;
|
|
1674
3786
|
}
|
|
1675
|
-
function attachSway(THREE,
|
|
3787
|
+
function attachSway(THREE, mesh2, options = {}) {
|
|
1676
3788
|
let anchorY = options.anchorY;
|
|
1677
3789
|
if (anchorY === void 0) {
|
|
1678
|
-
if (!
|
|
1679
|
-
anchorY =
|
|
3790
|
+
if (!mesh2.geometry.boundingBox) mesh2.geometry.computeBoundingBox();
|
|
3791
|
+
anchorY = mesh2.geometry.boundingBox ? mesh2.geometry.boundingBox.max.y : 0;
|
|
1680
3792
|
}
|
|
1681
|
-
const base = Array.isArray(
|
|
3793
|
+
const base = Array.isArray(mesh2.material) ? mesh2.material[0] : mesh2.material;
|
|
1682
3794
|
const m = swayMaterial(THREE, base, { ...options, anchorY });
|
|
1683
3795
|
m.userData.sway.uniforms.anchorY.value = anchorY;
|
|
1684
|
-
|
|
1685
|
-
|
|
1686
|
-
|
|
1687
|
-
|
|
3796
|
+
mesh2.material = m;
|
|
3797
|
+
mesh2.customDepthMaterial = m.userData.depthMaterial;
|
|
3798
|
+
mesh2.customDistanceMaterial = m.userData.distanceMaterial;
|
|
3799
|
+
mesh2.userData.sway = m.userData.sway;
|
|
1688
3800
|
return m.userData.sway;
|
|
1689
3801
|
}
|
|
1690
3802
|
|
|
@@ -1696,12 +3808,12 @@ uniform float swayAmplitude;
|
|
|
1696
3808
|
applyPose: (group, motion, t) => applyPose(THREE, group, motion, t),
|
|
1697
3809
|
syncObject: (world, objectId, parts, o) => syncObject(THREE, world, objectId, parts, o),
|
|
1698
3810
|
swayMaterial: (base, o) => swayMaterial(THREE, base, o),
|
|
1699
|
-
attachSway: (
|
|
3811
|
+
attachSway: (mesh2, o) => attachSway(THREE, mesh2, o),
|
|
1700
3812
|
createEffects: ((a, o = {}) => a.isObject3D ? createEffects(THREE, a, { document: options.document, ...o }) : createEffects(THREE, { document: options.document, ...a })),
|
|
1701
3813
|
createScreen: (o = {}) => {
|
|
1702
|
-
const
|
|
1703
|
-
if (!
|
|
1704
|
-
return createScreen(THREE, { ...o, document });
|
|
3814
|
+
const document2 = o.document ?? options.document;
|
|
3815
|
+
if (!document2) throw new Error("createScreen needs a document (option of createThreeAdapter or of the call)");
|
|
3816
|
+
return createScreen(THREE, { ...o, document: document2 });
|
|
1705
3817
|
},
|
|
1706
3818
|
createMirrors: (o = {}) => createMirrors(THREE, { Reflector: options.Reflector, envMap: options.envMap, ...o }),
|
|
1707
3819
|
createCord: (points, radius, material) => createCord(THREE, points, radius, material),
|