@@ -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 | // Donny outfits - starter tier | 953 | // Donny outfits - starter tier |
| 680 | donny_seafoam: { | 954 | donny_seafoam: { |
| 681 | id: 'donny_seafoam', | 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 | // Ollie outfits | 1624 | // Ollie outfits |
| 1072 | ollie_coral: { | 1625 | ollie_coral: { |
| 1073 | id: 'ollie_coral', | 1626 | id: 'ollie_coral', |
@@ -1482,6 +2035,362 @@ export const OUTFITS = { |
| 1482 | button2.position.set(0.44, 0.35, -0.15) | 2035 | button2.position.set(0.44, 0.35, -0.15) |
| 1483 | group.add(button2) | 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 | return group | 2394 | return group |
| 1486 | } | 2395 | } |
| 1487 | } | 2396 | } |