@navaramap/three-api 0.0.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/LICENSE ADDED
@@ -0,0 +1,8 @@
1
+ Copyright (c) 2026 MapLibre contributors
2
+
3
+ This project is dual-licensed under either of
4
+
5
+ - Apache License, Version 2.0 (see LICENSE-APACHE)
6
+ - MIT license (see LICENSE-MIT)
7
+
8
+ at your option.
package/README.md ADDED
@@ -0,0 +1,3 @@
1
+ # @navaramap/three-api
2
+
3
+ This is a api module for Navara for Web platform.
@@ -0,0 +1,54 @@
1
+ import { LatLngHeight } from '@navaramap/core';
2
+ /**
3
+ * Wrapper class for geodesic calculations on an ellipsoid surface.
4
+ * Precomputes common variables for optimization when instantiated.
5
+ *
6
+ * @example
7
+ * ```typescript
8
+ * const geodesic = new EllipsoidGeodesic(
9
+ * { lat: 0.5, lng: 2.0, height: 0 },
10
+ * { lat: 0.6, lng: 2.1, height: 0 }
11
+ * );
12
+ *
13
+ * console.log(geodesic.distance);
14
+ * const points = geodesic.interpolatePoints(1000);
15
+ * const midpoint = geodesic.interpolateDistance(geodesic.distance / 2);
16
+ *
17
+ * geodesic.dispose(); // Free WASM memory when done
18
+ * ```
19
+ */
20
+ export declare class EllipsoidGeodesic {
21
+ private _raw;
22
+ /**
23
+ * Create a new geodesic between two points on the ellipsoid.
24
+ * @param start - Start point in geodetic coordinates (lat/lng in radians)
25
+ * @param end - End point in geodetic coordinates (lat/lng in radians)
26
+ */
27
+ constructor(start: LatLngHeight, end: LatLngHeight);
28
+ /** Distance in meters between start and end points */
29
+ get distance(): number;
30
+ /** Heading at the start point in radians */
31
+ get startHeading(): number;
32
+ /** Heading at the end point in radians */
33
+ get endHeading(): number;
34
+ /** Start point in geodetic coordinates */
35
+ get start(): LatLngHeight;
36
+ /** End point in geodetic coordinates */
37
+ get end(): LatLngHeight;
38
+ /**
39
+ * Interpolate points along the geodesic path.
40
+ * @param granularity - Distance between interpolated points in meters (optional)
41
+ * @returns Array of interpolated points in geodetic coordinates
42
+ */
43
+ interpolatePoints(granularity?: number): LatLngHeight[];
44
+ /**
45
+ * Interpolate a point at a specific distance along the geodesic path.
46
+ * @param distance - Distance from start point in meters
47
+ * @returns Interpolated point in geodetic coordinates
48
+ */
49
+ interpolateDistance(distance: number): LatLngHeight;
50
+ /**
51
+ * Free WASM memory. Call this when the geodesic is no longer needed.
52
+ */
53
+ dispose(): void;
54
+ }
@@ -0,0 +1,106 @@
1
+ import { LatLngHeight, Window as WindowObject } from '@navaramap/core';
2
+ import { Vector3, Vector2, Matrix4, PerspectiveCamera } from 'three';
3
+ export type { LatLngHeight, LatLng } from '@navaramap/core';
4
+ export type { Window, Transform, CameraFrustum, Plane, Ray, Vec2, Vec3, } from '@navaramap/core';
5
+ export * from './intersection';
6
+ export * from './rte';
7
+ export * from './ellipsoidGeodesic';
8
+ /**
9
+ * Initializes the Navara API WASM module. Must be called before using other API functions.
10
+ */
11
+ export declare function initNavaraApi(): Promise<void>;
12
+ /**
13
+ * Converts geodetic coordinates (longitude, latitude, height) to a Cartesian Vector3 in ECEF coordinates.
14
+ * @param lle - Geodetic coordinates (lng in radians, lat in radians, height in meters)
15
+ * @returns Cartesian Vector3 in Earth-Centered Earth-Fixed (ECEF) coordinates
16
+ */
17
+ export declare function geodeticToVector3(lle: LatLngHeight): Vector3;
18
+ /**
19
+ * Converts a Cartesian Vector3 in ECEF coordinates to geodetic coordinates.
20
+ * @param xyz - Cartesian Vector3 in Earth-Centered Earth-Fixed (ECEF) coordinates
21
+ * @returns Geodetic coordinates (lng in radians, lat in radians, height in meters)
22
+ */
23
+ export declare function vector3ToGeodetic(xyz: Vector3): LatLngHeight;
24
+ /**
25
+ * Converts an angle from degrees to radians.
26
+ * @param degree - Angle in degrees
27
+ * @returns Angle in radians
28
+ */
29
+ export declare function degreeToRadian(degree: number): number;
30
+ /**
31
+ * Converts an angle from radians to degrees.
32
+ * @param radian - Angle in radians
33
+ * @returns Angle in degrees
34
+ */
35
+ export declare function radianToDegree(radian: number): number;
36
+ /**
37
+ * Converts screen coordinates to world coordinates by raycasting against the WGS84 ellipsoid.
38
+ * @param window - Window configuration with width, height, and pixelRatio
39
+ * @param camera - Three.js PerspectiveCamera
40
+ * @param vec2 - Screen coordinates in CSS pixels (same as MouseEvent clientX/clientY)
41
+ * @returns World position Vector3 in ECEF coordinates, or undefined if no intersection with ellipsoid
42
+ */
43
+ export declare function convertScreenToWorld(windowObject: WindowObject, camera: PerspectiveCamera, vec2: Vector2): Vector3 | undefined;
44
+ /**
45
+ * Computes the surface normal vector at a geodetic position on the WGS84 ellipsoid.
46
+ * @param lle - Geodetic coordinates (lng in radians, lat in radians, height in meters)
47
+ * @returns Unit normal vector pointing outward from the ellipsoid surface
48
+ */
49
+ export declare function geodeticSurfaceNormal(lle: LatLngHeight): Vector3;
50
+ /**
51
+ * Creates a local East-North-Up (ENU) reference frame transformation matrix at a position.
52
+ * @param origin - Origin position in ECEF coordinates
53
+ * @returns 4x4 transformation matrix from ENU to ECEF
54
+ */
55
+ export declare function eastNorthUpToFixedFrame(origin: Vector3): Matrix4;
56
+ /**
57
+ * Creates a local North-East-Down (NED) reference frame transformation matrix at a position.
58
+ * @param origin - Origin position in ECEF coordinates
59
+ * @returns 4x4 transformation matrix from NED to ECEF
60
+ */
61
+ export declare function northEastDownToFixedFrame(origin: Vector3): Matrix4;
62
+ /**
63
+ * Creates a local North-Up-East (NUE) reference frame transformation matrix at a position.
64
+ * @param origin - Origin position in ECEF coordinates
65
+ * @returns 4x4 transformation matrix from NUE to ECEF
66
+ */
67
+ export declare function northUpEastToFixedFrame(origin: Vector3): Matrix4;
68
+ /**
69
+ * Creates a local North-West-Up (NWU) reference frame transformation matrix at a position.
70
+ * @param origin - Origin position in ECEF coordinates
71
+ * @returns 4x4 transformation matrix from NWU to ECEF
72
+ */
73
+ export declare function northWestUpToFixedFrame(origin: Vector3): Matrix4;
74
+ /**
75
+ * Converts world coordinates to screen coordinates.
76
+ * @param window - Window configuration with width, height, and pixelRatio
77
+ * @param camera - Three.js PerspectiveCamera
78
+ * @param worldPos - World position Vector3 in ECEF coordinates
79
+ * @returns Screen coordinates in CSS pixels, or undefined if behind camera
80
+ */
81
+ export declare function convertWorldToScreen(windowObject: WindowObject, camera: PerspectiveCamera, worldPos: Vector3): Vector2 | undefined;
82
+ /**
83
+ * Returns the WGS84 ellipsoid semi-major axis (equatorial radius) in meters.
84
+ * @returns Semi-major axis (approximately 6,378,137 meters)
85
+ */
86
+ export declare function getWGS84SemiMajorAxis(): number;
87
+ /**
88
+ * Returns the WGS84 ellipsoid semi-minor axis (polar radius) in meters.
89
+ * @returns Semi-minor axis (approximately 6,356,752 meters)
90
+ */
91
+ export declare function getWGS84SemiMinorAxis(): number;
92
+ /**
93
+ * Returns the WGS84 ellipsoid first eccentricity squared.
94
+ * @returns Eccentricity squared value
95
+ */
96
+ export declare function getWGS84EccentricitySquared(): number;
97
+ /**
98
+ * Returns the WGS84 ellipsoid flattening ratio.
99
+ * @returns Flattening value (approximately 1/298.257)
100
+ */
101
+ export declare function getWGS84Flattening(): number;
102
+ /**
103
+ * Returns the WGS84 ellipsoid first eccentricity.
104
+ * @returns Eccentricity value
105
+ */
106
+ export declare function getWGS84Eccentricity(): number;
package/dist/index.js ADDED
@@ -0,0 +1,180 @@
1
+ import e, { CameraFrustum as t, EllipsoidGeodesic as n, LLE as r, Ray as i, Transform as a, Vec2 as o, Vec3 as s, Window as c, angleToDegree as l, angleToRadian as u, eastNorthUpToFixedFrame as ee, encodePosition as d, geodeticSurfaceNormal as f, geodeticToXyz as p, getHeightFromEllipsoid as m, getPickRay as h, getPlaneFromPointNormal as g, getRayPlaneIntersection as _, getWGS84Eccentricity as v, getWGS84EccentricitySquared as y, getWGS84Flattening as b, getWGS84SemiMajorAxis as x, getWGS84SemiMinorAxis as S, northEastDownToFixedFrame as C, northUpEastToFixedFrame as w, northWestUpToFixedFrame as T, screenToWorld as E, worldToScreen as D, xyzToGeodetic as O } from "@navaramap/engine-api";
2
+ import { Matrix4 as k, Plane as A, Ray as j, Vector2 as M, Vector3 as N } from "three";
3
+ //#region src/intersection.ts
4
+ function P(e, t) {
5
+ let n = g(new s(e.x, e.y, e.z), new s(t.x, t.y, t.z)), r = new A(new N(n.normal.x, n.normal.y, n.normal.z), -n.distance);
6
+ return n.free(), r;
7
+ }
8
+ function F(e, n, r) {
9
+ let i = new c(e.width, e.height, e.pixelRatio);
10
+ i.width *= i.pixelRatio, i.height *= i.pixelRatio;
11
+ let s = h(i, new a(n.position.x, n.position.y, n.position.z, n.quaternion.x, n.quaternion.y, n.quaternion.z, n.quaternion.w, n.scale.x, n.scale.y, n.scale.z), new t(n.near, n.far, u(n.fov), n.aspect), new o(r.x, r.y)), l = new j(new N(s.origin.x, s.origin.y, s.origin.z), new N(s.direction.x, s.direction.y, s.direction.z));
12
+ return s.free(), l;
13
+ }
14
+ function I(e, t) {
15
+ let n = _(new i(new s(e.origin.x, e.origin.y, e.origin.z), new s(e.direction.x, e.direction.y, e.direction.z)), g(new s(t.normal.x * -t.constant, t.normal.y * -t.constant, t.normal.z * -t.constant), new s(t.normal.x, t.normal.y, t.normal.z)));
16
+ if (n) {
17
+ let e = new N(n.x, n.y, n.z);
18
+ return n.free(), e;
19
+ }
20
+ }
21
+ function te(e) {
22
+ return m(new s(e.x, e.y, e.z));
23
+ }
24
+ //#endregion
25
+ //#region src/rte.ts
26
+ var L = Object.freeze({ value: 1 });
27
+ function R(e, t = new N(), n = new N()) {
28
+ let r = d(e.x, e.y, e.z);
29
+ return t.set(r.high.x, r.high.y, r.high.z), n.set(r.low.x, r.low.y, r.low.z), r.free(), {
30
+ high: t,
31
+ low: n
32
+ };
33
+ }
34
+ var z = (e, t, n = new k()) => {
35
+ n.multiplyMatrices(t, e);
36
+ let r = n.elements;
37
+ return r[12] = 0, r[13] = 0, r[14] = 0, n;
38
+ };
39
+ function B(e, t, n = new N(), r = new k()) {
40
+ r.multiplyMatrices(e, t), n.setFromMatrixPosition(r);
41
+ let i = r.elements;
42
+ return i[12] = 0, i[13] = 0, i[14] = 0, {
43
+ position: n,
44
+ rotationScale: r
45
+ };
46
+ }
47
+ var V = new k(), H = new N(), U = (e, t) => {
48
+ V.copy(t).invert(), H.copy(e).applyMatrix4(V);
49
+ let n = d(H.x, H.y, H.z), r = n.high, i = n.low, a = {
50
+ high: new N(r.x, r.y, r.z),
51
+ low: new N(i.x, i.y, i.z)
52
+ };
53
+ return n.free(), a;
54
+ }, W = class {
55
+ _raw;
56
+ constructor(e, t) {
57
+ this._raw = new n(new r(e.lat, e.lng, e.height), new r(t.lat, t.lng, t.height));
58
+ }
59
+ get distance() {
60
+ return this._raw.distance;
61
+ }
62
+ get startHeading() {
63
+ return this._raw.start_heading;
64
+ }
65
+ get endHeading() {
66
+ return this._raw.end_heading;
67
+ }
68
+ get start() {
69
+ let e = this._raw.start;
70
+ return {
71
+ lat: e.lat,
72
+ lng: e.lng,
73
+ height: e.height
74
+ };
75
+ }
76
+ get end() {
77
+ let e = this._raw.end;
78
+ return {
79
+ lat: e.lat,
80
+ lng: e.lng,
81
+ height: e.height
82
+ };
83
+ }
84
+ interpolatePoints(e) {
85
+ let t = this._raw.interpolateGeodeticPoints(e ?? null), n = [];
86
+ for (let e of t) n.push({
87
+ lat: e.lat,
88
+ lng: e.lng,
89
+ height: e.height
90
+ }), e.free();
91
+ return n;
92
+ }
93
+ interpolateDistance(e) {
94
+ let t = this._raw.interpolateDistance(e), n = {
95
+ lat: t.lat,
96
+ lng: t.lng,
97
+ height: t.height
98
+ };
99
+ return t.free(), n;
100
+ }
101
+ dispose() {
102
+ this._raw.free();
103
+ }
104
+ };
105
+ //#endregion
106
+ //#region src/index.ts
107
+ async function G() {
108
+ await e();
109
+ }
110
+ function K(e) {
111
+ let t = p(new r(e.lat, e.lng, e.height)), n = new N(t.x, t.y, t.z);
112
+ return t.free(), n;
113
+ }
114
+ function q(e) {
115
+ let t = O(new s(e.x, e.y, e.z)), n = {
116
+ lat: t.lat,
117
+ lng: t.lng,
118
+ height: t.height
119
+ };
120
+ return t.free(), n;
121
+ }
122
+ function J(e) {
123
+ return u(e);
124
+ }
125
+ function Y(e) {
126
+ return l(e);
127
+ }
128
+ function X(e, n, r) {
129
+ let i = new c(e.width, e.height, e.pixelRatio);
130
+ i.width *= i.pixelRatio, i.height *= i.pixelRatio;
131
+ let s = E(i, new a(n.position.x, n.position.y, n.position.z, n.quaternion.x, n.quaternion.y, n.quaternion.z, n.quaternion.w, n.scale.x, n.scale.y, n.scale.z), new t(n.near, n.far, u(n.fov), n.aspect), new o(r.x, r.y));
132
+ if (!s) return;
133
+ let l = new N(s.x, s.y, s.z);
134
+ return s.free(), l;
135
+ }
136
+ function Z(e) {
137
+ let t = f(new r(e.lat, e.lng, e.height)), n = new N(t.x, t.y, t.z);
138
+ return t.free(), n;
139
+ }
140
+ function Q(e) {
141
+ let t = ee(new s(e.x, e.y, e.z));
142
+ return new k().fromArray(t);
143
+ }
144
+ function ne(e) {
145
+ let t = C(new s(e.x, e.y, e.z));
146
+ return new k().fromArray(t);
147
+ }
148
+ function re(e) {
149
+ let t = w(new s(e.x, e.y, e.z));
150
+ return new k().fromArray(t);
151
+ }
152
+ function ie(e) {
153
+ let t = T(new s(e.x, e.y, e.z));
154
+ return new k().fromArray(t);
155
+ }
156
+ function ae(e, n, r) {
157
+ let i = new c(e.width, e.height, e.pixelRatio);
158
+ i.width *= i.pixelRatio, i.height *= i.pixelRatio;
159
+ let o = D(i, new a(n.position.x, n.position.y, n.position.z, n.quaternion.x, n.quaternion.y, n.quaternion.z, n.quaternion.w, n.scale.x, n.scale.y, n.scale.z), new t(n.near, n.far, u(n.fov), n.aspect), new s(r.x, r.y, r.z));
160
+ if (!o) return;
161
+ let l = new M(o.x, o.y);
162
+ return o.free(), l;
163
+ }
164
+ function oe() {
165
+ return x();
166
+ }
167
+ function $() {
168
+ return S();
169
+ }
170
+ function se() {
171
+ return y();
172
+ }
173
+ function ce() {
174
+ return b();
175
+ }
176
+ function le() {
177
+ return v();
178
+ }
179
+ //#endregion
180
+ export { W as EllipsoidGeodesic, L as RTE_ONE_UNIFORM, U as calcCameraPosition, z as calcModelMatrixRTE, B as composeWorldMatrixForRTE, X as convertScreenToWorld, ae as convertWorldToScreen, J as degreeToRadian, Q as eastNorthUpToFixedFrame, R as encodePositionRTE, Z as geodeticSurfaceNormal, K as geodeticToVector3, te as getHeightFromEllipsoid, F as getPickRay, P as getPlaneFromPointNormal, I as getRayPlaneIntersection, le as getWGS84Eccentricity, se as getWGS84EccentricitySquared, ce as getWGS84Flattening, oe as getWGS84SemiMajorAxis, $ as getWGS84SemiMinorAxis, G as initNavaraApi, ne as northEastDownToFixedFrame, re as northUpEastToFixedFrame, ie as northWestUpToFixedFrame, Y as radianToDegree, q as vector3ToGeodetic };
@@ -0,0 +1 @@
1
+ (function(e,t){typeof exports==`object`&&typeof module<`u`?t(exports,require("@navaramap/engine-api"),require("three")):typeof define==`function`&&define.amd?define([`exports`,`@navaramap/engine-api`,`three`],t):(e=typeof globalThis<`u`?globalThis:e||self,t(e.NavaraThreeApi={},e._navaramap_engine_api,e.three))})(this,function(e,t,n){Object.defineProperty(e,Symbol.toStringTag,{value:`Module`});var r=Object.create,i=Object.defineProperty,a=Object.getOwnPropertyDescriptor,o=Object.getOwnPropertyNames,s=Object.getPrototypeOf,c=Object.prototype.hasOwnProperty,l=(e,t,n,r)=>{if(t&&typeof t==`object`||typeof t==`function`)for(var s=o(t),l=0,u=s.length,d;l<u;l++)d=s[l],!c.call(e,d)&&d!==n&&i(e,d,{get:(e=>t[e]).bind(null,d),enumerable:!(r=a(t,d))||r.enumerable});return e};t=((e,t,n)=>(n=e==null?{}:r(s(e)),l(t||!e||!e.__esModule?i(n,`default`,{value:e,enumerable:!0}):n,e)))(t,1);function u(e,r){let i=(0,t.getPlaneFromPointNormal)(new t.Vec3(e.x,e.y,e.z),new t.Vec3(r.x,r.y,r.z)),a=new n.Plane(new n.Vector3(i.normal.x,i.normal.y,i.normal.z),-i.distance);return i.free(),a}function d(e,r,i){let a=new t.Window(e.width,e.height,e.pixelRatio);a.width*=a.pixelRatio,a.height*=a.pixelRatio;let o=(0,t.getPickRay)(a,new t.Transform(r.position.x,r.position.y,r.position.z,r.quaternion.x,r.quaternion.y,r.quaternion.z,r.quaternion.w,r.scale.x,r.scale.y,r.scale.z),new t.CameraFrustum(r.near,r.far,(0,t.angleToRadian)(r.fov),r.aspect),new t.Vec2(i.x,i.y)),s=new n.Ray(new n.Vector3(o.origin.x,o.origin.y,o.origin.z),new n.Vector3(o.direction.x,o.direction.y,o.direction.z));return o.free(),s}function f(e,r){let i=(0,t.getRayPlaneIntersection)(new t.Ray(new t.Vec3(e.origin.x,e.origin.y,e.origin.z),new t.Vec3(e.direction.x,e.direction.y,e.direction.z)),(0,t.getPlaneFromPointNormal)(new t.Vec3(r.normal.x*-r.constant,r.normal.y*-r.constant,r.normal.z*-r.constant),new t.Vec3(r.normal.x,r.normal.y,r.normal.z)));if(i){let e=new n.Vector3(i.x,i.y,i.z);return i.free(),e}}function p(e){return(0,t.getHeightFromEllipsoid)(new t.Vec3(e.x,e.y,e.z))}var m=Object.freeze({value:1});function h(e,r=new n.Vector3,i=new n.Vector3){let a=(0,t.encodePosition)(e.x,e.y,e.z);return r.set(a.high.x,a.high.y,a.high.z),i.set(a.low.x,a.low.y,a.low.z),a.free(),{high:r,low:i}}var g=(e,t,r=new n.Matrix4)=>{r.multiplyMatrices(t,e);let i=r.elements;return i[12]=0,i[13]=0,i[14]=0,r};function _(e,t,r=new n.Vector3,i=new n.Matrix4){i.multiplyMatrices(e,t),r.setFromMatrixPosition(i);let a=i.elements;return a[12]=0,a[13]=0,a[14]=0,{position:r,rotationScale:i}}var v=new n.Matrix4,y=new n.Vector3,b=(e,r)=>{v.copy(r).invert(),y.copy(e).applyMatrix4(v);let i=(0,t.encodePosition)(y.x,y.y,y.z),a=i.high,o=i.low,s={high:new n.Vector3(a.x,a.y,a.z),low:new n.Vector3(o.x,o.y,o.z)};return i.free(),s},x=class{_raw;constructor(e,n){this._raw=new t.EllipsoidGeodesic(new t.LLE(e.lat,e.lng,e.height),new t.LLE(n.lat,n.lng,n.height))}get distance(){return this._raw.distance}get startHeading(){return this._raw.start_heading}get endHeading(){return this._raw.end_heading}get start(){let e=this._raw.start;return{lat:e.lat,lng:e.lng,height:e.height}}get end(){let e=this._raw.end;return{lat:e.lat,lng:e.lng,height:e.height}}interpolatePoints(e){let t=this._raw.interpolateGeodeticPoints(e??null),n=[];for(let e of t)n.push({lat:e.lat,lng:e.lng,height:e.height}),e.free();return n}interpolateDistance(e){let t=this._raw.interpolateDistance(e),n={lat:t.lat,lng:t.lng,height:t.height};return t.free(),n}dispose(){this._raw.free()}};async function S(){await(0,t.default)()}function C(e){let r=(0,t.geodeticToXyz)(new t.LLE(e.lat,e.lng,e.height)),i=new n.Vector3(r.x,r.y,r.z);return r.free(),i}function w(e){let n=(0,t.xyzToGeodetic)(new t.Vec3(e.x,e.y,e.z)),r={lat:n.lat,lng:n.lng,height:n.height};return n.free(),r}function T(e){return(0,t.angleToRadian)(e)}function E(e){return(0,t.angleToDegree)(e)}function D(e,r,i){let a=new t.Window(e.width,e.height,e.pixelRatio);a.width*=a.pixelRatio,a.height*=a.pixelRatio;let o=(0,t.screenToWorld)(a,new t.Transform(r.position.x,r.position.y,r.position.z,r.quaternion.x,r.quaternion.y,r.quaternion.z,r.quaternion.w,r.scale.x,r.scale.y,r.scale.z),new t.CameraFrustum(r.near,r.far,(0,t.angleToRadian)(r.fov),r.aspect),new t.Vec2(i.x,i.y));if(!o)return;let s=new n.Vector3(o.x,o.y,o.z);return o.free(),s}function O(e){let r=(0,t.geodeticSurfaceNormal)(new t.LLE(e.lat,e.lng,e.height)),i=new n.Vector3(r.x,r.y,r.z);return r.free(),i}function k(e){let r=(0,t.eastNorthUpToFixedFrame)(new t.Vec3(e.x,e.y,e.z));return new n.Matrix4().fromArray(r)}function A(e){let r=(0,t.northEastDownToFixedFrame)(new t.Vec3(e.x,e.y,e.z));return new n.Matrix4().fromArray(r)}function j(e){let r=(0,t.northUpEastToFixedFrame)(new t.Vec3(e.x,e.y,e.z));return new n.Matrix4().fromArray(r)}function M(e){let r=(0,t.northWestUpToFixedFrame)(new t.Vec3(e.x,e.y,e.z));return new n.Matrix4().fromArray(r)}function N(e,r,i){let a=new t.Window(e.width,e.height,e.pixelRatio);a.width*=a.pixelRatio,a.height*=a.pixelRatio;let o=(0,t.worldToScreen)(a,new t.Transform(r.position.x,r.position.y,r.position.z,r.quaternion.x,r.quaternion.y,r.quaternion.z,r.quaternion.w,r.scale.x,r.scale.y,r.scale.z),new t.CameraFrustum(r.near,r.far,(0,t.angleToRadian)(r.fov),r.aspect),new t.Vec3(i.x,i.y,i.z));if(!o)return;let s=new n.Vector2(o.x,o.y);return o.free(),s}function P(){return(0,t.getWGS84SemiMajorAxis)()}function F(){return(0,t.getWGS84SemiMinorAxis)()}function I(){return(0,t.getWGS84EccentricitySquared)()}function L(){return(0,t.getWGS84Flattening)()}function R(){return(0,t.getWGS84Eccentricity)()}e.EllipsoidGeodesic=x,e.RTE_ONE_UNIFORM=m,e.calcCameraPosition=b,e.calcModelMatrixRTE=g,e.composeWorldMatrixForRTE=_,e.convertScreenToWorld=D,e.convertWorldToScreen=N,e.degreeToRadian=T,e.eastNorthUpToFixedFrame=k,e.encodePositionRTE=h,e.geodeticSurfaceNormal=O,e.geodeticToVector3=C,e.getHeightFromEllipsoid=p,e.getPickRay=d,e.getPlaneFromPointNormal=u,e.getRayPlaneIntersection=f,e.getWGS84Eccentricity=R,e.getWGS84EccentricitySquared=I,e.getWGS84Flattening=L,e.getWGS84SemiMajorAxis=P,e.getWGS84SemiMinorAxis=F,e.initNavaraApi=S,e.northEastDownToFixedFrame=A,e.northUpEastToFixedFrame=j,e.northWestUpToFixedFrame=M,e.radianToDegree=E,e.vector3ToGeodetic=w});
@@ -0,0 +1,30 @@
1
+ import { Window as WindowObject } from '@navaramap/core';
2
+ import { Vector3, Vector2, PerspectiveCamera, Ray as ThreeRay, Plane as ThreePlane } from 'three';
3
+ /**
4
+ * Creates a plane from a point and a normal vector.
5
+ * @param point - A point on the plane
6
+ * @param normal - The normal vector of the plane
7
+ * @returns A Plane object defined by the point and normal
8
+ */
9
+ export declare function getPlaneFromPointNormal(point: Vector3, normal: Vector3): ThreePlane;
10
+ /**
11
+ * Creates a picking ray from screen coordinates for raycasting.
12
+ * @param window - Window configuration with width, height, and pixelRatio
13
+ * @param camera - Three.js PerspectiveCamera
14
+ * @param vec2 - Screen coordinates in CSS pixels (same as MouseEvent clientX/clientY)
15
+ * @returns A Ray starting from the camera through the screen point
16
+ */
17
+ export declare function getPickRay(windowObject: WindowObject, camera: PerspectiveCamera, vec2: Vector2): ThreeRay;
18
+ /**
19
+ * Computes the intersection point between a ray and a plane.
20
+ * @param ray - The ray to test
21
+ * @param plane - The plane to intersect with
22
+ * @returns Intersection point Vector3 or undefined if no intersection
23
+ */
24
+ export declare function getRayPlaneIntersection(ray: ThreeRay, plane: ThreePlane): Vector3 | undefined;
25
+ /**
26
+ * Computes the height of a point above the WGS84 ellipsoid surface.
27
+ * @param point - Position in ECEF coordinates
28
+ * @returns Height above the ellipsoid in meters
29
+ */
30
+ export declare function getHeightFromEllipsoid(point: Vector3): number;
package/dist/rte.d.ts ADDED
@@ -0,0 +1,60 @@
1
+ import { Matrix4, Vector3 } from 'three';
2
+ /**
3
+ * Shared `u_rteOne` uniform for RTE vertex shaders. Always 1.0.
4
+ *
5
+ * Multiplying the high-order difference by a uniform the shader compiler
6
+ * cannot constant-fold blocks fast-math reassociation of
7
+ * `(high - camHigh) + (low - camLow)` into `(high + low) - (camHigh + camLow)`,
8
+ * which would collapse the math to absolute-ECEF f32 precision and make
9
+ * geometry freeze/snap in ~0.25-0.5 m steps as the camera moves
10
+ * (observed on ANGLE Metal). One frozen object is shared by every material.
11
+ */
12
+ export declare const RTE_ONE_UNIFORM: {
13
+ readonly value: number;
14
+ };
15
+ /**
16
+ * Encodes a position into high and low precision Vector3 components for RTE (Relative-To-Eye) rendering.
17
+ * This enables GPU double precision emulation by splitting a position into two float components.
18
+ * @param original - Position to encode
19
+ * @param resultHigh - Vector3 to store the high precision component (reused to avoid GC)
20
+ * @param resultLow - Vector3 to store the low precision component (reused to avoid GC)
21
+ */
22
+ export declare function encodePositionRTE(original: Vector3, resultHigh?: Vector3, resultLow?: Vector3): {
23
+ high: Vector3;
24
+ low: Vector3;
25
+ };
26
+ /**
27
+ * Calculates the model-view matrix for Relative-To-Eye (RTE) rendering, which improves precision for distant objects.
28
+ * @param objectMatrixWorld - The object's world transformation matrix
29
+ * @param matrixWorldInverse - The camera's inverse world matrix
30
+ * @param result - Optional matrix to store the result (creates new if omitted)
31
+ * @returns Model-view matrix with translation zeroed for RTE rendering
32
+ */
33
+ export declare const calcModelMatrixRTE: (objectMatrixWorld: Matrix4, matrixWorldInverse: Matrix4, result?: Matrix4) => Matrix4;
34
+ /**
35
+ * Composes a frame matrix with a local transform, then splits the result into
36
+ * a translation Vector3 and a rotation/scale-only Matrix4 for RTE rendering.
37
+ *
38
+ * The translation is intended to be encoded as high/low RTE uniforms for GPU
39
+ * precision, while the rotation/scale matrix is assigned to the mesh's
40
+ * matrixWorld so it becomes the shader's modelMatrix uniform.
41
+ *
42
+ * @param frameMatrix - The frame transformation matrix (e.g. NUE-to-ECEF)
43
+ * @param localMatrix - The local T*R*S transform to compose within the frame
44
+ * @param resultPosition - Vector3 to store the extracted translation (reused to avoid GC)
45
+ * @param resultRotationScale - Matrix4 to store the translation-zeroed matrix (reused to avoid GC)
46
+ */
47
+ export declare function composeWorldMatrixForRTE(frameMatrix: Matrix4, localMatrix: Matrix4, resultPosition?: Vector3, resultRotationScale?: Matrix4): {
48
+ position: Vector3;
49
+ rotationScale: Matrix4;
50
+ };
51
+ /**
52
+ * Calculates the camera position relative to an object with high/low precision encoding for RTE rendering.
53
+ * @param cameraPosition - The camera's world position
54
+ * @param modelMatrixWorld - The object's world transformation matrix
55
+ * @returns Object with high and low precision Vector3 components for GPU double precision emulation
56
+ */
57
+ export declare const calcCameraPosition: (cameraPosition: Vector3, modelMatrixWorld: Matrix4) => {
58
+ high: Vector3;
59
+ low: Vector3;
60
+ };
package/package.json ADDED
@@ -0,0 +1,43 @@
1
+ {
2
+ "name": "@navaramap/three-api",
3
+ "type": "module",
4
+ "version": "0.0.0",
5
+ "main": "dist/index.umd.cjs",
6
+ "module": "dist/index.js",
7
+ "types": "dist/index.d.ts",
8
+ "files": [
9
+ "src",
10
+ "dist"
11
+ ],
12
+ "license": "MIT OR Apache-2.0",
13
+ "repository": {
14
+ "type": "git",
15
+ "url": "git+https://github.com/reearth/navara.git",
16
+ "directory": "web/navara_three_api"
17
+ },
18
+ "publishConfig": {
19
+ "access": "public"
20
+ },
21
+ "peerDependencies": {
22
+ "three": ">=0.183.0"
23
+ },
24
+ "dependencies": {
25
+ "@navaramap/core": "0.0.0",
26
+ "@navaramap/engine-api": "0.0.0"
27
+ },
28
+ "devDependencies": {
29
+ "@types/three": "0.185.1",
30
+ "three": "0.185.1"
31
+ },
32
+ "scripts": {
33
+ "type-check": "tsc && tsc -p tsconfig.node.json",
34
+ "build": "pnpm type-check && vite build --watch=false",
35
+ "build:dev": "vite build --mode development",
36
+ "build:dev:watch": "pnpm build:dev --watch",
37
+ "test": "vitest --passWithNoTests --watch=false",
38
+ "lint": "eslint . --fix",
39
+ "lint:check": "eslint .",
40
+ "format": "prettier --write --ignore-path ../../.gitignore --ignore-path ../../.prettierignore .",
41
+ "format:check": "prettier --check --ignore-path ../../.gitignore --ignore-path ../../.prettierignore ."
42
+ }
43
+ }
@@ -0,0 +1,113 @@
1
+ import type { LatLngHeight } from "@navaramap/core";
2
+ import {
3
+ EllipsoidGeodesic as EllipsoidGeodesicImpl,
4
+ LLE,
5
+ } from "@navaramap/engine-api";
6
+
7
+ /**
8
+ * Wrapper class for geodesic calculations on an ellipsoid surface.
9
+ * Precomputes common variables for optimization when instantiated.
10
+ *
11
+ * @example
12
+ * ```typescript
13
+ * const geodesic = new EllipsoidGeodesic(
14
+ * { lat: 0.5, lng: 2.0, height: 0 },
15
+ * { lat: 0.6, lng: 2.1, height: 0 }
16
+ * );
17
+ *
18
+ * console.log(geodesic.distance);
19
+ * const points = geodesic.interpolatePoints(1000);
20
+ * const midpoint = geodesic.interpolateDistance(geodesic.distance / 2);
21
+ *
22
+ * geodesic.dispose(); // Free WASM memory when done
23
+ * ```
24
+ */
25
+ export class EllipsoidGeodesic {
26
+ private _raw: EllipsoidGeodesicImpl;
27
+
28
+ /**
29
+ * Create a new geodesic between two points on the ellipsoid.
30
+ * @param start - Start point in geodetic coordinates (lat/lng in radians)
31
+ * @param end - End point in geodetic coordinates (lat/lng in radians)
32
+ */
33
+ constructor(start: LatLngHeight, end: LatLngHeight) {
34
+ this._raw = new EllipsoidGeodesicImpl(
35
+ new LLE(start.lat, start.lng, start.height),
36
+ new LLE(end.lat, end.lng, end.height),
37
+ );
38
+ }
39
+
40
+ /** Distance in meters between start and end points */
41
+ get distance(): number {
42
+ return this._raw.distance;
43
+ }
44
+
45
+ /** Heading at the start point in radians */
46
+ get startHeading(): number {
47
+ return this._raw.start_heading;
48
+ }
49
+
50
+ /** Heading at the end point in radians */
51
+ get endHeading(): number {
52
+ return this._raw.end_heading;
53
+ }
54
+
55
+ /** Start point in geodetic coordinates */
56
+ get start(): LatLngHeight {
57
+ const lle = this._raw.start;
58
+ return { lat: lle.lat, lng: lle.lng, height: lle.height };
59
+ }
60
+
61
+ /** End point in geodetic coordinates */
62
+ get end(): LatLngHeight {
63
+ const lle = this._raw.end;
64
+ return { lat: lle.lat, lng: lle.lng, height: lle.height };
65
+ }
66
+
67
+ /**
68
+ * Interpolate points along the geodesic path.
69
+ * @param granularity - Distance between interpolated points in meters (optional)
70
+ * @returns Array of interpolated points in geodetic coordinates
71
+ */
72
+ interpolatePoints(granularity?: number): LatLngHeight[] {
73
+ const wasmPoints = this._raw.interpolateGeodeticPoints(granularity ?? null);
74
+
75
+ const results: LatLngHeight[] = [];
76
+ for (const point of wasmPoints) {
77
+ results.push({
78
+ lat: point.lat,
79
+ lng: point.lng,
80
+ height: point.height,
81
+ });
82
+ point.free();
83
+ }
84
+
85
+ return results;
86
+ }
87
+
88
+ /**
89
+ * Interpolate a point at a specific distance along the geodesic path.
90
+ * @param distance - Distance from start point in meters
91
+ * @returns Interpolated point in geodetic coordinates
92
+ */
93
+ interpolateDistance(distance: number): LatLngHeight {
94
+ const wasmPoint = this._raw.interpolateDistance(distance);
95
+
96
+ const result: LatLngHeight = {
97
+ lat: wasmPoint.lat,
98
+ lng: wasmPoint.lng,
99
+ height: wasmPoint.height,
100
+ };
101
+
102
+ wasmPoint.free();
103
+
104
+ return result;
105
+ }
106
+
107
+ /**
108
+ * Free WASM memory. Call this when the geodesic is no longer needed.
109
+ */
110
+ dispose(): void {
111
+ this._raw.free();
112
+ }
113
+ }
package/src/index.ts ADDED
@@ -0,0 +1,303 @@
1
+ import type { LatLngHeight, Window as WindowObject } from "@navaramap/core";
2
+ import initApi, {
3
+ Window,
4
+ LLE,
5
+ Vec3,
6
+ Vec2,
7
+ Transform,
8
+ CameraFrustum,
9
+ geodeticToXyz,
10
+ xyzToGeodetic,
11
+ angleToRadian,
12
+ angleToDegree,
13
+ screenToWorld,
14
+ worldToScreen,
15
+ geodeticSurfaceNormal as nvGeodeticSurfaceNormal,
16
+ eastNorthUpToFixedFrame as nvEastNorthUpToFixedFrame,
17
+ northEastDownToFixedFrame as nvNorthEastDownToFixedFrame,
18
+ northUpEastToFixedFrame as nvNorthUpEastToFixedFrame,
19
+ northWestUpToFixedFrame as nvNorthWestUpToFixedFrame,
20
+ getWGS84SemiMajorAxis as nvGetWGS84SemiMajorAxis,
21
+ getWGS84SemiMinorAxis as nvGetWGS84SemiMinorAxis,
22
+ getWGS84EccentricitySquared as nvGetWGS84EccentricitySquared,
23
+ getWGS84Flattening as nvGetWGS84Flattening,
24
+ getWGS84Eccentricity as nvGetWGS84Eccentricity,
25
+ } from "@navaramap/engine-api";
26
+ import { Vector3, Vector2, Matrix4, PerspectiveCamera } from "three";
27
+
28
+ export type { LatLngHeight, LatLng } from "@navaramap/core";
29
+ export type {
30
+ Window,
31
+ Transform,
32
+ CameraFrustum,
33
+ Plane,
34
+ Ray,
35
+ Vec2,
36
+ Vec3,
37
+ } from "@navaramap/core";
38
+
39
+ export * from "./intersection";
40
+ export * from "./rte";
41
+ export * from "./ellipsoidGeodesic";
42
+
43
+ /**
44
+ * Initializes the Navara API WASM module. Must be called before using other API functions.
45
+ */
46
+ export async function initNavaraApi() {
47
+ await initApi();
48
+ }
49
+
50
+ /**
51
+ * Converts geodetic coordinates (longitude, latitude, height) to a Cartesian Vector3 in ECEF coordinates.
52
+ * @param lle - Geodetic coordinates (lng in radians, lat in radians, height in meters)
53
+ * @returns Cartesian Vector3 in Earth-Centered Earth-Fixed (ECEF) coordinates
54
+ */
55
+ export function geodeticToVector3(lle: LatLngHeight): Vector3 {
56
+ const pos = geodeticToXyz(new LLE(lle.lat, lle.lng, lle.height));
57
+ const result = new Vector3(pos.x, pos.y, pos.z);
58
+ pos.free();
59
+ return result;
60
+ }
61
+
62
+ /**
63
+ * Converts a Cartesian Vector3 in ECEF coordinates to geodetic coordinates.
64
+ * @param xyz - Cartesian Vector3 in Earth-Centered Earth-Fixed (ECEF) coordinates
65
+ * @returns Geodetic coordinates (lng in radians, lat in radians, height in meters)
66
+ */
67
+ export function vector3ToGeodetic(xyz: Vector3): LatLngHeight {
68
+ const vec3 = new Vec3(xyz.x, xyz.y, xyz.z);
69
+ const lle = xyzToGeodetic(vec3);
70
+ const result = { lat: lle.lat, lng: lle.lng, height: lle.height };
71
+ lle.free();
72
+ return result;
73
+ }
74
+
75
+ /**
76
+ * Converts an angle from degrees to radians.
77
+ * @param degree - Angle in degrees
78
+ * @returns Angle in radians
79
+ */
80
+ export function degreeToRadian(degree: number): number {
81
+ return angleToRadian(degree);
82
+ }
83
+
84
+ /**
85
+ * Converts an angle from radians to degrees.
86
+ * @param radian - Angle in radians
87
+ * @returns Angle in degrees
88
+ */
89
+ export function radianToDegree(radian: number): number {
90
+ return angleToDegree(radian);
91
+ }
92
+
93
+ /**
94
+ * Converts screen coordinates to world coordinates by raycasting against the WGS84 ellipsoid.
95
+ * @param window - Window configuration with width, height, and pixelRatio
96
+ * @param camera - Three.js PerspectiveCamera
97
+ * @param vec2 - Screen coordinates in CSS pixels (same as MouseEvent clientX/clientY)
98
+ * @returns World position Vector3 in ECEF coordinates, or undefined if no intersection with ellipsoid
99
+ */
100
+ export function convertScreenToWorld(
101
+ windowObject: WindowObject,
102
+ camera: PerspectiveCamera,
103
+ vec2: Vector2,
104
+ ): Vector3 | undefined {
105
+ const window = new Window(
106
+ windowObject.width,
107
+ windowObject.height,
108
+ windowObject.pixelRatio,
109
+ );
110
+ window.width = window.width * window.pixelRatio;
111
+ window.height = window.height * window.pixelRatio;
112
+
113
+ const transform = new Transform(
114
+ camera.position.x,
115
+ camera.position.y,
116
+ camera.position.z,
117
+ camera.quaternion.x,
118
+ camera.quaternion.y,
119
+ camera.quaternion.z,
120
+ camera.quaternion.w,
121
+ camera.scale.x,
122
+ camera.scale.y,
123
+ camera.scale.z,
124
+ );
125
+
126
+ const frustum = new CameraFrustum(
127
+ camera.near,
128
+ camera.far,
129
+ angleToRadian(camera.fov),
130
+ camera.aspect,
131
+ );
132
+
133
+ const pos = screenToWorld(
134
+ window,
135
+ transform,
136
+ frustum,
137
+ new Vec2(vec2.x, vec2.y),
138
+ );
139
+
140
+ if (!pos) {
141
+ return undefined;
142
+ }
143
+ const result = new Vector3(pos.x, pos.y, pos.z);
144
+ pos.free();
145
+
146
+ return result;
147
+ }
148
+
149
+ /**
150
+ * Computes the surface normal vector at a geodetic position on the WGS84 ellipsoid.
151
+ * @param lle - Geodetic coordinates (lng in radians, lat in radians, height in meters)
152
+ * @returns Unit normal vector pointing outward from the ellipsoid surface
153
+ */
154
+ export function geodeticSurfaceNormal(lle: LatLngHeight): Vector3 {
155
+ const pos = nvGeodeticSurfaceNormal(new LLE(lle.lat, lle.lng, lle.height));
156
+ const result = new Vector3(pos.x, pos.y, pos.z);
157
+ pos.free();
158
+ return result;
159
+ }
160
+
161
+ /**
162
+ * Creates a local East-North-Up (ENU) reference frame transformation matrix at a position.
163
+ * @param origin - Origin position in ECEF coordinates
164
+ * @returns 4x4 transformation matrix from ENU to ECEF
165
+ */
166
+ export function eastNorthUpToFixedFrame(origin: Vector3): Matrix4 {
167
+ const vec3 = new Vec3(origin.x, origin.y, origin.z);
168
+ const arr = nvEastNorthUpToFixedFrame(vec3);
169
+ const matrix = new Matrix4().fromArray(arr);
170
+ return matrix;
171
+ }
172
+
173
+ /**
174
+ * Creates a local North-East-Down (NED) reference frame transformation matrix at a position.
175
+ * @param origin - Origin position in ECEF coordinates
176
+ * @returns 4x4 transformation matrix from NED to ECEF
177
+ */
178
+ export function northEastDownToFixedFrame(origin: Vector3): Matrix4 {
179
+ const vec3 = new Vec3(origin.x, origin.y, origin.z);
180
+ const arr = nvNorthEastDownToFixedFrame(vec3);
181
+ const matrix = new Matrix4().fromArray(arr);
182
+ return matrix;
183
+ }
184
+
185
+ /**
186
+ * Creates a local North-Up-East (NUE) reference frame transformation matrix at a position.
187
+ * @param origin - Origin position in ECEF coordinates
188
+ * @returns 4x4 transformation matrix from NUE to ECEF
189
+ */
190
+ export function northUpEastToFixedFrame(origin: Vector3): Matrix4 {
191
+ const vec3 = new Vec3(origin.x, origin.y, origin.z);
192
+ const arr = nvNorthUpEastToFixedFrame(vec3);
193
+ const matrix = new Matrix4().fromArray(arr);
194
+ return matrix;
195
+ }
196
+
197
+ /**
198
+ * Creates a local North-West-Up (NWU) reference frame transformation matrix at a position.
199
+ * @param origin - Origin position in ECEF coordinates
200
+ * @returns 4x4 transformation matrix from NWU to ECEF
201
+ */
202
+ export function northWestUpToFixedFrame(origin: Vector3): Matrix4 {
203
+ const vec3 = new Vec3(origin.x, origin.y, origin.z);
204
+ const arr = nvNorthWestUpToFixedFrame(vec3);
205
+ const matrix = new Matrix4().fromArray(arr);
206
+ return matrix;
207
+ }
208
+
209
+ /**
210
+ * Converts world coordinates to screen coordinates.
211
+ * @param window - Window configuration with width, height, and pixelRatio
212
+ * @param camera - Three.js PerspectiveCamera
213
+ * @param worldPos - World position Vector3 in ECEF coordinates
214
+ * @returns Screen coordinates in CSS pixels, or undefined if behind camera
215
+ */
216
+ export function convertWorldToScreen(
217
+ windowObject: WindowObject,
218
+ camera: PerspectiveCamera,
219
+ worldPos: Vector3,
220
+ ): Vector2 | undefined {
221
+ const window = new Window(
222
+ windowObject.width,
223
+ windowObject.height,
224
+ windowObject.pixelRatio,
225
+ );
226
+ window.width = window.width * window.pixelRatio;
227
+ window.height = window.height * window.pixelRatio;
228
+
229
+ const transform = new Transform(
230
+ camera.position.x,
231
+ camera.position.y,
232
+ camera.position.z,
233
+ camera.quaternion.x,
234
+ camera.quaternion.y,
235
+ camera.quaternion.z,
236
+ camera.quaternion.w,
237
+ camera.scale.x,
238
+ camera.scale.y,
239
+ camera.scale.z,
240
+ );
241
+
242
+ const frustum = new CameraFrustum(
243
+ camera.near,
244
+ camera.far,
245
+ angleToRadian(camera.fov),
246
+ camera.aspect,
247
+ );
248
+
249
+ const screenPos = worldToScreen(
250
+ window,
251
+ transform,
252
+ frustum,
253
+ new Vec3(worldPos.x, worldPos.y, worldPos.z),
254
+ );
255
+
256
+ if (!screenPos) {
257
+ return undefined;
258
+ }
259
+ const result = new Vector2(screenPos.x, screenPos.y);
260
+ screenPos.free();
261
+
262
+ return result;
263
+ }
264
+
265
+ /**
266
+ * Returns the WGS84 ellipsoid semi-major axis (equatorial radius) in meters.
267
+ * @returns Semi-major axis (approximately 6,378,137 meters)
268
+ */
269
+ export function getWGS84SemiMajorAxis(): number {
270
+ return nvGetWGS84SemiMajorAxis();
271
+ }
272
+
273
+ /**
274
+ * Returns the WGS84 ellipsoid semi-minor axis (polar radius) in meters.
275
+ * @returns Semi-minor axis (approximately 6,356,752 meters)
276
+ */
277
+ export function getWGS84SemiMinorAxis(): number {
278
+ return nvGetWGS84SemiMinorAxis();
279
+ }
280
+
281
+ /**
282
+ * Returns the WGS84 ellipsoid first eccentricity squared.
283
+ * @returns Eccentricity squared value
284
+ */
285
+ export function getWGS84EccentricitySquared(): number {
286
+ return nvGetWGS84EccentricitySquared();
287
+ }
288
+
289
+ /**
290
+ * Returns the WGS84 ellipsoid flattening ratio.
291
+ * @returns Flattening value (approximately 1/298.257)
292
+ */
293
+ export function getWGS84Flattening(): number {
294
+ return nvGetWGS84Flattening();
295
+ }
296
+
297
+ /**
298
+ * Returns the WGS84 ellipsoid first eccentricity.
299
+ * @returns Eccentricity value
300
+ */
301
+ export function getWGS84Eccentricity(): number {
302
+ return nvGetWGS84Eccentricity();
303
+ }
@@ -0,0 +1,163 @@
1
+ import type { Window as WindowObject } from "@navaramap/core";
2
+ import {
3
+ angleToRadian,
4
+ getPlaneFromPointNormal as nvGetPlaneFromPointNormal,
5
+ getPickRay as nvGetPickRay,
6
+ getRayPlaneIntersection as nvGetRayPlaneIntersection,
7
+ getHeightFromEllipsoid as nvGetHeightFromEllipsoid,
8
+ Vec3,
9
+ Vec2,
10
+ Window,
11
+ Transform,
12
+ CameraFrustum,
13
+ Ray as WASMRay,
14
+ } from "@navaramap/engine-api";
15
+ import {
16
+ Vector3,
17
+ Vector2,
18
+ PerspectiveCamera,
19
+ Ray as ThreeRay,
20
+ Plane as ThreePlane,
21
+ } from "three";
22
+
23
+ /**
24
+ * Creates a plane from a point and a normal vector.
25
+ * @param point - A point on the plane
26
+ * @param normal - The normal vector of the plane
27
+ * @returns A Plane object defined by the point and normal
28
+ */
29
+ export function getPlaneFromPointNormal(
30
+ point: Vector3,
31
+ normal: Vector3,
32
+ ): ThreePlane {
33
+ const pointVec3 = new Vec3(point.x, point.y, point.z);
34
+ const normalVec3 = new Vec3(normal.x, normal.y, normal.z);
35
+
36
+ const wasmPlane = nvGetPlaneFromPointNormal(pointVec3, normalVec3);
37
+
38
+ // Convert WASM Plane to Three.js Plane
39
+ const threeNormal = new Vector3(
40
+ wasmPlane.normal.x,
41
+ wasmPlane.normal.y,
42
+ wasmPlane.normal.z,
43
+ );
44
+ // Three.js Plane uses constant = -distance (dot product of normal and point on plane)
45
+ const threePlane = new ThreePlane(threeNormal, -wasmPlane.distance);
46
+
47
+ wasmPlane.free();
48
+
49
+ return threePlane;
50
+ }
51
+
52
+ /**
53
+ * Creates a picking ray from screen coordinates for raycasting.
54
+ * @param window - Window configuration with width, height, and pixelRatio
55
+ * @param camera - Three.js PerspectiveCamera
56
+ * @param vec2 - Screen coordinates in CSS pixels (same as MouseEvent clientX/clientY)
57
+ * @returns A Ray starting from the camera through the screen point
58
+ */
59
+ export function getPickRay(
60
+ windowObject: WindowObject,
61
+ camera: PerspectiveCamera,
62
+ vec2: Vector2,
63
+ ): ThreeRay {
64
+ const window = new Window(
65
+ windowObject.width,
66
+ windowObject.height,
67
+ windowObject.pixelRatio,
68
+ );
69
+ window.width = window.width * window.pixelRatio;
70
+ window.height = window.height * window.pixelRatio;
71
+
72
+ const transform = new Transform(
73
+ camera.position.x,
74
+ camera.position.y,
75
+ camera.position.z,
76
+ camera.quaternion.x,
77
+ camera.quaternion.y,
78
+ camera.quaternion.z,
79
+ camera.quaternion.w,
80
+ camera.scale.x,
81
+ camera.scale.y,
82
+ camera.scale.z,
83
+ );
84
+
85
+ const frustum = new CameraFrustum(
86
+ camera.near,
87
+ camera.far,
88
+ angleToRadian(camera.fov),
89
+ camera.aspect,
90
+ );
91
+
92
+ const wasmRay = nvGetPickRay(
93
+ window,
94
+ transform,
95
+ frustum,
96
+ new Vec2(vec2.x, vec2.y),
97
+ );
98
+
99
+ // Convert WASM Ray to Three.js Ray
100
+ const origin = new Vector3(
101
+ wasmRay.origin.x,
102
+ wasmRay.origin.y,
103
+ wasmRay.origin.z,
104
+ );
105
+ const direction = new Vector3(
106
+ wasmRay.direction.x,
107
+ wasmRay.direction.y,
108
+ wasmRay.direction.z,
109
+ );
110
+ const threeRay = new ThreeRay(origin, direction);
111
+
112
+ wasmRay.free();
113
+
114
+ return threeRay;
115
+ }
116
+
117
+ /**
118
+ * Computes the intersection point between a ray and a plane.
119
+ * @param ray - The ray to test
120
+ * @param plane - The plane to intersect with
121
+ * @returns Intersection point Vector3 or undefined if no intersection
122
+ */
123
+ export function getRayPlaneIntersection(
124
+ ray: ThreeRay,
125
+ plane: ThreePlane,
126
+ ): Vector3 | undefined {
127
+ // Convert Three.js Ray to WASM Ray
128
+ const wasmRay = new WASMRay(
129
+ new Vec3(ray.origin.x, ray.origin.y, ray.origin.z),
130
+ new Vec3(ray.direction.x, ray.direction.y, ray.direction.z),
131
+ );
132
+
133
+ // Convert Three.js Plane to WASM Plane
134
+ // Three.js uses constant = -distance, so we need to negate it
135
+ const wasmPlane = nvGetPlaneFromPointNormal(
136
+ new Vec3(
137
+ plane.normal.x * -plane.constant,
138
+ plane.normal.y * -plane.constant,
139
+ plane.normal.z * -plane.constant,
140
+ ),
141
+ new Vec3(plane.normal.x, plane.normal.y, plane.normal.z),
142
+ );
143
+
144
+ const intersection = nvGetRayPlaneIntersection(wasmRay, wasmPlane);
145
+
146
+ if (intersection) {
147
+ const result = new Vector3(intersection.x, intersection.y, intersection.z);
148
+ intersection.free();
149
+ return result;
150
+ }
151
+ return undefined;
152
+ }
153
+
154
+ /**
155
+ * Computes the height of a point above the WGS84 ellipsoid surface.
156
+ * @param point - Position in ECEF coordinates
157
+ * @returns Height above the ellipsoid in meters
158
+ */
159
+ export function getHeightFromEllipsoid(point: Vector3): number {
160
+ const vec3 = new Vec3(point.x, point.y, point.z);
161
+ const result = nvGetHeightFromEllipsoid(vec3);
162
+ return result;
163
+ }
package/src/rte.ts ADDED
@@ -0,0 +1,114 @@
1
+ import { encodePosition } from "@navaramap/engine-api";
2
+ import { Matrix4, Vector3 } from "three";
3
+
4
+ /**
5
+ * Shared `u_rteOne` uniform for RTE vertex shaders. Always 1.0.
6
+ *
7
+ * Multiplying the high-order difference by a uniform the shader compiler
8
+ * cannot constant-fold blocks fast-math reassociation of
9
+ * `(high - camHigh) + (low - camLow)` into `(high + low) - (camHigh + camLow)`,
10
+ * which would collapse the math to absolute-ECEF f32 precision and make
11
+ * geometry freeze/snap in ~0.25-0.5 m steps as the camera moves
12
+ * (observed on ANGLE Metal). One frozen object is shared by every material.
13
+ */
14
+ export const RTE_ONE_UNIFORM: { readonly value: number } = Object.freeze({
15
+ value: 1.0,
16
+ });
17
+
18
+ /**
19
+ * Encodes a position into high and low precision Vector3 components for RTE (Relative-To-Eye) rendering.
20
+ * This enables GPU double precision emulation by splitting a position into two float components.
21
+ * @param original - Position to encode
22
+ * @param resultHigh - Vector3 to store the high precision component (reused to avoid GC)
23
+ * @param resultLow - Vector3 to store the low precision component (reused to avoid GC)
24
+ */
25
+ export function encodePositionRTE(
26
+ original: Vector3,
27
+ resultHigh = new Vector3(),
28
+ resultLow = new Vector3(),
29
+ ): { high: Vector3; low: Vector3 } {
30
+ const encoded = encodePosition(original.x, original.y, original.z);
31
+ resultHigh.set(encoded.high.x, encoded.high.y, encoded.high.z);
32
+ resultLow.set(encoded.low.x, encoded.low.y, encoded.low.z);
33
+ encoded.free();
34
+ return { high: resultHigh, low: resultLow };
35
+ }
36
+
37
+ /**
38
+ * Calculates the model-view matrix for Relative-To-Eye (RTE) rendering, which improves precision for distant objects.
39
+ * @param objectMatrixWorld - The object's world transformation matrix
40
+ * @param matrixWorldInverse - The camera's inverse world matrix
41
+ * @param result - Optional matrix to store the result (creates new if omitted)
42
+ * @returns Model-view matrix with translation zeroed for RTE rendering
43
+ */
44
+ export const calcModelMatrixRTE = (
45
+ objectMatrixWorld: Matrix4,
46
+ matrixWorldInverse: Matrix4,
47
+ result = new Matrix4(),
48
+ ) => {
49
+ result.multiplyMatrices(matrixWorldInverse, objectMatrixWorld);
50
+ const e = result.elements;
51
+ e[12] = 0.0;
52
+ e[13] = 0.0;
53
+ e[14] = 0.0; // zero translation
54
+ return result;
55
+ };
56
+
57
+ /**
58
+ * Composes a frame matrix with a local transform, then splits the result into
59
+ * a translation Vector3 and a rotation/scale-only Matrix4 for RTE rendering.
60
+ *
61
+ * The translation is intended to be encoded as high/low RTE uniforms for GPU
62
+ * precision, while the rotation/scale matrix is assigned to the mesh's
63
+ * matrixWorld so it becomes the shader's modelMatrix uniform.
64
+ *
65
+ * @param frameMatrix - The frame transformation matrix (e.g. NUE-to-ECEF)
66
+ * @param localMatrix - The local T*R*S transform to compose within the frame
67
+ * @param resultPosition - Vector3 to store the extracted translation (reused to avoid GC)
68
+ * @param resultRotationScale - Matrix4 to store the translation-zeroed matrix (reused to avoid GC)
69
+ */
70
+ export function composeWorldMatrixForRTE(
71
+ frameMatrix: Matrix4,
72
+ localMatrix: Matrix4,
73
+ resultPosition = new Vector3(),
74
+ resultRotationScale = new Matrix4(),
75
+ ): { position: Vector3; rotationScale: Matrix4 } {
76
+ resultRotationScale.multiplyMatrices(frameMatrix, localMatrix);
77
+ resultPosition.setFromMatrixPosition(resultRotationScale);
78
+ const e = resultRotationScale.elements;
79
+ e[12] = 0;
80
+ e[13] = 0;
81
+ e[14] = 0;
82
+ return { position: resultPosition, rotationScale: resultRotationScale };
83
+ }
84
+
85
+ const INVERSE_MODEL_MATRIX = new Matrix4();
86
+ const CAMERA_MODEL_POSITION = new Vector3();
87
+ /**
88
+ * Calculates the camera position relative to an object with high/low precision encoding for RTE rendering.
89
+ * @param cameraPosition - The camera's world position
90
+ * @param modelMatrixWorld - The object's world transformation matrix
91
+ * @returns Object with high and low precision Vector3 components for GPU double precision emulation
92
+ */
93
+ export const calcCameraPosition = (
94
+ cameraPosition: Vector3,
95
+ modelMatrixWorld: Matrix4,
96
+ ) => {
97
+ INVERSE_MODEL_MATRIX.copy(modelMatrixWorld).invert();
98
+ CAMERA_MODEL_POSITION.copy(cameraPosition).applyMatrix4(INVERSE_MODEL_MATRIX);
99
+ const encoded = encodePosition(
100
+ CAMERA_MODEL_POSITION.x,
101
+ CAMERA_MODEL_POSITION.y,
102
+ CAMERA_MODEL_POSITION.z,
103
+ );
104
+ const high = encoded.high;
105
+ const low = encoded.low;
106
+ const result = {
107
+ high: new Vector3(high.x, high.y, high.z),
108
+ low: new Vector3(low.x, low.y, low.z),
109
+ };
110
+
111
+ encoded.free();
112
+
113
+ return result;
114
+ };
@@ -0,0 +1 @@
1
+ /// <reference types="vite/client" />