@@ -88,9 +88,10 @@ const buckyballVertices = () => {
8888
8989const BuckyballScene = ( { skills } : { skills : string [ ] } ) => {
9090 const groupRef = useRef < THREE . Group > ( null ) ;
91- const meshRef = useRef < THREE . Mesh > ( null ) ;
92- const [ cameraPos , setCameraPos ] = useState ( new THREE . Vector3 ( 0 , 0 , 5 ) ) ;
93- const [ visibleNodes , setVisibleNodes ] = useState < number [ ] > ( [ ] ) ;
91+ const [ cameraPos , setCameraPos ] = useState ( new THREE . Vector3 ( 0 , 0 , 7 ) ) ;
92+ const [ nodeAssignments , setNodeAssignments ] = useState < { nodeIndex : number , skillIndex : number } [ ] > ( [ ] ) ;
93+ const [ nodesToUpdate , setNodesToUpdate ] = useState < number [ ] > ( [ ] ) ;
94+ const [ frameCount , setFrameCount ] = useState ( 0 ) ;
9495 const vertices = useRef ( buckyballVertices ( ) ) ;
9596
9697 // Rotate the group and update camera position for skills visibility calculation
@@ -100,53 +101,86 @@ const BuckyballScene = ({ skills }: { skills: string[] }) => {
100101 groupRef . current . rotation . x += 0.0005 ;
101102 }
102103 setCameraPos ( state . camera . position ) ;
104+
105+ // Update frameCount only every 10 frames to avoid too frequent checks
106+ setFrameCount ( prev => ( prev + 1 ) % 10 ) ;
107+
108+ // Check every 10 frames which nodes are far away and should be updated
109+ if ( frameCount === 0 ) {
110+ const newNodesToUpdate : number [ ] = [ ] ;
111+
112+ // Check each node with an assignment
113+ nodeAssignments . forEach ( ( { nodeIndex } ) => {
114+ const position = vertices . current [ nodeIndex ] ;
115+ const positionVector = new THREE . Vector3 ( ...position ) ;
116+
117+ // Calculate distance and angle to determine if node is far from camera
118+ const cameraToPoint = positionVector . clone ( ) . sub ( state . camera . position ) ;
119+ const distance = cameraToPoint . length ( ) ;
120+ const dotProduct = positionVector . clone ( ) . normalize ( ) . dot ( state . camera . position . normalize ( ) ) ;
121+
122+ // If node is far away and facing away from camera, mark for update
123+ if ( dotProduct < - 0.5 && distance > 6 ) {
124+ newNodesToUpdate . push ( nodeIndex ) ;
125+ }
126+ } ) ;
127+
128+ if ( newNodesToUpdate . length > 0 ) {
129+ setNodesToUpdate ( newNodesToUpdate ) ;
130+ }
131+ }
103132 } ) ;
104133
105- // Update visible nodes - show more nodes at once
134+ // Initial assignment of skills to nodes
106135 useEffect ( ( ) => {
107- const updateVisibleNodes = ( ) => {
136+ if ( nodeAssignments . length === 0 ) {
108137 const totalNodes = vertices . current . length ;
109- // Show even more nodes - 10 instead of 8
110- const numVisibleNodes = Math . min ( 10 , skills . length ) ;
111- const newVisibleNodes = [ ] ;
138+ const numNodes = Math . min ( 10 , skills . length ) ;
139+ const initialAssignments = [ ] ;
112140
113- // Select random nodes to display skills
114- while ( newVisibleNodes . length < numVisibleNodes ) {
115- const randomIndex = Math . floor ( Math . random ( ) * totalNodes ) ;
116- if ( ! newVisibleNodes . includes ( randomIndex ) ) {
117- newVisibleNodes . push ( randomIndex ) ;
118- }
141+ // Create initial random assignments
142+ for ( let i = 0 ; i < numNodes ; i ++ ) {
143+ initialAssignments . push ( {
144+ nodeIndex : Math . floor ( Math . random ( ) * totalNodes ) ,
145+ skillIndex : i
146+ } ) ;
119147 }
120148
121- setVisibleNodes ( newVisibleNodes ) ;
122- } ;
123-
124- updateVisibleNodes ( ) ;
125- // Change more frequently
126- const interval = setInterval ( updateVisibleNodes , 2000 ) ;
127-
128- return ( ) => clearInterval ( interval ) ;
149+ setNodeAssignments ( initialAssignments ) ;
150+ }
129151 } , [ skills ] ) ;
130152
153+ // Update skills for nodes that are far away from camera
154+ useEffect ( ( ) => {
155+ if ( nodesToUpdate . length > 0 ) {
156+ setNodeAssignments ( prev => prev . map ( assignment => {
157+ if ( nodesToUpdate . includes ( assignment . nodeIndex ) ) {
158+ return { ...assignment , skillIndex : Math . floor ( Math . random ( ) * skills . length ) } ;
159+ }
160+ return assignment ;
161+ } ) ) ;
162+ setNodesToUpdate ( [ ] ) ;
163+ }
164+ } , [ nodesToUpdate , skills ] ) ;
165+
131166 return (
132167 < group ref = { groupRef } >
133168 { /* Create edges for the buckyball wireframe */ }
134169 < lineSegments >
135170 < edgesGeometry args = { [ new THREE . IcosahedronGeometry ( BUCKYBALL_RADIUS , 1 ) ] } />
136171 < lineBasicMaterial color = "#3b82f6" transparent opacity = { 0.5 } fog = { true } />
137172 </ lineSegments >
138-
139- { /* Create the invisible buckyball mesh for reference */ }
140- < mesh ref = { meshRef } visible = { false } >
141- < icosahedronGeometry args = { [ BUCKYBALL_RADIUS , 1 ] } />
142- < meshBasicMaterial color = "white" wireframe />
143- </ mesh >
144-
173+
145174 { /* Place skill nodes at vertex positions */ }
146175 { vertices . current . map ( ( position , i ) => {
147- const visible = visibleNodes . includes ( i ) ;
148- const skillIndex = visibleNodes . indexOf ( i ) ;
149- const text = visible && skillIndex < skills . length ? skills [ skillIndex ] : "" ;
176+ // Find if this node has a skill assignment
177+ const assignment = nodeAssignments . find ( a => a . nodeIndex === i ) ;
178+ const visible = ! ! assignment ;
179+ let text = "" ;
180+
181+ if ( visible && assignment ) {
182+ text = skills [ assignment . skillIndex % skills . length ] ;
183+ }
150184
151185 return (
152186 < SkillNode
0 commit comments