@@ -9,7 +9,8 @@ export const OUTFIT_TYPES = { |
| 9 | COLOR_ACCENT: 'color_accent', | 9 | COLOR_ACCENT: 'color_accent', |
| 10 | ACCESSORY_HEAD: 'accessory_head', | 10 | ACCESSORY_HEAD: 'accessory_head', |
| 11 | ACCESSORY_FACE: 'accessory_face', | 11 | ACCESSORY_FACE: 'accessory_face', |
| 12 | - ACCESSORY_HELD: 'accessory_held' | 12 | + ACCESSORY_HELD: 'accessory_held', |
| | 13 | + CLOTHING_BODY: 'clothing_body' // Actual rendered clothing meshes |
| 13 | } | 14 | } |
| 14 | | 15 | |
| 15 | // Character IDs | 16 | // Character IDs |
@@ -127,6 +128,412 @@ export const OUTFITS = { |
| 127 | } | 128 | } |
| 128 | }, | 129 | }, |
| 129 | | 130 | |
| | 131 | + // === DOUG CLOTHING - Actual rendered garments === |
| | 132 | + |
| | 133 | + // Simple tier - basic clothing |
| | 134 | + doug_tee_red: { |
| | 135 | + id: 'doug_tee_red', |
| | 136 | + name: 'Red T-Shirt', |
| | 137 | + character: CHARACTERS.DOUG, |
| | 138 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| | 139 | + price: 35, |
| | 140 | + meshFactory: (gradientMap) => { |
| | 141 | + const group = new THREE.Group() |
| | 142 | + const fabricMat = new THREE.MeshToonMaterial({ color: 0xcc3333, gradientMap }) |
| | 143 | + |
| | 144 | + // Main shirt body - wraps around duck torso |
| | 145 | + const torsoGeom = new THREE.SphereGeometry(0.44, 10, 8) |
| | 146 | + torsoGeom.scale(1.35, 0.72, 0.88) |
| | 147 | + const torso = new THREE.Mesh(torsoGeom, fabricMat) |
| | 148 | + torso.position.set(0, 0.28, 0) |
| | 149 | + group.add(torso) |
| | 150 | + |
| | 151 | + // Collar (V-neck) |
| | 152 | + const collarMat = new THREE.MeshToonMaterial({ color: 0xaa2222, gradientMap }) |
| | 153 | + const collarGeom = new THREE.TorusGeometry(0.12, 0.025, 4, 8, Math.PI) |
| | 154 | + const collar = new THREE.Mesh(collarGeom, collarMat) |
| | 155 | + collar.position.set(0.35, 0.42, 0) |
| | 156 | + collar.rotation.z = -Math.PI / 2 |
| | 157 | + collar.rotation.y = Math.PI / 2 |
| | 158 | + group.add(collar) |
| | 159 | + |
| | 160 | + // Short sleeves |
| | 161 | + const sleeveGeom = new THREE.CylinderGeometry(0.08, 0.1, 0.12, 6) |
| | 162 | + const leftSleeve = new THREE.Mesh(sleeveGeom, fabricMat) |
| | 163 | + leftSleeve.position.set(0.05, 0.32, 0.36) |
| | 164 | + leftSleeve.rotation.x = Math.PI / 2 |
| | 165 | + leftSleeve.rotation.z = 0.3 |
| | 166 | + group.add(leftSleeve) |
| | 167 | + |
| | 168 | + const rightSleeve = new THREE.Mesh(sleeveGeom, fabricMat) |
| | 169 | + rightSleeve.position.set(0.05, 0.32, -0.36) |
| | 170 | + rightSleeve.rotation.x = -Math.PI / 2 |
| | 171 | + rightSleeve.rotation.z = 0.3 |
| | 172 | + group.add(rightSleeve) |
| | 173 | + |
| | 174 | + return group |
| | 175 | + } |
| | 176 | + }, |
| | 177 | + |
| | 178 | + doug_sailor_shirt: { |
| | 179 | + id: 'doug_sailor_shirt', |
| | 180 | + name: 'Sailor Stripes', |
| | 181 | + character: CHARACTERS.DOUG, |
| | 182 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| | 183 | + price: 55, |
| | 184 | + meshFactory: (gradientMap) => { |
| | 185 | + const group = new THREE.Group() |
| | 186 | + const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap }) |
| | 187 | + const blueMat = new THREE.MeshToonMaterial({ color: 0x1e3a5f, gradientMap }) |
| | 188 | + |
| | 189 | + // Main shirt body |
| | 190 | + const torsoGeom = new THREE.SphereGeometry(0.44, 10, 8) |
| | 191 | + torsoGeom.scale(1.35, 0.72, 0.88) |
| | 192 | + const torso = new THREE.Mesh(torsoGeom, whiteMat) |
| | 193 | + torso.position.set(0, 0.28, 0) |
| | 194 | + group.add(torso) |
| | 195 | + |
| | 196 | + // Horizontal stripes (rings around body) |
| | 197 | + for (let i = 0; i < 4; i++) { |
| | 198 | + const stripeGeom = new THREE.TorusGeometry(0.38 - i * 0.02, 0.02, 4, 16) |
| | 199 | + const stripe = new THREE.Mesh(stripeGeom, blueMat) |
| | 200 | + stripe.position.set(-0.05 + i * 0.12, 0.18 + i * 0.06, 0) |
| | 201 | + stripe.rotation.y = Math.PI / 2 |
| | 202 | + stripe.rotation.z = 0.1 |
| | 203 | + group.add(stripe) |
| | 204 | + } |
| | 205 | + |
| | 206 | + // Sailor collar (big square back collar) |
| | 207 | + const collarGeom = new THREE.BoxGeometry(0.35, 0.02, 0.4) |
| | 208 | + const collar = new THREE.Mesh(collarGeom, blueMat) |
| | 209 | + collar.position.set(-0.15, 0.48, 0) |
| | 210 | + group.add(collar) |
| | 211 | + |
| | 212 | + // Collar flaps hanging down back |
| | 213 | + const flapGeom = new THREE.BoxGeometry(0.18, 0.2, 0.02) |
| | 214 | + const leftFlap = new THREE.Mesh(flapGeom, blueMat) |
| | 215 | + leftFlap.position.set(-0.2, 0.38, 0.18) |
| | 216 | + leftFlap.rotation.x = 0.2 |
| | 217 | + group.add(leftFlap) |
| | 218 | + |
| | 219 | + const rightFlap = new THREE.Mesh(flapGeom, blueMat) |
| | 220 | + rightFlap.position.set(-0.2, 0.38, -0.18) |
| | 221 | + rightFlap.rotation.x = -0.2 |
| | 222 | + group.add(rightFlap) |
| | 223 | + |
| | 224 | + // Red neckerchief |
| | 225 | + const tieMat = new THREE.MeshToonMaterial({ color: 0xcc2222, gradientMap }) |
| | 226 | + const tieGeom = new THREE.ConeGeometry(0.06, 0.15, 4) |
| | 227 | + const tie = new THREE.Mesh(tieGeom, tieMat) |
| | 228 | + tie.position.set(0.32, 0.32, 0) |
| | 229 | + tie.rotation.z = Math.PI |
| | 230 | + group.add(tie) |
| | 231 | + |
| | 232 | + return group |
| | 233 | + } |
| | 234 | + }, |
| | 235 | + |
| | 236 | + doug_cozy_sweater: { |
| | 237 | + id: 'doug_cozy_sweater', |
| | 238 | + name: 'Cozy Sweater', |
| | 239 | + character: CHARACTERS.DOUG, |
| | 240 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| | 241 | + price: 70, |
| | 242 | + meshFactory: (gradientMap) => { |
| | 243 | + const group = new THREE.Group() |
| | 244 | + const woolMat = new THREE.MeshToonMaterial({ color: 0x8b4513, gradientMap }) |
| | 245 | + const trimMat = new THREE.MeshToonMaterial({ color: 0xdaa520, gradientMap }) |
| | 246 | + |
| | 247 | + // Chunky sweater body |
| | 248 | + const torsoGeom = new THREE.SphereGeometry(0.46, 10, 8) |
| | 249 | + torsoGeom.scale(1.38, 0.75, 0.92) |
| | 250 | + const torso = new THREE.Mesh(torsoGeom, woolMat) |
| | 251 | + torso.position.set(0, 0.28, 0) |
| | 252 | + group.add(torso) |
| | 253 | + |
| | 254 | + // Ribbed collar (turtleneck) |
| | 255 | + const collarGeom = new THREE.CylinderGeometry(0.14, 0.16, 0.1, 8) |
| | 256 | + const collar = new THREE.Mesh(collarGeom, woolMat) |
| | 257 | + collar.position.set(0.38, 0.52, 0) |
| | 258 | + collar.rotation.z = -0.1 |
| | 259 | + group.add(collar) |
| | 260 | + |
| | 261 | + // Cable knit pattern (raised lines) |
| | 262 | + for (let i = 0; i < 3; i++) { |
| | 263 | + const cableGeom = new THREE.CylinderGeometry(0.015, 0.015, 0.5, 4) |
| | 264 | + const cable = new THREE.Mesh(cableGeom, trimMat) |
| | 265 | + cable.position.set(0.1, 0.28, -0.25 + i * 0.25) |
| | 266 | + cable.rotation.x = Math.PI / 2 |
| | 267 | + cable.rotation.z = 0.2 |
| | 268 | + group.add(cable) |
| | 269 | + } |
| | 270 | + |
| | 271 | + // Long sleeves |
| | 272 | + const sleeveGeom = new THREE.CylinderGeometry(0.09, 0.11, 0.2, 6) |
| | 273 | + const leftSleeve = new THREE.Mesh(sleeveGeom, woolMat) |
| | 274 | + leftSleeve.position.set(-0.05, 0.30, 0.38) |
| | 275 | + leftSleeve.rotation.x = Math.PI / 2 |
| | 276 | + leftSleeve.rotation.z = 0.4 |
| | 277 | + group.add(leftSleeve) |
| | 278 | + |
| | 279 | + const rightSleeve = new THREE.Mesh(sleeveGeom, woolMat) |
| | 280 | + rightSleeve.position.set(-0.05, 0.30, -0.38) |
| | 281 | + rightSleeve.rotation.x = -Math.PI / 2 |
| | 282 | + rightSleeve.rotation.z = 0.4 |
| | 283 | + group.add(rightSleeve) |
| | 284 | + |
| | 285 | + // Bottom ribbing |
| | 286 | + const ribGeom = new THREE.TorusGeometry(0.42, 0.03, 4, 16) |
| | 287 | + const rib = new THREE.Mesh(ribGeom, trimMat) |
| | 288 | + rib.position.set(-0.1, 0.08, 0) |
| | 289 | + rib.rotation.y = Math.PI / 2 |
| | 290 | + group.add(rib) |
| | 291 | + |
| | 292 | + return group |
| | 293 | + } |
| | 294 | + }, |
| | 295 | + |
| | 296 | + doug_captain_jacket: { |
| | 297 | + id: 'doug_captain_jacket', |
| | 298 | + name: "Captain's Jacket", |
| | 299 | + character: CHARACTERS.DOUG, |
| | 300 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| | 301 | + price: 95, |
| | 302 | + meshFactory: (gradientMap) => { |
| | 303 | + const group = new THREE.Group() |
| | 304 | + const navyMat = new THREE.MeshToonMaterial({ color: 0x1a1a3a, gradientMap }) |
| | 305 | + const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap }) |
| | 306 | + const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap }) |
| | 307 | + |
| | 308 | + // Jacket body |
| | 309 | + const torsoGeom = new THREE.SphereGeometry(0.45, 10, 8) |
| | 310 | + torsoGeom.scale(1.4, 0.76, 0.9) |
| | 311 | + const torso = new THREE.Mesh(torsoGeom, navyMat) |
| | 312 | + torso.position.set(0, 0.28, 0) |
| | 313 | + group.add(torso) |
| | 314 | + |
| | 315 | + // Jacket front panels (double-breasted) |
| | 316 | + const panelGeom = new THREE.BoxGeometry(0.08, 0.35, 0.25) |
| | 317 | + const leftPanel = new THREE.Mesh(panelGeom, navyMat) |
| | 318 | + leftPanel.position.set(0.38, 0.28, 0.12) |
| | 319 | + group.add(leftPanel) |
| | 320 | + |
| | 321 | + const rightPanel = new THREE.Mesh(panelGeom, navyMat) |
| | 322 | + rightPanel.position.set(0.38, 0.28, -0.12) |
| | 323 | + group.add(rightPanel) |
| | 324 | + |
| | 325 | + // Gold buttons (double row) |
| | 326 | + const buttonGeom = new THREE.SphereGeometry(0.025, 6, 4) |
| | 327 | + const buttonPositions = [ |
| | 328 | + { x: 0.42, y: 0.38, z: 0.08 }, { x: 0.42, y: 0.28, z: 0.08 }, { x: 0.42, y: 0.18, z: 0.08 }, |
| | 329 | + { x: 0.42, y: 0.38, z: -0.08 }, { x: 0.42, y: 0.28, z: -0.08 }, { x: 0.42, y: 0.18, z: -0.08 } |
| | 330 | + ] |
| | 331 | + for (const pos of buttonPositions) { |
| | 332 | + const button = new THREE.Mesh(buttonGeom, goldMat) |
| | 333 | + button.position.set(pos.x, pos.y, pos.z) |
| | 334 | + group.add(button) |
| | 335 | + } |
| | 336 | + |
| | 337 | + // Epaulettes (shoulder boards) |
| | 338 | + const epauletteGeom = new THREE.BoxGeometry(0.08, 0.02, 0.12) |
| | 339 | + const leftEpaulette = new THREE.Mesh(epauletteGeom, goldMat) |
| | 340 | + leftEpaulette.position.set(0.1, 0.45, 0.32) |
| | 341 | + group.add(leftEpaulette) |
| | 342 | + |
| | 343 | + const rightEpaulette = new THREE.Mesh(epauletteGeom, goldMat) |
| | 344 | + rightEpaulette.position.set(0.1, 0.45, -0.32) |
| | 345 | + group.add(rightEpaulette) |
| | 346 | + |
| | 347 | + // High collar |
| | 348 | + const collarGeom = new THREE.CylinderGeometry(0.13, 0.15, 0.08, 8, 1, true) |
| | 349 | + const collar = new THREE.Mesh(collarGeom, navyMat) |
| | 350 | + collar.position.set(0.36, 0.50, 0) |
| | 351 | + collar.rotation.z = -0.1 |
| | 352 | + group.add(collar) |
| | 353 | + |
| | 354 | + // Gold collar trim |
| | 355 | + const collarTrimGeom = new THREE.TorusGeometry(0.14, 0.012, 4, 12) |
| | 356 | + const collarTrim = new THREE.Mesh(collarTrimGeom, goldMat) |
| | 357 | + collarTrim.position.set(0.36, 0.54, 0) |
| | 358 | + collarTrim.rotation.x = Math.PI / 2 |
| | 359 | + collarTrim.rotation.z = -0.1 |
| | 360 | + group.add(collarTrim) |
| | 361 | + |
| | 362 | + // Sleeves with gold cuff trim |
| | 363 | + const sleeveGeom = new THREE.CylinderGeometry(0.09, 0.1, 0.18, 6) |
| | 364 | + const leftSleeve = new THREE.Mesh(sleeveGeom, navyMat) |
| | 365 | + leftSleeve.position.set(-0.02, 0.30, 0.38) |
| | 366 | + leftSleeve.rotation.x = Math.PI / 2 |
| | 367 | + leftSleeve.rotation.z = 0.35 |
| | 368 | + group.add(leftSleeve) |
| | 369 | + |
| | 370 | + const rightSleeve = new THREE.Mesh(sleeveGeom, navyMat) |
| | 371 | + rightSleeve.position.set(-0.02, 0.30, -0.38) |
| | 372 | + rightSleeve.rotation.x = -Math.PI / 2 |
| | 373 | + rightSleeve.rotation.z = 0.35 |
| | 374 | + group.add(rightSleeve) |
| | 375 | + |
| | 376 | + // Gold cuff rings |
| | 377 | + const cuffGeom = new THREE.TorusGeometry(0.095, 0.015, 4, 8) |
| | 378 | + const leftCuff = new THREE.Mesh(cuffGeom, goldMat) |
| | 379 | + leftCuff.position.set(-0.08, 0.26, 0.44) |
| | 380 | + leftCuff.rotation.y = Math.PI / 2 + 0.35 |
| | 381 | + group.add(leftCuff) |
| | 382 | + |
| | 383 | + const rightCuff = new THREE.Mesh(cuffGeom, goldMat) |
| | 384 | + rightCuff.position.set(-0.08, 0.26, -0.44) |
| | 385 | + rightCuff.rotation.y = Math.PI / 2 - 0.35 |
| | 386 | + group.add(rightCuff) |
| | 387 | + |
| | 388 | + return group |
| | 389 | + } |
| | 390 | + }, |
| | 391 | + |
| | 392 | + doug_pirate_captain: { |
| | 393 | + id: 'doug_pirate_captain', |
| | 394 | + name: 'Pirate Captain', |
| | 395 | + character: CHARACTERS.DOUG, |
| | 396 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| | 397 | + price: 150, |
| | 398 | + meshFactory: (gradientMap) => { |
| | 399 | + const group = new THREE.Group() |
| | 400 | + const coatMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap }) |
| | 401 | + const blackMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap }) |
| | 402 | + const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap }) |
| | 403 | + const whiteMat = new THREE.MeshToonMaterial({ color: 0xf5f5dc, gradientMap }) |
| | 404 | + const beltMat = new THREE.MeshToonMaterial({ color: 0x4a3020, gradientMap }) |
| | 405 | + |
| | 406 | + // Inner white shirt (ruffle front) |
| | 407 | + const shirtGeom = new THREE.SphereGeometry(0.42, 10, 8) |
| | 408 | + shirtGeom.scale(1.3, 0.7, 0.85) |
| | 409 | + const shirt = new THREE.Mesh(shirtGeom, whiteMat) |
| | 410 | + shirt.position.set(0, 0.28, 0) |
| | 411 | + group.add(shirt) |
| | 412 | + |
| | 413 | + // Ruffle details on shirt front |
| | 414 | + for (let i = 0; i < 4; i++) { |
| | 415 | + const ruffleGeom = new THREE.TorusGeometry(0.06 - i * 0.008, 0.015, 4, 8, Math.PI) |
| | 416 | + const ruffle = new THREE.Mesh(ruffleGeom, whiteMat) |
| | 417 | + ruffle.position.set(0.36, 0.35 - i * 0.06, 0) |
| | 418 | + ruffle.rotation.y = Math.PI / 2 |
| | 419 | + ruffle.rotation.z = -Math.PI / 2 |
| | 420 | + group.add(ruffle) |
| | 421 | + } |
| | 422 | + |
| | 423 | + // Long pirate coat (open front) |
| | 424 | + const coatLeftGeom = new THREE.BoxGeometry(0.1, 0.45, 0.35) |
| | 425 | + const coatLeft = new THREE.Mesh(coatLeftGeom, coatMat) |
| | 426 | + coatLeft.position.set(0.15, 0.22, 0.25) |
| | 427 | + coatLeft.rotation.y = 0.15 |
| | 428 | + group.add(coatLeft) |
| | 429 | + |
| | 430 | + const coatRightGeom = new THREE.BoxGeometry(0.1, 0.45, 0.35) |
| | 431 | + const coatRight = new THREE.Mesh(coatRightGeom, coatMat) |
| | 432 | + coatRight.position.set(0.15, 0.22, -0.25) |
| | 433 | + coatRight.rotation.y = -0.15 |
| | 434 | + group.add(coatRight) |
| | 435 | + |
| | 436 | + // Coat back |
| | 437 | + const coatBackGeom = new THREE.BoxGeometry(0.08, 0.5, 0.55) |
| | 438 | + const coatBack = new THREE.Mesh(coatBackGeom, coatMat) |
| | 439 | + coatBack.position.set(-0.25, 0.25, 0) |
| | 440 | + group.add(coatBack) |
| | 441 | + |
| | 442 | + // Coat tails (flowing behind) |
| | 443 | + const tailGeom = new THREE.BoxGeometry(0.06, 0.25, 0.2) |
| | 444 | + const leftTail = new THREE.Mesh(tailGeom, coatMat) |
| | 445 | + leftTail.position.set(-0.35, 0.08, 0.15) |
| | 446 | + leftTail.rotation.x = 0.2 |
| | 447 | + leftTail.rotation.z = 0.1 |
| | 448 | + group.add(leftTail) |
| | 449 | + |
| | 450 | + const rightTail = new THREE.Mesh(tailGeom, coatMat) |
| | 451 | + rightTail.position.set(-0.35, 0.08, -0.15) |
| | 452 | + rightTail.rotation.x = -0.2 |
| | 453 | + rightTail.rotation.z = 0.1 |
| | 454 | + group.add(rightTail) |
| | 455 | + |
| | 456 | + // Gold trim on coat edges |
| | 457 | + const trimGeom = new THREE.BoxGeometry(0.02, 0.4, 0.02) |
| | 458 | + const trimPositions = [ |
| | 459 | + { x: 0.22, y: 0.22, z: 0.42 }, { x: 0.22, y: 0.22, z: -0.42 }, |
| | 460 | + { x: 0.22, y: 0.22, z: 0.08 }, { x: 0.22, y: 0.22, z: -0.08 } |
| | 461 | + ] |
| | 462 | + for (const pos of trimPositions) { |
| | 463 | + const trim = new THREE.Mesh(trimGeom, goldMat) |
| | 464 | + trim.position.set(pos.x, pos.y, pos.z) |
| | 465 | + group.add(trim) |
| | 466 | + } |
| | 467 | + |
| | 468 | + // Wide leather belt |
| | 469 | + const beltGeom = new THREE.TorusGeometry(0.4, 0.035, 4, 16) |
| | 470 | + const belt = new THREE.Mesh(beltGeom, beltMat) |
| | 471 | + belt.position.set(0, 0.15, 0) |
| | 472 | + belt.rotation.y = Math.PI / 2 |
| | 473 | + belt.rotation.z = 0.05 |
| | 474 | + group.add(belt) |
| | 475 | + |
| | 476 | + // Belt buckle (skull shape simplified) |
| | 477 | + const buckleGeom = new THREE.BoxGeometry(0.08, 0.08, 0.03) |
| | 478 | + const buckle = new THREE.Mesh(buckleGeom, goldMat) |
| | 479 | + buckle.position.set(0.38, 0.15, 0) |
| | 480 | + group.add(buckle) |
| | 481 | + |
| | 482 | + // Skull detail on buckle |
| | 483 | + const skullGeom = new THREE.SphereGeometry(0.025, 6, 4) |
| | 484 | + const skull = new THREE.Mesh(skullGeom, whiteMat) |
| | 485 | + skull.position.set(0.40, 0.15, 0) |
| | 486 | + group.add(skull) |
| | 487 | + |
| | 488 | + // Diagonal bandolier strap |
| | 489 | + const strapGeom = new THREE.BoxGeometry(0.5, 0.04, 0.03) |
| | 490 | + const strap = new THREE.Mesh(strapGeom, beltMat) |
| | 491 | + strap.position.set(0.05, 0.32, 0) |
| | 492 | + strap.rotation.z = 0.6 |
| | 493 | + group.add(strap) |
| | 494 | + |
| | 495 | + // Coat sleeves |
| | 496 | + const sleeveGeom = new THREE.CylinderGeometry(0.1, 0.11, 0.22, 6) |
| | 497 | + const leftSleeve = new THREE.Mesh(sleeveGeom, coatMat) |
| | 498 | + leftSleeve.position.set(-0.05, 0.30, 0.4) |
| | 499 | + leftSleeve.rotation.x = Math.PI / 2 |
| | 500 | + leftSleeve.rotation.z = 0.4 |
| | 501 | + group.add(leftSleeve) |
| | 502 | + |
| | 503 | + const rightSleeve = new THREE.Mesh(sleeveGeom, coatMat) |
| | 504 | + rightSleeve.position.set(-0.05, 0.30, -0.4) |
| | 505 | + rightSleeve.rotation.x = -Math.PI / 2 |
| | 506 | + rightSleeve.rotation.z = 0.4 |
| | 507 | + group.add(rightSleeve) |
| | 508 | + |
| | 509 | + // Sleeve cuff ruffles |
| | 510 | + const cuffGeom = new THREE.TorusGeometry(0.1, 0.02, 4, 8) |
| | 511 | + const leftCuff = new THREE.Mesh(cuffGeom, whiteMat) |
| | 512 | + leftCuff.position.set(-0.12, 0.26, 0.48) |
| | 513 | + leftCuff.rotation.y = Math.PI / 2 + 0.4 |
| | 514 | + group.add(leftCuff) |
| | 515 | + |
| | 516 | + const rightCuff = new THREE.Mesh(cuffGeom, whiteMat) |
| | 517 | + rightCuff.position.set(-0.12, 0.26, -0.48) |
| | 518 | + rightCuff.rotation.y = Math.PI / 2 - 0.4 |
| | 519 | + group.add(rightCuff) |
| | 520 | + |
| | 521 | + // Gold buttons on coat |
| | 522 | + const buttonGeom = new THREE.SphereGeometry(0.02, 6, 4) |
| | 523 | + for (let i = 0; i < 3; i++) { |
| | 524 | + const leftBtn = new THREE.Mesh(buttonGeom, goldMat) |
| | 525 | + leftBtn.position.set(0.2, 0.35 - i * 0.08, 0.1) |
| | 526 | + group.add(leftBtn) |
| | 527 | + |
| | 528 | + const rightBtn = new THREE.Mesh(buttonGeom, goldMat) |
| | 529 | + rightBtn.position.set(0.2, 0.35 - i * 0.08, -0.1) |
| | 530 | + group.add(rightBtn) |
| | 531 | + } |
| | 532 | + |
| | 533 | + return group |
| | 534 | + } |
| | 535 | + }, |
| | 536 | + |
| 130 | // Donny outfits - starter tier | 537 | // Donny outfits - starter tier |
| 131 | donny_seafoam: { | 538 | donny_seafoam: { |
| 132 | id: 'donny_seafoam', | 539 | id: 'donny_seafoam', |
@@ -186,6 +593,197 @@ export const OUTFITS = { |
| 186 | } | 593 | } |
| 187 | }, | 594 | }, |
| 188 | | 595 | |
| | 596 | + // === DONNY CLOTHING - Narwhal garments === |
| | 597 | + |
| | 598 | + donny_sailor_vest: { |
| | 599 | + id: 'donny_sailor_vest', |
| | 600 | + name: 'Sailor Vest', |
| | 601 | + character: CHARACTERS.DONNY, |
| | 602 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| | 603 | + price: 45, |
| | 604 | + meshFactory: (gradientMap) => { |
| | 605 | + const group = new THREE.Group() |
| | 606 | + const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap }) |
| | 607 | + const blueMat = new THREE.MeshToonMaterial({ color: 0x1e3a5f, gradientMap }) |
| | 608 | + |
| | 609 | + // Vest body - fits narwhal's torpedo shape |
| | 610 | + const vestGeom = new THREE.CylinderGeometry(0.38, 0.42, 0.6, 10) |
| | 611 | + const vest = new THREE.Mesh(vestGeom, whiteMat) |
| | 612 | + vest.position.set(0, 0.15, 0) |
| | 613 | + vest.rotation.x = Math.PI / 2 |
| | 614 | + group.add(vest) |
| | 615 | + |
| | 616 | + // Blue stripes on vest |
| | 617 | + for (let i = 0; i < 3; i++) { |
| | 618 | + const stripeGeom = new THREE.TorusGeometry(0.39 + i * 0.01, 0.02, 4, 16) |
| | 619 | + const stripe = new THREE.Mesh(stripeGeom, blueMat) |
| | 620 | + stripe.position.set(0, -0.1 + i * 0.15, 0) |
| | 621 | + stripe.rotation.x = Math.PI / 2 |
| | 622 | + group.add(stripe) |
| | 623 | + } |
| | 624 | + |
| | 625 | + // Collar |
| | 626 | + const collarGeom = new THREE.TorusGeometry(0.36, 0.04, 4, 12, Math.PI) |
| | 627 | + const collar = new THREE.Mesh(collarGeom, blueMat) |
| | 628 | + collar.position.set(0, 0.42, 0.1) |
| | 629 | + collar.rotation.x = Math.PI / 2 + 0.3 |
| | 630 | + group.add(collar) |
| | 631 | + |
| | 632 | + return group |
| | 633 | + } |
| | 634 | + }, |
| | 635 | + |
| | 636 | + donny_admiral_coat: { |
| | 637 | + id: 'donny_admiral_coat', |
| | 638 | + name: 'Admiral Coat', |
| | 639 | + character: CHARACTERS.DONNY, |
| | 640 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| | 641 | + price: 100, |
| | 642 | + meshFactory: (gradientMap) => { |
| | 643 | + const group = new THREE.Group() |
| | 644 | + const navyMat = new THREE.MeshToonMaterial({ color: 0x1a1a3a, gradientMap }) |
| | 645 | + const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap }) |
| | 646 | + const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap }) |
| | 647 | + |
| | 648 | + // Main coat body |
| | 649 | + const coatGeom = new THREE.CylinderGeometry(0.4, 0.45, 0.7, 10) |
| | 650 | + const coat = new THREE.Mesh(coatGeom, navyMat) |
| | 651 | + coat.position.set(0, 0.12, 0) |
| | 652 | + coat.rotation.x = Math.PI / 2 |
| | 653 | + group.add(coat) |
| | 654 | + |
| | 655 | + // High collar |
| | 656 | + const collarGeom = new THREE.CylinderGeometry(0.38, 0.35, 0.12, 10, 1, true) |
| | 657 | + const collar = new THREE.Mesh(collarGeom, navyMat) |
| | 658 | + collar.position.set(0, 0.48, 0) |
| | 659 | + collar.rotation.x = Math.PI / 2 |
| | 660 | + group.add(collar) |
| | 661 | + |
| | 662 | + // Gold collar trim |
| | 663 | + const collarTrimGeom = new THREE.TorusGeometry(0.36, 0.015, 4, 12) |
| | 664 | + const collarTrim = new THREE.Mesh(collarTrimGeom, goldMat) |
| | 665 | + collarTrim.position.set(0, 0.54, 0) |
| | 666 | + collarTrim.rotation.x = Math.PI / 2 |
| | 667 | + group.add(collarTrim) |
| | 668 | + |
| | 669 | + // Epaulettes |
| | 670 | + const epGeom = new THREE.BoxGeometry(0.15, 0.03, 0.1) |
| | 671 | + const leftEp = new THREE.Mesh(epGeom, goldMat) |
| | 672 | + leftEp.position.set(0.35, 0.35, 0.2) |
| | 673 | + leftEp.rotation.z = 0.3 |
| | 674 | + group.add(leftEp) |
| | 675 | + |
| | 676 | + const rightEp = new THREE.Mesh(epGeom, goldMat) |
| | 677 | + rightEp.position.set(0.35, 0.35, -0.2) |
| | 678 | + rightEp.rotation.z = 0.3 |
| | 679 | + group.add(rightEp) |
| | 680 | + |
| | 681 | + // Gold buttons (row down front) |
| | 682 | + const buttonGeom = new THREE.SphereGeometry(0.025, 6, 4) |
| | 683 | + for (let i = 0; i < 4; i++) { |
| | 684 | + const button = new THREE.Mesh(buttonGeom, goldMat) |
| | 685 | + button.position.set(0.1 - i * 0.12, 0.42, 0) |
| | 686 | + group.add(button) |
| | 687 | + } |
| | 688 | + |
| | 689 | + // Medals/decorations |
| | 690 | + const medalGeom = new THREE.CircleGeometry(0.04, 6) |
| | 691 | + const medal1 = new THREE.Mesh(medalGeom, goldMat) |
| | 692 | + medal1.position.set(0.25, 0.38, 0.15) |
| | 693 | + medal1.rotation.y = -0.5 |
| | 694 | + group.add(medal1) |
| | 695 | + |
| | 696 | + const medal2 = new THREE.Mesh(medalGeom, goldMat) |
| | 697 | + medal2.position.set(0.2, 0.38, 0.12) |
| | 698 | + medal2.rotation.y = -0.5 |
| | 699 | + group.add(medal2) |
| | 700 | + |
| | 701 | + return group |
| | 702 | + } |
| | 703 | + }, |
| | 704 | + |
| | 705 | + donny_pirate_vest: { |
| | 706 | + id: 'donny_pirate_vest', |
| | 707 | + name: 'Pirate Vest', |
| | 708 | + character: CHARACTERS.DONNY, |
| | 709 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| | 710 | + price: 130, |
| | 711 | + meshFactory: (gradientMap) => { |
| | 712 | + const group = new THREE.Group() |
| | 713 | + const redMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap }) |
| | 714 | + const blackMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap }) |
| | 715 | + const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap }) |
| | 716 | + const beltMat = new THREE.MeshToonMaterial({ color: 0x4a3020, gradientMap }) |
| | 717 | + const whiteMat = new THREE.MeshToonMaterial({ color: 0xf5f5dc, gradientMap }) |
| | 718 | + |
| | 719 | + // Inner shirt |
| | 720 | + const shirtGeom = new THREE.CylinderGeometry(0.36, 0.4, 0.55, 10) |
| | 721 | + const shirt = new THREE.Mesh(shirtGeom, whiteMat) |
| | 722 | + shirt.position.set(0, 0.15, 0) |
| | 723 | + shirt.rotation.x = Math.PI / 2 |
| | 724 | + group.add(shirt) |
| | 725 | + |
| | 726 | + // Vest panels (open front) |
| | 727 | + const panelGeom = new THREE.BoxGeometry(0.4, 0.08, 0.25) |
| | 728 | + const leftPanel = new THREE.Mesh(panelGeom, redMat) |
| | 729 | + leftPanel.position.set(0.1, 0.3, 0.2) |
| | 730 | + leftPanel.rotation.z = 0.2 |
| | 731 | + group.add(leftPanel) |
| | 732 | + |
| | 733 | + const rightPanel = new THREE.Mesh(panelGeom, redMat) |
| | 734 | + rightPanel.position.set(0.1, 0.3, -0.2) |
| | 735 | + rightPanel.rotation.z = 0.2 |
| | 736 | + group.add(rightPanel) |
| | 737 | + |
| | 738 | + // Vest back |
| | 739 | + const backGeom = new THREE.BoxGeometry(0.5, 0.08, 0.35) |
| | 740 | + const back = new THREE.Mesh(backGeom, redMat) |
| | 741 | + back.position.set(-0.15, 0.2, 0) |
| | 742 | + group.add(back) |
| | 743 | + |
| | 744 | + // Gold trim on vest |
| | 745 | + const trimGeom = new THREE.BoxGeometry(0.35, 0.02, 0.02) |
| | 746 | + const leftTrim = new THREE.Mesh(trimGeom, goldMat) |
| | 747 | + leftTrim.position.set(0.12, 0.32, 0.32) |
| | 748 | + leftTrim.rotation.z = 0.2 |
| | 749 | + group.add(leftTrim) |
| | 750 | + |
| | 751 | + const rightTrim = new THREE.Mesh(trimGeom, goldMat) |
| | 752 | + rightTrim.position.set(0.12, 0.32, -0.32) |
| | 753 | + rightTrim.rotation.z = 0.2 |
| | 754 | + group.add(rightTrim) |
| | 755 | + |
| | 756 | + // Wide belt with skull buckle |
| | 757 | + const beltGeom = new THREE.TorusGeometry(0.4, 0.05, 4, 16) |
| | 758 | + const belt = new THREE.Mesh(beltGeom, beltMat) |
| | 759 | + belt.position.set(0, -0.1, 0) |
| | 760 | + belt.rotation.x = Math.PI / 2 |
| | 761 | + group.add(belt) |
| | 762 | + |
| | 763 | + // Buckle |
| | 764 | + const buckleGeom = new THREE.BoxGeometry(0.1, 0.08, 0.04) |
| | 765 | + const buckle = new THREE.Mesh(buckleGeom, goldMat) |
| | 766 | + buckle.position.set(0.38, -0.1, 0) |
| | 767 | + group.add(buckle) |
| | 768 | + |
| | 769 | + // Skull on buckle |
| | 770 | + const skullGeom = new THREE.SphereGeometry(0.03, 6, 4) |
| | 771 | + const skull = new THREE.Mesh(skullGeom, whiteMat) |
| | 772 | + skull.position.set(0.40, -0.1, 0) |
| | 773 | + group.add(skull) |
| | 774 | + |
| | 775 | + // Bandolier strap |
| | 776 | + const strapGeom = new THREE.BoxGeometry(0.6, 0.04, 0.03) |
| | 777 | + const strap = new THREE.Mesh(strapGeom, beltMat) |
| | 778 | + strap.position.set(0, 0.2, 0) |
| | 779 | + strap.rotation.z = 0.5 |
| | 780 | + strap.rotation.y = Math.PI / 2 |
| | 781 | + group.add(strap) |
| | 782 | + |
| | 783 | + return group |
| | 784 | + } |
| | 785 | + }, |
| | 786 | + |
| 189 | // Ollie outfits | 787 | // Ollie outfits |
| 190 | ollie_coral: { | 788 | ollie_coral: { |
| 191 | id: 'ollie_coral', | 789 | id: 'ollie_coral', |
@@ -234,6 +832,222 @@ export const OUTFITS = { |
| 234 | brim.rotation.z = -0.3 | 832 | brim.rotation.z = -0.3 |
| 235 | group.add(brim) | 833 | group.add(brim) |
| 236 | | 834 | |
| | 835 | + return group |
| | 836 | + } |
| | 837 | + }, |
| | 838 | + |
| | 839 | + // === OLLIE CLOTHING - Octopus garments === |
| | 840 | + |
| | 841 | + ollie_bowtie: { |
| | 842 | + id: 'ollie_bowtie', |
| | 843 | + name: 'Fancy Bow Tie', |
| | 844 | + character: CHARACTERS.OLLIE, |
| | 845 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| | 846 | + price: 40, |
| | 847 | + meshFactory: (gradientMap) => { |
| | 848 | + const group = new THREE.Group() |
| | 849 | + const silkMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap }) |
| | 850 | + const knotMat = new THREE.MeshToonMaterial({ color: 0x660000, gradientMap }) |
| | 851 | + |
| | 852 | + // Bow tie wings |
| | 853 | + const wingGeom = new THREE.BoxGeometry(0.12, 0.06, 0.04) |
| | 854 | + const leftWing = new THREE.Mesh(wingGeom, silkMat) |
| | 855 | + leftWing.position.set(0, 0.65, 0.08) |
| | 856 | + leftWing.rotation.z = 0.2 |
| | 857 | + group.add(leftWing) |
| | 858 | + |
| | 859 | + const rightWing = new THREE.Mesh(wingGeom, silkMat) |
| | 860 | + rightWing.position.set(0, 0.65, -0.08) |
| | 861 | + rightWing.rotation.z = -0.2 |
| | 862 | + group.add(rightWing) |
| | 863 | + |
| | 864 | + // Center knot |
| | 865 | + const knotGeom = new THREE.SphereGeometry(0.03, 6, 4) |
| | 866 | + const knot = new THREE.Mesh(knotGeom, knotMat) |
| | 867 | + knot.position.set(0, 0.65, 0) |
| | 868 | + knot.scale.set(1, 1, 1.5) |
| | 869 | + group.add(knot) |
| | 870 | + |
| | 871 | + // Small dangling ribbon ends |
| | 872 | + const ribbonGeom = new THREE.BoxGeometry(0.02, 0.08, 0.03) |
| | 873 | + const ribbon1 = new THREE.Mesh(ribbonGeom, silkMat) |
| | 874 | + ribbon1.position.set(0, 0.60, 0.02) |
| | 875 | + ribbon1.rotation.z = 0.1 |
| | 876 | + group.add(ribbon1) |
| | 877 | + |
| | 878 | + const ribbon2 = new THREE.Mesh(ribbonGeom, silkMat) |
| | 879 | + ribbon2.position.set(0, 0.60, -0.02) |
| | 880 | + ribbon2.rotation.z = -0.1 |
| | 881 | + group.add(ribbon2) |
| | 882 | + |
| | 883 | + return group |
| | 884 | + } |
| | 885 | + }, |
| | 886 | + |
| | 887 | + ollie_dapper_vest: { |
| | 888 | + id: 'ollie_dapper_vest', |
| | 889 | + name: 'Dapper Vest', |
| | 890 | + character: CHARACTERS.OLLIE, |
| | 891 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| | 892 | + price: 75, |
| | 893 | + meshFactory: (gradientMap) => { |
| | 894 | + const group = new THREE.Group() |
| | 895 | + const vestMat = new THREE.MeshToonMaterial({ color: 0x4a4a4a, gradientMap }) |
| | 896 | + const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap }) |
| | 897 | + const silkMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap }) |
| | 898 | + |
| | 899 | + // Vest body - wraps around octopus mantle |
| | 900 | + const vestGeom = new THREE.SphereGeometry(0.42, 10, 8) |
| | 901 | + vestGeom.scale(1, 0.8, 1) |
| | 902 | + const vest = new THREE.Mesh(vestGeom, vestMat) |
| | 903 | + vest.position.set(0, 0.35, 0) |
| | 904 | + group.add(vest) |
| | 905 | + |
| | 906 | + // Vest front opening (shows body) |
| | 907 | + const openingGeom = new THREE.PlaneGeometry(0.15, 0.4) |
| | 908 | + const openingMat = new THREE.MeshBasicMaterial({ color: 0xe07850, transparent: true, opacity: 0.3 }) |
| | 909 | + const opening = new THREE.Mesh(openingGeom, openingMat) |
| | 910 | + opening.position.set(0.42, 0.35, 0) |
| | 911 | + opening.rotation.y = Math.PI / 2 |
| | 912 | + group.add(opening) |
| | 913 | + |
| | 914 | + // Lapels |
| | 915 | + const lapelGeom = new THREE.BoxGeometry(0.08, 0.2, 0.05) |
| | 916 | + const leftLapel = new THREE.Mesh(lapelGeom, silkMat) |
| | 917 | + leftLapel.position.set(0.4, 0.45, 0.1) |
| | 918 | + leftLapel.rotation.z = -0.2 |
| | 919 | + leftLapel.rotation.y = 0.3 |
| | 920 | + group.add(leftLapel) |
| | 921 | + |
| | 922 | + const rightLapel = new THREE.Mesh(lapelGeom, silkMat) |
| | 923 | + rightLapel.position.set(0.4, 0.45, -0.1) |
| | 924 | + rightLapel.rotation.z = -0.2 |
| | 925 | + rightLapel.rotation.y = -0.3 |
| | 926 | + group.add(rightLapel) |
| | 927 | + |
| | 928 | + // Gold buttons |
| | 929 | + const buttonGeom = new THREE.SphereGeometry(0.02, 6, 4) |
| | 930 | + for (let i = 0; i < 3; i++) { |
| | 931 | + const button = new THREE.Mesh(buttonGeom, goldMat) |
| | 932 | + button.position.set(0.42, 0.5 - i * 0.1, 0) |
| | 933 | + group.add(button) |
| | 934 | + } |
| | 935 | + |
| | 936 | + // Watch chain |
| | 937 | + const chainGeom = new THREE.TorusGeometry(0.08, 0.008, 4, 8, Math.PI) |
| | 938 | + const chain = new THREE.Mesh(chainGeom, goldMat) |
| | 939 | + chain.position.set(0.35, 0.32, 0) |
| | 940 | + chain.rotation.y = Math.PI / 2 |
| | 941 | + chain.rotation.x = Math.PI / 2 |
| | 942 | + group.add(chain) |
| | 943 | + |
| | 944 | + // Watch fob |
| | 945 | + const fobGeom = new THREE.SphereGeometry(0.02, 6, 4) |
| | 946 | + const fob = new THREE.Mesh(fobGeom, goldMat) |
| | 947 | + fob.position.set(0.35, 0.24, 0) |
| | 948 | + group.add(fob) |
| | 949 | + |
| | 950 | + return group |
| | 951 | + } |
| | 952 | + }, |
| | 953 | + |
| | 954 | + ollie_gentleman_suit: { |
| | 955 | + id: 'ollie_gentleman_suit', |
| | 956 | + name: "Gentleman's Suit", |
| | 957 | + character: CHARACTERS.OLLIE, |
| | 958 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| | 959 | + price: 120, |
| | 960 | + meshFactory: (gradientMap) => { |
| | 961 | + const group = new THREE.Group() |
| | 962 | + const suitMat = new THREE.MeshToonMaterial({ color: 0x1a1a2e, gradientMap }) |
| | 963 | + const shirtMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap }) |
| | 964 | + const tieMat = new THREE.MeshToonMaterial({ color: 0x4a0000, gradientMap }) |
| | 965 | + const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap }) |
| | 966 | + |
| | 967 | + // White shirt underneath |
| | 968 | + const shirtGeom = new THREE.SphereGeometry(0.38, 10, 8) |
| | 969 | + shirtGeom.scale(1, 0.78, 1) |
| | 970 | + const shirt = new THREE.Mesh(shirtGeom, shirtMat) |
| | 971 | + shirt.position.set(0, 0.35, 0) |
| | 972 | + group.add(shirt) |
| | 973 | + |
| | 974 | + // Suit jacket |
| | 975 | + const jacketGeom = new THREE.SphereGeometry(0.44, 10, 8) |
| | 976 | + jacketGeom.scale(1, 0.82, 1) |
| | 977 | + const jacket = new THREE.Mesh(jacketGeom, suitMat) |
| | 978 | + jacket.position.set(0, 0.35, 0) |
| | 979 | + group.add(jacket) |
| | 980 | + |
| | 981 | + // Jacket front cutaway (shows shirt and tie) |
| | 982 | + const cutawayGeom = new THREE.PlaneGeometry(0.2, 0.45) |
| | 983 | + const cutawayMat = new THREE.MeshBasicMaterial({ visible: false }) |
| | 984 | + const cutaway = new THREE.Mesh(cutawayGeom, cutawayMat) |
| | 985 | + cutaway.position.set(0.45, 0.35, 0) |
| | 986 | + cutaway.rotation.y = Math.PI / 2 |
| | 987 | + group.add(cutaway) |
| | 988 | + |
| | 989 | + // Lapels |
| | 990 | + const lapelGeom = new THREE.BoxGeometry(0.12, 0.25, 0.04) |
| | 991 | + const leftLapel = new THREE.Mesh(lapelGeom, suitMat) |
| | 992 | + leftLapel.position.set(0.42, 0.48, 0.12) |
| | 993 | + leftLapel.rotation.z = -0.25 |
| | 994 | + leftLapel.rotation.y = 0.4 |
| | 995 | + group.add(leftLapel) |
| | 996 | + |
| | 997 | + const rightLapel = new THREE.Mesh(lapelGeom, suitMat) |
| | 998 | + rightLapel.position.set(0.42, 0.48, -0.12) |
| | 999 | + rightLapel.rotation.z = -0.25 |
| | 1000 | + rightLapel.rotation.y = -0.4 |
| | 1001 | + group.add(rightLapel) |
| | 1002 | + |
| | 1003 | + // Collar |
| | 1004 | + const collarGeom = new THREE.BoxGeometry(0.06, 0.08, 0.05) |
| | 1005 | + const leftCollar = new THREE.Mesh(collarGeom, shirtMat) |
| | 1006 | + leftCollar.position.set(0.38, 0.62, 0.06) |
| | 1007 | + leftCollar.rotation.z = -0.4 |
| | 1008 | + group.add(leftCollar) |
| | 1009 | + |
| | 1010 | + const rightCollar = new THREE.Mesh(collarGeom, shirtMat) |
| | 1011 | + rightCollar.position.set(0.38, 0.62, -0.06) |
| | 1012 | + rightCollar.rotation.z = -0.4 |
| | 1013 | + group.add(rightCollar) |
| | 1014 | + |
| | 1015 | + // Tie |
| | 1016 | + const tieKnotGeom = new THREE.BoxGeometry(0.04, 0.04, 0.03) |
| | 1017 | + const tieKnot = new THREE.Mesh(tieKnotGeom, tieMat) |
| | 1018 | + tieKnot.position.set(0.40, 0.58, 0) |
| | 1019 | + group.add(tieKnot) |
| | 1020 | + |
| | 1021 | + const tieBodyGeom = new THREE.BoxGeometry(0.06, 0.25, 0.02) |
| | 1022 | + const tieBody = new THREE.Mesh(tieBodyGeom, tieMat) |
| | 1023 | + tieBody.position.set(0.42, 0.42, 0) |
| | 1024 | + tieBody.rotation.z = -0.05 |
| | 1025 | + group.add(tieBody) |
| | 1026 | + |
| | 1027 | + // Tie point |
| | 1028 | + const tiePointGeom = new THREE.ConeGeometry(0.035, 0.06, 4) |
| | 1029 | + const tiePoint = new THREE.Mesh(tiePointGeom, tieMat) |
| | 1030 | + tiePoint.position.set(0.43, 0.28, 0) |
| | 1031 | + tiePoint.rotation.z = Math.PI |
| | 1032 | + group.add(tiePoint) |
| | 1033 | + |
| | 1034 | + // Pocket square |
| | 1035 | + const squareGeom = new THREE.BoxGeometry(0.04, 0.05, 0.02) |
| | 1036 | + const squareMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap }) |
| | 1037 | + const square = new THREE.Mesh(squareGeom, squareMat) |
| | 1038 | + square.position.set(0.4, 0.5, -0.2) |
| | 1039 | + group.add(square) |
| | 1040 | + |
| | 1041 | + // Buttons |
| | 1042 | + const buttonGeom = new THREE.SphereGeometry(0.015, 6, 4) |
| | 1043 | + const button1 = new THREE.Mesh(buttonGeom, goldMat) |
| | 1044 | + button1.position.set(0.44, 0.35, 0.15) |
| | 1045 | + group.add(button1) |
| | 1046 | + |
| | 1047 | + const button2 = new THREE.Mesh(buttonGeom, goldMat) |
| | 1048 | + button2.position.set(0.44, 0.35, -0.15) |
| | 1049 | + group.add(button2) |
| | 1050 | + |
| 237 | return group | 1051 | return group |
| 238 | } | 1052 | } |
| 239 | } | 1053 | } |