@carbonenginejs/core-math 0.1.0

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/src/mesh.js ADDED
@@ -0,0 +1,298 @@
1
+ /**
2
+ * Small mesh rebuild helpers for shared CarbonEngineJS mesh JSON.
3
+ *
4
+ * These helpers are deliberately framework-free and browser-safe. They accept
5
+ * plain arrays or typed arrays and return plain arrays unless otherwise noted.
6
+ */
7
+
8
+ import { num } from "./num.js";
9
+ import { vec3 } from "./vec3.js";
10
+
11
+ /**
12
+ * Calculate the unit face normal for a triangle.
13
+ *
14
+ * @param {ArrayLike<number>} a First xyz vertex.
15
+ * @param {ArrayLike<number>} b Second xyz vertex.
16
+ * @param {ArrayLike<number>} c Third xyz vertex.
17
+ * @returns {number[]} Unit triangle normal.
18
+ */
19
+ export function triangleNormal(a, b, c)
20
+ {
21
+ const normal = [ 0, 0, 0 ];
22
+ vec3.cross(
23
+ normal,
24
+ [ b[0] - a[0], b[1] - a[1], b[2] - a[2] ],
25
+ [ c[0] - a[0], c[1] - a[1], c[2] - a[2] ]
26
+ );
27
+ return vec3.normalize(normal, normal);
28
+ }
29
+
30
+ /**
31
+ * Twice the area of a triangle.
32
+ *
33
+ * @param {ArrayLike<number>} a First xyz vertex.
34
+ * @param {ArrayLike<number>} b Second xyz vertex.
35
+ * @param {ArrayLike<number>} c Third xyz vertex.
36
+ * @returns {number} Twice the triangle area.
37
+ */
38
+ export function triangleArea2(a, b, c)
39
+ {
40
+ const normal = [ 0, 0, 0 ];
41
+ vec3.cross(
42
+ normal,
43
+ [ b[0] - a[0], b[1] - a[1], b[2] - a[2] ],
44
+ [ c[0] - a[0], c[1] - a[1], c[2] - a[2] ]
45
+ );
46
+ return vec3.length(normal);
47
+ }
48
+
49
+ /**
50
+ * Whether a triangle is degenerate.
51
+ *
52
+ * @param {ArrayLike<number>} a First xyz vertex.
53
+ * @param {ArrayLike<number>} b Second xyz vertex.
54
+ * @param {ArrayLike<number>} c Third xyz vertex.
55
+ * @param {number} [epsilon] Area epsilon.
56
+ * @returns {boolean} True when the triangle has no useful area.
57
+ */
58
+ export function isDegenerateTriangle(a, b, c, epsilon = 1e-12)
59
+ {
60
+ return triangleArea2(a, b, c) <= epsilon;
61
+ }
62
+
63
+ /**
64
+ * Compute axis-aligned bounds from flat xyz positions.
65
+ *
66
+ * @param {ArrayLike<number>} positions Flat xyz positions.
67
+ * @returns {{minBounds: number[], maxBounds: number[]}} Bounds.
68
+ */
69
+ export function computeBoundsFromPositions(positions)
70
+ {
71
+ if (!positions.length)
72
+ {
73
+ return { minBounds: [ 0, 0, 0 ], maxBounds: [ 0, 0, 0 ] };
74
+ }
75
+
76
+ const
77
+ minBounds = [ positions[0], positions[1], positions[2] ],
78
+ maxBounds = [ positions[0], positions[1], positions[2] ];
79
+
80
+ for (let i = 3; i < positions.length; i += 3)
81
+ {
82
+ for (let c = 0; c < 3; c++)
83
+ {
84
+ const value = positions[i + c];
85
+ if (value < minBounds[c]) minBounds[c] = value;
86
+ if (value > maxBounds[c]) maxBounds[c] = value;
87
+ }
88
+ }
89
+
90
+ return { minBounds, maxBounds };
91
+ }
92
+
93
+ /**
94
+ * Compute axis-aligned bounds from `{ vertices: [[x,y,z], ...] }` triangles.
95
+ *
96
+ * @param {Array<{vertices: Array<ArrayLike<number>>}>} triangles Triangle records.
97
+ * @returns {{minBounds: number[], maxBounds: number[]}} Bounds.
98
+ */
99
+ export function computeBoundsFromTriangles(triangles)
100
+ {
101
+ if (!triangles.length)
102
+ {
103
+ return { minBounds: [ 0, 0, 0 ], maxBounds: [ 0, 0, 0 ] };
104
+ }
105
+
106
+ const
107
+ minBounds = [ Infinity, Infinity, Infinity ],
108
+ maxBounds = [ -Infinity, -Infinity, -Infinity ];
109
+
110
+ for (const triangle of triangles)
111
+ {
112
+ for (const vertex of triangle.vertices)
113
+ {
114
+ for (let c = 0; c < 3; c++)
115
+ {
116
+ if (vertex[c] < minBounds[c]) minBounds[c] = vertex[c];
117
+ if (vertex[c] > maxBounds[c]) maxBounds[c] = vertex[c];
118
+ }
119
+ }
120
+ }
121
+
122
+ return { minBounds, maxBounds };
123
+ }
124
+
125
+ /**
126
+ * Generate area-weighted vertex normals from positions and triangle indices.
127
+ *
128
+ * @param {ArrayLike<number>} positions Flat xyz positions.
129
+ * @param {ArrayLike<number>} indices Flat triangle indices.
130
+ * @returns {number[]} Flat xyz normals.
131
+ */
132
+ export function generateNormals(positions, indices)
133
+ {
134
+ const normals = new Array(positions.length).fill(0);
135
+
136
+ for (let i = 0; i < indices.length; i += 3)
137
+ {
138
+ const
139
+ ia = indices[i] * 3,
140
+ ib = indices[i + 1] * 3,
141
+ ic = indices[i + 2] * 3,
142
+ ax = positions[ia],
143
+ ay = positions[ia + 1],
144
+ az = positions[ia + 2],
145
+ faceNormal = [ 0, 0, 0 ];
146
+
147
+ vec3.cross(
148
+ faceNormal,
149
+ [ positions[ib] - ax, positions[ib + 1] - ay, positions[ib + 2] - az ],
150
+ [ positions[ic] - ax, positions[ic + 1] - ay, positions[ic + 2] - az ]
151
+ );
152
+
153
+ for (const offset of [ ia, ib, ic ])
154
+ {
155
+ normals[offset] += faceNormal[0];
156
+ normals[offset + 1] += faceNormal[1];
157
+ normals[offset + 2] += faceNormal[2];
158
+ }
159
+ }
160
+
161
+ for (let i = 0; i < normals.length; i += 3)
162
+ {
163
+ const n = vec3.normalize([ 0, 0, 0 ], [ normals[i], normals[i + 1], normals[i + 2] ]);
164
+ normals[i] = n[0];
165
+ normals[i + 1] = n[1];
166
+ normals[i + 2] = n[2];
167
+ }
168
+
169
+ return normals;
170
+ }
171
+
172
+ /**
173
+ * Generate per-vertex tangents from positions, normals, UVs and indices.
174
+ *
175
+ * @param {ArrayLike<number>} positions Flat xyz positions.
176
+ * @param {ArrayLike<number>} normals Flat xyz normals.
177
+ * @param {ArrayLike<number>} uvs Flat uv coordinates.
178
+ * @param {ArrayLike<number>} indices Flat triangle indices.
179
+ * @returns {number[]} Flat xyz tangents.
180
+ */
181
+ export function generateTangents(positions, normals, uvs, indices)
182
+ {
183
+ const
184
+ vertexCount = positions.length / 3,
185
+ tangents = new Array(vertexCount * 3).fill(0);
186
+
187
+ for (let i = 0; i < indices.length; i += 3)
188
+ {
189
+ const
190
+ i0 = indices[i],
191
+ i1 = indices[i + 1],
192
+ i2 = indices[i + 2],
193
+ p0 = i0 * 3,
194
+ p1 = i1 * 3,
195
+ p2 = i2 * 3,
196
+ t0 = i0 * 2,
197
+ t1 = i1 * 2,
198
+ t2 = i2 * 2,
199
+ x1 = positions[p1] - positions[p0],
200
+ y1 = positions[p1 + 1] - positions[p0 + 1],
201
+ z1 = positions[p1 + 2] - positions[p0 + 2],
202
+ x2 = positions[p2] - positions[p0],
203
+ y2 = positions[p2 + 1] - positions[p0 + 1],
204
+ z2 = positions[p2 + 2] - positions[p0 + 2],
205
+ s1 = uvs[t1] - uvs[t0],
206
+ v1 = uvs[t1 + 1] - uvs[t0 + 1],
207
+ s2 = uvs[t2] - uvs[t0],
208
+ v2 = uvs[t2 + 1] - uvs[t0 + 1],
209
+ divisor = s1 * v2 - s2 * v1;
210
+
211
+ if (Math.abs(divisor) <= num.EPSILON) continue;
212
+
213
+ const
214
+ scale = 1 / divisor,
215
+ tx = (v2 * x1 - v1 * x2) * scale,
216
+ ty = (v2 * y1 - v1 * y2) * scale,
217
+ tz = (v2 * z1 - v1 * z2) * scale;
218
+
219
+ for (const offset of [ p0, p1, p2 ])
220
+ {
221
+ tangents[offset] += tx;
222
+ tangents[offset + 1] += ty;
223
+ tangents[offset + 2] += tz;
224
+ }
225
+ }
226
+
227
+ for (let i = 0; i < tangents.length; i += 3)
228
+ {
229
+ const
230
+ nx = normals[i],
231
+ ny = normals[i + 1],
232
+ nz = normals[i + 2],
233
+ tx = tangents[i],
234
+ ty = tangents[i + 1],
235
+ tz = tangents[i + 2],
236
+ normalDotTangent = nx * tx + ny * ty + nz * tz,
237
+ tangent = vec3.normalize([ 0, 0, 0 ], [
238
+ tx - nx * normalDotTangent,
239
+ ty - ny * normalDotTangent,
240
+ tz - nz * normalDotTangent
241
+ ]);
242
+
243
+ tangents[i] = tangent[0];
244
+ tangents[i + 1] = tangent[1];
245
+ tangents[i + 2] = tangent[2];
246
+ }
247
+
248
+ return tangents;
249
+ }
250
+
251
+ /**
252
+ * Generate binormals as normalized `normal x tangent`.
253
+ *
254
+ * @param {ArrayLike<number>} normals Flat xyz normals.
255
+ * @param {ArrayLike<number>} tangents Flat xyz tangents.
256
+ * @param {object} [options] Generation options.
257
+ * @param {"right"|"left"} [options.uvHandedness] Handedness of generated basis.
258
+ * @returns {number[]} Flat xyz binormals.
259
+ */
260
+ export function generateBiNormals(normals, tangents, { uvHandedness = "right" } = {})
261
+ {
262
+ if (normals.length !== tangents.length)
263
+ {
264
+ throw new Error("generateBiNormals requires normals and tangents with matching lengths");
265
+ }
266
+
267
+ const
268
+ sign = uvHandedness === "left" ? -1 : 1,
269
+ binormals = new Array(normals.length);
270
+
271
+ for (let i = 0; i < normals.length; i += 3)
272
+ {
273
+ const b = vec3.normalize(
274
+ [ 0, 0, 0 ],
275
+ [
276
+ normals[i + 1] * tangents[i + 2] - normals[i + 2] * tangents[i + 1],
277
+ normals[i + 2] * tangents[i] - normals[i] * tangents[i + 2],
278
+ normals[i] * tangents[i + 1] - normals[i + 1] * tangents[i]
279
+ ]
280
+ );
281
+ binormals[i] = b[0] * sign;
282
+ binormals[i + 1] = b[1] * sign;
283
+ binormals[i + 2] = b[2] * sign;
284
+ }
285
+
286
+ return binormals;
287
+ }
288
+
289
+ export const mesh = Object.freeze({
290
+ triangleNormal,
291
+ triangleArea2,
292
+ isDegenerateTriangle,
293
+ computeBoundsFromPositions,
294
+ computeBoundsFromTriangles,
295
+ generateNormals,
296
+ generateTangents,
297
+ generateBiNormals
298
+ });
package/src/noise.js ADDED
@@ -0,0 +1,225 @@
1
+ import { vec3 } from "./vec3.js";
2
+ import { vec4 } from "./vec4.js";
3
+
4
+ export const noise = {};
5
+
6
+ /**
7
+ * Generates turbulent noise
8
+ *
9
+ * @param {vec4} out
10
+ * @param {number} pos_0
11
+ * @param {number} pos_1
12
+ * @param {number} pos_2
13
+ * @param {number} pos_3
14
+ * @param {number} power
15
+ * @returns {vec4} out
16
+ */
17
+ noise.turbulence = (function()
18
+ {
19
+ const
20
+ s_noiseLookup = [],
21
+ s_permutations = [],
22
+ s_globalNoiseTemps = [];
23
+
24
+ let s_initialized = false;
25
+
26
+ /**
27
+ * Initializes noise
28
+ */
29
+ function initialize()
30
+ {
31
+ for (let i = 0; i < 256; i++)
32
+ {
33
+ s_noiseLookup[i] = vec4.fromValues(Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5, Math.random() - 0.5);
34
+ s_permutations[i] = i;
35
+ }
36
+
37
+ let i = 256;
38
+ while (--i)
39
+ {
40
+ const
41
+ tmp = s_permutations[i],
42
+ index = Math.floor(Math.random() * 256);
43
+
44
+ s_permutations[i] = s_permutations[index];
45
+ s_permutations[index] = tmp;
46
+ }
47
+
48
+ for (let i = 0; i < 256; i++)
49
+ {
50
+ s_permutations[256 + i] = s_permutations[i];
51
+ s_noiseLookup[256 + i] = s_noiseLookup[i];
52
+ s_noiseLookup[256 * 2 + i] = s_noiseLookup[i];
53
+ }
54
+
55
+ for (let i = 0; i < 15; ++i)
56
+ {
57
+ s_globalNoiseTemps[i] = vec3.create();
58
+ }
59
+
60
+ s_initialized = true;
61
+ }
62
+
63
+ return function turbulence(out, pos_0, pos_1, pos_2, pos_3, power)
64
+ {
65
+ if (!s_initialized) initialize();
66
+
67
+ pos_0 += 4096;
68
+ pos_1 += 4096;
69
+ pos_2 += 4096;
70
+ pos_3 += 4096;
71
+
72
+ let a_0 = Math.floor(pos_0),
73
+ a_1 = Math.floor(pos_1),
74
+ a_2 = Math.floor(pos_2),
75
+ a_3 = Math.floor(pos_3);
76
+
77
+ const
78
+ t_0 = pos_0 - a_0,
79
+ t_1 = pos_1 - a_1,
80
+ t_2 = pos_2 - a_2,
81
+ t_3 = pos_3 - a_3;
82
+
83
+ a_0 &= 255;
84
+ a_1 &= 255;
85
+ a_2 &= 255;
86
+ a_3 &= 255;
87
+
88
+ const
89
+ b_0 = a_0 + 1,
90
+ b_1 = a_1 + 1,
91
+ b_2 = a_2 + 1,
92
+ b_3 = a_3 + 1;
93
+
94
+ const
95
+ i = s_permutations[a_0],
96
+ j = s_permutations[b_0];
97
+
98
+ const
99
+ b00 = s_permutations[i + a_1],
100
+ b10 = s_permutations[j + a_1],
101
+ b01 = s_permutations[i + b_1],
102
+ b11 = s_permutations[j + b_1];
103
+
104
+ let c00 = vec3.lerp(s_globalNoiseTemps[0], s_noiseLookup[b00 + a_2 + a_3], s_noiseLookup[b10 + a_2 + a_3], t_0);
105
+ let c10 = vec3.lerp(s_globalNoiseTemps[1], s_noiseLookup[b01 + a_2 + a_3], s_noiseLookup[b11 + a_2 + a_3], t_0);
106
+ let c01 = vec3.lerp(s_globalNoiseTemps[2], s_noiseLookup[b00 + b_2 + a_3], s_noiseLookup[b10 + b_2 + a_3], t_0);
107
+ let c11 = vec3.lerp(s_globalNoiseTemps[3], s_noiseLookup[b00 + b_2 + a_3], s_noiseLookup[b10 + b_2 + a_3], t_0);
108
+ let c0 = vec3.lerp(s_globalNoiseTemps[4], c00, c10, t_1);
109
+ let c1 = vec3.lerp(s_globalNoiseTemps[5], c01, c11, t_1);
110
+ const c = vec3.lerp(s_globalNoiseTemps[6], c0, c1, t_2);
111
+
112
+ c00 = vec3.lerp(s_globalNoiseTemps[7], s_noiseLookup[b00 + a_2 + b_3], s_noiseLookup[b10 + a_2 + b_3], t_0);
113
+ c10 = vec3.lerp(s_globalNoiseTemps[8], s_noiseLookup[b01 + a_2 + b_3], s_noiseLookup[b11 + a_2 + b_3], t_0);
114
+ c01 = vec3.lerp(s_globalNoiseTemps[9], s_noiseLookup[b00 + b_2 + b_3], s_noiseLookup[b10 + b_2 + b_3], t_0);
115
+ c11 = vec3.lerp(s_globalNoiseTemps[10], s_noiseLookup[b00 + b_2 + b_3], s_noiseLookup[b10 + b_2 + b_3], t_0);
116
+ c0 = vec3.lerp(s_globalNoiseTemps[11], c00, c10, t_1);
117
+ c1 = vec3.lerp(s_globalNoiseTemps[12], c01, c11, t_1);
118
+ const d = vec3.lerp(s_globalNoiseTemps[13], c0, c1, t_2);
119
+ const r = vec3.lerp(s_globalNoiseTemps[14], c, d, t_3);
120
+
121
+ out[0] += r[0] * power;
122
+ out[1] += r[1] * power;
123
+ out[2] += r[2] * power;
124
+ return out;
125
+ };
126
+ })();
127
+
128
+ /**
129
+ * Perlin_noise1
130
+ *
131
+ * @param {number} a
132
+ * @returns {number}
133
+ */
134
+ noise.perlin1 = (function()
135
+ {
136
+ let p_initialized = false,
137
+ p_B = 0x100,
138
+ p_BM = 0xff,
139
+ p_N = 0x1000,
140
+ p_p = null,
141
+ p_g1 = null;
142
+
143
+ /**
144
+ * Initializes Perlin Noise
145
+ */
146
+ function initialize()
147
+ {
148
+ p_p = new Array(p_B + p_B + 2);
149
+ p_g1 = new Array(p_B + p_B + 2);
150
+
151
+ let i = 0,
152
+ j = 0,
153
+ k = 0;
154
+
155
+ for (i = 0; i < p_B; i++)
156
+ {
157
+ p_p[i] = i;
158
+ p_g1[i] = Math.random() * 2 - 1;
159
+ }
160
+
161
+ while (--i)
162
+ {
163
+ k = p_p[i];
164
+ p_p[i] = p_p[j = Math.floor(Math.random() * p_B)];
165
+ p_p[j] = k;
166
+ }
167
+
168
+ for (i = 0; i < p_B + 2; i++)
169
+ {
170
+ p_p[p_B + i] = p_p[i];
171
+ p_g1[p_B + i] = p_g1[i];
172
+ }
173
+
174
+ p_initialized = true;
175
+ }
176
+
177
+ return function perlin1(a)
178
+ {
179
+ if (!p_initialized) initialize();
180
+
181
+ let t = a + p_N,
182
+ bx0 = Math.floor(t) & p_BM,
183
+ bx1 = (bx0 + 1) & p_BM,
184
+ rx0 = t - Math.floor(t),
185
+ rx1 = rx0 - 1;
186
+
187
+ let sx = rx0 * rx0 * (3.0 - 2.0 * rx0),
188
+ u = rx0 * p_g1[p_p[bx0]],
189
+ v = rx1 * p_g1[p_p[bx1]];
190
+
191
+ return u + sx * (v - u);
192
+ };
193
+ })();
194
+
195
+ /**
196
+ * PerlinNoise1D
197
+ *
198
+ * @param x
199
+ * @param alpha
200
+ * @param beta
201
+ * @param n
202
+ * @returns {number}
203
+ */
204
+ noise.perlin1D = function(x, alpha, beta, n)
205
+ {
206
+ let sum = 0,
207
+ p = x,
208
+ scale = 1;
209
+
210
+ for (let i = 0; i < n; ++i)
211
+ {
212
+ sum += noise.perlin1(p) / scale;
213
+ scale *= alpha;
214
+ p *= beta;
215
+ }
216
+ return sum;
217
+ };
218
+
219
+ export const {
220
+ turbulence,
221
+ perlin1,
222
+ perlin1D
223
+ } = noise;
224
+
225
+