@@ -676,6 +676,280 @@ export const OUTFITS = { |
| 676 | 676 | } |
| 677 | 677 | }, |
| 678 | 678 | |
| 679 | + // Doug outfits - full costumes with accessories |
| 680 | + doug_chef: { |
| 681 | + id: 'doug_chef', |
| 682 | + name: 'Chef Doug', |
| 683 | + character: CHARACTERS.DOUG, |
| 684 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| 685 | + conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD], |
| 686 | + price: 65, |
| 687 | + meshFactory: (gradientMap) => { |
| 688 | + const group = new THREE.Group() |
| 689 | + const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap }) |
| 690 | + const apronMat = new THREE.MeshToonMaterial({ color: 0xf5f5f5, gradientMap }) |
| 691 | + const strapMat = new THREE.MeshToonMaterial({ color: 0xe8e8e8, gradientMap }) |
| 692 | + |
| 693 | + // Chef's toque (tall puffy hat) |
| 694 | + const toqueBase = new THREE.CylinderGeometry(0.18, 0.2, 0.08, 12) |
| 695 | + const toqueBaseMesh = new THREE.Mesh(toqueBase, whiteMat) |
| 696 | + toqueBaseMesh.position.set(0.1, 0.75, 0) |
| 697 | + group.add(toqueBaseMesh) |
| 698 | + |
| 699 | + // Puffy top of toque (multiple spheres for volume) |
| 700 | + const puffGeom = new THREE.SphereGeometry(0.14, 8, 6) |
| 701 | + const puff1 = new THREE.Mesh(puffGeom, whiteMat) |
| 702 | + puff1.position.set(0.1, 0.9, 0) |
| 703 | + group.add(puff1) |
| 704 | + |
| 705 | + const puff2 = new THREE.Mesh(puffGeom, whiteMat) |
| 706 | + puff2.position.set(0.08, 0.98, 0.05) |
| 707 | + puff2.scale.set(0.9, 0.85, 0.9) |
| 708 | + group.add(puff2) |
| 709 | + |
| 710 | + const puff3 = new THREE.Mesh(puffGeom, whiteMat) |
| 711 | + puff3.position.set(0.12, 0.95, -0.05) |
| 712 | + puff3.scale.set(0.85, 0.8, 0.85) |
| 713 | + group.add(puff3) |
| 714 | + |
| 715 | + // Apron body |
| 716 | + const apronBodyGeom = new THREE.BoxGeometry(0.08, 0.4, 0.45) |
| 717 | + const apronBody = new THREE.Mesh(apronBodyGeom, apronMat) |
| 718 | + apronBody.position.set(0.38, 0.2, 0) |
| 719 | + group.add(apronBody) |
| 720 | + |
| 721 | + // Apron bib (upper part) |
| 722 | + const bibGeom = new THREE.BoxGeometry(0.06, 0.2, 0.3) |
| 723 | + const bib = new THREE.Mesh(bibGeom, apronMat) |
| 724 | + bib.position.set(0.4, 0.45, 0) |
| 725 | + group.add(bib) |
| 726 | + |
| 727 | + // Neck strap |
| 728 | + const neckStrapGeom = new THREE.TorusGeometry(0.15, 0.015, 4, 12, Math.PI) |
| 729 | + const neckStrap = new THREE.Mesh(neckStrapGeom, strapMat) |
| 730 | + neckStrap.position.set(0.25, 0.55, 0) |
| 731 | + neckStrap.rotation.z = Math.PI |
| 732 | + neckStrap.rotation.y = Math.PI / 2 |
| 733 | + group.add(neckStrap) |
| 734 | + |
| 735 | + // Waist ties (hanging at sides) |
| 736 | + const tieGeom = new THREE.BoxGeometry(0.2, 0.02, 0.03) |
| 737 | + const leftTie = new THREE.Mesh(tieGeom, strapMat) |
| 738 | + leftTie.position.set(0.25, 0.25, 0.25) |
| 739 | + leftTie.rotation.y = 0.3 |
| 740 | + group.add(leftTie) |
| 741 | + |
| 742 | + const rightTie = new THREE.Mesh(tieGeom, strapMat) |
| 743 | + rightTie.position.set(0.25, 0.25, -0.25) |
| 744 | + rightTie.rotation.y = -0.3 |
| 745 | + group.add(rightTie) |
| 746 | + |
| 747 | + // Apron pocket |
| 748 | + const pocketGeom = new THREE.BoxGeometry(0.02, 0.1, 0.15) |
| 749 | + const pocket = new THREE.Mesh(pocketGeom, strapMat) |
| 750 | + pocket.position.set(0.42, 0.18, 0) |
| 751 | + group.add(pocket) |
| 752 | + |
| 753 | + return group |
| 754 | + } |
| 755 | + }, |
| 756 | + |
| 757 | + doug_superhero: { |
| 758 | + id: 'doug_superhero', |
| 759 | + name: 'Super Duck', |
| 760 | + character: CHARACTERS.DOUG, |
| 761 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| 762 | + price: 80, |
| 763 | + meshFactory: (gradientMap) => { |
| 764 | + const group = new THREE.Group() |
| 765 | + const capeMat = new THREE.MeshToonMaterial({ color: 0xcc0000, gradientMap, side: THREE.DoubleSide }) |
| 766 | + const capeInnerMat = new THREE.MeshToonMaterial({ color: 0xffcc00, gradientMap, side: THREE.DoubleSide }) |
| 767 | + const maskMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap }) |
| 768 | + const emblemMat = new THREE.MeshToonMaterial({ color: 0xffcc00, gradientMap }) |
| 769 | + |
| 770 | + // Cape - draped from shoulders |
| 771 | + const capeShape = new THREE.Shape() |
| 772 | + capeShape.moveTo(-0.35, 0) |
| 773 | + capeShape.lineTo(-0.4, -0.5) |
| 774 | + capeShape.quadraticCurveTo(0, -0.6, 0.4, -0.5) |
| 775 | + capeShape.lineTo(0.35, 0) |
| 776 | + capeShape.quadraticCurveTo(0, 0.05, -0.35, 0) |
| 777 | + |
| 778 | + const capeGeom = new THREE.ShapeGeometry(capeShape) |
| 779 | + const cape = new THREE.Mesh(capeGeom, capeMat) |
| 780 | + cape.position.set(-0.2, 0.4, 0) |
| 781 | + cape.rotation.y = Math.PI / 2 |
| 782 | + cape.rotation.x = 0.2 |
| 783 | + group.add(cape) |
| 784 | + |
| 785 | + // Cape inner lining (yellow) |
| 786 | + const capeInner = new THREE.Mesh(capeGeom, capeInnerMat) |
| 787 | + capeInner.position.set(-0.22, 0.4, 0) |
| 788 | + capeInner.rotation.y = Math.PI / 2 |
| 789 | + capeInner.rotation.x = 0.2 |
| 790 | + group.add(capeInner) |
| 791 | + |
| 792 | + // Cape clasp at neck |
| 793 | + const claspGeom = new THREE.SphereGeometry(0.04, 8, 6) |
| 794 | + const claspL = new THREE.Mesh(claspGeom, emblemMat) |
| 795 | + claspL.position.set(0.15, 0.52, 0.18) |
| 796 | + group.add(claspL) |
| 797 | + |
| 798 | + const claspR = new THREE.Mesh(claspGeom, emblemMat) |
| 799 | + claspR.position.set(0.15, 0.52, -0.18) |
| 800 | + group.add(claspR) |
| 801 | + |
| 802 | + // Domino mask |
| 803 | + const maskGeom = new THREE.BoxGeometry(0.06, 0.08, 0.35) |
| 804 | + const mask = new THREE.Mesh(maskGeom, maskMat) |
| 805 | + mask.position.set(0.48, 0.58, 0) |
| 806 | + group.add(mask) |
| 807 | + |
| 808 | + // Eye holes in mask |
| 809 | + const eyeHoleGeom = new THREE.CircleGeometry(0.04, 8) |
| 810 | + const eyeHoleMat = new THREE.MeshBasicMaterial({ color: 0x000000 }) |
| 811 | + const eyeL = new THREE.Mesh(eyeHoleGeom, eyeHoleMat) |
| 812 | + eyeL.position.set(0.52, 0.58, 0.08) |
| 813 | + eyeL.rotation.y = Math.PI / 2 |
| 814 | + group.add(eyeL) |
| 815 | + |
| 816 | + const eyeR = new THREE.Mesh(eyeHoleGeom, eyeHoleMat) |
| 817 | + eyeR.position.set(0.52, 0.58, -0.08) |
| 818 | + eyeR.rotation.y = Math.PI / 2 |
| 819 | + group.add(eyeR) |
| 820 | + |
| 821 | + // Chest emblem - "D" shield |
| 822 | + const shieldGeom = new THREE.CircleGeometry(0.1, 6) |
| 823 | + const shield = new THREE.Mesh(shieldGeom, emblemMat) |
| 824 | + shield.position.set(0.42, 0.38, 0) |
| 825 | + shield.rotation.y = Math.PI / 2 |
| 826 | + group.add(shield) |
| 827 | + |
| 828 | + // D letter on shield |
| 829 | + const dGeom = new THREE.TorusGeometry(0.04, 0.015, 4, 8, Math.PI) |
| 830 | + const dLetter = new THREE.Mesh(dGeom, capeMat) |
| 831 | + dLetter.position.set(0.44, 0.38, 0) |
| 832 | + dLetter.rotation.y = Math.PI / 2 |
| 833 | + dLetter.rotation.z = -Math.PI / 2 |
| 834 | + group.add(dLetter) |
| 835 | + |
| 836 | + return group |
| 837 | + } |
| 838 | + }, |
| 839 | + |
| 840 | + doug_hoodie: { |
| 841 | + id: 'doug_hoodie', |
| 842 | + name: 'Cozy Hoodie', |
| 843 | + character: CHARACTERS.DOUG, |
| 844 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| 845 | + conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD], |
| 846 | + price: 55, |
| 847 | + meshFactory: (gradientMap) => { |
| 848 | + const group = new THREE.Group() |
| 849 | + const hoodieMat = new THREE.MeshToonMaterial({ color: 0x4a5568, gradientMap }) |
| 850 | + const hoodInnerMat = new THREE.MeshToonMaterial({ color: 0x2d3748, gradientMap }) |
| 851 | + const stringMat = new THREE.MeshToonMaterial({ color: 0xe2e8f0, gradientMap }) |
| 852 | + |
| 853 | + // Hoodie body |
| 854 | + const bodyGeom = new THREE.SphereGeometry(0.46, 10, 8) |
| 855 | + bodyGeom.scale(1.38, 0.76, 0.92) |
| 856 | + const body = new THREE.Mesh(bodyGeom, hoodieMat) |
| 857 | + body.position.set(0, 0.28, 0) |
| 858 | + group.add(body) |
| 859 | + |
| 860 | + // Hood (up, around head) |
| 861 | + const hoodGeom = new THREE.SphereGeometry(0.28, 10, 8, 0, Math.PI * 2, 0, Math.PI * 0.6) |
| 862 | + const hood = new THREE.Mesh(hoodGeom, hoodieMat) |
| 863 | + hood.position.set(0.15, 0.58, 0) |
| 864 | + hood.rotation.z = -0.3 |
| 865 | + hood.rotation.x = 0.1 |
| 866 | + group.add(hood) |
| 867 | + |
| 868 | + // Hood inner lining |
| 869 | + const hoodInnerGeom = new THREE.SphereGeometry(0.26, 10, 8, 0, Math.PI * 2, 0, Math.PI * 0.55) |
| 870 | + const hoodInner = new THREE.Mesh(hoodInnerGeom, hoodInnerMat) |
| 871 | + hoodInner.position.set(0.16, 0.58, 0) |
| 872 | + hoodInner.rotation.z = -0.3 |
| 873 | + hoodInner.rotation.x = 0.1 |
| 874 | + group.add(hoodInner) |
| 875 | + |
| 876 | + // Hoodie opening for face |
| 877 | + const openingGeom = new THREE.TorusGeometry(0.18, 0.025, 6, 12) |
| 878 | + const opening = new THREE.Mesh(openingGeom, hoodInnerMat) |
| 879 | + opening.position.set(0.38, 0.6, 0) |
| 880 | + opening.rotation.y = Math.PI / 2 |
| 881 | + opening.rotation.z = -0.1 |
| 882 | + group.add(opening) |
| 883 | + |
| 884 | + // Drawstrings |
| 885 | + const stringGeom = new THREE.CylinderGeometry(0.008, 0.008, 0.2, 4) |
| 886 | + const stringL = new THREE.Mesh(stringGeom, stringMat) |
| 887 | + stringL.position.set(0.42, 0.42, 0.1) |
| 888 | + stringL.rotation.z = 0.3 |
| 889 | + group.add(stringL) |
| 890 | + |
| 891 | + const stringR = new THREE.Mesh(stringGeom, stringMat) |
| 892 | + stringR.position.set(0.42, 0.42, -0.1) |
| 893 | + stringR.rotation.z = 0.3 |
| 894 | + group.add(stringR) |
| 895 | + |
| 896 | + // String tips (little plastic aglets) |
| 897 | + const agletGeom = new THREE.CylinderGeometry(0.012, 0.008, 0.03, 4) |
| 898 | + const agletL = new THREE.Mesh(agletGeom, stringMat) |
| 899 | + agletL.position.set(0.48, 0.32, 0.1) |
| 900 | + group.add(agletL) |
| 901 | + |
| 902 | + const agletR = new THREE.Mesh(agletGeom, stringMat) |
| 903 | + agletR.position.set(0.48, 0.32, -0.1) |
| 904 | + group.add(agletR) |
| 905 | + |
| 906 | + // Front pocket (kangaroo pouch) |
| 907 | + const pocketGeom = new THREE.SphereGeometry(0.2, 8, 6, 0, Math.PI, 0, Math.PI) |
| 908 | + pocketGeom.scale(1.2, 0.5, 0.8) |
| 909 | + const pocket = new THREE.Mesh(pocketGeom, hoodInnerMat) |
| 910 | + pocket.position.set(0.4, 0.15, 0) |
| 911 | + pocket.rotation.y = Math.PI / 2 |
| 912 | + pocket.rotation.z = Math.PI |
| 913 | + group.add(pocket) |
| 914 | + |
| 915 | + // Pocket opening |
| 916 | + const pocketOpenGeom = new THREE.TorusGeometry(0.12, 0.015, 4, 12, Math.PI) |
| 917 | + const pocketOpen = new THREE.Mesh(pocketOpenGeom, hoodieMat) |
| 918 | + pocketOpen.position.set(0.42, 0.18, 0) |
| 919 | + pocketOpen.rotation.y = Math.PI / 2 |
| 920 | + pocketOpen.rotation.x = Math.PI / 2 |
| 921 | + group.add(pocketOpen) |
| 922 | + |
| 923 | + // Sleeves |
| 924 | + const sleeveGeom = new THREE.CylinderGeometry(0.09, 0.11, 0.18, 6) |
| 925 | + const leftSleeve = new THREE.Mesh(sleeveGeom, hoodieMat) |
| 926 | + leftSleeve.position.set(0.05, 0.3, 0.38) |
| 927 | + leftSleeve.rotation.x = Math.PI / 2 |
| 928 | + leftSleeve.rotation.z = 0.3 |
| 929 | + group.add(leftSleeve) |
| 930 | + |
| 931 | + const rightSleeve = new THREE.Mesh(sleeveGeom, hoodieMat) |
| 932 | + rightSleeve.position.set(0.05, 0.3, -0.38) |
| 933 | + rightSleeve.rotation.x = -Math.PI / 2 |
| 934 | + rightSleeve.rotation.z = 0.3 |
| 935 | + group.add(rightSleeve) |
| 936 | + |
| 937 | + // Ribbed cuffs |
| 938 | + const cuffGeom = new THREE.TorusGeometry(0.085, 0.02, 4, 8) |
| 939 | + const leftCuff = new THREE.Mesh(cuffGeom, hoodInnerMat) |
| 940 | + leftCuff.position.set(0.0, 0.28, 0.46) |
| 941 | + leftCuff.rotation.y = Math.PI / 2 |
| 942 | + group.add(leftCuff) |
| 943 | + |
| 944 | + const rightCuff = new THREE.Mesh(cuffGeom, hoodInnerMat) |
| 945 | + rightCuff.position.set(0.0, 0.28, -0.46) |
| 946 | + rightCuff.rotation.y = Math.PI / 2 |
| 947 | + group.add(rightCuff) |
| 948 | + |
| 949 | + return group |
| 950 | + } |
| 951 | + }, |
| 952 | + |
| 679 | 953 | // Donny outfits - starter tier |
| 680 | 954 | donny_seafoam: { |
| 681 | 955 | id: 'donny_seafoam', |
@@ -1068,6 +1342,285 @@ export const OUTFITS = { |
| 1068 | 1342 | } |
| 1069 | 1343 | }, |
| 1070 | 1344 | |
| 1345 | + // Donny outfits - full costumes |
| 1346 | + donny_tuxedo: { |
| 1347 | + id: 'donny_tuxedo', |
| 1348 | + name: 'Tuxedo', |
| 1349 | + character: CHARACTERS.DONNY, |
| 1350 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| 1351 | + price: 90, |
| 1352 | + meshFactory: (gradientMap) => { |
| 1353 | + const group = new THREE.Group() |
| 1354 | + const blackMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap }) |
| 1355 | + const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap }) |
| 1356 | + const bowMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap }) |
| 1357 | + |
| 1358 | + // White shirt front |
| 1359 | + const shirtGeom = new THREE.CylinderGeometry(0.32, 0.36, 0.5, 10, 1, false, -Math.PI * 0.3, Math.PI * 0.6) |
| 1360 | + const shirt = new THREE.Mesh(shirtGeom, whiteMat) |
| 1361 | + shirt.position.set(0, 0.2, 0.15) |
| 1362 | + shirt.rotation.x = Math.PI / 2 |
| 1363 | + group.add(shirt) |
| 1364 | + |
| 1365 | + // Tuxedo jacket body |
| 1366 | + const jacketGeom = new THREE.CylinderGeometry(0.38, 0.44, 0.65, 10) |
| 1367 | + const jacket = new THREE.Mesh(jacketGeom, blackMat) |
| 1368 | + jacket.position.set(0, 0.15, 0) |
| 1369 | + jacket.rotation.x = Math.PI / 2 |
| 1370 | + group.add(jacket) |
| 1371 | + |
| 1372 | + // Jacket lapels (satin) |
| 1373 | + const lapelGeom = new THREE.BoxGeometry(0.35, 0.04, 0.12) |
| 1374 | + const leftLapel = new THREE.Mesh(lapelGeom, blackMat) |
| 1375 | + leftLapel.position.set(0.08, 0.4, 0.2) |
| 1376 | + leftLapel.rotation.z = 0.3 |
| 1377 | + leftLapel.rotation.y = 0.2 |
| 1378 | + group.add(leftLapel) |
| 1379 | + |
| 1380 | + const rightLapel = new THREE.Mesh(lapelGeom, blackMat) |
| 1381 | + rightLapel.position.set(0.08, 0.4, -0.2) |
| 1382 | + rightLapel.rotation.z = 0.3 |
| 1383 | + rightLapel.rotation.y = -0.2 |
| 1384 | + group.add(rightLapel) |
| 1385 | + |
| 1386 | + // Bow tie |
| 1387 | + const bowWingGeom = new THREE.BoxGeometry(0.08, 0.04, 0.03) |
| 1388 | + const bowL = new THREE.Mesh(bowWingGeom, bowMat) |
| 1389 | + bowL.position.set(0.05, 0.52, 0.06) |
| 1390 | + bowL.rotation.z = 0.2 |
| 1391 | + group.add(bowL) |
| 1392 | + |
| 1393 | + const bowR = new THREE.Mesh(bowWingGeom, bowMat) |
| 1394 | + bowR.position.set(0.05, 0.52, -0.06) |
| 1395 | + bowR.rotation.z = -0.2 |
| 1396 | + group.add(bowR) |
| 1397 | + |
| 1398 | + const bowKnotGeom = new THREE.SphereGeometry(0.025, 6, 4) |
| 1399 | + const bowKnot = new THREE.Mesh(bowKnotGeom, bowMat) |
| 1400 | + bowKnot.position.set(0.05, 0.52, 0) |
| 1401 | + group.add(bowKnot) |
| 1402 | + |
| 1403 | + // Jacket tails (back) |
| 1404 | + const tailGeom = new THREE.BoxGeometry(0.3, 0.06, 0.15) |
| 1405 | + const leftTail = new THREE.Mesh(tailGeom, blackMat) |
| 1406 | + leftTail.position.set(-0.25, -0.05, 0.12) |
| 1407 | + leftTail.rotation.x = -0.3 |
| 1408 | + group.add(leftTail) |
| 1409 | + |
| 1410 | + const rightTail = new THREE.Mesh(tailGeom, blackMat) |
| 1411 | + rightTail.position.set(-0.25, -0.05, -0.12) |
| 1412 | + rightTail.rotation.x = 0.3 |
| 1413 | + group.add(rightTail) |
| 1414 | + |
| 1415 | + // Shirt buttons |
| 1416 | + const buttonGeom = new THREE.SphereGeometry(0.015, 6, 4) |
| 1417 | + for (let i = 0; i < 3; i++) { |
| 1418 | + const btn = new THREE.Mesh(buttonGeom, blackMat) |
| 1419 | + btn.position.set(0.08, 0.35 - i * 0.1, 0.28) |
| 1420 | + group.add(btn) |
| 1421 | + } |
| 1422 | + |
| 1423 | + return group |
| 1424 | + } |
| 1425 | + }, |
| 1426 | + |
| 1427 | + donny_captain: { |
| 1428 | + id: 'donny_captain', |
| 1429 | + name: 'Sea Captain', |
| 1430 | + character: CHARACTERS.DONNY, |
| 1431 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| 1432 | + conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD], |
| 1433 | + price: 110, |
| 1434 | + meshFactory: (gradientMap) => { |
| 1435 | + const group = new THREE.Group() |
| 1436 | + const navyMat = new THREE.MeshToonMaterial({ color: 0x1a1a3a, gradientMap }) |
| 1437 | + const goldMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap }) |
| 1438 | + const whiteMat = new THREE.MeshToonMaterial({ color: 0xffffff, gradientMap }) |
| 1439 | + const blackMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap }) |
| 1440 | + |
| 1441 | + // Captain's coat |
| 1442 | + const coatGeom = new THREE.CylinderGeometry(0.4, 0.46, 0.7, 10) |
| 1443 | + const coat = new THREE.Mesh(coatGeom, navyMat) |
| 1444 | + coat.position.set(0, 0.12, 0) |
| 1445 | + coat.rotation.x = Math.PI / 2 |
| 1446 | + group.add(coat) |
| 1447 | + |
| 1448 | + // High collar |
| 1449 | + const collarGeom = new THREE.CylinderGeometry(0.36, 0.34, 0.1, 10, 1, true) |
| 1450 | + const collar = new THREE.Mesh(collarGeom, navyMat) |
| 1451 | + collar.position.set(0, 0.5, 0) |
| 1452 | + collar.rotation.x = Math.PI / 2 |
| 1453 | + group.add(collar) |
| 1454 | + |
| 1455 | + // Gold collar trim |
| 1456 | + const trimGeom = new THREE.TorusGeometry(0.35, 0.015, 4, 12) |
| 1457 | + const trim = new THREE.Mesh(trimGeom, goldMat) |
| 1458 | + trim.position.set(0, 0.55, 0) |
| 1459 | + trim.rotation.x = Math.PI / 2 |
| 1460 | + group.add(trim) |
| 1461 | + |
| 1462 | + // Epaulettes |
| 1463 | + const epGeom = new THREE.BoxGeometry(0.12, 0.03, 0.1) |
| 1464 | + const epL = new THREE.Mesh(epGeom, goldMat) |
| 1465 | + epL.position.set(0, 0.45, 0.38) |
| 1466 | + group.add(epL) |
| 1467 | + |
| 1468 | + const epR = new THREE.Mesh(epGeom, goldMat) |
| 1469 | + epR.position.set(0, 0.45, -0.38) |
| 1470 | + group.add(epR) |
| 1471 | + |
| 1472 | + // Gold fringe on epaulettes |
| 1473 | + for (let i = 0; i < 3; i++) { |
| 1474 | + const fringeGeom = new THREE.CylinderGeometry(0.008, 0.005, 0.06, 4) |
| 1475 | + const fringeL = new THREE.Mesh(fringeGeom, goldMat) |
| 1476 | + fringeL.position.set(-0.02, 0.42, 0.35 + i * 0.03) |
| 1477 | + group.add(fringeL) |
| 1478 | + |
| 1479 | + const fringeR = new THREE.Mesh(fringeGeom, goldMat) |
| 1480 | + fringeR.position.set(-0.02, 0.42, -0.35 - i * 0.03) |
| 1481 | + group.add(fringeR) |
| 1482 | + } |
| 1483 | + |
| 1484 | + // Gold buttons |
| 1485 | + const buttonGeom = new THREE.SphereGeometry(0.02, 6, 4) |
| 1486 | + for (let i = 0; i < 4; i++) { |
| 1487 | + const btn = new THREE.Mesh(buttonGeom, goldMat) |
| 1488 | + btn.position.set(0.08, 0.35 - i * 0.1, 0.15) |
| 1489 | + group.add(btn) |
| 1490 | + } |
| 1491 | + |
| 1492 | + // Captain's peaked cap |
| 1493 | + const capBrimGeom = new THREE.CylinderGeometry(0.22, 0.22, 0.03, 12) |
| 1494 | + const capBrim = new THREE.Mesh(capBrimGeom, blackMat) |
| 1495 | + capBrim.position.set(0.2, 0.7, 0) |
| 1496 | + capBrim.rotation.z = -0.2 |
| 1497 | + group.add(capBrim) |
| 1498 | + |
| 1499 | + // Cap crown |
| 1500 | + const capCrownGeom = new THREE.CylinderGeometry(0.18, 0.2, 0.12, 12) |
| 1501 | + const capCrown = new THREE.Mesh(capCrownGeom, navyMat) |
| 1502 | + capCrown.position.set(0.18, 0.78, 0) |
| 1503 | + capCrown.rotation.z = -0.2 |
| 1504 | + group.add(capCrown) |
| 1505 | + |
| 1506 | + // Cap badge |
| 1507 | + const badgeGeom = new THREE.CircleGeometry(0.06, 8) |
| 1508 | + const badge = new THREE.Mesh(badgeGeom, goldMat) |
| 1509 | + badge.position.set(0.32, 0.78, 0) |
| 1510 | + badge.rotation.y = Math.PI / 2 |
| 1511 | + badge.rotation.z = -0.2 |
| 1512 | + group.add(badge) |
| 1513 | + |
| 1514 | + // Anchor on badge |
| 1515 | + const anchorGeom = new THREE.TorusGeometry(0.025, 0.006, 4, 8, Math.PI) |
| 1516 | + const anchor = new THREE.Mesh(anchorGeom, whiteMat) |
| 1517 | + anchor.position.set(0.34, 0.78, 0) |
| 1518 | + anchor.rotation.y = Math.PI / 2 |
| 1519 | + anchor.rotation.x = Math.PI |
| 1520 | + group.add(anchor) |
| 1521 | + |
| 1522 | + return group |
| 1523 | + } |
| 1524 | + }, |
| 1525 | + |
| 1526 | + donny_magician: { |
| 1527 | + id: 'donny_magician', |
| 1528 | + name: 'Magician', |
| 1529 | + character: CHARACTERS.DONNY, |
| 1530 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| 1531 | + conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD], |
| 1532 | + price: 95, |
| 1533 | + meshFactory: (gradientMap) => { |
| 1534 | + const group = new THREE.Group() |
| 1535 | + const capeMat = new THREE.MeshToonMaterial({ color: 0x2d0a4e, gradientMap, side: THREE.DoubleSide }) |
| 1536 | + const capeInnerMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap, side: THREE.DoubleSide }) |
| 1537 | + const hatMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap }) |
| 1538 | + const starMat = new THREE.MeshToonMaterial({ color: 0xffd700, gradientMap }) |
| 1539 | + const vestMat = new THREE.MeshToonMaterial({ color: 0x4a0080, gradientMap }) |
| 1540 | + |
| 1541 | + // Vest underneath |
| 1542 | + const vestGeom = new THREE.CylinderGeometry(0.36, 0.4, 0.5, 10) |
| 1543 | + const vest = new THREE.Mesh(vestGeom, vestMat) |
| 1544 | + vest.position.set(0, 0.18, 0) |
| 1545 | + vest.rotation.x = Math.PI / 2 |
| 1546 | + group.add(vest) |
| 1547 | + |
| 1548 | + // Cape (draped around shoulders) |
| 1549 | + const capeShape = new THREE.Shape() |
| 1550 | + capeShape.moveTo(-0.4, 0) |
| 1551 | + capeShape.lineTo(-0.45, -0.55) |
| 1552 | + capeShape.quadraticCurveTo(0, -0.65, 0.45, -0.55) |
| 1553 | + capeShape.lineTo(0.4, 0) |
| 1554 | + capeShape.quadraticCurveTo(0, 0.05, -0.4, 0) |
| 1555 | + |
| 1556 | + const capeGeom = new THREE.ShapeGeometry(capeShape) |
| 1557 | + const cape = new THREE.Mesh(capeGeom, capeMat) |
| 1558 | + cape.position.set(-0.15, 0.35, 0) |
| 1559 | + cape.rotation.y = Math.PI / 2 |
| 1560 | + group.add(cape) |
| 1561 | + |
| 1562 | + // Cape inner (gold lining) |
| 1563 | + const capeInner = new THREE.Mesh(capeGeom, capeInnerMat) |
| 1564 | + capeInner.position.set(-0.17, 0.35, 0) |
| 1565 | + capeInner.rotation.y = Math.PI / 2 |
| 1566 | + group.add(capeInner) |
| 1567 | + |
| 1568 | + // Stars on cape |
| 1569 | + const starPositions = [ |
| 1570 | + { x: -0.2, y: 0.25, z: 0.2 }, |
| 1571 | + { x: -0.25, y: 0.1, z: -0.15 }, |
| 1572 | + { x: -0.22, y: 0.0, z: 0.25 } |
| 1573 | + ] |
| 1574 | + for (const pos of starPositions) { |
| 1575 | + const starGeom = new THREE.CircleGeometry(0.03, 5) |
| 1576 | + const star = new THREE.Mesh(starGeom, starMat) |
| 1577 | + star.position.set(pos.x, pos.y, pos.z) |
| 1578 | + star.rotation.y = Math.PI / 2 |
| 1579 | + star.rotation.z = Math.PI / 10 |
| 1580 | + group.add(star) |
| 1581 | + } |
| 1582 | + |
| 1583 | + // Cape clasp (gem) |
| 1584 | + const claspGeom = new THREE.OctahedronGeometry(0.04) |
| 1585 | + const claspMat = new THREE.MeshToonMaterial({ color: 0xff0066, gradientMap }) |
| 1586 | + const clasp = new THREE.Mesh(claspGeom, claspMat) |
| 1587 | + clasp.position.set(0.08, 0.5, 0) |
| 1588 | + group.add(clasp) |
| 1589 | + |
| 1590 | + // Top hat |
| 1591 | + const hatBrimGeom = new THREE.CylinderGeometry(0.22, 0.22, 0.03, 12) |
| 1592 | + const hatBrim = new THREE.Mesh(hatBrimGeom, hatMat) |
| 1593 | + hatBrim.position.set(0.15, 0.68, 0) |
| 1594 | + hatBrim.rotation.z = -0.15 |
| 1595 | + group.add(hatBrim) |
| 1596 | + |
| 1597 | + // Hat crown (tall) |
| 1598 | + const hatCrownGeom = new THREE.CylinderGeometry(0.14, 0.14, 0.25, 12) |
| 1599 | + const hatCrown = new THREE.Mesh(hatCrownGeom, hatMat) |
| 1600 | + hatCrown.position.set(0.13, 0.82, 0) |
| 1601 | + hatCrown.rotation.z = -0.15 |
| 1602 | + group.add(hatCrown) |
| 1603 | + |
| 1604 | + // Hat band |
| 1605 | + const bandGeom = new THREE.TorusGeometry(0.14, 0.015, 4, 12) |
| 1606 | + const band = new THREE.Mesh(bandGeom, starMat) |
| 1607 | + band.position.set(0.14, 0.72, 0) |
| 1608 | + band.rotation.x = Math.PI / 2 |
| 1609 | + band.rotation.y = 0.15 |
| 1610 | + group.add(band) |
| 1611 | + |
| 1612 | + // Star on hat |
| 1613 | + const hatStarGeom = new THREE.CircleGeometry(0.035, 5) |
| 1614 | + const hatStar = new THREE.Mesh(hatStarGeom, starMat) |
| 1615 | + hatStar.position.set(0.27, 0.82, 0) |
| 1616 | + hatStar.rotation.y = Math.PI / 2 |
| 1617 | + hatStar.rotation.z = Math.PI / 10 |
| 1618 | + group.add(hatStar) |
| 1619 | + |
| 1620 | + return group |
| 1621 | + } |
| 1622 | + }, |
| 1623 | + |
| 1071 | 1624 | // Ollie outfits |
| 1072 | 1625 | ollie_coral: { |
| 1073 | 1626 | id: 'ollie_coral', |
@@ -1482,6 +2035,362 @@ export const OUTFITS = { |
| 1482 | 2035 | button2.position.set(0.44, 0.35, -0.15) |
| 1483 | 2036 | group.add(button2) |
| 1484 | 2037 | |
| 2038 | + return group |
| 2039 | + } |
| 2040 | + }, |
| 2041 | + |
| 2042 | + // Ollie outfits - full costumes |
| 2043 | + ollie_scientist: { |
| 2044 | + id: 'ollie_scientist', |
| 2045 | + name: 'Mad Scientist', |
| 2046 | + character: CHARACTERS.OLLIE, |
| 2047 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| 2048 | + conflictsWith: [OUTFIT_TYPES.ACCESSORY_FACE], |
| 2049 | + price: 85, |
| 2050 | + meshFactory: (gradientMap) => { |
| 2051 | + const group = new THREE.Group() |
| 2052 | + const labCoatMat = new THREE.MeshToonMaterial({ color: 0xf5f5f5, gradientMap }) |
| 2053 | + const goggleMat = new THREE.MeshToonMaterial({ color: 0x8b4513, gradientMap }) |
| 2054 | + const lensMat = new THREE.MeshBasicMaterial({ color: 0x66ffff, transparent: true, opacity: 0.7 }) |
| 2055 | + const flaskMat = new THREE.MeshBasicMaterial({ color: 0x88ff88, transparent: true, opacity: 0.6 }) |
| 2056 | + const glassMat = new THREE.MeshBasicMaterial({ color: 0xccffcc, transparent: true, opacity: 0.4 }) |
| 2057 | + |
| 2058 | + // Lab coat body |
| 2059 | + const coatGeom = new THREE.SphereGeometry(0.46, 10, 8) |
| 2060 | + coatGeom.scale(1, 0.85, 1) |
| 2061 | + const coat = new THREE.Mesh(coatGeom, labCoatMat) |
| 2062 | + coat.position.set(0, 0.35, 0) |
| 2063 | + group.add(coat) |
| 2064 | + |
| 2065 | + // Coat collar |
| 2066 | + const collarGeom = new THREE.TorusGeometry(0.28, 0.04, 4, 12, Math.PI) |
| 2067 | + const collar = new THREE.Mesh(collarGeom, labCoatMat) |
| 2068 | + collar.position.set(0, 0.62, 0.15) |
| 2069 | + collar.rotation.x = Math.PI / 2 + 0.3 |
| 2070 | + group.add(collar) |
| 2071 | + |
| 2072 | + // Coat lapels |
| 2073 | + const lapelGeom = new THREE.BoxGeometry(0.08, 0.2, 0.06) |
| 2074 | + const lapelL = new THREE.Mesh(lapelGeom, labCoatMat) |
| 2075 | + lapelL.position.set(0.38, 0.5, 0.12) |
| 2076 | + lapelL.rotation.z = -0.2 |
| 2077 | + group.add(lapelL) |
| 2078 | + |
| 2079 | + const lapelR = new THREE.Mesh(lapelGeom, labCoatMat) |
| 2080 | + lapelR.position.set(0.38, 0.5, -0.12) |
| 2081 | + lapelR.rotation.z = -0.2 |
| 2082 | + group.add(lapelR) |
| 2083 | + |
| 2084 | + // Pocket with pens |
| 2085 | + const pocketGeom = new THREE.BoxGeometry(0.02, 0.1, 0.08) |
| 2086 | + const pocket = new THREE.Mesh(pocketGeom, labCoatMat) |
| 2087 | + pocket.position.set(0.45, 0.45, 0.15) |
| 2088 | + group.add(pocket) |
| 2089 | + |
| 2090 | + // Pens in pocket |
| 2091 | + const penMat = new THREE.MeshToonMaterial({ color: 0x1a1a1a, gradientMap }) |
| 2092 | + const penGeom = new THREE.CylinderGeometry(0.008, 0.008, 0.08, 4) |
| 2093 | + const pen1 = new THREE.Mesh(penGeom, penMat) |
| 2094 | + pen1.position.set(0.46, 0.52, 0.13) |
| 2095 | + group.add(pen1) |
| 2096 | + |
| 2097 | + const pen2Mat = new THREE.MeshToonMaterial({ color: 0x0000aa, gradientMap }) |
| 2098 | + const pen2 = new THREE.Mesh(penGeom, pen2Mat) |
| 2099 | + pen2.position.set(0.46, 0.51, 0.17) |
| 2100 | + pen2.rotation.z = 0.1 |
| 2101 | + group.add(pen2) |
| 2102 | + |
| 2103 | + // Wild goggles (multiple lenses!) |
| 2104 | + const goggleStrapGeom = new THREE.TorusGeometry(0.22, 0.025, 6, 16) |
| 2105 | + const goggleStrap = new THREE.Mesh(goggleStrapGeom, goggleMat) |
| 2106 | + goggleStrap.position.set(0.15, 0.7, 0) |
| 2107 | + goggleStrap.rotation.y = Math.PI / 2 |
| 2108 | + group.add(goggleStrap) |
| 2109 | + |
| 2110 | + // Main lens (large center) |
| 2111 | + const mainLensGeom = new THREE.CylinderGeometry(0.08, 0.08, 0.04, 12) |
| 2112 | + const mainLensFrame = new THREE.Mesh(mainLensGeom, goggleMat) |
| 2113 | + mainLensFrame.position.set(0.35, 0.7, 0) |
| 2114 | + mainLensFrame.rotation.z = Math.PI / 2 |
| 2115 | + group.add(mainLensFrame) |
| 2116 | + |
| 2117 | + const mainLens = new THREE.Mesh(new THREE.CircleGeometry(0.07, 12), lensMat) |
| 2118 | + mainLens.position.set(0.38, 0.7, 0) |
| 2119 | + mainLens.rotation.y = Math.PI / 2 |
| 2120 | + group.add(mainLens) |
| 2121 | + |
| 2122 | + // Extra lens (smaller, offset) |
| 2123 | + const extraLensGeom = new THREE.CylinderGeometry(0.04, 0.04, 0.03, 10) |
| 2124 | + const extraLensFrame = new THREE.Mesh(extraLensGeom, goggleMat) |
| 2125 | + extraLensFrame.position.set(0.38, 0.78, 0.08) |
| 2126 | + extraLensFrame.rotation.z = Math.PI / 2 |
| 2127 | + group.add(extraLensFrame) |
| 2128 | + |
| 2129 | + const extraLens = new THREE.Mesh(new THREE.CircleGeometry(0.035, 10), lensMat) |
| 2130 | + extraLens.position.set(0.40, 0.78, 0.08) |
| 2131 | + extraLens.rotation.y = Math.PI / 2 |
| 2132 | + group.add(extraLens) |
| 2133 | + |
| 2134 | + // Bubbling flask (held by tentacle) |
| 2135 | + const flaskGeom = new THREE.CylinderGeometry(0.02, 0.06, 0.15, 8) |
| 2136 | + const flask = new THREE.Mesh(flaskGeom, glassMat) |
| 2137 | + flask.position.set(0.25, 0.1, 0.35) |
| 2138 | + group.add(flask) |
| 2139 | + |
| 2140 | + // Flask neck |
| 2141 | + const neckGeom = new THREE.CylinderGeometry(0.02, 0.02, 0.05, 6) |
| 2142 | + const neck = new THREE.Mesh(neckGeom, glassMat) |
| 2143 | + neck.position.set(0.25, 0.2, 0.35) |
| 2144 | + group.add(neck) |
| 2145 | + |
| 2146 | + // Bubbling liquid |
| 2147 | + const liquidGeom = new THREE.SphereGeometry(0.045, 8, 6) |
| 2148 | + const liquid = new THREE.Mesh(liquidGeom, flaskMat) |
| 2149 | + liquid.position.set(0.25, 0.08, 0.35) |
| 2150 | + group.add(liquid) |
| 2151 | + |
| 2152 | + // Bubbles |
| 2153 | + const bubbleGeom = new THREE.SphereGeometry(0.012, 6, 4) |
| 2154 | + const bubbleMat = new THREE.MeshBasicMaterial({ color: 0xaaffaa, transparent: true, opacity: 0.5 }) |
| 2155 | + for (let i = 0; i < 3; i++) { |
| 2156 | + const bubble = new THREE.Mesh(bubbleGeom, bubbleMat) |
| 2157 | + bubble.position.set(0.25 + (Math.random() - 0.5) * 0.03, 0.12 + i * 0.03, 0.35) |
| 2158 | + group.add(bubble) |
| 2159 | + } |
| 2160 | + |
| 2161 | + return group |
| 2162 | + } |
| 2163 | + }, |
| 2164 | + |
| 2165 | + ollie_explorer: { |
| 2166 | + id: 'ollie_explorer', |
| 2167 | + name: 'Explorer', |
| 2168 | + character: CHARACTERS.OLLIE, |
| 2169 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| 2170 | + conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD], |
| 2171 | + price: 75, |
| 2172 | + meshFactory: (gradientMap) => { |
| 2173 | + const group = new THREE.Group() |
| 2174 | + const vestMat = new THREE.MeshToonMaterial({ color: 0xc4a574, gradientMap }) |
| 2175 | + const pocketMat = new THREE.MeshToonMaterial({ color: 0xa08050, gradientMap }) |
| 2176 | + const helmetMat = new THREE.MeshToonMaterial({ color: 0xf5f5dc, gradientMap }) |
| 2177 | + const bandMat = new THREE.MeshToonMaterial({ color: 0x4a3728, gradientMap }) |
| 2178 | + const buttonMat = new THREE.MeshToonMaterial({ color: 0x8b4513, gradientMap }) |
| 2179 | + |
| 2180 | + // Safari vest body |
| 2181 | + const vestGeom = new THREE.SphereGeometry(0.44, 10, 8) |
| 2182 | + vestGeom.scale(1, 0.82, 1) |
| 2183 | + const vest = new THREE.Mesh(vestGeom, vestMat) |
| 2184 | + vest.position.set(0, 0.35, 0) |
| 2185 | + group.add(vest) |
| 2186 | + |
| 2187 | + // Multiple pockets (it's a safari vest!) |
| 2188 | + const pocketGeom = new THREE.BoxGeometry(0.03, 0.1, 0.1) |
| 2189 | + |
| 2190 | + // Front pockets |
| 2191 | + const pocket1 = new THREE.Mesh(pocketGeom, pocketMat) |
| 2192 | + pocket1.position.set(0.44, 0.3, 0.15) |
| 2193 | + group.add(pocket1) |
| 2194 | + |
| 2195 | + const pocket2 = new THREE.Mesh(pocketGeom, pocketMat) |
| 2196 | + pocket2.position.set(0.44, 0.3, -0.15) |
| 2197 | + group.add(pocket2) |
| 2198 | + |
| 2199 | + const pocket3 = new THREE.Mesh(pocketGeom, pocketMat) |
| 2200 | + pocket3.position.set(0.44, 0.45, 0.1) |
| 2201 | + pocket3.scale.set(1, 0.7, 0.8) |
| 2202 | + group.add(pocket3) |
| 2203 | + |
| 2204 | + // Pocket flaps |
| 2205 | + const flapGeom = new THREE.BoxGeometry(0.02, 0.02, 0.1) |
| 2206 | + const flap1 = new THREE.Mesh(flapGeom, pocketMat) |
| 2207 | + flap1.position.set(0.45, 0.36, 0.15) |
| 2208 | + group.add(flap1) |
| 2209 | + |
| 2210 | + const flap2 = new THREE.Mesh(flapGeom, pocketMat) |
| 2211 | + flap2.position.set(0.45, 0.36, -0.15) |
| 2212 | + group.add(flap2) |
| 2213 | + |
| 2214 | + // Buttons on pockets |
| 2215 | + const buttonGeom = new THREE.SphereGeometry(0.012, 6, 4) |
| 2216 | + const btn1 = new THREE.Mesh(buttonGeom, buttonMat) |
| 2217 | + btn1.position.set(0.46, 0.36, 0.15) |
| 2218 | + group.add(btn1) |
| 2219 | + |
| 2220 | + const btn2 = new THREE.Mesh(buttonGeom, buttonMat) |
| 2221 | + btn2.position.set(0.46, 0.36, -0.15) |
| 2222 | + group.add(btn2) |
| 2223 | + |
| 2224 | + // Vest collar |
| 2225 | + const collarGeom = new THREE.TorusGeometry(0.2, 0.03, 4, 10, Math.PI) |
| 2226 | + const collar = new THREE.Mesh(collarGeom, vestMat) |
| 2227 | + collar.position.set(0.1, 0.6, 0.1) |
| 2228 | + collar.rotation.x = Math.PI / 2 + 0.3 |
| 2229 | + collar.rotation.z = 0.2 |
| 2230 | + group.add(collar) |
| 2231 | + |
| 2232 | + // Pith helmet |
| 2233 | + const helmetDomeGeom = new THREE.SphereGeometry(0.2, 12, 8, 0, Math.PI * 2, 0, Math.PI * 0.6) |
| 2234 | + const helmetDome = new THREE.Mesh(helmetDomeGeom, helmetMat) |
| 2235 | + helmetDome.position.set(0.12, 0.72, 0) |
| 2236 | + group.add(helmetDome) |
| 2237 | + |
| 2238 | + // Helmet brim |
| 2239 | + const brimGeom = new THREE.TorusGeometry(0.2, 0.04, 4, 16) |
| 2240 | + const brim = new THREE.Mesh(brimGeom, helmetMat) |
| 2241 | + brim.position.set(0.12, 0.68, 0) |
| 2242 | + brim.rotation.x = Math.PI / 2 |
| 2243 | + group.add(brim) |
| 2244 | + |
| 2245 | + // Helmet band |
| 2246 | + const helmetBandGeom = new THREE.TorusGeometry(0.17, 0.02, 4, 16) |
| 2247 | + const helmetBand = new THREE.Mesh(helmetBandGeom, bandMat) |
| 2248 | + helmetBand.position.set(0.12, 0.72, 0) |
| 2249 | + helmetBand.rotation.x = Math.PI / 2 |
| 2250 | + group.add(helmetBand) |
| 2251 | + |
| 2252 | + // Helmet top button |
| 2253 | + const topButtonGeom = new THREE.SphereGeometry(0.025, 6, 4) |
| 2254 | + const topButton = new THREE.Mesh(topButtonGeom, bandMat) |
| 2255 | + topButton.position.set(0.12, 0.88, 0) |
| 2256 | + group.add(topButton) |
| 2257 | + |
| 2258 | + return group |
| 2259 | + } |
| 2260 | + }, |
| 2261 | + |
| 2262 | + ollie_artist: { |
| 2263 | + id: 'ollie_artist', |
| 2264 | + name: 'Artist', |
| 2265 | + character: CHARACTERS.OLLIE, |
| 2266 | + type: OUTFIT_TYPES.CLOTHING_BODY, |
| 2267 | + conflictsWith: [OUTFIT_TYPES.ACCESSORY_HEAD], |
| 2268 | + price: 70, |
| 2269 | + meshFactory: (gradientMap) => { |
| 2270 | + const group = new THREE.Group() |
| 2271 | + const smockMat = new THREE.MeshToonMaterial({ color: 0x4a4a6a, gradientMap }) |
| 2272 | + const beretMat = new THREE.MeshToonMaterial({ color: 0x8b0000, gradientMap }) |
| 2273 | + const paletteMat = new THREE.MeshToonMaterial({ color: 0xdeb887, gradientMap }) |
| 2274 | + |
| 2275 | + // Paint splatter colors |
| 2276 | + const paintColors = [0xff4444, 0x44ff44, 0x4444ff, 0xffff44, 0xff44ff] |
| 2277 | + |
| 2278 | + // Smock body |
| 2279 | + const smockGeom = new THREE.SphereGeometry(0.46, 10, 8) |
| 2280 | + smockGeom.scale(1, 0.85, 1) |
| 2281 | + const smock = new THREE.Mesh(smockGeom, smockMat) |
| 2282 | + smock.position.set(0, 0.35, 0) |
| 2283 | + group.add(smock) |
| 2284 | + |
| 2285 | + // Paint splatters on smock |
| 2286 | + const splatPositions = [ |
| 2287 | + { x: 0.4, y: 0.4, z: 0.2 }, |
| 2288 | + { x: 0.42, y: 0.25, z: -0.15 }, |
| 2289 | + { x: 0.38, y: 0.45, z: -0.1 }, |
| 2290 | + { x: 0.35, y: 0.3, z: 0.25 }, |
| 2291 | + { x: 0.4, y: 0.2, z: 0.1 } |
| 2292 | + ] |
| 2293 | + for (let i = 0; i < splatPositions.length; i++) { |
| 2294 | + const splatGeom = new THREE.SphereGeometry(0.03 + Math.random() * 0.02, 6, 4) |
| 2295 | + splatGeom.scale(1, 0.3, 1) |
| 2296 | + const splatMat = new THREE.MeshToonMaterial({ color: paintColors[i], gradientMap }) |
| 2297 | + const splat = new THREE.Mesh(splatGeom, splatMat) |
| 2298 | + splat.position.set(splatPositions[i].x, splatPositions[i].y, splatPositions[i].z) |
| 2299 | + splat.rotation.y = Math.random() * Math.PI |
| 2300 | + group.add(splat) |
| 2301 | + } |
| 2302 | + |
| 2303 | + // Smock collar/neckline |
| 2304 | + const collarGeom = new THREE.TorusGeometry(0.22, 0.03, 4, 12) |
| 2305 | + const collar = new THREE.Mesh(collarGeom, smockMat) |
| 2306 | + collar.position.set(0.05, 0.6, 0) |
| 2307 | + collar.rotation.x = Math.PI / 2 |
| 2308 | + collar.rotation.y = 0.2 |
| 2309 | + group.add(collar) |
| 2310 | + |
| 2311 | + // Beret |
| 2312 | + const beretGeom = new THREE.SphereGeometry(0.18, 10, 8) |
| 2313 | + beretGeom.scale(1.3, 0.5, 1.3) |
| 2314 | + const beret = new THREE.Mesh(beretGeom, beretMat) |
| 2315 | + beret.position.set(0.15, 0.78, 0.05) |
| 2316 | + beret.rotation.z = -0.3 |
| 2317 | + group.add(beret) |
| 2318 | + |
| 2319 | + // Beret stem/tip |
| 2320 | + const stemGeom = new THREE.SphereGeometry(0.03, 6, 4) |
| 2321 | + const stem = new THREE.Mesh(stemGeom, beretMat) |
| 2322 | + stem.position.set(0.15, 0.85, 0.05) |
| 2323 | + group.add(stem) |
| 2324 | + |
| 2325 | + // Beret band |
| 2326 | + const bandGeom = new THREE.TorusGeometry(0.15, 0.015, 4, 12) |
| 2327 | + const band = new THREE.Mesh(bandGeom, beretMat) |
| 2328 | + band.position.set(0.13, 0.72, 0) |
| 2329 | + band.rotation.x = Math.PI / 2 |
| 2330 | + band.rotation.y = 0.3 |
| 2331 | + group.add(band) |
| 2332 | + |
| 2333 | + // Painter's palette (held by tentacle) |
| 2334 | + const paletteShape = new THREE.Shape() |
| 2335 | + paletteShape.moveTo(0, 0) |
| 2336 | + paletteShape.quadraticCurveTo(0.1, 0.05, 0.12, 0) |
| 2337 | + paletteShape.quadraticCurveTo(0.14, -0.06, 0.08, -0.1) |
| 2338 | + paletteShape.quadraticCurveTo(0, -0.12, -0.08, -0.1) |
| 2339 | + paletteShape.quadraticCurveTo(-0.12, -0.04, -0.1, 0.02) |
| 2340 | + paletteShape.quadraticCurveTo(-0.06, 0.06, 0, 0) |
| 2341 | + |
| 2342 | + const paletteGeom = new THREE.ExtrudeGeometry(paletteShape, { |
| 2343 | + steps: 1, depth: 0.015, bevelEnabled: false |
| 2344 | + }) |
| 2345 | + const palette = new THREE.Mesh(paletteGeom, paletteMat) |
| 2346 | + palette.position.set(0.2, 0.15, 0.35) |
| 2347 | + palette.rotation.x = -0.5 |
| 2348 | + palette.rotation.y = 0.3 |
| 2349 | + group.add(palette) |
| 2350 | + |
| 2351 | + // Thumb hole |
| 2352 | + const holeGeom = new THREE.TorusGeometry(0.02, 0.008, 4, 8) |
| 2353 | + const holeMat = new THREE.MeshToonMaterial({ color: 0x000000, gradientMap }) |
| 2354 | + const hole = new THREE.Mesh(holeGeom, holeMat) |
| 2355 | + hole.position.set(0.12, 0.15, 0.35) |
| 2356 | + hole.rotation.x = -0.5 + Math.PI / 2 |
| 2357 | + hole.rotation.z = 0.3 |
| 2358 | + group.add(hole) |
| 2359 | + |
| 2360 | + // Paint blobs on palette |
| 2361 | + const blobPositions = [ |
| 2362 | + { x: 0.22, y: 0.18, z: 0.32 }, |
| 2363 | + { x: 0.18, y: 0.17, z: 0.38 }, |
| 2364 | + { x: 0.25, y: 0.16, z: 0.36 }, |
| 2365 | + { x: 0.2, y: 0.19, z: 0.34 } |
| 2366 | + ] |
| 2367 | + for (let i = 0; i < blobPositions.length; i++) { |
| 2368 | + const blobGeom = new THREE.SphereGeometry(0.015, 6, 4) |
| 2369 | + blobGeom.scale(1, 0.4, 1) |
| 2370 | + const blobMat = new THREE.MeshToonMaterial({ color: paintColors[i], gradientMap }) |
| 2371 | + const blob = new THREE.Mesh(blobGeom, blobMat) |
| 2372 | + blob.position.set(blobPositions[i].x, blobPositions[i].y, blobPositions[i].z) |
| 2373 | + group.add(blob) |
| 2374 | + } |
| 2375 | + |
| 2376 | + // Paintbrush (tiny, held) |
| 2377 | + const brushHandleGeom = new THREE.CylinderGeometry(0.008, 0.01, 0.12, 4) |
| 2378 | + const brushHandleMat = new THREE.MeshToonMaterial({ color: 0x8b4513, gradientMap }) |
| 2379 | + const brushHandle = new THREE.Mesh(brushHandleGeom, brushHandleMat) |
| 2380 | + brushHandle.position.set(0.15, 0.08, 0.4) |
| 2381 | + brushHandle.rotation.z = 0.8 |
| 2382 | + brushHandle.rotation.x = 0.3 |
| 2383 | + group.add(brushHandle) |
| 2384 | + |
| 2385 | + // Brush bristles |
| 2386 | + const bristleGeom = new THREE.ConeGeometry(0.015, 0.03, 6) |
| 2387 | + const bristleMat = new THREE.MeshToonMaterial({ color: 0x4444ff, gradientMap }) |
| 2388 | + const bristles = new THREE.Mesh(bristleGeom, bristleMat) |
| 2389 | + bristles.position.set(0.1, 0.12, 0.42) |
| 2390 | + bristles.rotation.z = 0.8 + Math.PI |
| 2391 | + bristles.rotation.x = 0.3 |
| 2392 | + group.add(bristles) |
| 2393 | + |
| 1485 | 2394 | return group |
| 1486 | 2395 | } |
| 1487 | 2396 | } |