// math.gl // SPDX-License-Identifier: MIT and Apache-2.0 // Copyright (c) vis.gl contributors /* eslint-disable */ import {Vector3, _MathUtils} from '@math.gl/core'; import type {Ellipsoid} from '../ellipsoid'; const scratchVector = new Vector3(); const scaleToGeodeticSurfaceIntersection = new Vector3(); const scaleToGeodeticSurfaceGradient = new Vector3(); // Scales the provided Cartesian position along the geodetic surface normal // so that it is on the surface of this ellipsoid. If the position is // at the center of the ellipsoid, this function returns undefined. export function scaleToGeodeticSurface( cartesian: number[], ellipsoid: Ellipsoid, result: number[] = [] ): number[] { const {oneOverRadii, oneOverRadiiSquared, centerToleranceSquared} = ellipsoid; scratchVector.from(cartesian); const positionX = scratchVector.x; const positionY = scratchVector.y; const positionZ = scratchVector.z; const oneOverRadiiX = oneOverRadii.x; const oneOverRadiiY = oneOverRadii.y; const oneOverRadiiZ = oneOverRadii.z; const x2 = positionX * positionX * oneOverRadiiX * oneOverRadiiX; const y2 = positionY * positionY * oneOverRadiiY * oneOverRadiiY; const z2 = positionZ * positionZ * oneOverRadiiZ * oneOverRadiiZ; // Compute the squared ellipsoid norm. const squaredNorm = x2 + y2 + z2; const ratio = Math.sqrt(1.0 / squaredNorm); // When very close to center or at center if (!Number.isFinite(ratio)) { return undefined; } // As an initial approximation, assume that the radial intersection is the projection point. const intersection = scaleToGeodeticSurfaceIntersection; intersection.copy(cartesian).scale(ratio); // If the position is near the center, the iteration will not converge. if (squaredNorm < centerToleranceSquared) { return intersection.to(result); } const oneOverRadiiSquaredX = oneOverRadiiSquared.x; const oneOverRadiiSquaredY = oneOverRadiiSquared.y; const oneOverRadiiSquaredZ = oneOverRadiiSquared.z; // Use the gradient at the intersection point in place of the true unit normal. // The difference in magnitude will be absorbed in the multiplier. const gradient = scaleToGeodeticSurfaceGradient; gradient.set( intersection.x * oneOverRadiiSquaredX * 2.0, intersection.y * oneOverRadiiSquaredY * 2.0, intersection.z * oneOverRadiiSquaredZ * 2.0 ); // Compute the initial guess at the normal vector multiplier, lambda. let lambda = ((1.0 - ratio) * scratchVector.len()) / (0.5 * gradient.len()); let correction = 0.0; let xMultiplier; let yMultiplier; let zMultiplier; let func; do { lambda -= correction; xMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredX); yMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredY); zMultiplier = 1.0 / (1.0 + lambda * oneOverRadiiSquaredZ); const xMultiplier2 = xMultiplier * xMultiplier; const yMultiplier2 = yMultiplier * yMultiplier; const zMultiplier2 = zMultiplier * zMultiplier; const xMultiplier3 = xMultiplier2 * xMultiplier; const yMultiplier3 = yMultiplier2 * yMultiplier; const zMultiplier3 = zMultiplier2 * zMultiplier; func = x2 * xMultiplier2 + y2 * yMultiplier2 + z2 * zMultiplier2 - 1.0; // "denominator" here refers to the use of this expression in the velocity and acceleration // computations in the sections to follow. const denominator = x2 * xMultiplier3 * oneOverRadiiSquaredX + y2 * yMultiplier3 * oneOverRadiiSquaredY + z2 * zMultiplier3 * oneOverRadiiSquaredZ; const derivative = -2.0 * denominator; correction = func / derivative; } while (Math.abs(func) > _MathUtils.EPSILON12); return scratchVector.scale([xMultiplier, yMultiplier, zMultiplier]).to(result); }