@@ -5,14 +5,22 @@ export class AthleteRenderer {
55 static loader = new GLTFLoader ( ) ;
66
77 static cache = {
8- man : null ,
9- woman : null
8+ man_gold : null ,
9+ man_silver : null ,
10+ man_bronze : null ,
11+ woman_gold : null ,
12+ woman_silver : null ,
13+ woman_bronze : null
1014 } ;
1115
12- static loadModels ( manUrl , womanUrl ) {
16+ static loadModels ( manGoldUrl , manSilverUrl , manBronzeUrl , womanGoldUrl , womanSilverUrl , womanBronzeUrl ) {
1317 return Promise . all ( [
14- this . _load ( manUrl ) . then ( m => ( this . cache . man = m ) ) ,
15- this . _load ( womanUrl ) . then ( m => ( this . cache . woman = m ) )
18+ this . _load ( manGoldUrl ) . then ( m => ( this . cache . man_gold = m ) ) ,
19+ this . _load ( manSilverUrl ) . then ( m => ( this . cache . man_silver = m ) ) ,
20+ this . _load ( manBronzeUrl ) . then ( m => ( this . cache . man_bronze = m ) ) ,
21+ this . _load ( womanGoldUrl ) . then ( m => ( this . cache . woman_gold = m ) ) ,
22+ this . _load ( womanSilverUrl ) . then ( m => ( this . cache . woman_silver = m ) ) ,
23+ this . _load ( womanBronzeUrl ) . then ( m => ( this . cache . woman_bronze = m ) )
1624 ] ) ;
1725 }
1826
@@ -23,7 +31,9 @@ export class AthleteRenderer {
2331 }
2432
2533 static addHuman ( scene , athlete , pos , gender = "man" ) {
26- const base = this . cache [ gender ] . clone ( true ) ;
34+ const cacheKey = `${ gender } _${ athlete . medal . toLowerCase ( ) } ` ;
35+
36+ const base = this . cache [ cacheKey ] . clone ( true ) ;
2737
2838 const group = new THREE . Group ( ) ;
2939 group . position . set ( pos . x , pos . podiumHeight , 0 ) ;
@@ -39,6 +49,12 @@ export class AthleteRenderer {
3949 base . scale . z *= weightScale ;
4050 base . position . y = 0 ;
4151
52+ const medalMat = new THREE . MeshStandardMaterial ( {
53+ color : this . _medalColor ( athlete . medal ) ,
54+ metalness : 0.8 ,
55+ roughness : 0.2
56+ } ) ;
57+
4258 base . traverse ( ( obj ) => {
4359 if ( ! obj . isMesh ) return ;
4460 const mat = obj . material ;
@@ -47,83 +63,17 @@ export class AthleteRenderer {
4763 mat . side = THREE . DoubleSide ;
4864 if ( mat . map ) mat . map . colorSpace = THREE . SRGBColorSpace ;
4965 if ( mat . emissiveMap ) mat . emissiveMap . colorSpace = THREE . SRGBColorSpace ;
50- } ) ;
5166
52- base . traverse ( ( obj ) => {
53- if ( ! obj . isMesh ) return ;
54- const name = obj . name . toLowerCase ( ) ;
55- if ( name . includes ( "cloth" ) || name . includes ( "shirt" ) || name . includes ( "body" ) ) {
56- obj . material = new THREE . MeshStandardMaterial ( {
57- color : 0xf2f2f2 ,
58- roughness : 0.8
59- } ) ;
67+ if ( ! obj . name . includes ( "_basic" ) ) {
68+ obj . material = medalMat ;
6069 }
6170 } ) ;
6271
6372 group . add ( base ) ;
64-
65- // Real bounding box so all positions adapt to any model scale
66- const tempBox = new THREE . Box3 ( ) . setFromObject ( group ) ;
67- const modelWorldHeight = tempBox . max . y - tempBox . min . y ;
68-
69- const chestWorldY = tempBox . min . y + modelWorldHeight * 0.72 ;
70- const neckWorldY = tempBox . min . y + modelWorldHeight * 0.88 ;
71- const frontWorldZ = tempBox . max . z + 0.02 ;
72-
73- // Group-local Y (group sits at pos.podiumHeight)
74- const chestLocalY = chestWorldY - pos . podiumHeight ;
75- const neckLocalY = neckWorldY - pos . podiumHeight ;
76-
77- const medalRadius = modelWorldHeight * 0.032 ;
78- const strapRadius = medalRadius * 0.12 ;
79- const neckHalfWidth = modelWorldHeight * 0.045 ; // where straps leave the neck
80- const neckZ = frontWorldZ - medalRadius * 1.5 ; // straps start slightly behind
81-
82- // Two diagonal ribbon straps forming a V around the neck
83- const strapMat = new THREE . MeshStandardMaterial ( { color : 0xcc1111 , roughness : 0.65 } ) ;
84- const medalCenter = new THREE . Vector3 ( 0 , chestLocalY , frontWorldZ ) ;
85-
86- [
87- new THREE . Vector3 ( - neckHalfWidth , neckLocalY , neckZ ) ,
88- new THREE . Vector3 ( neckHalfWidth , neckLocalY , neckZ )
89- ] . forEach ( strapStart => {
90- this . _addSegment ( group , strapStart , medalCenter , strapRadius , strapMat ) ;
91- } ) ;
92-
93- // Medal disc (drawn after straps so it sits on top)
94- const medalMat = new THREE . MeshStandardMaterial ( {
95- color : this . _medalColor ( athlete . medal ) ,
96- metalness : 0.75 ,
97- roughness : 0.25
98- } ) ;
99- const medalDisc = new THREE . Mesh (
100- new THREE . CylinderGeometry ( medalRadius , medalRadius , medalRadius * 0.25 , 32 ) ,
101- medalMat
102- ) ;
103- medalDisc . position . set ( 0 , chestLocalY , frontWorldZ ) ;
104- medalDisc . rotation . x = Math . PI / 2 ;
105- group . add ( medalDisc ) ;
106-
10773 scene . add ( group ) ;
10874 return group ;
10975 }
11076
111- // Draws a cylinder aligned between two arbitrary 3D points
112- static _addSegment ( group , from , to , radius , material ) {
113- const dir = new THREE . Vector3 ( ) . subVectors ( to , from ) ;
114- const length = dir . length ( ) ;
115- const mid = new THREE . Vector3 ( ) . lerpVectors ( from , to , 0.5 ) ;
116-
117- const mesh = new THREE . Mesh (
118- new THREE . CylinderGeometry ( radius , radius , length , 8 ) ,
119- material
120- ) ;
121- mesh . position . copy ( mid ) ;
122- // CylinderGeometry is Y-up by default; rotate to align with dir
123- mesh . quaternion . setFromUnitVectors ( new THREE . Vector3 ( 0 , 1 , 0 ) , dir . clone ( ) . normalize ( ) ) ;
124- group . add ( mesh ) ;
125- }
126-
12777 static _medalColor ( medal ) {
12878 switch ( medal ) {
12979 case "Gold" : return 0xf4c542 ;
@@ -132,4 +82,4 @@ export class AthleteRenderer {
13282 default : return 0xffffff ;
13383 }
13484 }
135- }
85+ }
0 commit comments