@pirireis/webglobeplugins 0.10.13-alpha → 0.11.0-alpha
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/Math/arc-generate-points.js +361 -0
- package/altitude-locator/plugin.js +4 -7
- package/package.json +1 -1
- package/point-heat-map/plugin-webworker.js +2 -0
- package/programs/totems/camerauniformblock copy.js +171 -0
- package/programs/totems/camerauniformblock.js +81 -35
- package/programs/totems/camerauniformblock1.js +171 -0
- package/shape-on-terrain/arc/naive/plugin.js +110 -74
- package/types.js +1 -4
- package/Math/methods1.js +0 -183
|
@@ -0,0 +1,361 @@
|
|
|
1
|
+
// import { normilze as vec3Normalize, dot as vec3Dot, cross as vec3Cross, scale as vec3Scale, add as vec3Add, sub as vec3Sub } from './vec3';
|
|
2
|
+
// --- Utility Functions for 3D Vector Operations ---
|
|
3
|
+
/**
|
|
4
|
+
* Normalizes a 3D vector to unit length.
|
|
5
|
+
* @param v The input vector.
|
|
6
|
+
* @returns The normalized vector. Returns [0,0,0] if the input vector is a zero vector.
|
|
7
|
+
*/
|
|
8
|
+
function vec3Normalize(v) {
|
|
9
|
+
const len = Math.sqrt(v[0] * v[0] + v[1] * v[1] + v[2] * v[2]);
|
|
10
|
+
if (len < 1e-9) { // Use a small epsilon to handle near-zero vectors
|
|
11
|
+
return [0, 0, 0];
|
|
12
|
+
}
|
|
13
|
+
return [v[0] / len, v[1] / len, v[2] / len];
|
|
14
|
+
}
|
|
15
|
+
/**
|
|
16
|
+
* Calculates the dot product of two 3D vectors.
|
|
17
|
+
* @param v1 The first vector.
|
|
18
|
+
* @param v2 The second vector.
|
|
19
|
+
* @returns The dot product.
|
|
20
|
+
*/
|
|
21
|
+
function vec3Dot(v1, v2) {
|
|
22
|
+
return v1[0] * v2[0] + v1[1] * v2[1] + v1[2] * v2[2];
|
|
23
|
+
}
|
|
24
|
+
/**
|
|
25
|
+
* Calculates the cross product of two 3D vectors.
|
|
26
|
+
* @param v1 The first vector.
|
|
27
|
+
* @param v2 The second vector.
|
|
28
|
+
* @returns The cross product vector.
|
|
29
|
+
*/
|
|
30
|
+
function vec3Cross(v1, v2) {
|
|
31
|
+
return [
|
|
32
|
+
v1[1] * v2[2] - v1[2] * v2[1],
|
|
33
|
+
v1[2] * v2[0] - v1[0] * v2[2],
|
|
34
|
+
v1[0] * v2[1] - v1[1] * v2[0]
|
|
35
|
+
];
|
|
36
|
+
}
|
|
37
|
+
/**
|
|
38
|
+
* Scales a 3D vector by a scalar.
|
|
39
|
+
* @param v The input vector.
|
|
40
|
+
* @param s The scalar value.
|
|
41
|
+
* @returns The scaled vector.
|
|
42
|
+
*/
|
|
43
|
+
function vec3Scale(v, s) {
|
|
44
|
+
return [v[0] * s, v[1] * s, v[2] * s];
|
|
45
|
+
}
|
|
46
|
+
/**
|
|
47
|
+
* Adds two 3D vectors.
|
|
48
|
+
* @param v1 The first vector.
|
|
49
|
+
* @param v2 The second vector.
|
|
50
|
+
* @returns The resulting vector.
|
|
51
|
+
*/
|
|
52
|
+
function vec3Add(v1, v2) {
|
|
53
|
+
return [v1[0] + v2[0], v1[1] + v2[1], v1[2] + v2[2]];
|
|
54
|
+
}
|
|
55
|
+
/**
|
|
56
|
+
* Subtracts the second 3D vector from the first.
|
|
57
|
+
* @param v1 The first vector.
|
|
58
|
+
* @param v2 The second vector.
|
|
59
|
+
* @returns The resulting vector.
|
|
60
|
+
*/
|
|
61
|
+
function vec3Sub(v1, v2) {
|
|
62
|
+
return [v1[0] - v2[0], v1[1] - v2[1], v1[2] - v2[2]];
|
|
63
|
+
}
|
|
64
|
+
/**
|
|
65
|
+
* Clamps a numerical value within a specified range.
|
|
66
|
+
* @param value The value to clamp.
|
|
67
|
+
* @param min The minimum allowed value.
|
|
68
|
+
* @param max The maximum allowed value.
|
|
69
|
+
* @returns The clamped value.
|
|
70
|
+
*/
|
|
71
|
+
function clamp(value, min, max) {
|
|
72
|
+
return Math.max(min, Math.min(value, max));
|
|
73
|
+
}
|
|
74
|
+
function vec3Distance(v1, v2) {
|
|
75
|
+
const dx = v1[0] - v2[0];
|
|
76
|
+
const dy = v1[1] - v2[1];
|
|
77
|
+
const dz = v1[2] - v2[2];
|
|
78
|
+
return Math.sqrt(dx * dx + dy * dy + dz * dz);
|
|
79
|
+
}
|
|
80
|
+
/**
|
|
81
|
+
* Rotates a vector around a given axis by a specified angle using Rodrigues' rotation formula.
|
|
82
|
+
* The axis must be a unit vector for correct results.
|
|
83
|
+
* @param v The vector to rotate.
|
|
84
|
+
* @param axis The unit axis of rotation.
|
|
85
|
+
* @param angle The angle of rotation in radians.
|
|
86
|
+
* @returns The rotated vector.
|
|
87
|
+
*/
|
|
88
|
+
function rotateVector(v, axis, angle) {
|
|
89
|
+
const cosAngle = Math.cos(angle);
|
|
90
|
+
const sinAngle = Math.sin(angle);
|
|
91
|
+
const oneMinusCos = 1.0 - cosAngle;
|
|
92
|
+
const dotAxisV = vec3Dot(axis, v);
|
|
93
|
+
const crossAxisV = vec3Cross(axis, v);
|
|
94
|
+
// Rodrigues' rotation formula:
|
|
95
|
+
// v' = v * cos(angle) + (axis x v) * sin(angle) + axis * (axis . v) * (1 - cos(angle))
|
|
96
|
+
const term1 = vec3Scale(v, cosAngle);
|
|
97
|
+
const term2 = vec3Scale(crossAxisV, sinAngle);
|
|
98
|
+
const term3 = vec3Scale(axis, dotAxisV * oneMinusCos);
|
|
99
|
+
return vec3Add(vec3Add(term1, term2), term3);
|
|
100
|
+
}
|
|
101
|
+
/**
|
|
102
|
+
* Solves a quadratic equation Ax^2 + Bx + C = 0 for x, returning a root within a specified range.
|
|
103
|
+
* Handles linear cases (A approx 0) and multiple roots, selecting the most appropriate one for CDF inversion.
|
|
104
|
+
* @param a Coefficient A.
|
|
105
|
+
* @param b Coefficient B.
|
|
106
|
+
* @param c Coefficient C.
|
|
107
|
+
* @param minVal The minimum expected value for the root.
|
|
108
|
+
* @param maxVal The maximum expected value for the root.
|
|
109
|
+
* @returns The selected root within the range, or null if no valid real root.
|
|
110
|
+
*/
|
|
111
|
+
function solveQuadratic(a, b, c, minVal, maxVal) {
|
|
112
|
+
const epsilon = 1e-12; // Small value for floating point comparisons
|
|
113
|
+
// Case 1: Linear equation (A is close to zero)
|
|
114
|
+
if (Math.abs(a) < epsilon) {
|
|
115
|
+
if (Math.abs(b) < epsilon) {
|
|
116
|
+
// Both A and B are zero. If C is also zero, infinitely many solutions, pick minVal.
|
|
117
|
+
// Otherwise, no solutions.
|
|
118
|
+
return Math.abs(c) < epsilon ? minVal : null;
|
|
119
|
+
}
|
|
120
|
+
const x = -c / b;
|
|
121
|
+
// Check if the linear solution is within the expected range
|
|
122
|
+
return (x >= minVal - epsilon && x <= maxVal + epsilon) ? x : null;
|
|
123
|
+
}
|
|
124
|
+
// Case 2: Quadratic equation
|
|
125
|
+
const discriminant = b * b - 4 * a * c;
|
|
126
|
+
if (discriminant < -epsilon) { // No real roots (discriminant is significantly negative)
|
|
127
|
+
return null;
|
|
128
|
+
}
|
|
129
|
+
if (discriminant < epsilon) { // One real root (discriminant is close to zero)
|
|
130
|
+
const x = -b / (2 * a);
|
|
131
|
+
return (x >= minVal - epsilon && x <= maxVal + epsilon) ? x : null;
|
|
132
|
+
}
|
|
133
|
+
// Case 3: Two distinct real roots
|
|
134
|
+
const sqrtDisc = Math.sqrt(discriminant);
|
|
135
|
+
const x1 = (-b + sqrtDisc) / (2 * a);
|
|
136
|
+
const x2 = (-b - sqrtDisc) / (2 * a);
|
|
137
|
+
const validX1 = (x1 >= minVal - epsilon && x1 <= maxVal + epsilon);
|
|
138
|
+
const validX2 = (x2 >= minVal - epsilon && x2 <= maxVal + epsilon);
|
|
139
|
+
if (validX1 && validX2) {
|
|
140
|
+
// If both roots are valid, pick the one that is logically correct for CDF inversion.
|
|
141
|
+
// For an increasing CDF, we generally want the smallest valid non-negative root.
|
|
142
|
+
return Math.min(x1, x2);
|
|
143
|
+
}
|
|
144
|
+
else if (validX1) {
|
|
145
|
+
return x1;
|
|
146
|
+
}
|
|
147
|
+
else if (validX2) {
|
|
148
|
+
return x2;
|
|
149
|
+
}
|
|
150
|
+
else {
|
|
151
|
+
return null; // No valid roots within the range
|
|
152
|
+
}
|
|
153
|
+
}
|
|
154
|
+
// --- Main Function for Arc Point Generation ---
|
|
155
|
+
/**
|
|
156
|
+
* Generates points on the shortest arc between p0 and p1 on a unit sphere.
|
|
157
|
+
* The arc is formed by rotating p0 around axisA. The density of points
|
|
158
|
+
* is higher closer to the attractionPoint.
|
|
159
|
+
*
|
|
160
|
+
* @param p0 The starting point of the arc (will be normalized).
|
|
161
|
+
* @param p1 The ending point of the arc (will be normalized).
|
|
162
|
+
* @param axisA The axis of rotation (will be normalized).
|
|
163
|
+
* @param attractionPoint The point on the unit sphere that attracts sampled points (will be normalized).
|
|
164
|
+
* @param pointCount The total number of points to generate on the arc, including p0 and p1.
|
|
165
|
+
* @param strength Controls the density bias. A value of 0 means uniform sampling.
|
|
166
|
+
* Higher positive values (e.g., 1 to 10) result in stronger attraction.
|
|
167
|
+
* @returns An array of `Vec3` representing the deterministically sampled points on the arc.
|
|
168
|
+
*/
|
|
169
|
+
export function generateArcPoints(p0, p1, axisA, attractionPoint, pointCount, strength) {
|
|
170
|
+
const sampledPoints = [];
|
|
171
|
+
const epsilon = 1e-7; // General epsilon for vector comparisons
|
|
172
|
+
// 1. Normalize all input vectors
|
|
173
|
+
let nP0 = vec3Normalize(p0);
|
|
174
|
+
let nP1 = vec3Normalize(p1);
|
|
175
|
+
let nAxisA = vec3Normalize(axisA);
|
|
176
|
+
let nAttractionPoint = vec3Normalize(attractionPoint);
|
|
177
|
+
// Handle edge cases for pointCount
|
|
178
|
+
if (pointCount < 1) {
|
|
179
|
+
return sampledPoints;
|
|
180
|
+
}
|
|
181
|
+
if (pointCount === 1) {
|
|
182
|
+
sampledPoints.push(nP0);
|
|
183
|
+
return sampledPoints;
|
|
184
|
+
}
|
|
185
|
+
// 2. Determine the total rotation angle required to go from p0 to p1 around axisA
|
|
186
|
+
// Project p0 and p1 onto the plane perpendicular to axisA
|
|
187
|
+
const p0Projected = vec3Sub(nP0, vec3Scale(nAxisA, vec3Dot(nP0, nAxisA)));
|
|
188
|
+
const p1Projected = vec3Sub(nP1, vec3Scale(nAxisA, vec3Dot(nP1, nAxisA)));
|
|
189
|
+
const lenP0Proj = Math.sqrt(vec3Dot(p0Projected, p0Projected));
|
|
190
|
+
const lenP1Proj = Math.sqrt(vec3Dot(p1Projected, p1Projected));
|
|
191
|
+
// Handle cases where p0 or p1 are colinear with the rotation axis
|
|
192
|
+
if (lenP0Proj < epsilon || lenP1Proj < epsilon) {
|
|
193
|
+
// If start and end points are effectively the same (on or very near the axis)
|
|
194
|
+
if (Math.abs(vec3Dot(nP0, nP1) - 1.0) < epsilon) {
|
|
195
|
+
for (let i = 0; i < pointCount; i++) {
|
|
196
|
+
sampledPoints.push(nP0); // Return identical points as no meaningful arc exists
|
|
197
|
+
}
|
|
198
|
+
return sampledPoints;
|
|
199
|
+
}
|
|
200
|
+
else {
|
|
201
|
+
console.warn("generateArcPoints: p0 or p1 is colinear with axisA. Cannot form a valid arc by rotation around axisA to reach p1. Falling back to linear interpolation.");
|
|
202
|
+
// Fallback: simple linear interpolation and normalize
|
|
203
|
+
for (let i = 0; i < pointCount; i++) {
|
|
204
|
+
const t = i / (pointCount - 1);
|
|
205
|
+
const interpolated = vec3Add(vec3Scale(nP0, (1 - t)), vec3Scale(nP1, t));
|
|
206
|
+
sampledPoints.push(vec3Normalize(interpolated));
|
|
207
|
+
}
|
|
208
|
+
return sampledPoints;
|
|
209
|
+
}
|
|
210
|
+
}
|
|
211
|
+
const nP0Projected = vec3Normalize(p0Projected);
|
|
212
|
+
const nP1Projected = vec3Normalize(p1Projected);
|
|
213
|
+
// Calculate total rotation angle using atan2 for a signed angle
|
|
214
|
+
const crossProj = vec3Cross(nP0Projected, nP1Projected);
|
|
215
|
+
const totalRotationAngle = Math.atan2(vec3Dot(crossProj, nAxisA), vec3Dot(nP0Projected, nP1Projected));
|
|
216
|
+
// If total rotation angle is negligible, all points are essentially p0
|
|
217
|
+
if (Math.abs(totalRotationAngle) < epsilon) {
|
|
218
|
+
for (let i = 0; i < pointCount; i++) {
|
|
219
|
+
sampledPoints.push(nP0);
|
|
220
|
+
}
|
|
221
|
+
return sampledPoints;
|
|
222
|
+
}
|
|
223
|
+
// 3. Find alpha_A: the angle parameter (from p0) where the arc point is closest to attractionPoint
|
|
224
|
+
// We search over `t` from 0 to 1, and map to actual rotation angles.
|
|
225
|
+
let alpha_A = 0.0; // This will be the actual rotation angle
|
|
226
|
+
let minAngleToAttraction = Math.PI; // Initialize with max possible angle
|
|
227
|
+
const testSteps = 1000; // Granularity for finding alpha_A
|
|
228
|
+
for (let i = 0; i <= testSteps; i++) {
|
|
229
|
+
const t_test = i / testSteps; // Normalized parameter [0, 1]
|
|
230
|
+
const currentAlpha = totalRotationAngle * t_test; // Actual rotation angle
|
|
231
|
+
const pArcTest = rotateVector(nP0, nAxisA, currentAlpha); // Point on arc
|
|
232
|
+
const dotProduct = clamp(vec3Dot(pArcTest, nAttractionPoint), -1.0, 1.0);
|
|
233
|
+
const currentAngle = Math.acos(dotProduct);
|
|
234
|
+
if (currentAngle < minAngleToAttraction) {
|
|
235
|
+
minAngleToAttraction = currentAngle;
|
|
236
|
+
alpha_A = currentAlpha;
|
|
237
|
+
}
|
|
238
|
+
}
|
|
239
|
+
// 4. Define the PDF parameters (for normalized parameter t in [0,1])
|
|
240
|
+
// The density function for 't' (normalized angle from 0 to 1)
|
|
241
|
+
const minDensity = 1.0;
|
|
242
|
+
// Strength ensures positive density. Higher strength gives more pronounced peak.
|
|
243
|
+
const maxDensity = minDensity + Math.max(0, strength);
|
|
244
|
+
// Normalize alpha_A to the [0, 1] range for the PDF/CDF calculations
|
|
245
|
+
// This represents the peak position (t_peak) within the normalized parameter space.
|
|
246
|
+
const alpha_A_normalized = totalRotationAngle !== 0 ? clamp(alpha_A / totalRotationAngle, 0, 1) : 0;
|
|
247
|
+
// Normalization constant for the PDF (integral over [0,1] must be 1)
|
|
248
|
+
// Area of trapezoid = 0.5 * (base1 + base2) * height
|
|
249
|
+
// In our case, the "bases" are minDensity and maxDensity, "height" is 1 (the t-interval length)
|
|
250
|
+
const pdfArea = 0.5 * (minDensity + maxDensity);
|
|
251
|
+
const pdfNormalizationConstant = 1.0 / pdfArea;
|
|
252
|
+
// Calculate CDF value at the peak point (alpha_A_normalized)
|
|
253
|
+
// This splits the CDF into two regions for the quadratic solver
|
|
254
|
+
const cdfAtAlphaA_norm = pdfNormalizationConstant * (minDensity * alpha_A_normalized + 0.5 * (maxDensity - minDensity) * alpha_A_normalized);
|
|
255
|
+
// 5. Generate deterministic points using inverse transform sampling
|
|
256
|
+
for (let i = 0; i < pointCount; i++) {
|
|
257
|
+
// Generate uniformly spaced values `u` in [0, 1]
|
|
258
|
+
const u = i / (pointCount - 1);
|
|
259
|
+
let t_sampled; // This will be the sampled normalized parameter [0, 1]
|
|
260
|
+
if (u <= cdfAtAlphaA_norm) {
|
|
261
|
+
// Case 1: The sampled point falls in the first part of the PDF (0 to alpha_A_normalized)
|
|
262
|
+
// F(t) = normConst * (minDensity * t + 0.5 * (maxDensity - minDensity) / alpha_A_normalized * t^2)
|
|
263
|
+
// Rearrange to A*t^2 + B*t + C = 0 for solving:
|
|
264
|
+
const A = (alpha_A_normalized !== 0) ? pdfNormalizationConstant * 0.5 * (maxDensity - minDensity) / alpha_A_normalized : 0;
|
|
265
|
+
const B = pdfNormalizationConstant * minDensity;
|
|
266
|
+
const C = -u;
|
|
267
|
+
const result = solveQuadratic(A, B, C, 0, alpha_A_normalized);
|
|
268
|
+
// Fallback for unexpected null (e.g., numerical issues)
|
|
269
|
+
t_sampled = result !== null ? result : (u / (cdfAtAlphaA_norm || epsilon)) * alpha_A_normalized;
|
|
270
|
+
}
|
|
271
|
+
else {
|
|
272
|
+
// Case 2: The sampled point falls in the second part of the PDF (alpha_A_normalized to 1)
|
|
273
|
+
// F(t) - cdfAtAlphaA_norm = normConst * [ maxDensity * (t - alpha_A_normalized) - 0.5 * (maxDensity - minDensity) / (1 - alpha_A_normalized) * (t - alpha_A_normalized)^2 ]
|
|
274
|
+
// Let x = t - alpha_A_normalized. Solve for x: A'*x^2 + B'*x + C' = 0
|
|
275
|
+
const rangeAfterA = 1 - alpha_A_normalized;
|
|
276
|
+
const A_prime = (rangeAfterA !== 0) ? -pdfNormalizationConstant * 0.5 * (maxDensity - minDensity) / rangeAfterA : 0;
|
|
277
|
+
const B_prime = pdfNormalizationConstant * maxDensity;
|
|
278
|
+
const C_prime = -(u - cdfAtAlphaA_norm);
|
|
279
|
+
const result_x = solveQuadratic(A_prime, B_prime, C_prime, 0, rangeAfterA);
|
|
280
|
+
// Fallback for unexpected null
|
|
281
|
+
t_sampled = (result_x !== null ? alpha_A_normalized + result_x :
|
|
282
|
+
alpha_A_normalized + (u - cdfAtAlphaA_norm) / ((1 - cdfAtAlphaA_norm) || epsilon) * rangeAfterA);
|
|
283
|
+
}
|
|
284
|
+
// Ensure t_sampled is within [0, 1] due to potential float inaccuracies
|
|
285
|
+
t_sampled = clamp(t_sampled, 0, 1);
|
|
286
|
+
// Convert the normalized parameter 't_sampled' back to the actual rotation angle
|
|
287
|
+
const actualRotationAngle = totalRotationAngle * t_sampled;
|
|
288
|
+
// Rotate the starting point p0 by this angle around axisA to get the final point
|
|
289
|
+
const currentPoint = rotateVector(nP0, nAxisA, actualRotationAngle);
|
|
290
|
+
sampledPoints.push(currentPoint);
|
|
291
|
+
}
|
|
292
|
+
return sampledPoints;
|
|
293
|
+
}
|
|
294
|
+
// --- Example Usage (for demonstration and testing) ---
|
|
295
|
+
// Example 1: Basic arc, attraction point in the middle
|
|
296
|
+
const p0_ex1 = [1, 0, 0];
|
|
297
|
+
const p1_ex1 = [0, 1, 0]; // 90 deg rotation around Z-axis
|
|
298
|
+
const axisA_ex1 = [0, 0, 1];
|
|
299
|
+
const attractionPoint_ex1 = [Math.sqrt(0.5), Math.sqrt(0.5), 0]; // On the arc, middle of P0-P1
|
|
300
|
+
const numPoints_ex1 = 50;
|
|
301
|
+
const strength_ex1 = 100.0; // Strong attraction
|
|
302
|
+
console.log("--- Example 1: Attraction point in the middle of the arc ---");
|
|
303
|
+
const points1 = generateArcPoints(p0_ex1, p1_ex1, axisA_ex1, attractionPoint_ex1, numPoints_ex1, strength_ex1);
|
|
304
|
+
console.log(`Generated ${points1.length} points.`);
|
|
305
|
+
// To observe concentration, you might visualize these points or calculate their 't' parameter distribution.
|
|
306
|
+
// For console output, let's print a few:
|
|
307
|
+
console.log("First 5 points:", points1.slice(0, 5).map(p => p.map(coord => coord.toFixed(4))));
|
|
308
|
+
console.log("Last 5 points:", points1.slice(-5).map(p => p.map(coord => coord.toFixed(4))));
|
|
309
|
+
// Expected: Points should be denser around [0.707, 0.707, 0]
|
|
310
|
+
const sequentialDistances1 = points1.map((p, i) => {
|
|
311
|
+
if (i === 0)
|
|
312
|
+
return 0; // Skip the first point
|
|
313
|
+
return vec3Distance(points1[i - 1], p);
|
|
314
|
+
});
|
|
315
|
+
console.log("Sequential distances between points (should be roughly equal for uniform distribution):", sequentialDistances1.map(d => d.toFixed(4)));
|
|
316
|
+
// Example 2: Attraction point near p0
|
|
317
|
+
const p0_ex2 = [1, 0, 0];
|
|
318
|
+
const p1_ex2 = [0, 1, 0];
|
|
319
|
+
const axisA_ex2 = [0, 0, 1];
|
|
320
|
+
const attractionPoint_ex2 = [0.99, 0.01, 0]; // Very close to P0
|
|
321
|
+
const numPoints_ex2 = 50;
|
|
322
|
+
const strength_ex2 = 10.0; // Very strong attraction
|
|
323
|
+
console.log("\n--- Example 2: Attraction point near the start (p0) ---");
|
|
324
|
+
const points2 = generateArcPoints(p0_ex2, p1_ex2, axisA_ex2, attractionPoint_ex2, numPoints_ex2, strength_ex2);
|
|
325
|
+
console.log(`Generated ${points2.length} points.`);
|
|
326
|
+
console.log("First 5 points:", points2.slice(0, 5).map(p => p.map(coord => coord.toFixed(4))));
|
|
327
|
+
console.log("Last 5 points:", points2.slice(-5).map(p => p.map(coord => coord.toFixed(4))));
|
|
328
|
+
// Expected: Points should be denser near [1, 0, 0]
|
|
329
|
+
// Example 3: Attraction point away from the arc (expect less concentration)
|
|
330
|
+
const p0_ex3 = [1, 0, 0];
|
|
331
|
+
const p1_ex3 = [0, 1, 0];
|
|
332
|
+
const axisA_ex3 = [0, 0, 1];
|
|
333
|
+
const attractionPoint_ex3 = [0, 0, 1]; // North pole, away from the XY plane arc
|
|
334
|
+
const numPoints_ex3 = 50;
|
|
335
|
+
const strength_ex3 = 5.0;
|
|
336
|
+
console.log("\n--- Example 3: Attraction point away from the arc ---");
|
|
337
|
+
const points3 = generateArcPoints(p0_ex3, p1_ex3, axisA_ex3, attractionPoint_ex3, numPoints_ex3, strength_ex3);
|
|
338
|
+
console.log(`Generated ${points3.length} points.`);
|
|
339
|
+
console.log("First 5 points:", points3.slice(0, 5).map(p => p.map(coord => coord.toFixed(4))));
|
|
340
|
+
console.log("Last 5 points:", points3.slice(-5).map(p => p.map(coord => coord.toFixed(4))));
|
|
341
|
+
// Expected: Points should be relatively uniformly distributed, as no point on the arc is significantly closer.
|
|
342
|
+
// The "closest point" on the arc will likely be determined by its closest angular projection.
|
|
343
|
+
// Example 4: No attraction (uniform distribution)
|
|
344
|
+
const p0_ex4 = [1, 0, 0];
|
|
345
|
+
const p1_ex4 = [0, 1, 0];
|
|
346
|
+
const axisA_ex4 = [0, 0, 1];
|
|
347
|
+
const attractionPoint_ex4 = [Math.sqrt(1 / 2), Math.sqrt(1 / 2), 0]; // Irrelevant when strength is 0
|
|
348
|
+
const numPoints_ex4 = 50;
|
|
349
|
+
const strength_ex4 = 4.0; // No attraction (uniform)
|
|
350
|
+
console.log("\n--- Example 4: Uniform distribution (strength = 0) ---");
|
|
351
|
+
const points4 = generateArcPoints(p0_ex4, p1_ex4, axisA_ex4, attractionPoint_ex4, numPoints_ex4, strength_ex4);
|
|
352
|
+
console.log(`Generated ${points4.length} points.`);
|
|
353
|
+
console.log("First 5 points:", points4.slice(0, 5).map(p => p.map(coord => coord.toFixed(4))));
|
|
354
|
+
console.log("Last 5 points:", points4.slice(-5).map(p => p.map(coord => coord.toFixed(4))));
|
|
355
|
+
// Expected: Points should be uniformly spaced.
|
|
356
|
+
const sequentialDistances = points4.map((p, i) => {
|
|
357
|
+
if (i === 0)
|
|
358
|
+
return 0; // Skip the first point
|
|
359
|
+
return vec3Distance(points4[i - 1], p);
|
|
360
|
+
});
|
|
361
|
+
console.log("Sequential distances between points (should be roughly equal for uniform distribution):", sequentialDistances.map(d => d.toFixed(4)));
|
|
@@ -8,13 +8,13 @@ import { PickerDisplayer } from '../util/picking/picker-displayer.js';
|
|
|
8
8
|
import { wgs84ToCartesian3d, wgs84ToMercator } from '../Math/methods';
|
|
9
9
|
import { constraintFloat, opacityCheck } from '../util/check/typecheck.js';
|
|
10
10
|
import { createBufferAndReadInfo } from '../util/gl-util/buffer/attribute-loader';
|
|
11
|
-
import { CameraUniformBlockTotemCache } from '../programs/totems/camerauniformblock
|
|
11
|
+
import { CameraUniformBlockTotemCache } from '../programs/totems/camerauniformblock';
|
|
12
12
|
/**
|
|
13
13
|
* is used with depth we can create a line from surface to the point.
|
|
14
14
|
*/
|
|
15
15
|
const glowOverSize = 1.35; // 1.25 is the default value in the shader
|
|
16
|
-
const _0vec6 =
|
|
17
|
-
const
|
|
16
|
+
const _0vec6 = /* @__PURE__ */ new Float32Array(6);
|
|
17
|
+
const _0vec6_3_6subarray = /* @__PURE__ */ _0vec6.subarray(3, 6);
|
|
18
18
|
class PointGlowLineToEarthPlugin {
|
|
19
19
|
constructor({ isGlowPointOn = true, isGlowSurfaceOn = true }) {
|
|
20
20
|
this.globe = null;
|
|
@@ -55,10 +55,7 @@ class PointGlowLineToEarthPlugin {
|
|
|
55
55
|
bufferManager: new BufferManager(gl, 6, { bufferType, initialCapacity }),
|
|
56
56
|
adaptor: (item) => {
|
|
57
57
|
wgs84ToCartesian3d(_0vec6, item.long, item.lat, item.altitude / 1000);
|
|
58
|
-
wgs84ToCartesian3d(
|
|
59
|
-
_0vec6[3] = _0vec3[0];
|
|
60
|
-
_0vec6[4] = _0vec3[1];
|
|
61
|
-
_0vec6[5] = _0vec3[2];
|
|
58
|
+
wgs84ToCartesian3d(_0vec6_3_6subarray, item.long, item.lat, 0 / 1000);
|
|
62
59
|
return new Float32Array(_0vec6);
|
|
63
60
|
}
|
|
64
61
|
}],
|
package/package.json
CHANGED
|
@@ -64,6 +64,8 @@ class PointHeatmapPlugin {
|
|
|
64
64
|
// TODO: WORK ON THIS
|
|
65
65
|
this._time = time;
|
|
66
66
|
if (this.isReady() && this._throttleListener === null) {
|
|
67
|
+
if (this._isFreed)
|
|
68
|
+
return;
|
|
67
69
|
this._throttleListener = setTimeout(() => {
|
|
68
70
|
this.timeTrackInterpolationWorker.postMessage({ time: this._time });
|
|
69
71
|
}, 0);
|
|
@@ -0,0 +1,171 @@
|
|
|
1
|
+
import { globeProgramCache } from "../programcache";
|
|
2
|
+
// import { getFrustumPlanes } from "../../Math/frustum/from-projection-matrix";
|
|
3
|
+
// import { getFrustum } from "../../Math/frustum/from-globeinfo"
|
|
4
|
+
// import { Plane } from "../../Math/";
|
|
5
|
+
export const CameraUniformBlockString = `
|
|
6
|
+
layout(std140) uniform CameraUniformBlock {
|
|
7
|
+
mat4 view; // 64 bytes 0
|
|
8
|
+
mat4 projection; // 64 bytes 64
|
|
9
|
+
vec3 translate; // 12 bytes 128
|
|
10
|
+
bool is3D; // 4 bytes 140
|
|
11
|
+
vec2 mapWH; // 8 bytes 144
|
|
12
|
+
vec2 screenWH; // 8 bytes 152
|
|
13
|
+
float z_level; // 4 bytes 160 | 164
|
|
14
|
+
float world_distance; // 4 bytes 164
|
|
15
|
+
float world_tilt; // 4 bytes 168
|
|
16
|
+
float world_north_angle; // 4 bytes 172
|
|
17
|
+
vec2 world_center_radian; // 8 bytes 176 | 184
|
|
18
|
+
}; // 14 lines
|
|
19
|
+
`;
|
|
20
|
+
const Radian = Math.PI / 180.0;
|
|
21
|
+
export default class CameraUniformBlockTotem {
|
|
22
|
+
constructor() {
|
|
23
|
+
this.id = "CameraUniformBlockTotem";
|
|
24
|
+
this.description = `Sets a uniform block and provides buffer for it. The following is the glsl uniform block:` + CameraUniformBlockString;
|
|
25
|
+
this.gl = null;
|
|
26
|
+
this.globe = null;
|
|
27
|
+
this.ubo = null;
|
|
28
|
+
// this._frustumPlanes = {
|
|
29
|
+
// left: new Plane(),
|
|
30
|
+
// right: new Plane(),
|
|
31
|
+
// top: new Plane(),
|
|
32
|
+
// bottom: new Plane(),
|
|
33
|
+
// near: new Plane(),
|
|
34
|
+
// far: new Plane()
|
|
35
|
+
// }
|
|
36
|
+
this._isMovedParams = {
|
|
37
|
+
lastLod: null,
|
|
38
|
+
isMoved: false,
|
|
39
|
+
};
|
|
40
|
+
}
|
|
41
|
+
init(globe, gl) {
|
|
42
|
+
this.gl = gl;
|
|
43
|
+
this.globe = globe;
|
|
44
|
+
this.ubo = this._createUBO();
|
|
45
|
+
this.traslateFloat32 = new Float32Array(3);
|
|
46
|
+
this.mapWHFloat32 = new Float32Array(2);
|
|
47
|
+
this.setGeometry();
|
|
48
|
+
this.resize();
|
|
49
|
+
}
|
|
50
|
+
_createUBO() {
|
|
51
|
+
const { gl } = this;
|
|
52
|
+
const ubo = gl.createBuffer();
|
|
53
|
+
gl.bindBuffer(gl.UNIFORM_BUFFER, ubo);
|
|
54
|
+
// 184 bytes in reality. Overflow on linux for some reason. So, 200 bytes.
|
|
55
|
+
gl.bufferData(gl.UNIFORM_BUFFER, 200, gl.STREAM_DRAW);
|
|
56
|
+
gl.bindBufferBase(gl.UNIFORM_BUFFER, 0, ubo);
|
|
57
|
+
gl.bindBuffer(gl.UNIFORM_BUFFER, null);
|
|
58
|
+
return ubo;
|
|
59
|
+
}
|
|
60
|
+
resize() {
|
|
61
|
+
const { gl, globe, ubo } = this;
|
|
62
|
+
gl.bindBuffer(gl.UNIFORM_BUFFER, ubo);
|
|
63
|
+
gl.bufferSubData(gl.UNIFORM_BUFFER, 152, new Float32Array([globe.api_ScrW(), globe.api_ScrH()]));
|
|
64
|
+
gl.bindBuffer(gl.UNIFORM_BUFFER, null);
|
|
65
|
+
}
|
|
66
|
+
setGeometry() {
|
|
67
|
+
const { gl, globe, ubo } = this;
|
|
68
|
+
const is3D = globe.api_GetCurrentGeometry() === 0;
|
|
69
|
+
gl.bindBuffer(gl.UNIFORM_BUFFER, ubo);
|
|
70
|
+
gl.bufferSubData(gl.UNIFORM_BUFFER, 140, new Float32Array([is3D]));
|
|
71
|
+
}
|
|
72
|
+
draw3D(projection, modelView, translate) {
|
|
73
|
+
const { gl, traslateFloat32, ubo, mapWHFloat32, globe } = this;
|
|
74
|
+
gl.bindBuffer(gl.UNIFORM_BUFFER, ubo);
|
|
75
|
+
{ // view, projection, translat
|
|
76
|
+
gl.bufferSubData(gl.UNIFORM_BUFFER, 0, modelView);
|
|
77
|
+
gl.bufferSubData(gl.UNIFORM_BUFFER, 64, projection);
|
|
78
|
+
traslateFloat32.set([translate.x, translate.y, translate.z], 0);
|
|
79
|
+
gl.bufferSubData(gl.UNIFORM_BUFFER, 128, traslateFloat32);
|
|
80
|
+
}
|
|
81
|
+
{
|
|
82
|
+
// zoom level
|
|
83
|
+
gl.bufferSubData(gl.UNIFORM_BUFFER, 160, new Float32Array([globe.api_GetCurrentLODWithDecimal()]));
|
|
84
|
+
}
|
|
85
|
+
{ // mapWH
|
|
86
|
+
if (globe.api_GetCurrentGeometry() === 1) {
|
|
87
|
+
const { width, height } = globe.api_GetCurrentWorldWH();
|
|
88
|
+
mapWHFloat32.set([width, height]);
|
|
89
|
+
gl.bufferSubData(gl.UNIFORM_BUFFER, 144, mapWHFloat32);
|
|
90
|
+
}
|
|
91
|
+
}
|
|
92
|
+
{
|
|
93
|
+
// float world_distance; // 4 bytes 164
|
|
94
|
+
// float world_tilt; // 4 bytes 168
|
|
95
|
+
// float world_north_angle; // 4 bytes 172
|
|
96
|
+
// vec2 world_center_radian; // 8 bytes 180
|
|
97
|
+
const { CenterLong, CenterLat, Distance, Tilt, NorthAng } = globe.api_GetCurrentLookInfo();
|
|
98
|
+
gl.bufferSubData(gl.UNIFORM_BUFFER, 164, new Float32Array([
|
|
99
|
+
Distance, Radian * Tilt, Radian * NorthAng, Radian * CenterLong, Radian * CenterLat
|
|
100
|
+
]));
|
|
101
|
+
}
|
|
102
|
+
// this._frustumPlanes = getFrustumPlanes(projection, translate);
|
|
103
|
+
gl.bindBuffer(gl.UNIFORM_BUFFER, null);
|
|
104
|
+
{ // isMoved
|
|
105
|
+
const currentLOD = globe.api_GetCurrentLODWithDecimal();
|
|
106
|
+
this._isMovedParams.isMoved = this._isMovedParams.lastLod !== currentLOD || globe.api_IsScreenMoving();
|
|
107
|
+
this._isMovedParams.lastLod = currentLOD;
|
|
108
|
+
}
|
|
109
|
+
// getFrustum(globe, 50, this._frustumPlanes);
|
|
110
|
+
this._normalizedCameraVector = (() => {
|
|
111
|
+
const { Fp, FUPos } = globe;
|
|
112
|
+
const cameraVector = [Fp.x, Fp.y, Fp.z];
|
|
113
|
+
const length = Math.sqrt(cameraVector.reduce((sum, val) => sum + val * val, 0));
|
|
114
|
+
return normalizedCameraVector.map(val => val / length);
|
|
115
|
+
})();
|
|
116
|
+
}
|
|
117
|
+
assignBindingPoint(program, bindingPoint) {
|
|
118
|
+
const { gl } = this;
|
|
119
|
+
const cameraBlockIndex = gl.getUniformBlockIndex(program, "CameraUniformBlock");
|
|
120
|
+
gl.uniformBlockBinding(program, cameraBlockIndex, bindingPoint);
|
|
121
|
+
}
|
|
122
|
+
getUBO() {
|
|
123
|
+
return this.ubo;
|
|
124
|
+
}
|
|
125
|
+
getFrustumPlanes() {
|
|
126
|
+
return this._frustumPlanes;
|
|
127
|
+
}
|
|
128
|
+
bind(bindingPoint) {
|
|
129
|
+
const { gl, ubo } = this;
|
|
130
|
+
gl.bindBufferBase(gl.UNIFORM_BUFFER, bindingPoint, ubo);
|
|
131
|
+
}
|
|
132
|
+
unbind(bindingPoint) {
|
|
133
|
+
const { gl } = this;
|
|
134
|
+
gl.bindBufferBase(gl.UNIFORM_BUFFER, bindingPoint, null);
|
|
135
|
+
}
|
|
136
|
+
isMoved() {
|
|
137
|
+
return this._isMovedParams.isMoved;
|
|
138
|
+
}
|
|
139
|
+
getCameraVector() {
|
|
140
|
+
return [this.globe.Fp.x, this.globe.Fp.y, this.globe.Fp.z];
|
|
141
|
+
}
|
|
142
|
+
getNormalizedCameraVector() {
|
|
143
|
+
return this._normalizedCameraVector;
|
|
144
|
+
}
|
|
145
|
+
getCameraUpPosition() {
|
|
146
|
+
return [this.globe.FUPos.x, this.globe.FUPos.y, this.globe.FUPos.z];
|
|
147
|
+
}
|
|
148
|
+
free() {
|
|
149
|
+
const { gl, ubo } = this;
|
|
150
|
+
gl.deleteBuffer(ubo);
|
|
151
|
+
}
|
|
152
|
+
readBuffer() {
|
|
153
|
+
const result = new Float32Array(41);
|
|
154
|
+
this.gl.bindBuffer(this.gl.UNIFORM_BUFFER, this.ubo);
|
|
155
|
+
this.gl.getBufferSubData(this.gl.UNIFORM_BUFFER, 0, result);
|
|
156
|
+
this.gl.bindBuffer(this.gl.UNIFORM_BUFFER, null);
|
|
157
|
+
return {
|
|
158
|
+
view: result.slice(0, 16),
|
|
159
|
+
projection: result.slice(16, 32),
|
|
160
|
+
translate: result.slice(32, 35),
|
|
161
|
+
is3D: result[35],
|
|
162
|
+
mapWH: result.slice(36, 38),
|
|
163
|
+
screenWH: result.slice(38, 40),
|
|
164
|
+
z_level: result[40]
|
|
165
|
+
};
|
|
166
|
+
}
|
|
167
|
+
}
|
|
168
|
+
export const CameraUniformBlockTotemCache = Object.freeze({
|
|
169
|
+
get: (globe) => { return globeProgramCache.getProgram(globe, CameraUniformBlockTotem); },
|
|
170
|
+
release: (globe) => { return globeProgramCache.releaseProgram(globe, CameraUniformBlockTotem); }
|
|
171
|
+
});
|