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
6 changes: 6 additions & 0 deletions src/three/plugins/images/ImageOverlayPlugin.d.ts
Original file line number Diff line number Diff line change
Expand Up @@ -57,10 +57,16 @@ export class GeoJSONOverlay extends ImageOverlay {

frame?: Matrix4 | null,

// clip options
alphaMode?: 'layer' | 'global',
alphaTest?: number, // 0..1, 0 = no hard clip
alphaInvert?: boolean, // false = cut inside (keep outside); true = cut outside (keep inside)

} );

}


export class WMSTilesOverlay extends ImageOverlay {

constructor( options: {
Expand Down
122 changes: 107 additions & 15 deletions src/three/plugins/images/ImageOverlayPlugin.js
Original file line number Diff line number Diff line change
Expand Up @@ -1288,27 +1288,30 @@ export class ImageOverlayPlugin {
const { overlayInfo, overlays, tileControllers } = this;
const tileController = tileControllers.get( tile );

// by this point all targets should be present and we can force the memory to update
this.tiles.recalculateBytesUsed( tile );
if ( ! tileController || tileController.signal.aborted ) return;

// if the tile has been disposed before this function is called then exit early
if ( ! tileController || tileController.signal.aborted ) {
// Find a "global" alpha overlay (first match)
const globalAlphaOverlay = overlays.find( o =>
o && typeof o === 'object' &&
o.alphaMode === 'global' &&
( o.alphaTest ?? 0 ) > 0
);

return;

}
// regular color overlays
const regularOverlays = overlays.filter( o => ! o?.isClipOverlay );

// update the uvs and texture overlays for each mesh
overlays.forEach( ( overlay, i ) => {
regularOverlays.forEach( ( overlay, i ) => {

const { tileInfo } = overlayInfo.get( overlay );
const { meshInfo, target } = tileInfo.get( tile );

meshInfo.forEach( ( { attribute }, mesh ) => {

const { geometry, material } = mesh;
const params = this.meshParams.get( mesh );

// assign the new uvs
// layer uvs
const key = `layer_uv_${ i }`;
if ( geometry.getAttribute( key ) !== attribute ) {

Expand All @@ -1317,21 +1320,106 @@ export class ImageOverlayPlugin {

}

// set the uniform array lengths
params.layerMaps.length = overlays.length;
params.layerColor.length = overlays.length;
// ensure arrays sized for all regular overlays
params.layerMaps.length = regularOverlays.length;
params.layerColor.length = regularOverlays.length;

// assign the uniforms
params.layerMaps.value[ i ] = target !== null ? target.texture : null;
if ( params.layerAlphaTest ) {

params.layerAlphaTest.length = regularOverlays.length;

}

// assign uniforms
params.layerMaps.value[ i ] = target ? ( target.texture || target.textures?.[ 0 ] || null ) : null;
params.layerColor.value[ i ] = overlay;

material.defines.LAYER_COUNT = overlays.length;
// Apply per-layer hard threshold only for alphaMode 'layer'
if ( params.layerAlphaTest ) {

params.layerAlphaTest.value[ i ] =
overlay.alphaMode === 'layer' ? ( overlay.alphaTest ?? 0.0 ) : 0.0;

}

material.defines.LAYER_COUNT = regularOverlays.length;
material.needsUpdate = true;

} );

} );

// Choose which overlay to use for global clipping:
// 1) Explicit clip overlay (legacy), otherwise
// 2) First overlay with alphaMode='global' and alphaTest>0
const chosenGlobal = globalAlphaOverlay;

if ( chosenGlobal ) {

const { tileInfo } = overlayInfo.get( chosenGlobal );
const { meshInfo, target } = tileInfo.get( tile );

meshInfo.forEach( ( { attribute }, mesh ) => {

const { geometry, material } = mesh;
const params = this.meshParams.get( mesh );

// bind clip uv
if ( geometry.getAttribute( 'clip_uv' ) !== attribute ) {

geometry.setAttribute( 'clip_uv', attribute );
geometry.dispose();

}

const hasMap = target ? 1 : 0;
const texture = target ? ( target.texture || target.textures?.[ 0 ] || null ) : null;

params.overlayClipMap.value = texture;
params.overlayClipThreshold.value = ( chosenGlobal.alphaTest ?? 0.5 );

// In new API we always keep inside based on alpha; legacy clipOverlay keeps its own setting
params.overlayClipInside.value = ( chosenGlobal.alphaInvert ? 0 : 1 );


// Always enable and force define so all tiles take the branch
params.overlayClipEnabled.value = 1;
if ( 'overlayClipForce' in params ) params.overlayClipForce.value = 1;
if ( 'overlayClipHasMap' in params ) params.overlayClipHasMap.value = hasMap;
if ( 'overlayClipFallbackAlpha' in params ) params.overlayClipFallbackAlpha.value = 0.0;

material.needsUpdate = true;

} );

} else {

// clear clip state on meshes
overlays.forEach( overlay => {

const { tileInfo } = overlayInfo.get( overlay );
const { meshInfo } = tileInfo.get( tile );

meshInfo.forEach( ( mesh ) => {

const params = this.meshParams.get( mesh );

if ( params ) {

params.overlayClipMap.value = null;
params.overlayClipEnabled.value = 0;
if ( 'overlayClipForce' in params ) params.overlayClipForce.value = 0;
if ( 'overlayClipHasMap' in params ) params.overlayClipHasMap.value = 0;

}
mesh.material.needsUpdate = true;

} );

} );

}

}

_scheduleCleanup() {
Expand Down Expand Up @@ -1505,6 +1593,10 @@ export class GeoJSONOverlay extends ImageOverlay {
this.imageSource = new GeoJSONImageSource( options );
this.imageSource.fetchData = ( ...args ) => this.fetch( ...args );

this.alphaTest = options.alphaTest ?? 0.0; // 0..1, 0 = no hard clip
this.alphaMode = options.alphaMode ?? 'layer';
this.alphaInvert = options.alphaInvert ?? false;

}

init() {
Expand Down
Loading
Loading