UNPKG

3d-tiles-renderer

Version:

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

226 lines (140 loc) 4.53 kB
import { TextAnchorAnnotation } from './annotations/TextAnchorAnnotation.js'; // Manages the set of anchors per export class TextAnchorManager { constructor() { this.added = new Set(); this.removed = new Set(); this._anchorsById = new Map(); this._linesById = new Map(); // flat sets of every tracked line / anchor, maintained on add / delete for allocation-free // iteration this.lines = new Set(); this.anchors = new Set(); } reset() { this.added.clear(); this.removed.clear(); } update() { // gather the removed items const { _anchorsById, removed } = this; _anchorsById.forEach( ( anchors, id ) => { anchors.forEach( anchor => { if ( anchor.isEmpty() ) { anchors.delete( anchor ); this.anchors.delete( anchor ); removed.add( anchor ); } } ); // remove any empty anchor lists if ( anchors.size === 0 ) { _anchorsById.delete( id ); } } ); } // add a set of lines // NOTE: This is is designed to be called with all lines from a single tile at once addLines( lines ) { const { _anchorsById, _linesById, added } = this; const newLineMap = new Map(); lines.forEach( line => { if ( ! newLineMap.has( line.id ) ) { newLineMap.set( line.id, [] ); } newLineMap.get( line.id ).push( line ); } ); // for each new line newLineMap.forEach( ( newLines, id ) => { // create the sets if they don't exist yet for these sets of lines if ( ! _anchorsById.has( id ) ) { _anchorsById.set( id, new Set() ); } if ( ! _linesById.has( id ) ) { _linesById.set( id, new Set() ); } // all lines are expected to be of the same LoD and tile if passed in at once const baseLine = newLines[ 0 ]; // For each existing anchor, match it to the closest new anchor in the relevant set const anchorSet = _anchorsById.get( id ); anchorSet.forEach( anchor => { // find the closest anchor in the set of lines let bestDist = Infinity; let bestLine = null; let bestIndex = - 1; if ( ! baseLine.hasCoverage( anchor.lat, anchor.lon ) || anchor.hasLoD( baseLine.lodLevel ) ) { return; } newLines.forEach( line => { line.anchorPositions.forEach( ( anchorPosition, index ) => { if ( anchorPosition.ref === null ) { // TODO: should we use a consistent canonical position here? Or the current "best"? // TODO: does it make sense to "snap" this anchor to the closest line point, instead? Avoiding // a jump? But then we will slowly accumulate anchors. Or we can "snap" at first and then revert once // it hides? Or slide towards the goal (probably uncomfortable)? const dLat = anchor.lat - anchorPosition.lat; const dLon = anchor.lon - anchorPosition.lon; const dist = dLat * dLat + dLon * dLon; if ( dist < bestDist ) { bestDist = dist; bestLine = line; bestIndex = index; } } } ); } ); // save the reference if ( bestLine ) { anchor.addLine( bestLine, bestIndex ); bestLine.anchorPositions[ bestIndex ].ref = anchor; } } ); // Then, add any anchors that need to be created newLines.forEach( line => { line.anchorPositions.forEach( ( anchorPosition, index ) => { if ( anchorPosition.ref === null ) { const anchor = new TextAnchorAnnotation( id ); anchor.addLine( line, index ); if ( line.hasCoverage( anchor.lat, anchor.lon ) ) { anchorPosition.ref = anchor; anchorSet.add( anchor ); this.anchors.add( anchor ); added.add( anchor ); } } } ); } ); } ); // finally, add the lines to the set newLineMap.forEach( ( lines, id ) => { const lineSet = _linesById.get( id ); lines.forEach( line => { lineSet.add( line ); this.lines.add( line ); } ); } ); } deleteLines( lines ) { lines.forEach( line => this.deleteLine( line ) ); } // remove a path; anchors left with no associated paths are dropped deleteLine( line ) { const { _anchorsById, _linesById } = this; const id = line.id; _linesById.get( id ).delete( line ); this.lines.delete( line ); if ( _linesById.get( id ).size === 0 ) { _linesById.delete( id ); } const existingAnchors = _anchorsById.get( id ); if ( ! existingAnchors ) { // this can happen if a line has no anchors added return; } existingAnchors.forEach( anchor => { anchor.removeLine( line ); } ); } }