Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

feat(geospatial): Add makeOBBFromRegion() #43

Merged
merged 13 commits into from
Jan 4, 2025
49 changes: 49 additions & 0 deletions modules/core/src/classes/matrix3.ts
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,7 @@

import {NumericArray, NumericArray9} from '@math.gl/types';
import {Matrix} from './base/matrix';
import {Vector3} from './vector3';
import {checkVector} from '../lib/validators';

import {vec4_transformMat3} from '../lib/gl-matrix-extras';
Expand Down Expand Up @@ -202,6 +203,54 @@ export class Matrix3 extends Matrix {
return this.check();
}

/**
* Computes the product of this matrix and a column vector.
*
* @param {Vector3} cartesian The column.
* @param {Vector3} result The object onto which to store the result.
* @returns {Vector3} The modified result parameter.
*/
multiplyByVector(cartesian: Vector3, result?: Vector3): Vector3 {
if (!result) result = new Vector3();

const vX = cartesian.x;
const vY = cartesian.y;
const vZ = cartesian.z;

const x = this[0] * vX + this[3] * vY + this[6] * vZ;
const y = this[1] * vX + this[4] * vY + this[7] * vZ;
const z = this[2] * vX + this[5] * vY + this[8] * vZ;

result.x = x;
result.y = y;
result.z = z;

return result;
}

/**
* Computes the product of this matrix times a (non-uniform) scale, as if the scale were a scale matrix.
*
* @param {Vector3} scale The non-uniform scale on the right-hand side.
* @param {Matrix3} result The object onto which to store the result.
* @returns {Matrix3} The modified result parameter.
*/
multiplyByScale(scale: Vector3, result?: Matrix3): Matrix3 {
if (!result) result = new Matrix3();

result[0] = this[0] * scale.x;
result[1] = this[1] * scale.x;
result[2] = this[2] * scale.x;
result[3] = this[3] * scale.y;
result[4] = this[4] * scale.y;
result[5] = this[5] * scale.y;
result[6] = this[6] * scale.z;
result[7] = this[7] * scale.z;
result[8] = this[8] * scale.z;

return result;
}

