Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
14 changes: 11 additions & 3 deletions src/three/plugins/mvt/MVTGlyphMaterial.js
Original file line number Diff line number Diff line change
@@ -1,6 +1,8 @@
import { PointsMaterial, Vector2 } from 'three';
import { PointsMaterial, Vector2, Vector4 } from 'three';
import { MVTGlyphAtlasTexture } from './MVTGlyphAtlasTexture.js';

const _viewport = /* @__PURE__ */ new Vector4();

/**
* A `PointsMaterial` that draws each point sprite as a glyph from an `MVTGlyphAtlasTexture` with fading.
* @private
Expand Down Expand Up @@ -63,6 +65,7 @@ export class MVTGlyphMaterial extends PointsMaterial {
this.transparent = true;
this.depthTest = false;
this.depthWrite = false;
this.resolution = new Vector2();

// owns the glyph atlas ( unless one is provided ); the cell size is kept in sync with it
// and pushed to the uniforms after compile
Expand Down Expand Up @@ -103,6 +106,7 @@ export class MVTGlyphMaterial extends PointsMaterial {

uniform sampler2D glyphAtlas;
uniform vec2 glyphCellSize;
uniform float opacity;
varying vec2 vGlyphUV;
varying float vAlpha;
varying float vAngle;
Expand All @@ -125,7 +129,7 @@ export class MVTGlyphMaterial extends PointsMaterial {

}

diffuseColor.a *= vAlpha;
diffuseColor.a *= vAlpha * opacity;
gl_FragColor = diffuseColor;

#include <tonemapping_fragment>
Expand All @@ -142,10 +146,14 @@ export class MVTGlyphMaterial extends PointsMaterial {

}

onBeforeRender() {
onBeforeRender( renderer ) {

this._glyphAtlas.getSlotSize( this._glyphCellSize );

// viewport size in pixels, refreshed each frame in onBeforeRender for screen-space raycasting
renderer.getViewport( _viewport );
this.resolution.set( _viewport.z, _viewport.w );

}

}
13 changes: 10 additions & 3 deletions src/three/plugins/mvt/MVTGlyphs.d.ts
Original file line number Diff line number Diff line change
@@ -1,14 +1,21 @@
import { Points, Raycaster, Vector2 } from 'three';
import { Group, Raycaster } from 'three';
import { MVTGlyphMaterial } from './MVTGlyphMaterial.js';
import { MVTGlyphAtlasTexture } from './MVTGlyphAtlasTexture.js';

export class MVTGlyphs extends Points {
export class MVTGlyphs extends Group {

static readonly DrawMode: {
OBSCURED: string;
DRAW_THROUGH: string;
OVERLAY: string;
};

size: number;
readonly glyphAtlas: MVTGlyphAtlasTexture;
resolution: Vector2;
fadeInDuration: number;
fadeOutDuration: number;
obscuredOpacity: number;
drawMode: string;

constructor( material: MVTGlyphMaterial );

Expand Down
166 changes: 133 additions & 33 deletions src/three/plugins/mvt/MVTGlyphs.js
Original file line number Diff line number Diff line change
@@ -1,64 +1,102 @@
/** @import { MVTGlyphAtlasTexture } from './MVTGlyphAtlasTexture.js' */
/** @import { MVTGlyphMaterial } from './MVTGlyphMaterial.js' */
import { BufferAttribute, BufferGeometry, Matrix4, Points, Vector2, Vector3, Vector4 } from 'three';
/** @import { MVTGlyphAtlasTexture } from './MVTGlyphAtlasTexture.js'; */
import { BufferAttribute, BufferGeometry, GreaterDepth, Group, Matrix4, Points, Vector2, Vector3, Vector4 } from 'three';
import { MVTGlyphMaterial } from './MVTGlyphMaterial.js';

const _mvMatrix = /* @__PURE__ */ new Matrix4();
const _uvTarget = {};

// scratch reused across raycasts
const _viewport = /* @__PURE__ */ new Vector4();
const _point4 = /* @__PURE__ */ new Vector4();
const _ssOrigin = /* @__PURE__ */ new Vector4();
const _ssRay = /* @__PURE__ */ new Vector2();
const _ssPoint = /* @__PURE__ */ new Vector2();
const _worldPoint = /* @__PURE__ */ new Vector3();

