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,52 @@ 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+ const rootSize = this . root ! . voxelOBB . box3D . getSize ( new THREE . Vector3 ( ) ) ;
522+ const octreeSize = Math . max ( rootSize . x , rootSize . y , rootSize . z ) ;
523+
524+ for ( const pts of this . group . children ) {
525+ const node = pts . userData . node ;
526+ const depth = node . depth ;
527+ const nodeStartOffset = visibilityTextureData . offsets . get ( node . voxelKey ) ;
528+ const octreeSpacing = node . source . spacing ;
529+
530+ // Compute the bounding box min of the node in the local
531+ // space of the THREE.Points object, for getLOD() to work
532+ // correctly. The geometry.boundingBox is already in local
533+ // space (positions relative to node.origin with rotation).
534+ const geomBBox = ( pts as THREE . Points ) . geometry . boundingBox ;
535+ const bboxMin = geomBBox
536+ ? geomBBox . min . clone ( )
537+ : new THREE . Vector3 ( ) ;
538+
539+ pts . onBeforeRender = ( _renderer , _scene , _camera , _geometry , material ) => {
540+ // @ts -expect-error Material is not typed yet
541+ material . uniforms . nodeStartOffset . value = nodeStartOffset ;
542+ // @ts -expect-error Material is not typed yet
543+ material . uniforms . octreeSize . value = octreeSize ;
544+ // @ts -expect-error Material is not typed yet
545+ material . uniforms . octreeSpacing . value = octreeSpacing ;
546+ // @ts -expect-error Material is not typed yet
547+ material . uniforms . nodeDepth . value = depth ;
548+ // @ts -expect-error Material is not typed yet
549+ material . uniforms . nodeBBoxMin . value . copy ( bboxMin ) ;
550+ // @ts -expect-error Material is not typed yet
551+ material . uniformsNeedUpdate = true ;
552+ } ;
553+ }
554+ }
509555 }
510556
511557 // @ts -expect-error Layer and Picking are not typed yet
@@ -529,6 +575,99 @@ abstract class PointCloudLayer<S extends PointCloudSource = PointCloudSource>
529575 }
530576 }
531577 }
578+
579+ getNodes ( children : THREE . Object3D [ ] ) {
580+ const nodes = new Set < PointCloudNode > ( ) ;
581+ // Sometimes child node is loaded before parent node, so we need to
582+ // make sure to add all the parent nodes of the visible nodes
583+ const collectNodeParentRecursively = ( node : PointCloudNode ) : void => {
584+ if ( nodes . has ( node ) ) {
585+ return ;
586+ }
587+ nodes . add ( node ) ;
588+ if ( node . parent ) {
589+ collectNodeParentRecursively ( node . parent ) ;
590+ }
591+ } ;
592+ children . forEach ( child => collectNodeParentRecursively ( child . userData . node ) ) ;
593+ return Array . from ( nodes ) ;
594+ }
595+
596+ // Encoding the octree hierarchy in breadth-first order
597+ // into a texture for adaptive point size rendering
598+ // Explanation p36: https://www.cg.tuwien.ac.at/research/publications/2016/SCHUETZ-2016-POT/SCHUETZ-2016-POT-thesis.pdf
599+ computeVisibilityTextureData ( nodes : PointCloudNode [ ] ) {
600+ // sort by level and hierarchy order
601+ const sort = function sortNodes ( a : PointCloudNode , b : PointCloudNode ) {
602+ if ( a . depth !== b . depth ) { return a . depth - b . depth ; }
603+ // @ts -expect-error PointCloudNode has x properties
604+ if ( a . x !== b . x ) { return a . x - b . x ; }
605+ // @ts -expect-error PointCloudNode has y properties
606+ if ( a . y !== b . y ) { return a . y - b . y ; }
607+ // @ts -expect-error PointCloudNode has z properties
608+ return a . z - b . z ;
609+ } ;
610+ // breadth-first order
611+ const orderedNodes = nodes . toSorted ( sort ) ;
612+
613+ const data = new Uint8Array ( orderedNodes . length * 4 ) ;
614+ const visibleNodeTextureOffsets = new Map < string , number > ( ) ;
615+ const offsetsToChild : number [ ] = new Array ( orderedNodes . length ) . fill ( Infinity ) ;
616+
617+ // Helper function to get octree child index from node
618+ const getChildIndex = ( node : PointCloudNode ) : number => {
619+ if ( ! node . parent ) {
620+ return 0 ;
621+ }
622+ const parent = node . parent ;
623+ // @ts -expect-error PointCloudNode has x properties
624+ const dx = node . x - parent . x * 2 ;
625+ // @ts -expect-error PointCloudNode has y properties
626+ const dy = node . y - parent . y * 2 ;
627+ // @ts -expect-error PointCloudNode has z properties
628+ const dz = node . z - parent . z * 2 ;
629+ // Octree child index (Potree convention): 4*x + 2*y + z
630+ return 4 * dx + 2 * dy + dz ;
631+ } ;
632+
633+ for ( let nodeIndex = 0 ; nodeIndex < orderedNodes . length ; nodeIndex ++ ) {
634+ const node = orderedNodes [ nodeIndex ] ;
635+ // @ts -expect-error PointCloudNode has voxelKey properties
636+ visibleNodeTextureOffsets . set ( node . voxelKey , nodeIndex ) ;
637+
638+ if ( node . parent ) {
639+ const childIndex = getChildIndex ( node ) ;
640+ // @ts -expect-error PointCloudNode has voxelKey properties
641+ const parentIndex = visibleNodeTextureOffsets . get ( node . parent . voxelKey ) ;
642+
643+ if ( parentIndex === undefined ) {
644+ continue ;
645+ }
646+
647+ const parentOffsetToChild = nodeIndex - parentIndex ;
648+ const offsetToFirstChild =
649+ Math . min ( offsetsToChild [ parentIndex ] , parentOffsetToChild ) ;
650+ offsetsToChild [ parentIndex ] = offsetToFirstChild ;
651+
652+ // The 8 bits of the red value indicate
653+ // which of the children are visible
654+ data [ parentIndex * 4 ] = data [ parentIndex * 4 ] | ( 1 << childIndex ) ;
655+ // Offset to child is stored on 2 bytes,
656+ // so it can support up to 65536 nodes per subtree.
657+ // The green channel contains the relative offset
658+ // to the node’s first child (8 most significant bits)
659+ data [ parentIndex * 4 + 1 ] = offsetToFirstChild >> 8 ;
660+ // The blue channel contains the relative offset
661+ // to the node’s first child (8 least significant bits)
662+ data [ parentIndex * 4 + 2 ] = offsetToFirstChild % 256 ;
663+ }
664+ }
665+
666+ return {
667+ data,
668+ offsets : visibleNodeTextureOffsets ,
669+ } ;
670+ }
532671}
533672
534673export default PointCloudLayer ;
0 commit comments