rotate(radians: number): this {
mat3_rotate(this, this, radians);
return this.check();
Expand Down
2 changes: 2 additions & 0 deletions modules/core/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,8 @@ export {
// math.gl global utility methods
config,
configure,
safeMod,
normalizeAngle,
formatValue,
isArray,
clone,
Expand Down
67 changes: 67 additions & 0 deletions modules/core/src/lib/common.ts
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@
import type {NumericArray} from '@math.gl/types';

import type {MathArray} from '../classes/base/math-array';
import {EPSILON14, TWO_PI, PI} from './math-utils';

const RADIANS_TO_DEGREES = (1 / Math.PI) * 180;
const DEGREES_TO_RADIANS = (1 / 180) * Math.PI;
Expand Down Expand Up @@ -122,6 +123,72 @@ export function degrees(
return map(radians, (radians) => radians * RADIANS_TO_DEGREES, result);
}

/**
* The modulo operation that also works for negative dividends.
*
* @param m The dividend.
* @param n The divisor.
* @returns The remainder.
*/
export function safeMod(m: number, n: number): number {
if (Math.sign(m) === Math.sign(n) && Math.abs(m) < Math.abs(n)) {
return m;
}

return ((m % n) + n) % n;
}

/**
* Produces an angle restricted to its equivalent in a normalized range
*
* @param angle in radians
* @param range 'zero-to-two-pi' - in the range 0 <= angle <= 2PI, | 'negative-pi-to-pi' - -Pi <= angle <= Pi
* @returns The angle in the range [0, <code>TWO_PI</code>] or [<code>-PI</code>, <code>PI</code>]..
*/
export function normalizeAngle(
angle: number,
range: 'zero-to-two-pi' | 'negative-pi-to-pi'
): number {
switch (range) {
case 'negative-pi-to-pi':
return negativePiToPi(angle);
case 'zero-to-two-pi':
return zeroToTwoPi(angle);
default:
return angle;
}
}

/**
* Produces an angle in the range 0 <= angle <= 2Pi which is equivalent to the provided angle.
*
* @param angle in radians
* @returns The angle in the range [0, <code>TWO_PI</code>].
*/
function zeroToTwoPi(angle: number): number {
if (angle >= 0 && angle <= TWO_PI) {
return angle;
}
const remainder = safeMod(angle, TWO_PI);
if (Math.abs(remainder) < EPSILON14 && Math.abs(angle) > EPSILON14) {
return TWO_PI;
}
return remainder;
}

/**
* Produces an angle in the range -Pi <= angle <= Pi which is equivalent to the provided angle.
*
* @param angle in radians
* @returns The angle in the range [<code>-PI</code>, <code>PI</code>].
*/
function negativePiToPi(angle: number): number {
if (angle >= -PI && angle <= PI) {
return angle;
}
return zeroToTwoPi(angle + PI) - PI;
}

/**
* "GLSL equivalent" of `Math.sin`: Works on single values and vectors
* @deprecated
Expand Down
1 change: 1 addition & 0 deletions modules/core/src/lib/math-utils.ts
Original file line number Diff line number Diff line change
Expand Up @@ -29,4 +29,5 @@ export const PI_OVER_TWO = Math.PI / 2;
export const PI_OVER_FOUR = Math.PI / 4;
export const PI_OVER_SIX = Math.PI / 6;

export const PI = Math.PI;
export const TWO_PI = Math.PI * 2;
1 change: 1 addition & 0 deletions modules/culling/src/index.ts
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ export {BoundingSphere} from './lib/bounding-volumes/bounding-sphere';
export {OrientedBoundingBox} from './lib/bounding-volumes/oriented-bounding-box';
export {CullingVolume} from './lib/culling-volume';
export {Plane} from './lib/plane';
export {Ray} from './lib/ray';

export {PerspectiveOffCenterFrustum as _PerspectiveOffCenterFrustum} from './lib/perspective-off-center-frustum';
export {PerspectiveFrustum as _PerspectiveFrustum} from './lib/perspective-frustum';
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,10 @@

import {NumericArray} from '@math.gl/types';
import {Vector3, Matrix3, Matrix4, Quaternion} from '@math.gl/core';

import type {BoundingVolume} from './bounding-volume';
import {BoundingSphere} from './bounding-sphere';
import type {Plane} from '../plane';
import {Plane} from '../plane';
import {INTERSECTION} from '../../constants';

const scratchVector3 = new Vector3();
Expand Down
32 changes: 31 additions & 1 deletion modules/culling/src/lib/plane.ts
Original file line number Diff line number Diff line change
Expand Up @@ -6,7 +6,8 @@
// See LICENSE.md and https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md

/* eslint-disable */
import {Vector3, equals, assert, NumericArray} from '@math.gl/core';
import {Vector3, equals, assert, NumericArray, _MathUtils} from '@math.gl/core';
import {Ray} from './ray';

const scratchPosition = new Vector3();
const scratchNormal = new Vector3();
Expand Down Expand Up @@ -86,4 +87,33 @@ export class Plane {

return scratchPoint.subtract(scaledNormal).to(result);
}

/**
* Computes the intersection of a ray and this plane.
*
* @param {Ray} ray The ray.
* @param {Vector3} [result] The object onto which to store the result.
* @returns {Vector3} The intersection point or undefined if there is no intersections.
*/
intersectWithRay(ray: Ray, result?: Vector3): Vector3 {
if (!result) result = new Vector3();

const origin = ray.origin;
const direction = ray.direction;
const normal = this.normal;
const denominator = normal.dot(direction);

if (Math.abs(denominator) < _MathUtils.EPSILON15) {
return undefined;
}

const t = (-this.distance - normal.dot(origin)) / denominator;

if (t < 0) {
return undefined;
}

result = result.copy(direction).multiplyByScalar(t);
return origin.add(result);
}
}
31 changes: 31 additions & 0 deletions modules/culling/src/lib/ray.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,31 @@
// math.gl
// SPDX-License-Identifier: MIT and Apache-2.0
// Copyright (c) vis.gl contributors

// This file is derived from the Cesium library under Apache 2 license
// See LICENSE.md and https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md

import {Vector3} from '@math.gl/core';

/* Represents a ray that extends infinitely from the provided origin in the provided direction. */
export class Ray {
origin: Vector3;
direction: Vector3;

/**
* Creates a new ray that extends infinitely from the provided origin in the provided direction.
*
* @param [origin=Vector3] The origin of the ray.
* @param [direction=Vector3] The direction of the ray.
*/
constructor(origin?: Vector3, direction?: Vector3) {
if (origin) origin = origin.clone();
else origin = new Vector3();

if (direction) direction = direction.clone().normalize();
else direction = new Vector3();

this.origin = origin;
this.direction = direction;
}
}
15 changes: 15 additions & 0 deletions modules/culling/test/lib/ray.spec.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
// This file is derived from the Cesium math library under Apache 2 license
// See LICENSE.md and https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md

/* eslint-disable */
import test from 'tape-promise/tape';
// import {tapeEquals} from 'test/utils/tape-assertions';

import {Vector3, _MathUtils} from '@math.gl/core';
import {Ray} from '@math.gl/culling';

test('Ray#constructs', (t) => {
const ray = new Ray(new Vector3(1, 0, 0));
t.ok(ray);
t.end();
});
1 change: 1 addition & 0 deletions modules/geospatial/package.json
Original file line number Diff line number Diff line change
Expand Up @@ -40,6 +40,7 @@
],
"dependencies": {
"@math.gl/core": "4.1.0-alpha.9",
"@math.gl/culling": "4.1.0-alpha.9",
"@math.gl/types": "4.1.0-alpha.9"
},
"gitHead": "e1a95300cb225a90da6e90333d4adf290f7ba501"
Expand Down
98 changes: 98 additions & 0 deletions modules/geospatial/src/ellipsoid-tangent-plane.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,98 @@
// math.gl
// SPDX-License-Identifier: MIT and Apache-2.0
// Copyright (c) vis.gl contributors