const DRAW_MODE = /* @__PURE__ */ Object.freeze( {
// depth tested, hidden behind terrain
OBSCURED: 0,

// render hidden portions as partially transparent
DRAW_THROUGH: 1,

// draw full opaque on top of everything
OVERLAY: 2,
} );

/**
* Base `Points` object that renders a batch of glyphs from a shared `MVTGlyphAtlasTexture`, fading
* each item in and out.
* @extends Points
* Base object that renders a batch of glyphs from a shared `MVTGlyphAtlasTexture`, fading each item
* in and out. Manages the geometry and two child draws sharing it: an opaque pass and a transparent
* "obscured" pass, combined per `drawMode`.
* @extends Group
*/
export class MVTGlyphs extends Points {
export class MVTGlyphs extends Group {

/**
* Draw modes for `drawMode`: `OBSCURED`, `DRAW_THROUGH`, and `OVERLAY`.
* @type {{ OBSCURED: string, DRAW_THROUGH: string, OVERLAY: string }}
*/
static get DrawMode() {

return DRAW_MODE;

}

/**
* Glyph size in pixels.
* @type {number}
*/
get size() {

return this.material.size;
return this._opaque.material.size;

}

set size( v ) {

this.material.size = v;
this._opaque.material.size = v;
this._obscured.material.size = v;

}

/**
* The glyph atlas this object samples from.
* The texture atlas used for rendering glyphs.
* @type {MVTGlyphAtlasTexture}
*/
get glyphAtlas() {

return this.material.glyphAtlas;
return this._opaque.material.glyphAtlas;

}

/**
* @param {MVTGlyphMaterial} material - Material used to draw the glyphs.
* How glyphs interact with the depth buffer; one of `MVTGlyphs.DrawMode`.
* @type {string}
*/
get drawMode() {

return this._drawMode;

}

set drawMode( mode ) {

this._drawMode = mode;
this._applyDrawMode();

}

get geometry() {

return this._opaque.geometry;

}

constructor( material ) {

super( new BufferGeometry(), material );
super();

this.renderOrder = 1000;
this.frustumCulled = false;

// viewport size in pixels, refreshed each frame in onBeforeRender for screen-space raycasting
this.resolution = new Vector2();

/**
* Seconds a glyph takes to fade in.
* @type {number}
Expand All @@ -71,23 +109,56 @@ export class MVTGlyphs extends Points {
*/
this.fadeOutDuration = 0.3;

/**
* Opacity of the ghosted, obscured glyphs in the `DRAW_THROUGH` draw mode.
* @type {number}
*/
this.obscuredOpacity = 0.5;

// Map<itemId, { item, fade: 0..1, state: 'in' | 'visible' | 'out' }> keyed by stable id,
// plus an insertion-ordered list of the same entries for stable geometry layout
this._entryMap = new Map();
this._orderedEntries = [];
this._lastUpdateTime = - 1;

// create children for draw through
const geometry = new BufferGeometry();
const opaque = new Points( geometry, new MVTGlyphMaterial() );
opaque.frustumCulled = false;
opaque.renderOrder = 1000;
opaque.onAfterRender = ( renderer, scene, camera ) => {

this._recenter( camera );

};

const obscured = new Points( geometry, new MVTGlyphMaterial() );
obscured.frustumCulled = false;
obscured.material.glyphAtlas = opaque.material.glyphAtlas;
obscured.material.depthTest = true;
obscured.material.depthFunc = GreaterDepth;
obscured.renderOrder = 1001;

// add the children
this.add( obscured, opaque );

// store references, update draw mode
this._opaque = opaque;
this._obscured = obscured;
this.drawMode = DRAW_MODE.OVERLAY;

}

/**
* Disposes the glyph atlas, geometry, and material.
* Disposes the glyph atlas, geometry, and materials.
* @returns {void}
*/
dispose() {

this.glyphAtlas.dispose();
this.geometry.dispose();
this.material.dispose();
this._opaque.material.dispose();
this._obscured.material.dispose();

}

Expand Down Expand Up @@ -173,21 +244,14 @@ export class MVTGlyphs extends Points {

}

onBeforeRender( renderer, scene, camera ) {

// use the active viewport (not getDrawingBufferSize) so raycasting matches the GPU's
// NDC→pixel mapping in sub-viewport scenarios
renderer.getViewport( _viewport );
this.resolution.set( _viewport.z, _viewport.w );

}

raycast( raycaster, intersects ) {

const camera = raycaster.camera;
if ( ! camera ) return;

const { geometry, material, matrixWorld, resolution } = this;
const { geometry, matrixWorld } = this;
const { material } = this._opaque;
const { resolution } = material;
const posAttr = geometry.getAttribute( 'position' );
if ( ! posAttr || posAttr.count === 0 ) return;

Expand All @@ -206,7 +270,7 @@ export class MVTGlyphs extends Points {

_mvMatrix.multiplyMatrices( camera.matrixWorldInverse, matrixWorld );

for ( let i = 0, l = this.geometry.drawRange.count; i < l; i ++ ) {
for ( let i = 0, l = geometry.drawRange.count; i < l; i ++ ) {

_point4.fromBufferAttribute( posAttr, i );
_point4.w = 1;
Expand Down Expand Up @@ -246,11 +310,47 @@ export class MVTGlyphs extends Points {

}

// end traversal
return false;

}

// configure the two child draws for the current draw mode
_applyDrawMode() {

const { _opaque, _obscured, obscuredOpacity, _drawMode } = this;
switch ( _drawMode ) {

case DRAW_MODE.OVERLAY:
_opaque.visible = true;
_opaque.material.depthTest = false;

_obscured.visible = false;
break;

case DRAW_MODE.DRAW_THROUGH:
_opaque.visible = true;
_opaque.material.depthTest = true;

_obscured.visible = true;
_obscured.material.opacity = obscuredOpacity;
break;

case DRAW_MODE.OBSCURED:
default:
_opaque.visible = true;
_opaque.material.depthTest = true;

_obscured.visible = false;
break;

}

}

onAfterRender( renderer, scene, camera ) {
// keep the root near the camera to avoid gpu jitter at globe scale
_recenter( camera ) {

// keep the root near the camera to avoid gpu jitter at globe scale
const { parent } = this;
if ( parent ) {

Expand Down
3 changes: 1 addition & 2 deletions src/three/plugins/mvt/MVTIconGlyphs.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { MVTGlyphMaterial } from './MVTGlyphMaterial.js';
import { MVTGlyphs } from './MVTGlyphs.js';

/**
Expand Down Expand Up @@ -28,7 +27,7 @@ export class MVTIconGlyphs extends MVTGlyphs {
slotCount = 64,
} = options;

super( new MVTGlyphMaterial() );
super();

/**
* Returns the atlas key for a given item, or null for none.
Expand Down
8 changes: 4 additions & 4 deletions src/three/plugins/mvt/MVTLabelGlyphs.js
Original file line number Diff line number Diff line change
@@ -1,4 +1,3 @@
import { MVTGlyphMaterial } from './MVTGlyphMaterial.js';
import { MVTGlyphs } from './MVTGlyphs.js';

/**
Expand Down Expand Up @@ -31,7 +30,7 @@ export class MVTLabelGlyphs extends MVTGlyphs {
strokeWidth = 0,
} = options;

super( new MVTGlyphMaterial( { size } ) );
super();

// CSS font used to rasterize glyphs, sized to fit the atlas slot
const fontSize = Math.round( glyphSize * 0.7 );
Expand All @@ -51,6 +50,7 @@ export class MVTLabelGlyphs extends MVTGlyphs {
this._needed = new Set();

this.glyphAtlas.resize( slotCount, glyphSize );
this.size = size;

}

Expand All @@ -61,10 +61,10 @@ export class MVTLabelGlyphs extends MVTGlyphs {
*/
measureChar( char ) {

const { _advanceCache, material, glyphAtlas, _font } = this;
const { _advanceCache, glyphAtlas, _font } = this;
if ( ! _advanceCache.has( char ) ) {

const multiplier = material.size / glyphAtlas.slotSize;
const multiplier = this.size / glyphAtlas.slotSize;
const info = glyphAtlas.measureChar( char, _font );
const advance = info.width + 2;

Expand Down
Loading