@@ -14,7 +14,13 @@ interface GraphProps {
1414 data : AllStarDTO ;
1515}
1616
17- type Body = { mesh : THREE . Mesh ; angle : number ; center : THREE . Vector3 ; star : StarProps } ;
17+ interface Body {
18+ mesh : THREE . Mesh ;
19+ angle : number ;
20+ center : THREE . Vector3 ;
21+ star : StarProps ;
22+ distance : number ;
23+ }
1824
1925export default function Planet ( { onOpen, data } : GraphProps ) {
2026 const { selectedType, selectedColor } = useBookmarkStore ( ) ;
@@ -112,26 +118,29 @@ export default function Planet({ onOpen, data }: GraphProps) {
112118 } ) ;
113119
114120 const orbitRadius = 5 ;
115- const orbitSpeed = 0.005 ;
116- const loader = new THREE . TextureLoader ( ) ;
121+ const maxDistance = 8 ; // 최대 거리
122+ const minDistance = 4 ; // 최소 거리
117123 const placementRange = 40 ;
118- const centerRadius = 3 ;
124+ const centerRadius = 1.5 ;
119125 const minCenterDist = orbitRadius * 2 + centerRadius * 2 ;
120126 const centers : THREE . Vector3 [ ] = [ ] ;
121127
122128 const bodies : Body [ ] = [ ] ;
123129
124130 function makeLabel ( text : string ) : THREE . Sprite {
131+ // 최대 15자, 초과 시 ...
132+ const maxLabelLength = 15 ;
133+ const label = text . length > maxLabelLength ? text . slice ( 0 , maxLabelLength ) + "..." : text ;
125134 const canvas = document . createElement ( "canvas" ) ;
126135 const ctx = canvas . getContext ( "2d" ) ! ;
127- const fontSize = 48 ;
136+ const fontSize = 24 ;
128137 ctx . font = `${ fontSize } px Arial` ;
129- const widthText = ctx . measureText ( text ) . width ;
138+ const widthText = ctx . measureText ( label ) . width ;
130139 canvas . width = widthText ;
131140 canvas . height = fontSize ;
132141 ctx . font = `${ fontSize } px Arial` ;
133142 ctx . fillStyle = "#ccc" ;
134- ctx . fillText ( text , 0 , fontSize * 0.8 ) ;
143+ ctx . fillText ( label , 0 , fontSize * 0.8 ) ;
135144 const texture = new THREE . CanvasTexture ( canvas ) ;
136145 texture . needsUpdate = true ;
137146 const material = new THREE . SpriteMaterial ( {
@@ -162,55 +171,72 @@ export default function Planet({ onOpen, data }: GraphProps) {
162171
163172 const cgeo = new THREE . SphereGeometry ( centerRadius , 32 , 32 ) ;
164173 const cmat = new THREE . MeshStandardMaterial ( {
165- emissive : 0xffaa00 ,
174+ emissive : 0xd7bdce ,
166175 emissiveIntensity : 0.7 ,
167- color : 0xffaa00 ,
176+ color : 0xd7bdce ,
168177 } ) ;
169178 const centerMesh = new THREE . Mesh ( cgeo , cmat ) ;
170179 centerMesh . position . copy ( center ) ;
171180 centerMesh . castShadow = true ;
172181 centerMesh . receiveShadow = true ;
173182 scene . add ( centerMesh ) ;
174183
184+ // shared keyword 라벨을 센터 구체 위에 표시
175185 const kwText = ( groupKeywords [ root ] || [ ] ) . join ( ", " ) ;
176186 if ( kwText ) {
177187 const label = makeLabel ( kwText ) ;
178- label . position . copy ( center ) ;
188+ label . position . set ( center . x , center . y + centerRadius + 0.5 , center . z ) ;
179189 scene . add ( label ) ;
180190 }
181191
182- const ring = new THREE . RingGeometry ( orbitRadius - 0.5 , orbitRadius + 0.5 , 64 ) ;
183- const ringMat = new THREE . MeshBasicMaterial ( {
184- color : 0x888888 ,
185- side : THREE . DoubleSide ,
186- transparent : true ,
187- opacity : 0.3 ,
188- } ) ;
189- const ringMesh = new THREE . Mesh ( ring , ringMat ) ;
190- ringMesh . rotation . x = Math . PI / 2 ;
191- ringMesh . position . copy ( center ) ;
192- scene . add ( ringMesh ) ;
193-
194192 members . forEach ( ( starId , j ) => {
195193 const star = stars . find ( ( s ) => s . starId === starId ) ! ;
196194 const size = 0.5 + ( star . views / Math . max ( ...stars . map ( ( s ) => s . views ) ) ) * 0.5 ;
197195 const geo = new THREE . SphereGeometry ( size , 32 , 32 ) ;
198196
197+ // 각 star는 selectedColor로 표시
199198 const mat = new THREE . MeshStandardMaterial ( { color : selectedColor } ) ;
200199 const mesh = new THREE . Mesh ( geo , mat ) ;
201200 mesh . userData = {
202201 starId : star . starId ,
203202 title : star . title ,
204203 faviconUrl : star . faviconUrl || "/velog.png" ,
205204 } ;
206- const angle = ( j / members . length ) * Math . PI * 2 ;
207- mesh . position . set (
208- cx + orbitRadius * Math . cos ( angle ) ,
209- 0 ,
210- cz + orbitRadius * Math . sin ( angle )
211- ) ;
205+
206+ // lastAccessedAt을 기준으로 거리 계산
207+ const lastAccessed = new Date ( star . lastAccessedAt ) . getTime ( ) ;
208+ const now = new Date ( ) . getTime ( ) ;
209+ const timeDiff = now - lastAccessed ;
210+ const maxTimeDiff = 30 * 24 * 60 * 60 * 1000 ; // 30일
211+ const distance = minDistance + ( maxDistance - minDistance ) * ( timeDiff / maxTimeDiff ) ;
212+
213+ // 랜덤한 각도로 배치 및 공전 각도 부여
214+ const angle = Math . random ( ) * Math . PI * 2 ;
215+ mesh . position . set ( cx + distance * Math . cos ( angle ) , 0 , cz + distance * Math . sin ( angle ) ) ;
212216 scene . add ( mesh ) ;
213- bodies . push ( { mesh, angle, center : center . clone ( ) , star } ) ;
217+ bodies . push ( { mesh, angle, center : center . clone ( ) , star, distance } ) ;
218+
219+ // ★ 행성 위에 title 라벨 추가
220+ const label = makeLabel ( star . title ) ;
221+ label . position . set (
222+ mesh . position . x ,
223+ mesh . position . y + size + 0.5 , // 구체 위에 띄우기
224+ mesh . position . z
225+ ) ;
226+ scene . add ( label ) ;
227+
228+ // ★ 각 행성의 궤도 반지름에 맞는 얇은 띠 추가
229+ const orbitRingGeo = new THREE . RingGeometry ( distance - 0.05 , distance + 0.05 , 64 ) ;
230+ const orbitRingMat = new THREE . MeshBasicMaterial ( {
231+ color : 0xcccccc ,
232+ side : THREE . DoubleSide ,
233+ transparent : true ,
234+ opacity : 0.2 ,
235+ } ) ;
236+ const orbitRingMesh = new THREE . Mesh ( orbitRingGeo , orbitRingMat ) ;
237+ orbitRingMesh . rotation . x = Math . PI / 2 ;
238+ orbitRingMesh . position . copy ( center ) ;
239+ scene . add ( orbitRingMesh ) ;
214240 } ) ;
215241 } ) ;
216242
@@ -257,13 +283,6 @@ export default function Planet({ onOpen, data }: GraphProps) {
257283
258284 const animate = ( ) => {
259285 requestAnimationFrame ( animate ) ;
260- bodies . forEach ( ( b ) => {
261- if ( hovered !== b ) {
262- b . angle += orbitSpeed ;
263- b . mesh . position . x = b . center . x + orbitRadius * Math . cos ( b . angle ) ;
264- b . mesh . position . z = b . center . z + orbitRadius * Math . sin ( b . angle ) ;
265- }
266- } ) ;
267286 controls . update ( ) ;
268287 renderer . render ( scene , camera ) ;
269288 } ;
0 commit comments