UNPKG

3d-tiles-renderer

Version:

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

319 lines (213 loc) 7.82 kB
import { MathUtils, Vector2, Vector3 } from 'three'; import { GeometryClipper, hashVertex } from './GeometryClipper.js'; import { Ellipsoid } from '../../../three/renderer/math/Ellipsoid.js'; const _cart = {}; const _vec = /* @__PURE__ */ new Vector3(); const _pos0 = /* @__PURE__ */ new Vector3(); const _pos1 = /* @__PURE__ */ new Vector3(); const _pos2 = /* @__PURE__ */ new Vector3(); const _pos3 = /* @__PURE__ */ new Vector3(); const _temp = /* @__PURE__ */ new Vector3(); const _temp2 = /* @__PURE__ */ new Vector3(); const _uv0 = /* @__PURE__ */ new Vector2(); const _uv1 = /* @__PURE__ */ new Vector2(); const _uv2 = /* @__PURE__ */ new Vector2(); export class QuantizedMeshClipper extends GeometryClipper { constructor() { super(); this.ellipsoid = new Ellipsoid(); this.skirtLength = 1000; this.smoothSkirtNormals = true; this.solid = false; this.minLat = - Math.PI / 2; this.maxLat = Math.PI / 2; this.minLon = - Math.PI; this.maxLon = Math.PI; this.attributeList = [ 'position', 'normal', 'uv' ]; } clipToQuadrant( sourceMesh, left, bottom ) { const { solid, skirtLength, ellipsoid, smoothSkirtNormals } = this; this.clearSplitOperations(); this.addSplitOperation( getUvSplitOperation( 'x' ), ! left ); this.addSplitOperation( getUvSplitOperation( 'y' ), ! bottom ); let botResult, skirtResult; const capGroup = sourceMesh.geometry.groups[ 0 ]; const capResult = this.getClippedData( sourceMesh, capGroup ); this.adjustVertices( capResult, sourceMesh.position, 0 ); if ( solid ) { botResult = { index: capResult.index.slice().reverse(), attributes: {}, }; for ( const key in capResult.attributes ) { botResult.attributes[ key ] = capResult.attributes[ key ].slice(); } const normal = botResult.attributes.normal; if ( normal ) { for ( let i = 0; i < normal.length; i += 3 ) { normal[ i + 0 ] *= - 1; normal[ i + 1 ] *= - 1; normal[ i + 2 ] *= - 1; } } this.adjustVertices( botResult, sourceMesh.position, - skirtLength ); } if ( skirtLength > 0 ) { skirtResult = { index: [], attributes: { position: [], normal: [], uv: [], }, }; // push data onto the let nextIndex = 0; const vertToNewIndexMap = {}; const pushVertex = ( pos, uv, norm ) => { const hash = hashVertex( ...pos, ...norm, ...uv ); if ( ! ( hash in vertToNewIndexMap ) ) { vertToNewIndexMap[ hash ] = nextIndex; nextIndex ++; skirtResult.attributes.position.push( ...pos ); skirtResult.attributes.normal.push( ...norm ); skirtResult.attributes.uv.push( ...uv ); } skirtResult.index.push( vertToNewIndexMap[ hash ] ); }; // TODO: this seems to have some problematic cases at the root tiles near the poles const capIndex = capResult.index; const capUv = capResult.attributes.uv; const capPosition = capResult.attributes.position; const capNormal = capResult.attributes.normal; const capTriangles = capResult.index.length / 3; for ( let i = 0; i < capTriangles; i ++ ) { const triOffset = 3 * i; for ( let e = 0; e < 3; e ++ ) { const ne = ( e + 1 ) % 3; const i0 = capIndex[ triOffset + e ]; const i1 = capIndex[ triOffset + ne ]; _uv0.fromArray( capUv, i0 * 2 ); _uv1.fromArray( capUv, i1 * 2 ); // find the vertices that lie on the edge if ( _uv0.x === _uv1.x && ( _uv0.x === 0 || _uv0.x === 0.5 || _uv0.x === 1.0 ) || _uv0.y === _uv1.y && ( _uv0.y === 0 || _uv0.y === 0.5 || _uv0.y === 1.0 ) ) { _pos0.fromArray( capPosition, i0 * 3 ); _pos1.fromArray( capPosition, i1 * 3 ); const u0 = _pos0; const u1 = _pos1; const b0 = _pos2.copy( _pos0 ); const b1 = _pos3.copy( _pos1 ); _temp.copy( b0 ).add( sourceMesh.position ); ellipsoid.getPositionToNormal( _temp, _temp ); b0.addScaledVector( _temp, - skirtLength ); _temp.copy( b1 ).add( sourceMesh.position ); ellipsoid.getPositionToNormal( _temp, _temp ); b1.addScaledVector( _temp, - skirtLength ); if ( smoothSkirtNormals && capNormal ) { _temp.fromArray( capNormal, i0 * 3 ); _temp2.fromArray( capNormal, i1 * 3 ); } else { _temp.subVectors( u0, u1 ); _temp2.subVectors( u0, b0 ).cross( _temp ).normalize(); _temp.copy( _temp2 ); } pushVertex( u1, _uv1, _temp2 ); pushVertex( u0, _uv0, _temp ); pushVertex( b0, _uv0, _temp ); pushVertex( u1, _uv1, _temp2 ); pushVertex( b0, _uv0, _temp ); pushVertex( b1, _uv1, _temp2 ); } } } } const capLength = capResult.index.length; const result = capResult; if ( botResult ) { const { index, attributes } = botResult; const offset = result.attributes.position.length / 3; for ( let i = 0, l = index.length; i < l; i ++ ) { result.index.push( index[ i ] + offset ); } for ( const key in capResult.attributes ) { result.attributes[ key ].push( ...attributes[ key ] ); } } if ( skirtResult ) { const { index, attributes } = skirtResult; const offset = result.attributes.position.length / 3; for ( let i = 0, l = index.length; i < l; i ++ ) { result.index.push( index[ i ] + offset ); } for ( const key in capResult.attributes ) { result.attributes[ key ].push( ...attributes[ key ] ); } } // offset the uvs const xUvOffset = left ? 0 : - 0.5; const yUvOffset = bottom ? 0 : - 0.5; const uv = result.attributes.uv; for ( let i = 0, l = uv.length; i < l; i += 2 ) { uv[ i ] = ( uv[ i ] + xUvOffset ) * 2.0; uv[ i + 1 ] = ( uv[ i + 1 ] + yUvOffset ) * 2.0; } // construct the result const resultMesh = this.constructMesh( result.attributes, result.index, sourceMesh ); resultMesh.userData.minHeight = sourceMesh.userData.minHeight; resultMesh.userData.maxHeight = sourceMesh.userData.maxHeight; let materialIndex = 0; let start = 0; resultMesh.geometry.addGroup( start, capLength, materialIndex ); start += capLength; materialIndex ++; if ( botResult ) { resultMesh.geometry.addGroup( start, botResult.index.length, materialIndex ); start += botResult.index.length; materialIndex ++; } if ( skirtResult ) { resultMesh.geometry.addGroup( start, skirtResult.index.length, materialIndex ); start += skirtResult.index.length; materialIndex ++; } return resultMesh; } adjustVertices( info, position, offset ) { const { ellipsoid, minLat, maxLat, minLon, maxLon } = this; const { attributes, vertexIsClipped } = info; const posArr = attributes.position; const uvArr = attributes.uv; const vertexCount = posArr.length / 3; for ( let i = 0; i < vertexCount; i ++ ) { const uv = _uv0.fromArray( uvArr, i * 2 ); if ( vertexIsClipped && vertexIsClipped[ i ] ) { if ( Math.abs( uv.x - 0.5 ) < 1e-10 ) { uv.x = 0.5; } if ( Math.abs( uv.y - 0.5 ) < 1e-10 ) { uv.y = 0.5; } _uv0.toArray( uvArr, i * 2 ); } const lat = MathUtils.lerp( minLat, maxLat, uv.y ); const lon = MathUtils.lerp( minLon, maxLon, uv.x ); const point = _vec.fromArray( posArr, i * 3 ).add( position ); ellipsoid.getPositionToCartographic( point, _cart ); ellipsoid.getCartographicToPosition( lat, lon, _cart.height + offset, point ); point.sub( position ); point.toArray( posArr, i * 3 ); } } } function getUvSplitOperation( axis ) { return ( geometry, i0, i1, i2, barycoord ) => { const uv = geometry.attributes.uv; _uv0.fromBufferAttribute( uv, i0 ); _uv1.fromBufferAttribute( uv, i1 ); _uv2.fromBufferAttribute( uv, i2 ); return _uv0[ axis ] * barycoord.x + _uv1[ axis ] * barycoord.y + _uv2[ axis ] * barycoord.z - 0.5; }; }