UNPKG

3d-tiles-renderer

Version:

https://github.com/AnalyticalGraphicsInc/3d-tiles/tree/master/specification

410 lines (277 loc) 12.1 kB
import { Vector3, Spherical, MathUtils, Ray, Matrix4, Sphere, Euler } from 'three'; import { swapToGeoFrame, latitudeToSphericalPhi } from './GeoUtils.js'; const _spherical = /* @__PURE__ */ new Spherical(); const _norm = /* @__PURE__ */ new Vector3(); const _vec = /* @__PURE__ */ new Vector3(); const _vec2 = /* @__PURE__ */ new Vector3(); const _matrix = /* @__PURE__ */ new Matrix4(); const _matrix2 = /* @__PURE__ */ new Matrix4(); const _matrix3 = /* @__PURE__ */ new Matrix4(); const _sphere = /* @__PURE__ */ new Sphere(); const _euler = /* @__PURE__ */ new Euler(); const _vecX = /* @__PURE__ */ new Vector3(); const _vecY = /* @__PURE__ */ new Vector3(); const _vecZ = /* @__PURE__ */ new Vector3(); const _pos = /* @__PURE__ */ new Vector3(); const _ray = /* @__PURE__ */ new Ray(); const EPSILON12 = 1e-12; const CENTER_EPS = 0.1; export const ENU_FRAME = 0; export const CAMERA_FRAME = 1; export const OBJECT_FRAME = 2; export class Ellipsoid { constructor( x = 1, y = 1, z = 1 ) { this.name = ''; this.radius = new Vector3( x, y, z ); } intersectRay( ray, target ) { _matrix.makeScale( ...this.radius ).invert(); _sphere.center.set( 0, 0, 0 ); _sphere.radius = 1; _ray.copy( ray ).applyMatrix4( _matrix ); if ( _ray.intersectSphere( _sphere, target ) ) { _matrix.makeScale( ...this.radius ); target.applyMatrix4( _matrix ); return target; } else { return null; } } // returns a frame with Z indicating altitude, Y pointing north, X pointing east getEastNorthUpFrame( lat, lon, height, target ) { if ( height.isMatrix4 ) { target = height; height = 0; console.warn( 'Ellipsoid: The signature for "getEastNorthUpFrame" has changed.' ); } this.getEastNorthUpAxes( lat, lon, _vecX, _vecY, _vecZ ); this.getCartographicToPosition( lat, lon, height, _pos ); return target.makeBasis( _vecX, _vecY, _vecZ ).setPosition( _pos ); } // returns a frame with z indicating altitude and az, el, roll rotation within that frame // - azimuth: measured off of true north, increasing towards "east" (z-axis) // - elevation: measured off of the horizon, increasing towards sky (x-axis) // - roll: rotation around northern axis (y-axis) getOrientedEastNorthUpFrame( lat, lon, height, az, el, roll, target ) { return this.getObjectFrame( lat, lon, height, az, el, roll, target, ENU_FRAME ); } // returns a frame similar to the ENU frame but rotated to match three.js object and camera conventions // OBJECT_FRAME: oriented such that "+Y" is up and "+Z" is forward. // CAMERA_FRAME: oriented such that "+Y" is up and "-Z" is forward. getObjectFrame( lat, lon, height, az, el, roll, target, frame = OBJECT_FRAME ) { this.getEastNorthUpFrame( lat, lon, height, _matrix ); _euler.set( el, roll, - az, 'ZXY' ); target .makeRotationFromEuler( _euler ) .premultiply( _matrix ); // Add in the orientation adjustment for objects and cameras so "forward" and "up" are oriented // correctly if ( frame === CAMERA_FRAME ) { _euler.set( Math.PI / 2, 0, 0, 'XYZ' ); _matrix2.makeRotationFromEuler( _euler ); target.multiply( _matrix2 ); } else if ( frame === OBJECT_FRAME ) { _euler.set( - Math.PI / 2, 0, Math.PI, 'XYZ' ); _matrix2.makeRotationFromEuler( _euler ); target.multiply( _matrix2 ); } return target; } getCartographicFromObjectFrame( matrix, target, frame = OBJECT_FRAME ) { // if working with a frame that is not the ENU_FRAME then multiply in the // offset for a camera or object so "forward" and "up" are oriented correct if ( frame === CAMERA_FRAME ) { _euler.set( - Math.PI / 2, 0, 0, 'XYZ' ); _matrix2.makeRotationFromEuler( _euler ).premultiply( matrix ); } else if ( frame === OBJECT_FRAME ) { _euler.set( - Math.PI / 2, 0, Math.PI, 'XYZ' ); _matrix2.makeRotationFromEuler( _euler ).premultiply( matrix ); } else { _matrix2.copy( matrix ); } // get the cartographic position of the frame _pos.setFromMatrixPosition( _matrix2 ); this.getPositionToCartographic( _pos, target ); // get the relative rotation this.getEastNorthUpFrame( target.lat, target.lon, 0, _matrix ).invert(); _matrix2.premultiply( _matrix ); _euler.setFromRotationMatrix( _matrix2, 'ZXY' ); target.azimuth = - _euler.z; target.elevation = _euler.x; target.roll = _euler.y; return target; } getEastNorthUpAxes( lat, lon, vecEast, vecNorth, vecUp, point = _pos ) { this.getCartographicToPosition( lat, lon, 0, point ); this.getCartographicToNormal( lat, lon, vecUp ); // up vecEast.set( - point.y, point.x, 0 ).normalize(); // east vecNorth.crossVectors( vecUp, vecEast ).normalize(); // north } // azimuth: measured off of true north, increasing towards "east" // elevation: measured off of the horizon, increasing towards sky // roll: rotation around northern axis getAzElRollFromRotationMatrix( lat, lon, rotationMatrix, target, frame = ENU_FRAME ) { console.warn( 'Ellipsoid: "getAzElRollFromRotationMatrix" is deprecated. Use "getCartographicFromObjectFrame", instead.' ); this.getCartographicToPosition( lat, lon, 0, _pos ); _matrix3.copy( rotationMatrix ).setPosition( _pos ); this.getCartographicFromObjectFrame( _matrix3, target, frame ); delete target.height; delete target.lat; delete target.lon; return target; } getRotationMatrixFromAzElRoll( lat, lon, az, el, roll, target, frame = ENU_FRAME ) { console.warn( 'Ellipsoid: "getRotationMatrixFromAzElRoll" function has been deprecated. Use "getObjectFrame", instead.' ); this.getObjectFrame( lat, lon, 0, az, el, roll, target, frame ); target.setPosition( 0, 0, 0 ); return target; } getFrame( lat, lon, az, el, roll, height, target, frame = ENU_FRAME ) { console.warn( 'Ellipsoid: "getFrame" function has been deprecated. Use "getObjectFrame", instead.' ); return this.getObjectFrame( lat, lon, height, az, el, roll, target, frame ); } getCartographicToPosition( lat, lon, height, target ) { // From Cesium function Ellipsoid.cartographicToCartesian // https://github.com/CesiumGS/cesium/blob/665ec32e813d5d6fe906ec3e87187f6c38ed5e49/packages/engine/Source/core/renderer/Ellipsoid.js#L396 this.getCartographicToNormal( lat, lon, _norm ); const radius = this.radius; _vec.copy( _norm ); _vec.x *= radius.x ** 2; _vec.y *= radius.y ** 2; _vec.z *= radius.z ** 2; const gamma = Math.sqrt( _norm.dot( _vec ) ); _vec.divideScalar( gamma ); return target.copy( _vec ).addScaledVector( _norm, height ); } getPositionToCartographic( pos, target ) { // From Cesium function Ellipsoid.cartesianToCartographic // https://github.com/CesiumGS/cesium/blob/665ec32e813d5d6fe906ec3e87187f6c38ed5e49/packages/engine/Source/core/renderer/Ellipsoid.js#L463 this.getPositionToSurfacePoint( pos, _vec ); this.getPositionToNormal( pos, _norm ); const heightDelta = _vec2.subVectors( pos, _vec ); target.lon = Math.atan2( _norm.y, _norm.x ); target.lat = Math.asin( _norm.z ); target.height = Math.sign( heightDelta.dot( pos ) ) * heightDelta.length(); return target; } getCartographicToNormal( lat, lon, target ) { _spherical.set( 1, latitudeToSphericalPhi( lat ), lon ); target.setFromSpherical( _spherical ).normalize(); // swap frame from the three.js frame to the geo coord frame swapToGeoFrame( target ); return target; } getPositionToNormal( pos, target ) { const radius = this.radius; target.copy( pos ); target.x /= radius.x ** 2; target.y /= radius.y ** 2; target.z /= radius.z ** 2; target.normalize(); return target; } getPositionToSurfacePoint( pos, target ) { // From Cesium function Ellipsoid.scaleToGeodeticSurface // https://github.com/CesiumGS/cesium/blob/d11b746e5809ac115fcff65b7b0c6bdfe81dcf1c/packages/engine/Source/core/renderer/scaleToGeodeticSurface.js#L25 const radius = this.radius; const invRadiusSqX = 1 / ( radius.x ** 2 ); const invRadiusSqY = 1 / ( radius.y ** 2 ); const invRadiusSqZ = 1 / ( radius.z ** 2 ); const x2 = pos.x * pos.x * invRadiusSqX; const y2 = pos.y * pos.y * invRadiusSqY; const z2 = pos.z * pos.z * invRadiusSqZ; // Compute the squared ellipsoid norm. const squaredNorm = x2 + y2 + z2; const ratio = Math.sqrt( 1.0 / squaredNorm ); // As an initial approximation, assume that the radial intersection is the projection point. const intersection = _vec.copy( pos ).multiplyScalar( ratio ); if ( squaredNorm < CENTER_EPS ) { return ! isFinite( ratio ) ? null : target.copy( intersection ); } // 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 = _vec2.set( intersection.x * invRadiusSqX * 2.0, intersection.y * invRadiusSqY * 2.0, intersection.z * invRadiusSqZ * 2.0 ); // Compute the initial guess at the normal vector multiplier, lambda. let lambda = ( 1.0 - ratio ) * pos.length() / ( 0.5 * gradient.length() ); let correction = 0.0; let func, denominator; let xMultiplier, yMultiplier, zMultiplier; let xMultiplier2, yMultiplier2, zMultiplier2; let xMultiplier3, yMultiplier3, zMultiplier3; do { lambda -= correction; xMultiplier = 1.0 / ( 1.0 + lambda * invRadiusSqX ); yMultiplier = 1.0 / ( 1.0 + lambda * invRadiusSqY ); zMultiplier = 1.0 / ( 1.0 + lambda * invRadiusSqZ ); xMultiplier2 = xMultiplier * xMultiplier; yMultiplier2 = yMultiplier * yMultiplier; zMultiplier2 = zMultiplier * zMultiplier; xMultiplier3 = xMultiplier2 * xMultiplier; yMultiplier3 = yMultiplier2 * yMultiplier; 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. denominator = x2 * xMultiplier3 * invRadiusSqX + y2 * yMultiplier3 * invRadiusSqY + z2 * zMultiplier3 * invRadiusSqZ; const derivative = - 2.0 * denominator; correction = func / derivative; } while ( Math.abs( func ) > EPSILON12 ); return target.set( pos.x * xMultiplier, pos.y * yMultiplier, pos.z * zMultiplier ); } calculateHorizonDistance( latitude, elevation ) { // from https://aty.sdsu.edu/explain/atmos_refr/horizon.html // OG = sqrt ( 2 R h + h2 ) . const effectiveRadius = this.calculateEffectiveRadius( latitude ); return Math.sqrt( 2 * effectiveRadius * elevation + elevation ** 2 ); } calculateEffectiveRadius( latitude ) { // This radius represents the distance from the center of the ellipsoid to the surface along the normal at the given latitude. // from https://en.wikipedia.org/wiki/Earth_radius#Prime_vertical // N = a / sqrt(1 - e^2 * sin^2(phi)) const semiMajorAxis = this.radius.x; const semiMinorAxis = this.radius.z; const eSquared = 1 - ( semiMinorAxis ** 2 / semiMajorAxis ** 2 ); const phi = latitude * MathUtils.DEG2RAD; const sinPhiSquared = Math.sin( phi ) ** 2; const N = semiMajorAxis / Math.sqrt( 1 - eSquared * sinPhiSquared ); return N; } getPositionElevation( pos ) { // logic from "getPositionToCartographic" this.getPositionToSurfacePoint( pos, _vec ); const heightDelta = _vec2.subVectors( pos, _vec ); return Math.sign( heightDelta.dot( pos ) ) * heightDelta.length(); } // Returns an estimate of the closest point on the ellipsoid to the ray. Returns // the surface intersection if they collide. closestPointToRayEstimate( ray, target ) { if ( this.intersectRay( ray, target ) ) { return target; } else { _matrix.makeScale( ...this.radius ).invert(); _ray.copy( ray ).applyMatrix4( _matrix ); _vec.set( 0, 0, 0 ); _ray.closestPointToPoint( _vec, target ).normalize(); _matrix.makeScale( ...this.radius ); return target.applyMatrix4( _matrix ); } } copy( source ) { this.radius.copy( source.radius ); return this; } clone() { return new this.constructor().copy( this ); } }