// This file is derived from the Cesium library under Apache 2 license
// See LICENSE.md and https://github.com/AnalyticalGraphicsInc/cesium/blob/master/LICENSE.md

import {Vector2, Vector3, Matrix4} from '@math.gl/core';
import {Plane, Ray} from '@math.gl/culling';
import {Ellipsoid} from './ellipsoid';

const scratchOrigin = new Vector3();
const scratchCart3 = new Vector3();
const scratchEastNorthUp = new Matrix4();
const scratchPlane = new Plane();

const scratchProjectPointOntoPlaneRay = new Ray();
const scratchProjectPointOntoPlaneCartesian3 = new Vector3();
const scratchDirection = new Vector3();

/** A plane tangent to the WGS84 ellipsoid at the provided origin */
export class EllipsoidTangentPlane {
private _origin: Vector3;
private _xAxis: Vector3;
private _yAxis: Vector3;
private _plane: Plane;

/**
* Creates a new plane tangent to the WGS84 ellipsoid at the provided origin.
* If origin is not on the surface of the ellipsoid, it's surface projection will be used.
*
* @param {Cartesian3} origin The point on the surface of the ellipsoid where the tangent plane touches.
*/
constructor(origin: number[]) {
origin = Ellipsoid.WGS84.scaleToGeodeticSurface(origin, scratchOrigin);

const eastNorthUp = Ellipsoid.WGS84.eastNorthUpToFixedFrame(origin, scratchEastNorthUp);

this._origin = origin as Vector3;
this._xAxis = new Vector3(scratchCart3.from(eastNorthUp.getColumn(0)));
this._yAxis = new Vector3(scratchCart3.from(eastNorthUp.getColumn(1)));
const normal = new Vector3(scratchCart3.from(eastNorthUp.getColumn(2)));

this._plane = scratchPlane.fromPointNormal(origin, normal);
}

/**
* Computes the projection of the provided 3D position onto the 2D plane, along the plane normal.
*
* @param {Vector3} cartesian The point to project.
* @param {Vector2} [result] The object onto which to store the result.
* @returns {Vector2} The modified result parameter or a new Cartesian2 instance if none was provided.
*/
projectPointToNearestOnPlane(cartesian: Vector3, result?: Vector2): Vector2 {
if (!result) result = new Vector2();

const plane = this._plane;

const ray = scratchProjectPointOntoPlaneRay;
scratchProjectPointOntoPlaneRay.origin = cartesian;
scratchProjectPointOntoPlaneRay.direction = scratchDirection.copy(plane.normal);

let intersectionPoint = plane.intersectWithRay(ray, scratchProjectPointOntoPlaneCartesian3);

if (!intersectionPoint) {
ray.direction = ray.direction.negate();
intersectionPoint = plane.intersectWithRay(ray, scratchProjectPointOntoPlaneCartesian3);
}

const v = intersectionPoint.subtract(this._origin);
const x = this._xAxis.dot(v);
const y = this._yAxis.dot(v);

result.x = x;
result.y = y;
return result;
}

get plane() {
return this._plane;
}

get origin() {
return this._origin;
}

get xAxis() {
return this._xAxis;
}

get yAxis() {
return this._yAxis;
}

get zAxis() {
return this._plane.normal;
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -8,12 +8,12 @@
/* eslint-disable */
import {Vector3, Matrix4, assert, equals, _MathUtils, NumericArray, vec3} from '@math.gl/core';

import {WGS84_RADIUS_X, WGS84_RADIUS_Y, WGS84_RADIUS_Z} from '../constants';
import {fromCartographicToRadians, toCartographicFromRadians} from '../type-utils';
import {WGS84_RADIUS_X, WGS84_RADIUS_Y, WGS84_RADIUS_Z} from './constants';
import {fromCartographicToRadians, toCartographicFromRadians} from './type-utils';

import type {AxisDirection} from './helpers/ellipsoid-transform';
import {localFrameToFixedFrame} from './helpers/ellipsoid-transform';
import {scaleToGeodeticSurface} from './helpers/scale-to-geodetic-surface';
import type {AxisDirection} from './ellipsoid-helpers/ellipsoid-transform';
import {localFrameToFixedFrame} from './ellipsoid-helpers/ellipsoid-transform';
import {scaleToGeodeticSurface} from './ellipsoid-helpers/scale-to-geodetic-surface';

const scratchVector = new Vector3();
const scratchNormal = new Vector3();
Expand Down
Loading
Loading