11import * as THREE from 'three' ;
22import GeometryLayer from 'Layer/GeometryLayer' ;
3- import PointsMaterial , { PNTS_MODE } from 'Renderer/PointsMaterial' ;
3+ import PointsMaterial , { PNTS_MODE , PNTS_SIZE_MODE } from 'Renderer/PointsMaterial' ;
44import Picking from 'Core/Picking' ;
55
66import type PointCloudNode from 'Core/PointCloudNode' ;
@@ -506,6 +506,58 @@ abstract class PointCloudLayer<S extends PointCloudSource = PointCloudSource>
506506 this . dispatchEvent ( { type : 'dispose-model' , scene : obj , tile : obj . userData . node } ) ;
507507 }
508508 }
509+
510+ // @ts -expect-error PointsMaterial is not typed yet
511+ if ( this . material . sizeMode === PNTS_SIZE_MODE . ADAPTIVE ) {
512+ const nodes = this . getNodes ( this . group . children ) ;
513+ const visibilityTextureData = this . computeVisibilityTextureData ( nodes ) ;
514+
515+ // @ts -expect-error PointsMaterial is not typed yet
516+ const vnt = this . material . visibleNodes ;
517+ const data = vnt . image . data ;
518+ data . set ( visibilityTextureData . data ) ;
519+ vnt . needsUpdate = true ;
520+
521+ // Use natBox (native octree space) for octreeSize, as the
522+ // octree hierarchy is defined in the source CRS coordinate system
523+ const rootSize = this . root ! . voxelOBB . natBox . getSize ( new THREE . Vector3 ( ) ) ;
524+ const octreeSize = Math . max ( rootSize . x , rootSize . y , rootSize . z ) ;
525+
526+ for ( const pts of this . group . children ) {
527+ const node = pts . userData . node ;
528+ const depth = node . depth ;
529+ const nodeStartOffset = visibilityTextureData . offsets . get ( node . voxelKey ) ;
530+ const octreeSpacing = node . source . spacing ;
531+
532+ // Compute the bounding box min of the node's octree cell
533+ // in the local space of the THREE.Points geometry.
534+ // We must use voxelOBB.natBox.min (the octree cell boundary
535+ // in source CRS) rather than geomBBox.min (which only covers
536+ // the actual points, not the full octree cell).
537+ const natMin = node . voxelOBB . natBox . min ;
538+ const origin = node . origin ;
539+ const bboxMin = new THREE . Vector3 (
540+ natMin . x - origin . x ,
541+ natMin . y - origin . y ,
542+ natMin . z - origin . z ,
543+ ) ;
544+
545+ pts . onBeforeRender = ( _renderer , _scene , _camera , _geometry , material ) => {
546+ // @ts -expect-error Material is not typed yet
547+ material . uniforms . nodeStartOffset . value = nodeStartOffset ;
548+ // @ts -expect-error Material is not typed yet
549+ material . uniforms . octreeSize . value = octreeSize ;
550+ // @ts -expect-error Material is not typed yet
551+ material . uniforms . octreeSpacing . value = octreeSpacing ;
552+ // @ts -expect-error Material is not typed yet
553+ material . uniforms . nodeDepth . value = depth ;
554+ // @ts -expect-error Material is not typed yet
555+ material . uniforms . nodeBBoxMin . value . copy ( bboxMin ) ;
556+ // @ts -expect-error Material is not typed yet
557+ material . uniformsNeedUpdate = true ;
558+ } ;
559+ }
560+ }
509561 }
510562
511563 // @ts -expect-error Layer and Picking are not typed yet
@@ -529,6 +581,99 @@ abstract class PointCloudLayer<S extends PointCloudSource = PointCloudSource>
529581 }
530582 }
531583 }
584+
585+ getNodes ( children : THREE . Object3D [ ] ) {
586+ const nodes = new Set < PointCloudNode > ( ) ;
587+ // Sometimes child node is loaded before parent node, so we need to
588+ // make sure to add all the parent nodes of the visible nodes
589+ const collectNodeParentRecursively = ( node : PointCloudNode ) : void => {
590+ if ( nodes . has ( node ) ) {
591+ return ;
592+ }
593+ nodes . add ( node ) ;
594+ if ( node . parent ) {
595+ collectNodeParentRecursively ( node . parent ) ;
596+ }
597+ } ;
598+ children . forEach ( child => collectNodeParentRecursively ( child . userData . node ) ) ;
599+ return Array . from ( nodes ) ;
600+ }
601+
602+ // Encoding the octree hierarchy in breadth-first order
603+ // into a texture for adaptive point size rendering
604+ // Explanation p36: https://www.cg.tuwien.ac.at/research/publications/2016/SCHUETZ-2016-POT/SCHUETZ-2016-POT-thesis.pdf
605+ computeVisibilityTextureData ( nodes : PointCloudNode [ ] ) {
606+ // sort by level and hierarchy order
607+ const sort = function sortNodes ( a : PointCloudNode , b : PointCloudNode ) {
608+ if ( a . depth !== b . depth ) { return a . depth - b . depth ; }
609+ // @ts -expect-error PointCloudNode has x properties
610+ if ( a . x !== b . x ) { return a . x - b . x ; }
611+ // @ts -expect-error PointCloudNode has y properties
612+ if ( a . y !== b . y ) { return a . y - b . y ; }
613+ // @ts -expect-error PointCloudNode has z properties
614+ return a . z - b . z ;
615+ } ;
616+ // breadth-first order
617+ const orderedNodes = nodes . toSorted ( sort ) ;
618+
619+ const data = new Uint8Array ( orderedNodes . length * 4 ) ;
620+ const visibleNodeTextureOffsets = new Map < string , number > ( ) ;
621+ const offsetsToChild : number [ ] = new Array ( orderedNodes . length ) . fill ( Infinity ) ;
622+
623+ // Helper function to get octree child index from node
624+ const getChildIndex = ( node : PointCloudNode ) : number => {
625+ if ( ! node . parent ) {
626+ return 0 ;
627+ }
628+ const parent = node . parent ;
629+ // @ts -expect-error PointCloudNode has x properties
630+ const dx = node . x - parent . x * 2 ;
631+ // @ts -expect-error PointCloudNode has y properties
632+ const dy = node . y - parent . y * 2 ;
633+ // @ts -expect-error PointCloudNode has z properties
634+ const dz = node . z - parent . z * 2 ;
635+ // Octree child index (Potree convention): 4*x + 2*y + z
636+ return 4 * dx + 2 * dy + dz ;
637+ } ;
638+
639+ for ( let nodeIndex = 0 ; nodeIndex < orderedNodes . length ; nodeIndex ++ ) {
640+ const node = orderedNodes [ nodeIndex ] ;
641+ // @ts -expect-error PointCloudNode has voxelKey properties
642+ visibleNodeTextureOffsets . set ( node . voxelKey , nodeIndex ) ;
643+
644+ if ( node . parent ) {
645+ const childIndex = getChildIndex ( node ) ;
646+ // @ts -expect-error PointCloudNode has voxelKey properties
647+ const parentIndex = visibleNodeTextureOffsets . get ( node . parent . voxelKey ) ;
648+
649+ if ( parentIndex === undefined ) {
650+ continue ;
651+ }
652+
653+ const parentOffsetToChild = nodeIndex - parentIndex ;
654+ const offsetToFirstChild =
655+ Math . min ( offsetsToChild [ parentIndex ] , parentOffsetToChild ) ;
656+ offsetsToChild [ parentIndex ] = offsetToFirstChild ;
657+
658+ // The 8 bits of the red value indicate
659+ // which of the children are visible
660+ data [ parentIndex * 4 ] = data [ parentIndex * 4 ] | ( 1 << childIndex ) ;
661+ // Offset to child is stored on 2 bytes,
662+ // so it can support up to 65536 nodes per subtree.
663+ // The green channel contains the relative offset
664+ // to the node’s first child (8 most significant bits)
665+ data [ parentIndex * 4 + 1 ] = offsetToFirstChild >> 8 ;
666+ // The blue channel contains the relative offset
667+ // to the node’s first child (8 least significant bits)
668+ data [ parentIndex * 4 + 2 ] = offsetToFirstChild % 256 ;
669+ }
670+ }
671+
672+ return {
673+ data,
674+ offsets : visibleNodeTextureOffsets ,
675+ } ;
676+ }
532677}
533678
534679export default PointCloudLayer ;
0 commit comments