@pirireis/webglobeplugins 0.8.15-alpha → 0.8.17
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.ts +239 -0
- package/Math/bounds/line-bbox.js +225 -0
- package/Math/constants.ts +8 -0
- package/Math/methodology/arc-part-on-screen.ts +47 -0
- package/Math/methods.js +1 -3
- package/Math/plane.ts +167 -0
- package/Math/quaternion.ts +159 -0
- package/Math/ray.ts +101 -0
- package/Math/roadmap.md +10 -0
- package/Math/types.ts +36 -0
- package/Math/utils.js +3 -0
- package/Math/vector3d.ts +230 -0
- package/jest.config.js +6 -0
- package/package.json +1 -1
- package/point-heat-map/plugin-webworker.js +1 -1
- package/programs/line-on-globe/circle-accurate-flat.js +18 -3
- package/tests/Math/arc.test.ts +52 -0
- package/tests/Math/plane.test.ts +45 -0
- package/tests/Math/quaternion.test.ts +98 -0
- package/tests/Math/ray-plane.test.ts +176 -0
- package/tests/Math/vector3d.test.ts +71 -0
- package/util/geometry/index.js +8 -5
- package/util/surface-line-data/arc-bboxes.ts +42 -0
- package/util/surface-line-data/arcs-to-cuts.js +74 -0
- package/util/surface-line-data/flow.ts +52 -0
- package/util/surface-line-data/rbush-manager.js +0 -0
- package/util/surface-line-data/types.ts +27 -0
- package/util/surface-line-data/web-worker.js +0 -0
- package/waveparticles/plugin.js +1 -1
- package/webworker-test/index.js +0 -10
- package/webworker-test/module.js +0 -10
- package/webworker-test/worker.js +0 -8
- /package/{webpack.config.js → util/surface-line-data/cut-arc.js} +0 -0
package/Math/arc.ts
ADDED
|
@@ -0,0 +1,239 @@
|
|
|
1
|
+
|
|
2
|
+
import { Vector3D } from "./vector3d";
|
|
3
|
+
import { Plane } from "./plane";
|
|
4
|
+
import { Quaternion } from "./quaternion";
|
|
5
|
+
import { Radians } from "./types";
|
|
6
|
+
import { Ray } from "./ray";
|
|
7
|
+
|
|
8
|
+
const _quaternion = /*@__PURE__*/ new Quaternion(0, 0, 0, 0);
|
|
9
|
+
const _ray = /*@__PURE__*/ new Ray(new Vector3D(0, 0, 0), new Vector3D(0, 0, 0));
|
|
10
|
+
const _0vector = /*@__PURE__*/ new Vector3D(0, 0, 0);
|
|
11
|
+
const _0plane = /*@__PURE__*/ new Plane(_0vector, 0);
|
|
12
|
+
|
|
13
|
+
const _A_x_otherA = /*@__PURE__*/ new Vector3D(0, 0, 0);
|
|
14
|
+
const _A_x_otherB = /*@__PURE__*/ new Vector3D(0, 0, 0);
|
|
15
|
+
const _AxB = /*@__PURE__*/ new Vector3D(0, 0, 0);
|
|
16
|
+
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
|
|
20
|
+
|
|
21
|
+
export class Arc {
|
|
22
|
+
pointA: Vector3D;
|
|
23
|
+
pointB: Vector3D;
|
|
24
|
+
_dot: number;
|
|
25
|
+
_AxB: Vector3D;
|
|
26
|
+
_theta: Radians;
|
|
27
|
+
_imageinaryPlane: Plane;
|
|
28
|
+
constructor(
|
|
29
|
+
pointA: Vector3D,
|
|
30
|
+
pointB: Vector3D,
|
|
31
|
+
) {
|
|
32
|
+
this.pointA = pointA.normalize();
|
|
33
|
+
this.pointB = pointB.normalize();
|
|
34
|
+
this._AxB = pointA.clone().cross(pointB);
|
|
35
|
+
this._dot = this.pointA.dot(this.pointB);
|
|
36
|
+
this._theta = Math.acos(this._dot);
|
|
37
|
+
this._imageinaryPlane = new Plane(this._AxB.clone().normalize(), 0);
|
|
38
|
+
}
|
|
39
|
+
|
|
40
|
+
|
|
41
|
+
copy(arc: Arc): Arc {
|
|
42
|
+
this.pointA.copy(arc.pointA);
|
|
43
|
+
this.pointB.copy(arc.pointB);
|
|
44
|
+
this._AxB.copy(arc._AxB);
|
|
45
|
+
this._dot = arc._dot;
|
|
46
|
+
this._theta = arc._theta;
|
|
47
|
+
this._imageinaryPlane.copy(arc._imageinaryPlane);
|
|
48
|
+
return this;
|
|
49
|
+
}
|
|
50
|
+
|
|
51
|
+
|
|
52
|
+
clone(): Arc {
|
|
53
|
+
return new Arc(this.pointA.clone(), this.pointB.clone());
|
|
54
|
+
}
|
|
55
|
+
|
|
56
|
+
|
|
57
|
+
equals(arc: Arc): boolean {
|
|
58
|
+
return (this.pointA.equals(arc.pointA) && this.pointB.equals(arc.pointB)) || (this.pointA.equals(arc.pointB) && this.pointB.equals(arc.pointA));
|
|
59
|
+
}
|
|
60
|
+
|
|
61
|
+
|
|
62
|
+
slerp(t: number): Vector3D {
|
|
63
|
+
|
|
64
|
+
const pointA = this.pointA;
|
|
65
|
+
const pointB = this.pointB;
|
|
66
|
+
|
|
67
|
+
const step = t / this._theta;
|
|
68
|
+
const sinTheta = Math.sin(this._theta);
|
|
69
|
+
const sinStep = Math.sin(step * this._theta);
|
|
70
|
+
const sinOneMinusStep = Math.sin((1 - step) * this._theta);
|
|
71
|
+
|
|
72
|
+
const x = (sinOneMinusStep * pointA[0] + sinStep * pointB[0]) / sinTheta;
|
|
73
|
+
const y = (sinOneMinusStep * pointA[1] + sinStep * pointB[1]) / sinTheta;
|
|
74
|
+
const z = (sinOneMinusStep * pointA[2] + sinStep * pointB[2]) / sinTheta;
|
|
75
|
+
|
|
76
|
+
return new Vector3D(x, y, z);
|
|
77
|
+
|
|
78
|
+
}
|
|
79
|
+
|
|
80
|
+
|
|
81
|
+
|
|
82
|
+
|
|
83
|
+
// TODO: EDGE CASE: the points are on same location
|
|
84
|
+
intersectionMedium(medium: Plane, target: Arc | null = null): Arc | null {
|
|
85
|
+
// Orianted in right hand rule
|
|
86
|
+
if (this.pointA.equals(this.pointB)) return null; // arc is too short
|
|
87
|
+
|
|
88
|
+
const result = this._imageinaryPlane.intersectionPlane(medium);
|
|
89
|
+
if (result instanceof Plane) {
|
|
90
|
+
return this.clone();
|
|
91
|
+
}
|
|
92
|
+
|
|
93
|
+
if (result === null) { console.log("null intersection"); return null; } // planes are parallel
|
|
94
|
+
|
|
95
|
+
const intersectionPoints = _ray.set(result.origin, result.direction).intersectionSphere(_0vector.set(0, 0, 0), 1);
|
|
96
|
+
if (intersectionPoints === null) {
|
|
97
|
+
return null; // no intersection
|
|
98
|
+
}
|
|
99
|
+
|
|
100
|
+
|
|
101
|
+
const [A, B] = (() => {
|
|
102
|
+
Vector3D.crossVectors(...intersectionPoints, _AxB);
|
|
103
|
+
if (_AxB.dot(this._AxB) >= 0) {
|
|
104
|
+
return [intersectionPoints[0], intersectionPoints[1]];
|
|
105
|
+
} else {
|
|
106
|
+
return [intersectionPoints[1], intersectionPoints[0]];
|
|
107
|
+
}
|
|
108
|
+
})();
|
|
109
|
+
|
|
110
|
+
|
|
111
|
+
Vector3D.crossVectors(this.pointA, A, _A_x_otherA);
|
|
112
|
+
Vector3D.crossVectors(this.pointA, B, _A_x_otherB);
|
|
113
|
+
|
|
114
|
+
// think as this.pointA is on Vector3D(0, 0, 1) on some space
|
|
115
|
+
const _yA = this.pointA.dot(A);
|
|
116
|
+
const _yB = this.pointA.dot(B);
|
|
117
|
+
|
|
118
|
+
const _xA = _A_x_otherA.dot(this._AxB);
|
|
119
|
+
const _xB = _A_x_otherB.dot(this._AxB);
|
|
120
|
+
|
|
121
|
+
|
|
122
|
+
if (_xA < 0 && _xB < 0) {
|
|
123
|
+
return null; // no intersection
|
|
124
|
+
}
|
|
125
|
+
if (_yA < this._dot && _yB < this._dot) {
|
|
126
|
+
return null; // no intersection
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
const results: [Vector3D, Vector3D] = [_xA < 0 ? this.pointA.clone() : A, _yB <= this._dot ? this.pointB.clone() : B];
|
|
131
|
+
|
|
132
|
+
if (target === null) {
|
|
133
|
+
target = new Arc(...results);
|
|
134
|
+
} else {
|
|
135
|
+
target.setFromUnitVectors(...results);
|
|
136
|
+
}
|
|
137
|
+
|
|
138
|
+
return target;
|
|
139
|
+
}
|
|
140
|
+
|
|
141
|
+
|
|
142
|
+
|
|
143
|
+
|
|
144
|
+
setFromUnitVectors(
|
|
145
|
+
pointA: Vector3D,
|
|
146
|
+
pointB: Vector3D,
|
|
147
|
+
) {
|
|
148
|
+
|
|
149
|
+
// following values might be calculated lazily
|
|
150
|
+
this.pointA.copy(pointA).normalize();
|
|
151
|
+
this.pointB.copy(pointB).normalize();
|
|
152
|
+
this._AxB = pointA.clone().cross(pointB);
|
|
153
|
+
this._dot = this.pointA.dot(this.pointB);
|
|
154
|
+
this._theta = Math.acos(this._dot);
|
|
155
|
+
this._imageinaryPlane = new Plane(this._AxB.clone().normalize(), 0);
|
|
156
|
+
}
|
|
157
|
+
|
|
158
|
+
|
|
159
|
+
|
|
160
|
+
populatePoints3D(pointCount: number, startProportion: number = 0, endProportion: number = 1): Float32Array {
|
|
161
|
+
|
|
162
|
+
const points3D = new Float32Array(pointCount * 3);
|
|
163
|
+
const step = (endProportion - startProportion) / (pointCount - 1);
|
|
164
|
+
const _AxBnormalized = this._AxB.clone().normalize();
|
|
165
|
+
|
|
166
|
+
_0vector.copy(this.pointA);
|
|
167
|
+
|
|
168
|
+
if (startProportion > 0) {
|
|
169
|
+
_quaternion.setFromAxisAngle(_AxBnormalized, this._theta * startProportion);
|
|
170
|
+
_0vector.applyQuaternion(_quaternion);
|
|
171
|
+
}
|
|
172
|
+
_quaternion.setFromAxisAngle(_AxBnormalized, this._theta * step);
|
|
173
|
+
let index = 0;
|
|
174
|
+
for (let i = 0; i < pointCount; i++) {
|
|
175
|
+
points3D[index++] = _0vector.x;
|
|
176
|
+
points3D[index++] = _0vector.y;
|
|
177
|
+
points3D[index++] = _0vector.z;
|
|
178
|
+
_0vector.applyQuaternion(_quaternion);
|
|
179
|
+
}
|
|
180
|
+
|
|
181
|
+
return points3D;
|
|
182
|
+
}
|
|
183
|
+
// cutBoundingBoxes(dotStep: number = 0.025): { minX: number, minY: number, maxX: number, maxY: number }[] {
|
|
184
|
+
// const pointA = this.pointA;
|
|
185
|
+
// const to = this.pointB;
|
|
186
|
+
// const a = [pointA.x, pointA.y, pointA.z];
|
|
187
|
+
// const b = [to.x, to.y, to.z];
|
|
188
|
+
|
|
189
|
+
// const theta = Math.acos(a[0] * b[0] + a[1] * b[1] + a[2] * b[2]);
|
|
190
|
+
// const step = dotStep / theta;
|
|
191
|
+
|
|
192
|
+
// if (step >= 1) {
|
|
193
|
+
// return [{ minX: Math.min(pointA.x, to.x), minY: Math.min(pointA.y, to.y), maxX: Math.max(pointA.x, to.x), maxY: Math.max(pointA.y, to.y) }];
|
|
194
|
+
// }
|
|
195
|
+
|
|
196
|
+
// let points = [pointA] as Vector3D[];
|
|
197
|
+
// let currentStep = 0;
|
|
198
|
+
// while (currentStep < 1) {
|
|
199
|
+
// currentStep += step;
|
|
200
|
+
// points.push(this.slerp(currentStep, currentStep + step, 1)[0]);
|
|
201
|
+
// }
|
|
202
|
+
|
|
203
|
+
// return points.map(point => ({ minX: point.x, minY: point.y, maxX: point.x, maxY: point.y }));
|
|
204
|
+
// }
|
|
205
|
+
|
|
206
|
+
// boundingBox(): { minLon: Radians, minLat: Radians, maxLon: Radians, maxLat: Radians } {
|
|
207
|
+
|
|
208
|
+
// // Do two poınts remaın on the same half when the circle is cut by Z axis?
|
|
209
|
+
// const Z = Math.sign(this.pointA.z + this.pointB.z);
|
|
210
|
+
// _0vector.set(0, 0, Z);
|
|
211
|
+
// const zDot = this.greatCirclePlaneNormalVector.dot(_0vector);
|
|
212
|
+
// const zDif = Math.abs(this.pointA.z - this.pointB.z);
|
|
213
|
+
// const minPossibleZDistanceIfOnTheSameHalf = (1 - this.dot) * zDot;
|
|
214
|
+
|
|
215
|
+
// const aLonLat = this.pointA.toLonLat();
|
|
216
|
+
// const bLonLat = this.pointB.toLonLat();
|
|
217
|
+
|
|
218
|
+
// if (zDif >= minPossibleZDistanceIfOnTheSameHalf) {
|
|
219
|
+
// return {
|
|
220
|
+
// minLon: Math.min(aLonLat.lon, bLonLat.lon),
|
|
221
|
+
// minLat: Math.min(aLonLat.lat, bLonLat.lat),
|
|
222
|
+
// maxLon: Math.max(aLonLat.lon, bLonLat.lon),
|
|
223
|
+
// maxLat: Math.max(aLonLat.lat, bLonLat.lat)
|
|
224
|
+
// }
|
|
225
|
+
// }
|
|
226
|
+
|
|
227
|
+
// // if not, we need to calculate Z peek of the arc
|
|
228
|
+
// const z = Math.asin(zDot * Z);
|
|
229
|
+
// return {
|
|
230
|
+
// minLon: Math.min(aLonLat.lon, bLonLat.lon),
|
|
231
|
+
// minLat: Math.min(aLonLat.lat, bLonLat.lat, z),
|
|
232
|
+
// maxLon: Math.max(aLonLat.lon, bLonLat.lon),
|
|
233
|
+
// maxLat: Math.max(aLonLat.lat, bLonLat.lat, z)
|
|
234
|
+
|
|
235
|
+
// }
|
|
236
|
+
// }
|
|
237
|
+
|
|
238
|
+
}
|
|
239
|
+
|
|
@@ -0,0 +1,225 @@
|
|
|
1
|
+
function isClose(a, b, rtol = 1e-5, atol = 1e-8) {
|
|
2
|
+
// JavaScript equivalent of NumPy's isclose
|
|
3
|
+
return Math.abs(a - b) <= (atol + rtol * Math.abs(b));
|
|
4
|
+
}
|
|
5
|
+
|
|
6
|
+
function mod(n, m) {
|
|
7
|
+
// Proper modulo operation that handles negative numbers
|
|
8
|
+
return ((n % m) + m) % m;
|
|
9
|
+
}
|
|
10
|
+
|
|
11
|
+
function calculateInitialBearing(phi1, lambda1, phi2, lambda2) {
|
|
12
|
+
// Calculates the initial bearing (forward azimuth) from point 1 to point 2
|
|
13
|
+
if (isClose(Math.cos(phi1), 0)) { // Starting from a pole
|
|
14
|
+
if (phi1 > 0) { // North pole
|
|
15
|
+
return Math.PI; // Bearing is South
|
|
16
|
+
} else { // South pole
|
|
17
|
+
return 0; // Bearing is North
|
|
18
|
+
}
|
|
19
|
+
}
|
|
20
|
+
|
|
21
|
+
const deltaLambda = lambda2 - lambda1;
|
|
22
|
+
const y = Math.sin(deltaLambda) * Math.cos(phi2);
|
|
23
|
+
const x = Math.cos(phi1) * Math.sin(phi2) - Math.sin(phi1) * Math.cos(phi2) * Math.cos(deltaLambda);
|
|
24
|
+
const theta = Math.atan2(y, x);
|
|
25
|
+
return theta;
|
|
26
|
+
}
|
|
27
|
+
|
|
28
|
+
function getGreatCircleArcBBox(phi1Rad, lambda1Rad, phi2Rad, lambda2Rad) {
|
|
29
|
+
// Calculates the bounding box around the great circle arc between two points
|
|
30
|
+
// on a spherical surface, handling edge cases like poles and antimeridian crossing.
|
|
31
|
+
// Args: Latitudes and longitudes in radians
|
|
32
|
+
// Returns: [phiMin, phiMax, lambdaMin, lambdaMax] in radians
|
|
33
|
+
|
|
34
|
+
// === Edge Case: Same Points ===
|
|
35
|
+
if (isClose(phi1Rad, phi2Rad) && isClose(lambda1Rad, lambda2Rad)) {
|
|
36
|
+
return [phi1Rad, phi1Rad, lambda1Rad, lambda1Rad];
|
|
37
|
+
}
|
|
38
|
+
|
|
39
|
+
// === Edge Case: Antipodal Points ===
|
|
40
|
+
const deltaLambdaNorm = mod(lambda1Rad - lambda2Rad + Math.PI, 2 * Math.PI) - Math.PI;
|
|
41
|
+
if (isClose(phi1Rad, -phi2Rad) && isClose(Math.abs(deltaLambdaNorm), Math.PI)) {
|
|
42
|
+
// Path covers all longitudes and passes through poles
|
|
43
|
+
return [-Math.PI / 2, Math.PI / 2, -Math.PI, Math.PI];
|
|
44
|
+
}
|
|
45
|
+
|
|
46
|
+
// === Longitude Bounds ===
|
|
47
|
+
let lambda1Adj = lambda1Rad;
|
|
48
|
+
let lambda2Adj = lambda2Rad;
|
|
49
|
+
|
|
50
|
+
// Handle antimeridian crossing
|
|
51
|
+
let deltaLambda = lambda2Rad - lambda1Rad;
|
|
52
|
+
if (deltaLambda > Math.PI) {
|
|
53
|
+
deltaLambda -= 2 * Math.PI;
|
|
54
|
+
} else if (deltaLambda <= -Math.PI) {
|
|
55
|
+
deltaLambda += 2 * Math.PI;
|
|
56
|
+
}
|
|
57
|
+
|
|
58
|
+
const crossesAntimeridian = Math.abs(lambda2Rad - lambda1Rad) > Math.PI;
|
|
59
|
+
|
|
60
|
+
if (crossesAntimeridian) {
|
|
61
|
+
// Add 2*pi to the smaller longitude
|
|
62
|
+
if (lambda1Rad < lambda2Rad) {
|
|
63
|
+
lambda1Adj += 2 * Math.PI;
|
|
64
|
+
} else {
|
|
65
|
+
lambda2Adj += 2 * Math.PI;
|
|
66
|
+
}
|
|
67
|
+
}
|
|
68
|
+
|
|
69
|
+
const lambdaMin = Math.min(lambda1Adj, lambda2Adj);
|
|
70
|
+
const lambdaMax = Math.max(lambda1Adj, lambda2Adj);
|
|
71
|
+
|
|
72
|
+
// === Latitude Bounds ===
|
|
73
|
+
let phiMin = Math.min(phi1Rad, phi2Rad);
|
|
74
|
+
let phiMax = Math.max(phi1Rad, phi2Rad);
|
|
75
|
+
|
|
76
|
+
// === Vertex Check ===
|
|
77
|
+
// Check the maximum/minimum latitude reached by the great circle
|
|
78
|
+
const theta12 = calculateInitialBearing(phi1Rad, lambda1Rad, phi2Rad, lambda2Rad);
|
|
79
|
+
const theta21 = calculateInitialBearing(phi2Rad, lambda2Rad, phi1Rad, lambda1Rad);
|
|
80
|
+
|
|
81
|
+
const cosPhi1 = Math.cos(phi1Rad);
|
|
82
|
+
if (!isClose(cosPhi1, 0)) { // Starting point is not a pole
|
|
83
|
+
// Compute the absolute latitude of the vertex
|
|
84
|
+
const argAcos = Math.abs(Math.sin(theta12) * cosPhi1);
|
|
85
|
+
const phiVtxAbs = Math.acos(Math.min(Math.max(argAcos, -1.0), 1.0));
|
|
86
|
+
|
|
87
|
+
// Check if the path crosses the northern vertex
|
|
88
|
+
if (isClose(theta12, 0) && phiVtxAbs > phiMax) { // Starting due north
|
|
89
|
+
phiMax = phiVtxAbs;
|
|
90
|
+
} else if (isClose(Math.abs(theta12), Math.PI) && -phiVtxAbs < phiMin) { // Starting due south
|
|
91
|
+
phiMin = -phiVtxAbs;
|
|
92
|
+
} else {
|
|
93
|
+
// General case: Check if the path crosses the vertex
|
|
94
|
+
const finalBearingAtP2 = mod(theta21 + Math.PI + Math.PI, 2 * Math.PI) - Math.PI;
|
|
95
|
+
|
|
96
|
+
// Northern vertex check
|
|
97
|
+
if ((-Math.PI / 2 < theta12 && theta12 < Math.PI / 2) &&
|
|
98
|
+
!(finalBearingAtP2 >= -Math.PI / 2 && finalBearingAtP2 <= Math.PI / 2)) {
|
|
99
|
+
if (phiVtxAbs > phiMax) {
|
|
100
|
+
phiMax = phiVtxAbs;
|
|
101
|
+
}
|
|
102
|
+
}
|
|
103
|
+
|
|
104
|
+
// Southern vertex check
|
|
105
|
+
if (!(theta12 >= -Math.PI / 2 && theta12 <= Math.PI / 2) &&
|
|
106
|
+
(-Math.PI / 2 < finalBearingAtP2 && finalBearingAtP2 < Math.PI / 2)) {
|
|
107
|
+
if (-phiVtxAbs < phiMin) {
|
|
108
|
+
phiMin = -phiVtxAbs;
|
|
109
|
+
}
|
|
110
|
+
}
|
|
111
|
+
}
|
|
112
|
+
}
|
|
113
|
+
|
|
114
|
+
return [phiMin, phiMax, lambdaMin, lambdaMax];
|
|
115
|
+
}
|
|
116
|
+
|
|
117
|
+
|
|
118
|
+
|
|
119
|
+
|
|
120
|
+
// --- Example Usage ---
|
|
121
|
+
function degreesToRadians(deg) {
|
|
122
|
+
return deg * Math.PI / 180;
|
|
123
|
+
}
|
|
124
|
+
|
|
125
|
+
function radiansToDegrees(rad) {
|
|
126
|
+
return rad * 180 / Math.PI;
|
|
127
|
+
}
|
|
128
|
+
|
|
129
|
+
|
|
130
|
+
export { getGreatCircleArcBBox };
|
|
131
|
+
|
|
132
|
+
// Test Case 1: London to Tokyo
|
|
133
|
+
const lat1Deg = 51.5, lon1Deg = -0.1;
|
|
134
|
+
const lat2Deg = 35.7, lon2Deg = 139.7;
|
|
135
|
+
|
|
136
|
+
const phi1 = degreesToRadians(lat1Deg);
|
|
137
|
+
const lambda1 = degreesToRadians(lon1Deg);
|
|
138
|
+
const phi2 = degreesToRadians(lat2Deg);
|
|
139
|
+
const lambda2 = degreesToRadians(lon2Deg);
|
|
140
|
+
|
|
141
|
+
const [phiMinRad, phiMaxRad, lambdaMinRad, lambdaMaxRad] = getGreatCircleArcBBox(phi1, lambda1, phi2, lambda2);
|
|
142
|
+
|
|
143
|
+
const phiMinDeg = radiansToDegrees(phiMinRad);
|
|
144
|
+
const phiMaxDeg = radiansToDegrees(phiMaxRad);
|
|
145
|
+
const lambdaMinDeg = radiansToDegrees(lambdaMinRad);
|
|
146
|
+
const lambdaMaxDeg = radiansToDegrees(lambdaMaxRad);
|
|
147
|
+
|
|
148
|
+
console.log("Input Points (Radians):");
|
|
149
|
+
console.log(` P1: Latitude=${lat1Deg}, Longitude=${lon1Deg}`);
|
|
150
|
+
console.log(` P2: Latitude=${lat2Deg}, Longitude=${lon2Deg}`);
|
|
151
|
+
console.log(` P1: Latitude=${phi1.toFixed(4)}, Longitude=${lambda1.toFixed(4)}`);
|
|
152
|
+
console.log(` P2: Latitude=${phi2.toFixed(4)}, Longitude=${lambda2.toFixed(4)}`);
|
|
153
|
+
console.log("-".repeat(20));
|
|
154
|
+
console.log("Bounding Box (Radians):");
|
|
155
|
+
// console phys://console.log(` Minimum Latitude (phi_min): ${phiMinRad.toFixed(4)}`);
|
|
156
|
+
console.log(` Maximum Latitude (phi_max): ${phiMaxRad.toFixed(4)}`);
|
|
157
|
+
console.log(` Minimum Longitude (lambda_min): ${lambdaMinRad.toFixed(4)}`);
|
|
158
|
+
console.log(` Maximum Longitude (lambda_max): ${lambdaMaxRad.toFixed(4)}`);
|
|
159
|
+
console.log("-".repeat(20));
|
|
160
|
+
console.log("Bounding Box (Degrees):");
|
|
161
|
+
console.log(` Minimum Latitude: ${phiMinDeg.toFixed(2)}°`);
|
|
162
|
+
console.log(` Maximum Latitude: ${phiMaxDeg.toFixed(2)}°`);
|
|
163
|
+
console.log(` Minimum Longitude: ${lambdaMinDeg.toFixed(2)}°`);
|
|
164
|
+
console.log(` Maximum Longitude: ${lambdaMaxDeg.toFixed(2)}°`);
|
|
165
|
+
|
|
166
|
+
// --- Antimeridian Crossing Test ---
|
|
167
|
+
console.log("\n" + "=".repeat(30));
|
|
168
|
+
console.log("Antimeridian Crossing Test");
|
|
169
|
+
const lat1DegAm = -18, lon1DegAm = 178;
|
|
170
|
+
const lat2DegAm = -14, lon2DegAm = -172;
|
|
171
|
+
|
|
172
|
+
const phi1Am = degreesToRadians(lat1DegAm);
|
|
173
|
+
const lambda1Am = degreesToRadians(lon1DegAm);
|
|
174
|
+
const phi2Am = degreesToRadians(lat2DegAm);
|
|
175
|
+
const lambda2Am = degreesToRadians(lon2DegAm);
|
|
176
|
+
|
|
177
|
+
const [phiMinRadAm, phiMaxRadAm, lambdaMinRadAm, lambdaMaxRadAm] = getGreatCircleArcBBox(phi1Am, lambda1Am, phi2Am, lambda2Am);
|
|
178
|
+
|
|
179
|
+
const phiMinDegAm = radiansToDegrees(phiMinRadAm);
|
|
180
|
+
const phiMaxDegAm = radiansToDegrees(phiMaxRadAm);
|
|
181
|
+
const lambdaMinDegAm = radiansToDegrees(lambdaMinRadAm);
|
|
182
|
+
const lambdaMaxDegAm = radiansToDegrees(lambdaMaxRadAm);
|
|
183
|
+
|
|
184
|
+
console.log(`Input Points (Degrees): P1=(${lat1DegAm}, ${lon1DegAm}), P2=(${lat2DegAm}, ${lon2DegAm})`);
|
|
185
|
+
console.log("Bounding Box (Radians):");
|
|
186
|
+
console.log(` phi_min=${phiMinRadAm.toFixed(4)}, phi_max=${phiMaxRadAm.toFixed(4)}`);
|
|
187
|
+
console.log(` lambda_min=${lambdaMinRadAm.toFixed(4)}, lambda_max=${lambdaMaxRadAm.toFixed(4)}`);
|
|
188
|
+
console.log("Bounding Box (Degrees):");
|
|
189
|
+
console.log(` Min Latitude: ${phiMinDegAm.toFixed(2)}°`);
|
|
190
|
+
console.log(` Max Latitude: ${phiMaxDegAm.toFixed(2)}°`);
|
|
191
|
+
console.log(` Min Longitude: ${lambdaMinDegAm.toFixed(2)}° (178° expected)`);
|
|
192
|
+
console.log(` Max Longitude: ${lambdaMaxDegAm.toFixed(2)}° (188° expected, which is -172° + 360°)`);
|
|
193
|
+
|
|
194
|
+
// --- Polar Crossing Test ---
|
|
195
|
+
console.log("\n" + "=".repeat(30));
|
|
196
|
+
console.log("Polar Crossing Test");
|
|
197
|
+
const lat1DegP = 80, lon1DegP = 20;
|
|
198
|
+
const lat2DegP = 80, lon2DegP = -100;
|
|
199
|
+
|
|
200
|
+
const phi1P = degreesToRadians(lat1DegP);
|
|
201
|
+
const lambda1P = degreesToRadians(lon1DegP);
|
|
202
|
+
const phi2P = degreesToRadians(lat2DegP);
|
|
203
|
+
const lambda2P = degreesToRadians(lon2DegP);
|
|
204
|
+
|
|
205
|
+
const [phiMinRadP, phiMaxRadP, lambdaMinRadP, lambdaMaxRadP] = getGreatCircleArcBBox(phi1P, lambda1P, phi2P, lambda2P);
|
|
206
|
+
|
|
207
|
+
const phiMinDegP = radiansToDegrees(phiMinRadP);
|
|
208
|
+
const phiMaxDegP = radiansToDegrees(phiMaxRadP);
|
|
209
|
+
const lambdaMinDegP = radiansToDegrees(lambdaMinRadP);
|
|
210
|
+
const lambdaMaxDegP = radiansToDegrees(lambdaMaxRadP);
|
|
211
|
+
|
|
212
|
+
console.log(`Input Points (Degrees): P1=(${lat1DegP}, ${lon1DegP}), P2=(${lat2DegP}, ${lon2DegP})`);
|
|
213
|
+
console.log("Bounding Box (Radians):");
|
|
214
|
+
console.log(` phi_min=${phiMinRadP.toFixed(4)}, phi_max=${phiMaxRadP.toFixed(4)}`);
|
|
215
|
+
console.log(` lambda_min=${lambdaMinRadP.toFixed(4)}, lambda_max=${lambdaMaxRadP.toFixed(4)}`);
|
|
216
|
+
console.log("Bounding Box (Degrees):");
|
|
217
|
+
console.log(` Min Latitude: ${phiMinDegP.toFixed(2)}°`);
|
|
218
|
+
console.log(` Max Latitude: ${phiMaxDegP.toFixed(2)}° (Should be close to North Pole > 80°)`);
|
|
219
|
+
console.log(` Min Longitude: ${lambdaMinDegP.toFixed(2)}°`);
|
|
220
|
+
console.log(` Max Longitude: ${lambdaMaxDegP.toFixed(2)}°`);
|
|
221
|
+
|
|
222
|
+
|
|
223
|
+
|
|
224
|
+
// --- line points intersection with bbox ---
|
|
225
|
+
|
|
@@ -0,0 +1,47 @@
|
|
|
1
|
+
import { Plane } from "../plane";
|
|
2
|
+
import { Vector3D } from "../vector3d";
|
|
3
|
+
import { Arc } from "../arc";
|
|
4
|
+
|
|
5
|
+
|
|
6
|
+
|
|
7
|
+
const Radians = Math.PI / 180;
|
|
8
|
+
|
|
9
|
+
export class ArcPartOnScreen {
|
|
10
|
+
private _oldLookInfo: { CenterLong: number, CenterLat: number, Distance: number, Tilt: number, NorthAng: number };
|
|
11
|
+
private _boundPlane: Plane;
|
|
12
|
+
|
|
13
|
+
constructor() {
|
|
14
|
+
this._boundPlane = new Plane(new Vector3D(0, 0, 0), 0);
|
|
15
|
+
this._oldLookInfo = { CenterLong: 0, CenterLat: -190, Distance: 0, Tilt: 0, NorthAng: 0 };
|
|
16
|
+
}
|
|
17
|
+
|
|
18
|
+
|
|
19
|
+
updateBoundPlane(lookInfo): boolean {
|
|
20
|
+
if (this._oldLookInfo && this._oldLookInfo.CenterLong === lookInfo.CenterLong && this._oldLookInfo.CenterLat === lookInfo.CenterLat) {
|
|
21
|
+
return false;
|
|
22
|
+
}
|
|
23
|
+
/**
|
|
24
|
+
* lookInfo {CenterLong: 47.75355881750585, CenterLat: 42.3417076206803, Distance: 2286603.4020860917, Tilt: 0.05, NorthAng: -13.829359965098265}
|
|
25
|
+
*/
|
|
26
|
+
this._oldLookInfo.CenterLong = lookInfo.CenterLong;
|
|
27
|
+
this._oldLookInfo.CenterLat = lookInfo.CenterLat;
|
|
28
|
+
this._oldLookInfo.Distance = lookInfo.Distance;
|
|
29
|
+
this._oldLookInfo.Tilt = lookInfo.Tilt;
|
|
30
|
+
this._oldLookInfo.NorthAng = lookInfo.NorthAng;
|
|
31
|
+
|
|
32
|
+
Plane.fromGlobeLookInfo(Radians * lookInfo.CenterLong, Radians * lookInfo.CenterLat, lookInfo.Distance, this._boundPlane);
|
|
33
|
+
return true;
|
|
34
|
+
}
|
|
35
|
+
|
|
36
|
+
|
|
37
|
+
|
|
38
|
+
getArcPartOnScreen(arc: Arc, target: Arc): boolean {
|
|
39
|
+
// _imaginaryPlane.set(arc.greatCirclePlaneNormalVector, arc.pointA.dot(arc.greatCirclePlaneNormalVector));
|
|
40
|
+
// const intersection = _imaginaryPlane.intersectionPlane(this._boundPlane);
|
|
41
|
+
// if (!(intersection instanceof Ray)) return false;
|
|
42
|
+
// _ray.copy(intersection as Ray);
|
|
43
|
+
// const [point1, point2] = _ray.intersectionSphere(_unitShphereCenter, _unitShphereRadius) as [Vector3D, Vector3D];
|
|
44
|
+
const result = arc.intersectionMedium(this._boundPlane, target);
|
|
45
|
+
return !(result === null);
|
|
46
|
+
}
|
|
47
|
+
}
|
package/Math/methods.js
CHANGED
|
@@ -1,6 +1,5 @@
|
|
|
1
1
|
|
|
2
|
-
|
|
3
|
-
const WORLD_RADIUS_MERCATOR = 6378136.99911;
|
|
2
|
+
import { WORLD_RADIUS_3D, WORLD_RADIUS_MERCATOR } from './constants.ts';
|
|
4
3
|
|
|
5
4
|
/**
|
|
6
5
|
* @typedef {Array<number>} vec3 [x, y, z]
|
|
@@ -193,7 +192,6 @@ const pixelXYLenghtToUnitVectorWithHeight = (pixelXYHeight) => {
|
|
|
193
192
|
|
|
194
193
|
export {
|
|
195
194
|
RADIANS,
|
|
196
|
-
WORLD_RADIUS_3D,
|
|
197
195
|
normalize3,
|
|
198
196
|
dot3,
|
|
199
197
|
length3,
|