Enhanced building architecture: chimneys, porches, bay windows on houses; balconies and cornices on apartments; glass curtain-wall office; sawtooth roof factory with loading dock; rose window church with buttresses; clock tower school; cinema with ticket booth; hospital with helipad; museum with columns and dome; park with gazebo and flower beds; ornate town square fountain
Browse files- web/3d.html +793 -314
web/3d.html
CHANGED
|
@@ -683,48 +683,101 @@ function updateBadge(badge, count) {
|
|
| 683 |
function createHouse(id, locData) {
|
| 684 |
const group = new THREE.Group();
|
| 685 |
const h = hash(id);
|
|
|
|
| 686 |
const w = 3 + (h % 2), d = 3 + ((h >> 2) % 2), wallH = 2.5 + (h % 3) * 0.3;
|
| 687 |
const wallColor = BLDG_COLORS.house[h % BLDG_COLORS.house.length];
|
| 688 |
const roofColor = BLDG_COLORS.roof[h % BLDG_COLORS.roof.length];
|
|
|
|
| 689 |
|
| 690 |
-
// Walls
|
| 691 |
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), mat(wallColor));
|
| 692 |
walls.position.y = wallH / 2;
|
| 693 |
-
walls.castShadow = true;
|
| 694 |
-
walls.receiveShadow = true;
|
| 695 |
addEdges(walls, 0x000000, 0.08);
|
| 696 |
group.add(walls);
|
| 697 |
|
| 698 |
-
|
| 699 |
-
|
| 700 |
-
|
| 701 |
-
|
| 702 |
-
|
| 703 |
-
|
| 704 |
-
|
| 705 |
-
|
| 706 |
-
|
| 707 |
-
|
| 708 |
-
|
| 709 |
-
|
| 710 |
-
|
| 711 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 712 |
door.position.set(0, 0.6, d / 2 + 0.05);
|
| 713 |
-
door.userData.isDoor = true;
|
| 714 |
-
|
|
|
|
| 715 |
|
| 716 |
-
// Windows
|
| 717 |
const winMat = mat(isNight ? BLDG_COLORS.windowLit : BLDG_COLORS.window,
|
| 718 |
isNight ? { emissive: BLDG_COLORS.windowLit, emissiveIntensity: 0.5 } : {});
|
| 719 |
for (let side of [-1, 1]) {
|
| 720 |
const win = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.5, 0.1), winMat);
|
| 721 |
win.position.set(side * (w / 2 + 0.05), wallH * 0.6, 0);
|
| 722 |
win.rotation.y = Math.PI / 2;
|
| 723 |
-
win.userData.isWindow = true;
|
| 724 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 725 |
}
|
| 726 |
|
| 727 |
-
// Bed
|
| 728 |
const bed = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.15, 0.7), mat(0x8b6040));
|
| 729 |
bed.position.set(0.6, 0.08, 0); bed.userData.isFurniture = true; group.add(bed);
|
| 730 |
const mattress = new THREE.Mesh(new THREE.BoxGeometry(1.1, 0.1, 0.6), mat(0xe8e0d0));
|
|
@@ -732,8 +785,8 @@ function createHouse(id, locData) {
|
|
| 732 |
const pillow = new THREE.Mesh(new THREE.BoxGeometry(0.25, 0.08, 0.35), mat(0xf0f0f0));
|
| 733 |
pillow.position.set(1.1, 0.24, 0); pillow.userData.isFurniture = true; group.add(pillow);
|
| 734 |
|
| 735 |
-
const label = createLabel(locData.label, group, wallH + roofH + 1);
|
| 736 |
-
const badge = createOccupantBadge(group, wallH + roofH);
|
| 737 |
group.userData = { id, type: 'house', label, badge, locData };
|
| 738 |
return group;
|
| 739 |
}
|
|
@@ -741,47 +794,90 @@ function createHouse(id, locData) {
|
|
| 741 |
function createApartment(id, locData) {
|
| 742 |
const group = new THREE.Group();
|
| 743 |
const h = hash(id);
|
|
|
|
| 744 |
const w = 5, d = 4, wallH = 7 + (h % 4);
|
| 745 |
const wallColor = BLDG_COLORS.apartment[h % BLDG_COLORS.apartment.length];
|
|
|
|
| 746 |
|
| 747 |
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), mat(wallColor));
|
| 748 |
walls.position.y = wallH / 2;
|
| 749 |
-
walls.castShadow = true;
|
| 750 |
-
walls
|
| 751 |
-
addEdges(walls);
|
| 752 |
-
group.add(walls);
|
| 753 |
|
| 754 |
-
|
| 755 |
-
|
| 756 |
-
new THREE.
|
| 757 |
-
|
| 758 |
-
|
| 759 |
-
|
| 760 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 761 |
|
| 762 |
-
// Window grid
|
| 763 |
const winMat = mat(isNight ? BLDG_COLORS.windowLit : BLDG_COLORS.window,
|
| 764 |
isNight ? { emissive: BLDG_COLORS.windowLit, emissiveIntensity: 0.4 } : {});
|
| 765 |
const floors = Math.floor(wallH / 2);
|
|
|
|
| 766 |
for (let f = 0; f < floors; f++) {
|
|
|
|
| 767 |
for (let wx = -1; wx <= 1; wx++) {
|
| 768 |
-
const win = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.
|
| 769 |
-
win.position.set(wx * 1.4,
|
| 770 |
-
win.userData.isWindow = true;
|
| 771 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 772 |
}
|
| 773 |
}
|
| 774 |
|
| 775 |
-
// Beds inside
|
| 776 |
for (let bx = -1; bx <= 1; bx += 2) {
|
| 777 |
const bed = new THREE.Mesh(new THREE.BoxGeometry(1.0, 0.12, 0.5), mat(0x8b6040));
|
| 778 |
bed.position.set(bx * 1.2, 0.06, 0); bed.userData.isFurniture = true; group.add(bed);
|
| 779 |
-
const
|
| 780 |
-
|
| 781 |
}
|
| 782 |
|
| 783 |
-
const
|
| 784 |
-
const
|
|
|
|
| 785 |
group.userData = { id, type: 'apartment', label, badge, locData };
|
| 786 |
return group;
|
| 787 |
}
|
|
@@ -830,28 +926,58 @@ function createShop(id, locData) {
|
|
| 830 |
|
| 831 |
function createOffice(id, locData) {
|
| 832 |
const group = new THREE.Group();
|
| 833 |
-
const w = 5, d = 5, wallH =
|
|
|
|
| 834 |
|
| 835 |
-
const
|
| 836 |
-
new THREE.BoxGeometry(w, wallH, d),
|
| 837 |
-
mat(
|
| 838 |
);
|
| 839 |
-
|
| 840 |
-
|
| 841 |
-
|
| 842 |
-
|
| 843 |
-
|
| 844 |
-
|
| 845 |
-
|
| 846 |
-
|
| 847 |
-
|
| 848 |
-
for (let
|
| 849 |
-
const
|
| 850 |
-
|
| 851 |
-
|
| 852 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 853 |
}
|
| 854 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 855 |
|
| 856 |
const label = createLabel(locData.label, group, wallH + 2);
|
| 857 |
const badge = createOccupantBadge(group, wallH + 1);
|
|
@@ -862,71 +988,138 @@ function createOffice(id, locData) {
|
|
| 862 |
function createTower(id, locData) {
|
| 863 |
const group = new THREE.Group();
|
| 864 |
const w = 4.5, d = 4.5, wallH = 14;
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 865 |
|
| 866 |
-
|
| 867 |
-
const
|
| 868 |
-
|
| 869 |
-
|
| 870 |
-
);
|
| 871 |
-
|
| 872 |
-
|
| 873 |
-
|
| 874 |
-
|
| 875 |
-
|
| 876 |
-
|
| 877 |
-
|
| 878 |
-
|
| 879 |
-
|
| 880 |
-
|
| 881 |
-
);
|
| 882 |
-
body.position.y = wallH / 2;
|
| 883 |
-
body.castShadow = true;
|
| 884 |
-
body.receiveShadow = true;
|
| 885 |
-
addEdges(body);
|
| 886 |
-
group.add(body);
|
| 887 |
-
|
| 888 |
-
// Glass strips
|
| 889 |
-
const glassMat = mat(BLDG_COLORS.window, { roughness: 0.15, metalness: 0.4, opacity: 0.6, transparent: true });
|
| 890 |
-
for (let f = 0; f < 6; f++) {
|
| 891 |
-
const strip = new THREE.Mesh(new THREE.BoxGeometry(w * 0.8, 0.8, 0.1), glassMat);
|
| 892 |
-
strip.position.set(0, 2 + f * 2, d / 2 + 0.05);
|
| 893 |
-
strip.userData.isWindow = true;
|
| 894 |
-
group.add(strip);
|
| 895 |
-
}
|
| 896 |
-
|
| 897 |
-
// Antenna
|
| 898 |
-
const antenna = new THREE.Mesh(
|
| 899 |
-
new THREE.CylinderGeometry(0.08, 0.08, 3, 6),
|
| 900 |
-
mat(0x888888, { metalness: 0.5 })
|
| 901 |
-
);
|
| 902 |
-
antenna.position.y = wallH + 1.5;
|
| 903 |
-
group.add(antenna);
|
| 904 |
|
| 905 |
-
const
|
| 906 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 907 |
group.userData = { id, type: 'tower', label, badge, locData };
|
| 908 |
return group;
|
| 909 |
}
|
| 910 |
|
| 911 |
function createHospital(id, locData) {
|
| 912 |
const group = new THREE.Group();
|
| 913 |
-
const w = 7, d = 5, wallH =
|
| 914 |
|
| 915 |
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), mat(BLDG_COLORS.hospital));
|
| 916 |
-
walls.position.y = wallH / 2;
|
| 917 |
-
walls
|
| 918 |
-
|
| 919 |
-
|
| 920 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 921 |
|
| 922 |
-
// Red cross
|
| 923 |
const crossMat = mat(0xcc2222);
|
| 924 |
const crossH = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.4, 0.1), crossMat);
|
| 925 |
-
crossH.position.set(0, wallH
|
| 926 |
-
group.add(crossH);
|
| 927 |
const crossV = new THREE.Mesh(new THREE.BoxGeometry(0.4, 1.5, 0.1), crossMat);
|
| 928 |
-
crossV.position.set(0, wallH
|
| 929 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 930 |
|
| 931 |
const label = createLabel(locData.label, group, wallH + 2);
|
| 932 |
const badge = createOccupantBadge(group, wallH + 1);
|
|
@@ -936,52 +1129,80 @@ function createHospital(id, locData) {
|
|
| 936 |
|
| 937 |
function createChurch(id, locData) {
|
| 938 |
const group = new THREE.Group();
|
| 939 |
-
const w = 4, d = 6, wallH = 4;
|
|
|
|
| 940 |
|
| 941 |
-
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d),
|
| 942 |
-
walls.position.y = wallH / 2;
|
| 943 |
-
walls
|
| 944 |
-
walls.receiveShadow = true;
|
| 945 |
-
addEdges(walls);
|
| 946 |
-
group.add(walls);
|
| 947 |
|
| 948 |
-
// Roof
|
| 949 |
const roofGeo = new THREE.BufferGeometry();
|
| 950 |
-
const hw = w / 2 + 0.2, hd = d / 2 + 0.2;
|
| 951 |
-
const
|
| 952 |
-
|
| 953 |
-
-hw,
|
| 954 |
-
-hw,
|
| 955 |
-
|
| 956 |
-
hw, wallH, -hd, hw, wallH, hd, 0, wallH + rh, -hd, 0, wallH + rh, hd,
|
| 957 |
]);
|
| 958 |
-
|
| 959 |
-
roofGeo.
|
| 960 |
-
roofGeo.setIndex(idx);
|
| 961 |
roofGeo.computeVertexNormals();
|
| 962 |
const roof = new THREE.Mesh(roofGeo, mat(BLDG_COLORS.roof[0]));
|
| 963 |
-
roof.castShadow = true;
|
| 964 |
-
|
| 965 |
-
|
| 966 |
-
|
| 967 |
-
|
| 968 |
-
|
| 969 |
-
|
| 970 |
-
|
| 971 |
-
|
| 972 |
-
|
| 973 |
-
|
| 974 |
-
|
| 975 |
-
// Steeple base
|
| 976 |
-
const stBase = new THREE.Mesh(
|
| 977 |
-
new THREE.BoxGeometry(1.6, 2, 1.6),
|
| 978 |
-
mat(BLDG_COLORS.church)
|
| 979 |
-
);
|
| 980 |
-
stBase.position.set(0, wallH + 1, -d / 2 + 1);
|
| 981 |
-
group.add(stBase);
|
| 982 |
|
| 983 |
-
const
|
| 984 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 985 |
group.userData = { id, type: 'church', label, badge, locData };
|
| 986 |
return group;
|
| 987 |
}
|
|
@@ -991,32 +1212,60 @@ function createSchool(id, locData) {
|
|
| 991 |
const w = 6, d = 5, wallH = 3.5;
|
| 992 |
|
| 993 |
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), mat(BLDG_COLORS.school));
|
| 994 |
-
walls.position.y = wallH / 2;
|
| 995 |
-
walls
|
| 996 |
-
walls.receiveShadow = true;
|
| 997 |
-
addEdges(walls);
|
| 998 |
-
group.add(walls);
|
| 999 |
|
| 1000 |
-
|
| 1001 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1002 |
for (let i = -2; i <= 2; i++) {
|
|
|
|
| 1003 |
const win = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.8, 0.1), winMat);
|
| 1004 |
win.position.set(i * 1.1, wallH * 0.6, d / 2 + 0.05);
|
| 1005 |
-
win.userData.isWindow = true;
|
| 1006 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1007 |
}
|
| 1008 |
|
| 1009 |
-
|
| 1010 |
-
|
| 1011 |
-
pole.position.set(w / 2 - 0.5, wallH + 1.5, -d / 2 + 0.5);
|
| 1012 |
-
group.add(pole);
|
| 1013 |
-
|
| 1014 |
const flag = new THREE.Mesh(new THREE.PlaneGeometry(1, 0.6), mat(0xcc3333, { side: THREE.DoubleSide }));
|
| 1015 |
-
flag.position.set(w / 2
|
| 1016 |
-
|
|
|
|
|
|
|
| 1017 |
|
| 1018 |
-
const label = createLabel(locData.label, group,
|
| 1019 |
-
const badge = createOccupantBadge(group,
|
| 1020 |
group.userData = { id, type: 'school', label, badge, locData };
|
| 1021 |
return group;
|
| 1022 |
}
|
|
@@ -1024,42 +1273,66 @@ function createSchool(id, locData) {
|
|
| 1024 |
function createFactory(id, locData) {
|
| 1025 |
const group = new THREE.Group();
|
| 1026 |
const w = 7, d = 5, wallH = 4.5;
|
|
|
|
|
|
|
| 1027 |
|
| 1028 |
-
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d),
|
| 1029 |
-
walls.position.y = wallH / 2;
|
| 1030 |
-
walls
|
| 1031 |
-
walls.receiveShadow = true;
|
| 1032 |
-
addEdges(walls);
|
| 1033 |
-
group.add(walls);
|
| 1034 |
-
|
| 1035 |
-
// Chimney
|
| 1036 |
-
const chimney = new THREE.Mesh(
|
| 1037 |
-
new THREE.CylinderGeometry(0.5, 0.6, 4, 8),
|
| 1038 |
-
mat(0x555555)
|
| 1039 |
-
);
|
| 1040 |
-
chimney.position.set(w / 2 - 1, wallH + 2, -d / 2 + 1);
|
| 1041 |
-
chimney.castShadow = true;
|
| 1042 |
-
group.add(chimney);
|
| 1043 |
-
|
| 1044 |
-
// Second chimney
|
| 1045 |
-
const chimney2 = new THREE.Mesh(
|
| 1046 |
-
new THREE.CylinderGeometry(0.4, 0.5, 3, 8),
|
| 1047 |
-
mat(0x555555)
|
| 1048 |
-
);
|
| 1049 |
-
chimney2.position.set(w / 2 - 2.5, wallH + 1.5, -d / 2 + 1);
|
| 1050 |
-
chimney2.castShadow = true;
|
| 1051 |
-
group.add(chimney2);
|
| 1052 |
-
|
| 1053 |
-
// Sawtooth roof detail
|
| 1054 |
-
const roofCap = new THREE.Mesh(
|
| 1055 |
-
new THREE.BoxGeometry(w + 0.2, 0.3, d + 0.2),
|
| 1056 |
-
mat(0x555555)
|
| 1057 |
-
);
|
| 1058 |
-
roofCap.position.y = wallH;
|
| 1059 |
-
group.add(roofCap);
|
| 1060 |
|
| 1061 |
-
const
|
| 1062 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1063 |
group.userData = { id, type: 'factory', label, badge, locData };
|
| 1064 |
return group;
|
| 1065 |
}
|
|
@@ -1069,65 +1342,122 @@ function createCinema(id, locData) {
|
|
| 1069 |
const w = 5.5, d = 5, wallH = 5;
|
| 1070 |
|
| 1071 |
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), mat(BLDG_COLORS.cinema));
|
| 1072 |
-
walls.position.y = wallH / 2;
|
| 1073 |
-
walls
|
| 1074 |
-
walls.receiveShadow = true;
|
| 1075 |
-
addEdges(walls);
|
| 1076 |
-
group.add(walls);
|
| 1077 |
-
|
| 1078 |
-
// Marquee
|
| 1079 |
-
const marquee = new THREE.Mesh(
|
| 1080 |
-
new THREE.BoxGeometry(w + 1, 0.8, 0.5),
|
| 1081 |
-
mat(0xddcc44, { emissive: 0x887722, emissiveIntensity: 0.3 })
|
| 1082 |
-
);
|
| 1083 |
-
marquee.position.set(0, wallH * 0.8, d / 2 + 0.3);
|
| 1084 |
-
group.add(marquee);
|
| 1085 |
|
| 1086 |
-
const
|
| 1087 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1088 |
group.userData = { id, type: 'cinema', label, badge, locData };
|
| 1089 |
return group;
|
| 1090 |
}
|
| 1091 |
|
| 1092 |
function createPark(id, locData) {
|
| 1093 |
const group = new THREE.Group();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1094 |
|
| 1095 |
-
// Park ground
|
| 1096 |
-
const parkGround = new THREE.Mesh(
|
| 1097 |
-
new THREE.BoxGeometry(8, 0.1, 6),
|
| 1098 |
-
mat(BLDG_COLORS.park)
|
| 1099 |
-
);
|
| 1100 |
-
parkGround.position.y = 0.05;
|
| 1101 |
-
parkGround.receiveShadow = true;
|
| 1102 |
-
group.add(parkGround);
|
| 1103 |
-
|
| 1104 |
-
// Path
|
| 1105 |
-
const path = new THREE.Mesh(
|
| 1106 |
-
new THREE.BoxGeometry(1, 0.12, 6),
|
| 1107 |
-
mat(0xccbbaa)
|
| 1108 |
-
);
|
| 1109 |
-
path.position.y = 0.06;
|
| 1110 |
-
group.add(path);
|
| 1111 |
-
|
| 1112 |
-
// Trees
|
| 1113 |
for (let i = 0; i < 5; i++) {
|
| 1114 |
const tx = (Math.random() - 0.5) * 6;
|
| 1115 |
const tz = (Math.random() - 0.5) * 4;
|
| 1116 |
-
if (Math.abs(tx) > 1) {
|
| 1117 |
const t = createTree(0, 0, 0.35 + Math.random() * 0.25);
|
| 1118 |
t.position.set(tx, 0, tz);
|
| 1119 |
-
scene.remove(t);
|
| 1120 |
-
group.add(t);
|
| 1121 |
}
|
| 1122 |
}
|
| 1123 |
|
| 1124 |
-
|
| 1125 |
-
|
| 1126 |
-
|
| 1127 |
-
|
| 1128 |
-
|
| 1129 |
-
|
| 1130 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1131 |
|
| 1132 |
const label = createLabel(locData.label, group, 5);
|
| 1133 |
const badge = createOccupantBadge(group, 4);
|
|
@@ -1174,19 +1504,50 @@ function createSportsField(id, locData) {
|
|
| 1174 |
function createPolice(id, locData) {
|
| 1175 |
const group = new THREE.Group();
|
| 1176 |
const w = 5, d = 4, wallH = 3.5;
|
| 1177 |
-
const
|
|
|
|
|
|
|
| 1178 |
walls.position.y = wallH / 2; walls.castShadow = true; walls.receiveShadow = true;
|
| 1179 |
addEdges(walls); group.add(walls);
|
|
|
|
| 1180 |
const roof = new THREE.Mesh(new THREE.BoxGeometry(w + 0.4, 0.25, d + 0.4), mat(0x2a4a6a));
|
| 1181 |
roof.position.y = wallH; group.add(roof);
|
| 1182 |
-
const
|
| 1183 |
-
|
| 1184 |
-
|
| 1185 |
-
|
| 1186 |
-
|
| 1187 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1188 |
win.position.set(sx, wallH * 0.6, d / 2 + 0.06); win.userData.isWindow = true; group.add(win);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1189 |
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1190 |
const label = createLabel(locData.label, group, wallH + 2);
|
| 1191 |
const badge = createOccupantBadge(group, wallH + 1);
|
| 1192 |
group.userData = { id, type: 'police', label, badge, locData };
|
|
@@ -1216,25 +1577,73 @@ function createFireStation(id, locData) {
|
|
| 1216 |
|
| 1217 |
function createMuseum(id, locData) {
|
| 1218 |
const group = new THREE.Group();
|
| 1219 |
-
const w = 6, d = 5, wallH = 4;
|
| 1220 |
-
const
|
|
|
|
|
|
|
|
|
|
| 1221 |
walls.position.y = wallH / 2; walls.castShadow = true; walls.receiveShadow = true;
|
| 1222 |
addEdges(walls); group.add(walls);
|
| 1223 |
-
|
| 1224 |
-
|
| 1225 |
-
|
| 1226 |
-
);
|
| 1227 |
-
|
| 1228 |
-
|
| 1229 |
-
|
| 1230 |
-
|
| 1231 |
-
|
| 1232 |
-
|
| 1233 |
-
|
| 1234 |
-
|
| 1235 |
-
|
| 1236 |
-
|
| 1237 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1238 |
group.userData = { id, type: 'museum', label, badge, locData };
|
| 1239 |
return group;
|
| 1240 |
}
|
|
@@ -1244,22 +1653,63 @@ function createMall(id, locData) {
|
|
| 1244 |
const h = hash(id);
|
| 1245 |
const w = 8, d = 6, wallH = 5;
|
| 1246 |
const clr = Array.isArray(BLDG_COLORS.mall) ? BLDG_COLORS.mall[h % BLDG_COLORS.mall.length] : BLDG_COLORS.mall;
|
|
|
|
|
|
|
| 1247 |
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), mat(clr));
|
| 1248 |
walls.position.y = wallH / 2; walls.castShadow = true; walls.receiveShadow = true;
|
| 1249 |
addEdges(walls); group.add(walls);
|
| 1250 |
-
|
|
|
|
| 1251 |
roof.position.y = wallH; group.add(roof);
|
| 1252 |
-
|
| 1253 |
-
|
| 1254 |
-
|
| 1255 |
-
);
|
| 1256 |
-
|
| 1257 |
-
|
| 1258 |
-
|
| 1259 |
-
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1260 |
}
|
| 1261 |
-
|
| 1262 |
-
const
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1263 |
group.userData = { id, type: 'mall', label, badge, locData };
|
| 1264 |
return group;
|
| 1265 |
}
|
|
@@ -1318,37 +1768,66 @@ function createMarket(id, locData) {
|
|
| 1318 |
|
| 1319 |
function createSquare(id, locData) {
|
| 1320 |
const group = new THREE.Group();
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1321 |
|
| 1322 |
-
|
| 1323 |
-
|
| 1324 |
-
|
| 1325 |
-
|
| 1326 |
-
|
| 1327 |
-
|
| 1328 |
-
|
| 1329 |
-
|
| 1330 |
-
|
| 1331 |
-
|
| 1332 |
-
|
| 1333 |
-
mat(0x999988)
|
| 1334 |
-
);
|
| 1335 |
-
basin.position.y = 0.25;
|
| 1336 |
-
group.add(basin);
|
| 1337 |
-
const waterTop = new THREE.Mesh(
|
| 1338 |
-
new THREE.CylinderGeometry(1.1, 1.1, 0.05, 12),
|
| 1339 |
-
mat(PALETTE.water, { roughness: 0.2, metalness: 0.1 })
|
| 1340 |
-
);
|
| 1341 |
-
waterTop.position.y = 0.48;
|
| 1342 |
-
group.add(waterTop);
|
| 1343 |
-
const spout = new THREE.Mesh(
|
| 1344 |
-
new THREE.CylinderGeometry(0.1, 0.15, 1, 8),
|
| 1345 |
-
mat(0x888888)
|
| 1346 |
-
);
|
| 1347 |
-
spout.position.y = 1;
|
| 1348 |
-
group.add(spout);
|
| 1349 |
|
| 1350 |
-
const label = createLabel(locData.label, group,
|
| 1351 |
-
const badge = createOccupantBadge(group,
|
| 1352 |
group.userData = { id, type: 'square', label, badge, locData };
|
| 1353 |
return group;
|
| 1354 |
}
|
|
|
|
| 683 |
function createHouse(id, locData) {
|
| 684 |
const group = new THREE.Group();
|
| 685 |
const h = hash(id);
|
| 686 |
+
const variant = h % 4;
|
| 687 |
const w = 3 + (h % 2), d = 3 + ((h >> 2) % 2), wallH = 2.5 + (h % 3) * 0.3;
|
| 688 |
const wallColor = BLDG_COLORS.house[h % BLDG_COLORS.house.length];
|
| 689 |
const roofColor = BLDG_COLORS.roof[h % BLDG_COLORS.roof.length];
|
| 690 |
+
const trimColor = ((wallColor & 0xfefefe) >> 1) + 0x404040;
|
| 691 |
|
|
|
|
| 692 |
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), mat(wallColor));
|
| 693 |
walls.position.y = wallH / 2;
|
| 694 |
+
walls.castShadow = true; walls.receiveShadow = true;
|
|
|
|
| 695 |
addEdges(walls, 0x000000, 0.08);
|
| 696 |
group.add(walls);
|
| 697 |
|
| 698 |
+
const roofH = 1.5 + (variant === 2 ? 0.5 : 0);
|
| 699 |
+
if (variant === 1) {
|
| 700 |
+
const roofGeo = new THREE.BufferGeometry();
|
| 701 |
+
const hw = w / 2 + 0.25, hd = d / 2 + 0.25, rh = roofH;
|
| 702 |
+
const v = new Float32Array([
|
| 703 |
+
-hw,wallH,-hd, hw,wallH,-hd, 0,wallH+rh,-hd,
|
| 704 |
+
-hw,wallH,hd, hw,wallH,hd, 0,wallH+rh,hd,
|
| 705 |
+
-hw,wallH,-hd, -hw,wallH,hd, 0,wallH+rh,-hd, 0,wallH+rh,hd,
|
| 706 |
+
hw,wallH,-hd, hw,wallH,hd, 0,wallH+rh,-hd, 0,wallH+rh,hd,
|
| 707 |
+
]);
|
| 708 |
+
roofGeo.setAttribute('position', new THREE.BufferAttribute(v, 3));
|
| 709 |
+
roofGeo.setIndex([0,1,2, 3,5,4, 2,3,0, 2,5,3, 1,4,5, 1,5,2]);
|
| 710 |
+
roofGeo.computeVertexNormals();
|
| 711 |
+
const roof = new THREE.Mesh(roofGeo, mat(roofColor));
|
| 712 |
+
roof.castShadow = true; group.add(roof);
|
| 713 |
+
} else {
|
| 714 |
+
const roofGeo = new THREE.ConeGeometry(Math.max(w, d) * 0.75, roofH, 4);
|
| 715 |
+
roofGeo.rotateY(Math.PI / 4);
|
| 716 |
+
const roof = new THREE.Mesh(roofGeo, mat(roofColor));
|
| 717 |
+
roof.position.y = wallH + roofH / 2;
|
| 718 |
+
roof.castShadow = true; group.add(roof);
|
| 719 |
+
}
|
| 720 |
+
|
| 721 |
+
if (variant !== 3) {
|
| 722 |
+
const chimW = 0.35, chimH = 1.2 + (h % 3) * 0.3;
|
| 723 |
+
const chimney = new THREE.Mesh(new THREE.BoxGeometry(chimW, chimH, chimW), mat(0x884422));
|
| 724 |
+
chimney.position.set(w * 0.25, wallH + roofH * 0.5 + chimH / 2, -d * 0.2);
|
| 725 |
+
chimney.castShadow = true; group.add(chimney);
|
| 726 |
+
const chimCap = new THREE.Mesh(new THREE.BoxGeometry(chimW + 0.1, 0.08, chimW + 0.1), mat(0x666666));
|
| 727 |
+
chimCap.position.set(w * 0.25, wallH + roofH * 0.5 + chimH + 0.04, -d * 0.2);
|
| 728 |
+
group.add(chimCap);
|
| 729 |
+
}
|
| 730 |
+
|
| 731 |
+
const porchW = w * 0.6, porchD = 0.8;
|
| 732 |
+
const porchRoof = new THREE.Mesh(new THREE.BoxGeometry(porchW + 0.3, 0.08, porchD + 0.3), mat(roofColor));
|
| 733 |
+
porchRoof.position.set(0, wallH * 0.78, d / 2 + porchD / 2 + 0.1);
|
| 734 |
+
porchRoof.castShadow = true; group.add(porchRoof);
|
| 735 |
+
for (let s of [-1, 1]) {
|
| 736 |
+
const post = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.04, wallH * 0.78, 6), mat(0xf0f0f0));
|
| 737 |
+
post.position.set(s * porchW / 2, wallH * 0.39, d / 2 + porchD);
|
| 738 |
+
group.add(post);
|
| 739 |
+
}
|
| 740 |
+
const step = new THREE.Mesh(new THREE.BoxGeometry(porchW, 0.12, porchD), mat(0xc8b8a0));
|
| 741 |
+
step.position.set(0, 0.06, d / 2 + porchD / 2 + 0.1);
|
| 742 |
+
step.receiveShadow = true; group.add(step);
|
| 743 |
+
|
| 744 |
+
const door = new THREE.Mesh(new THREE.BoxGeometry(0.6, 1.2, 0.1), mat(BLDG_COLORS.door));
|
| 745 |
door.position.set(0, 0.6, d / 2 + 0.05);
|
| 746 |
+
door.userData.isDoor = true; group.add(door);
|
| 747 |
+
const knob = new THREE.Mesh(new THREE.SphereGeometry(0.04, 6, 4), mat(0xccaa44, { metalness: 0.6 }));
|
| 748 |
+
knob.position.set(0.2, 0.55, d / 2 + 0.11); group.add(knob);
|
| 749 |
|
|
|
|
| 750 |
const winMat = mat(isNight ? BLDG_COLORS.windowLit : BLDG_COLORS.window,
|
| 751 |
isNight ? { emissive: BLDG_COLORS.windowLit, emissiveIntensity: 0.5 } : {});
|
| 752 |
for (let side of [-1, 1]) {
|
| 753 |
const win = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.5, 0.1), winMat);
|
| 754 |
win.position.set(side * (w / 2 + 0.05), wallH * 0.6, 0);
|
| 755 |
win.rotation.y = Math.PI / 2;
|
| 756 |
+
win.userData.isWindow = true; group.add(win);
|
| 757 |
+
for (let sh of [-0.35, 0.35]) {
|
| 758 |
+
const shutter = new THREE.Mesh(new THREE.BoxGeometry(0.12, 0.55, 0.06), mat(trimColor));
|
| 759 |
+
shutter.position.set(side * (w / 2 + 0.07), wallH * 0.6, sh);
|
| 760 |
+
group.add(shutter);
|
| 761 |
+
}
|
| 762 |
+
}
|
| 763 |
+
const frontWin = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.5, 0.1), winMat);
|
| 764 |
+
frontWin.position.set(w * 0.28, wallH * 0.6, d / 2 + 0.05);
|
| 765 |
+
frontWin.userData.isWindow = true; group.add(frontWin);
|
| 766 |
+
|
| 767 |
+
if (variant === 3) {
|
| 768 |
+
const bayW = 1.2, bayD = 0.6, bayH = wallH * 0.7;
|
| 769 |
+
const bay = new THREE.Mesh(new THREE.BoxGeometry(bayW, bayH, bayD), mat(wallColor));
|
| 770 |
+
bay.position.set(-w * 0.3, bayH / 2, d / 2 + bayD / 2);
|
| 771 |
+
bay.castShadow = true; group.add(bay);
|
| 772 |
+
const bayRoof = new THREE.Mesh(new THREE.BoxGeometry(bayW + 0.15, 0.08, bayD + 0.15), mat(roofColor));
|
| 773 |
+
bayRoof.position.set(-w * 0.3, bayH + 0.04, d / 2 + bayD / 2); group.add(bayRoof);
|
| 774 |
+
for (let bx of [-0.35, 0, 0.35]) {
|
| 775 |
+
const bw = new THREE.Mesh(new THREE.BoxGeometry(0.25, 0.4, 0.06), winMat);
|
| 776 |
+
bw.position.set(-w * 0.3 + bx, bayH * 0.6, d / 2 + bayD + 0.03);
|
| 777 |
+
bw.userData.isWindow = true; group.add(bw);
|
| 778 |
+
}
|
| 779 |
}
|
| 780 |
|
|
|
|
| 781 |
const bed = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.15, 0.7), mat(0x8b6040));
|
| 782 |
bed.position.set(0.6, 0.08, 0); bed.userData.isFurniture = true; group.add(bed);
|
| 783 |
const mattress = new THREE.Mesh(new THREE.BoxGeometry(1.1, 0.1, 0.6), mat(0xe8e0d0));
|
|
|
|
| 785 |
const pillow = new THREE.Mesh(new THREE.BoxGeometry(0.25, 0.08, 0.35), mat(0xf0f0f0));
|
| 786 |
pillow.position.set(1.1, 0.24, 0); pillow.userData.isFurniture = true; group.add(pillow);
|
| 787 |
|
| 788 |
+
const label = createLabel(locData.label, group, wallH + roofH + 1.5);
|
| 789 |
+
const badge = createOccupantBadge(group, wallH + roofH + 0.5);
|
| 790 |
group.userData = { id, type: 'house', label, badge, locData };
|
| 791 |
return group;
|
| 792 |
}
|
|
|
|
| 794 |
function createApartment(id, locData) {
|
| 795 |
const group = new THREE.Group();
|
| 796 |
const h = hash(id);
|
| 797 |
+
const variant = h % 3;
|
| 798 |
const w = 5, d = 4, wallH = 7 + (h % 4);
|
| 799 |
const wallColor = BLDG_COLORS.apartment[h % BLDG_COLORS.apartment.length];
|
| 800 |
+
const accentColor = wallColor - 0x181818;
|
| 801 |
|
| 802 |
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), mat(wallColor));
|
| 803 |
walls.position.y = wallH / 2;
|
| 804 |
+
walls.castShadow = true; walls.receiveShadow = true;
|
| 805 |
+
addEdges(walls); group.add(walls);
|
|
|
|
|
|
|
| 806 |
|
| 807 |
+
if (variant === 1 && wallH >= 9) {
|
| 808 |
+
const setW = w - 1, setD = d - 0.5, setH = 2.5;
|
| 809 |
+
const upper = new THREE.Mesh(new THREE.BoxGeometry(setW, setH, setD), mat(wallColor + 0x080808));
|
| 810 |
+
upper.position.y = wallH + setH / 2; upper.castShadow = true; addEdges(upper); group.add(upper);
|
| 811 |
+
const upperCap = new THREE.Mesh(new THREE.BoxGeometry(setW + 0.2, 0.2, setD + 0.2), mat(accentColor));
|
| 812 |
+
upperCap.position.y = wallH + setH; group.add(upperCap);
|
| 813 |
+
}
|
| 814 |
+
|
| 815 |
+
const corniceMat = mat(accentColor);
|
| 816 |
+
const roofCap = new THREE.Mesh(new THREE.BoxGeometry(w + 0.4, 0.35, d + 0.4), corniceMat);
|
| 817 |
+
roofCap.position.y = wallH; group.add(roofCap);
|
| 818 |
+
const midCornice = new THREE.Mesh(new THREE.BoxGeometry(w + 0.15, 0.12, d + 0.15), corniceMat);
|
| 819 |
+
midCornice.position.y = wallH * 0.5; group.add(midCornice);
|
| 820 |
+
const baseCornice = new THREE.Mesh(new THREE.BoxGeometry(w + 0.2, 0.2, d + 0.2), corniceMat);
|
| 821 |
+
baseCornice.position.y = 0.1; group.add(baseCornice);
|
| 822 |
+
|
| 823 |
+
const entranceW = 2.5, entranceD = 1.2;
|
| 824 |
+
const canopy = new THREE.Mesh(new THREE.BoxGeometry(entranceW, 0.1, entranceD), mat(accentColor - 0x101010));
|
| 825 |
+
canopy.position.set(0, 2.8, d / 2 + entranceD / 2); canopy.castShadow = true; group.add(canopy);
|
| 826 |
+
const eDoor = new THREE.Mesh(new THREE.BoxGeometry(1.4, 2.2, 0.1), mat(0x3a3a5a));
|
| 827 |
+
eDoor.position.set(0, 1.1, d / 2 + 0.05); eDoor.userData.isDoor = true; group.add(eDoor);
|
| 828 |
+
const eGlass = new THREE.Mesh(new THREE.BoxGeometry(1.0, 1.5, 0.06),
|
| 829 |
+
mat(BLDG_COLORS.window, { transparent: true, opacity: 0.5, roughness: 0.2 }));
|
| 830 |
+
eGlass.position.set(0, 1.3, d / 2 + 0.08); eGlass.userData.isWindow = true; group.add(eGlass);
|
| 831 |
|
|
|
|
| 832 |
const winMat = mat(isNight ? BLDG_COLORS.windowLit : BLDG_COLORS.window,
|
| 833 |
isNight ? { emissive: BLDG_COLORS.windowLit, emissiveIntensity: 0.4 } : {});
|
| 834 |
const floors = Math.floor(wallH / 2);
|
| 835 |
+
const balconyFloors = variant === 0 ? [1, 3] : variant === 2 ? [0, 2, 4] : [2];
|
| 836 |
for (let f = 0; f < floors; f++) {
|
| 837 |
+
const fy = 1.5 + f * 2;
|
| 838 |
for (let wx = -1; wx <= 1; wx++) {
|
| 839 |
+
const win = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.7, 0.1), winMat);
|
| 840 |
+
win.position.set(wx * 1.4, fy, d / 2 + 0.05);
|
| 841 |
+
win.userData.isWindow = true; group.add(win);
|
| 842 |
+
const sill = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.06, 0.12), corniceMat);
|
| 843 |
+
sill.position.set(wx * 1.4, fy - 0.38, d / 2 + 0.08); group.add(sill);
|
| 844 |
+
}
|
| 845 |
+
if (balconyFloors.includes(f) && f > 0) {
|
| 846 |
+
const balcW = w * 0.85, balcD = 0.7;
|
| 847 |
+
const balcFloor = new THREE.Mesh(new THREE.BoxGeometry(balcW, 0.1, balcD), mat(0xa0a0a0));
|
| 848 |
+
balcFloor.position.set(0, fy - 0.45, d / 2 + balcD / 2 + 0.05); group.add(balcFloor);
|
| 849 |
+
const railMat = mat(0x444444, { metalness: 0.3 });
|
| 850 |
+
const rail = new THREE.Mesh(new THREE.BoxGeometry(balcW, 0.04, 0.04), railMat);
|
| 851 |
+
rail.position.set(0, fy - 0.05, d / 2 + balcD + 0.05); group.add(rail);
|
| 852 |
+
for (let rx = -balcW / 2 + 0.15; rx <= balcW / 2; rx += 0.3) {
|
| 853 |
+
const bar = new THREE.Mesh(new THREE.BoxGeometry(0.02, 0.35, 0.02), railMat);
|
| 854 |
+
bar.position.set(rx, fy - 0.25, d / 2 + balcD + 0.05); group.add(bar);
|
| 855 |
+
}
|
| 856 |
+
}
|
| 857 |
+
for (let side of [-1, 1]) {
|
| 858 |
+
const sWin = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.6, 0.45), winMat);
|
| 859 |
+
sWin.position.set(side * (w / 2 + 0.05), fy, 0);
|
| 860 |
+
sWin.userData.isWindow = true; group.add(sWin);
|
| 861 |
+
}
|
| 862 |
+
}
|
| 863 |
+
|
| 864 |
+
if (variant === 2) {
|
| 865 |
+
for (let rx = -1.5; rx <= 1.5; rx += 3) {
|
| 866 |
+
const roofBox = new THREE.Mesh(new THREE.BoxGeometry(0.8, 0.6, 0.8), mat(0x888888));
|
| 867 |
+
roofBox.position.set(rx, wallH + 0.6, 0); group.add(roofBox);
|
| 868 |
}
|
| 869 |
}
|
| 870 |
|
|
|
|
| 871 |
for (let bx = -1; bx <= 1; bx += 2) {
|
| 872 |
const bed = new THREE.Mesh(new THREE.BoxGeometry(1.0, 0.12, 0.5), mat(0x8b6040));
|
| 873 |
bed.position.set(bx * 1.2, 0.06, 0); bed.userData.isFurniture = true; group.add(bed);
|
| 874 |
+
const mattObj = new THREE.Mesh(new THREE.BoxGeometry(0.9, 0.08, 0.45), mat(0xe0d8c8));
|
| 875 |
+
mattObj.position.set(bx * 1.2, 0.14, 0); mattObj.userData.isFurniture = true; group.add(mattObj);
|
| 876 |
}
|
| 877 |
|
| 878 |
+
const totalH = variant === 1 && wallH >= 9 ? wallH + 2.5 : wallH;
|
| 879 |
+
const label = createLabel(locData.label, group, totalH + 2);
|
| 880 |
+
const badge = createOccupantBadge(group, totalH + 1);
|
| 881 |
group.userData = { id, type: 'apartment', label, badge, locData };
|
| 882 |
return group;
|
| 883 |
}
|
|
|
|
| 926 |
|
| 927 |
function createOffice(id, locData) {
|
| 928 |
const group = new THREE.Group();
|
| 929 |
+
const w = 5, d = 5, wallH = 6;
|
| 930 |
+
const frameMat = mat(0x555565, { metalness: 0.4, roughness: 0.3 });
|
| 931 |
|
| 932 |
+
const core = new THREE.Mesh(
|
| 933 |
+
new THREE.BoxGeometry(w - 0.3, wallH, d - 0.3),
|
| 934 |
+
mat(0x404050, { roughness: 0.4, metalness: 0.2 })
|
| 935 |
);
|
| 936 |
+
core.position.y = wallH / 2; core.castShadow = true; core.receiveShadow = true;
|
| 937 |
+
group.add(core);
|
| 938 |
+
|
| 939 |
+
const glassMat = mat(BLDG_COLORS.window, { roughness: 0.1, metalness: 0.4, opacity: 0.55, transparent: true });
|
| 940 |
+
for (let face = 0; face < 4; face++) {
|
| 941 |
+
const fw = face % 2 === 0 ? w : d;
|
| 942 |
+
const fd = face % 2 === 0 ? d : w;
|
| 943 |
+
const faceGroup = new THREE.Group();
|
| 944 |
+
const floors = 3;
|
| 945 |
+
for (let f = 0; f < floors; f++) {
|
| 946 |
+
const fy = 0.8 + f * 1.8;
|
| 947 |
+
const band = new THREE.Mesh(new THREE.BoxGeometry(fw + 0.05, 0.08, 0.08), frameMat);
|
| 948 |
+
band.position.set(0, fy - 0.15, fd / 2 + 0.02); faceGroup.add(band);
|
| 949 |
+
for (let gx = -1; gx <= 1; gx++) {
|
| 950 |
+
const pane = new THREE.Mesh(new THREE.BoxGeometry(fw * 0.28, 1.4, 0.06), glassMat);
|
| 951 |
+
pane.position.set(gx * (fw * 0.3), fy + 0.55, fd / 2 + 0.04);
|
| 952 |
+
pane.userData.isWindow = true; faceGroup.add(pane);
|
| 953 |
+
}
|
| 954 |
+
for (let mx = -1.5; mx <= 1.5; mx++) {
|
| 955 |
+
const mullion = new THREE.Mesh(new THREE.BoxGeometry(0.06, 1.5, 0.06), frameMat);
|
| 956 |
+
mullion.position.set(mx * (fw * 0.2), fy + 0.55, fd / 2 + 0.05); faceGroup.add(mullion);
|
| 957 |
+
}
|
| 958 |
}
|
| 959 |
+
const topBand = new THREE.Mesh(new THREE.BoxGeometry(fw + 0.05, 0.08, 0.08), frameMat);
|
| 960 |
+
topBand.position.set(0, wallH - 0.1, fd / 2 + 0.02); faceGroup.add(topBand);
|
| 961 |
+
faceGroup.rotation.y = (face * Math.PI) / 2;
|
| 962 |
+
group.add(faceGroup);
|
| 963 |
+
}
|
| 964 |
+
|
| 965 |
+
const roofCap = new THREE.Mesh(new THREE.BoxGeometry(w + 0.3, 0.25, d + 0.3), frameMat);
|
| 966 |
+
roofCap.position.y = wallH; group.add(roofCap);
|
| 967 |
+
const parapet = new THREE.Mesh(new THREE.BoxGeometry(w + 0.35, 0.5, d + 0.35),
|
| 968 |
+
mat(0x555565, { metalness: 0.3 }));
|
| 969 |
+
parapet.position.y = wallH + 0.25;
|
| 970 |
+
const parapetInner = new THREE.Mesh(new THREE.BoxGeometry(w - 0.1, 0.6, d - 0.1), mat(0x555565));
|
| 971 |
+
parapetInner.position.y = wallH + 0.25;
|
| 972 |
+
const parapetCSG = parapet; group.add(parapetCSG);
|
| 973 |
+
|
| 974 |
+
const lobbyW = 2, lobbyH = 2.5, lobbyD = 1;
|
| 975 |
+
const lobby = new THREE.Mesh(new THREE.BoxGeometry(lobbyW, lobbyH, lobbyD),
|
| 976 |
+
mat(0x88aacc, { transparent: true, opacity: 0.4, roughness: 0.1, metalness: 0.3 }));
|
| 977 |
+
lobby.position.set(0, lobbyH / 2, d / 2 + lobbyD / 2);
|
| 978 |
+
lobby.userData.isWindow = true; group.add(lobby);
|
| 979 |
+
const lobbyFrame = new THREE.Mesh(new THREE.BoxGeometry(lobbyW + 0.15, 0.1, lobbyD + 0.15), frameMat);
|
| 980 |
+
lobbyFrame.position.set(0, lobbyH, d / 2 + lobbyD / 2); group.add(lobbyFrame);
|
| 981 |
|
| 982 |
const label = createLabel(locData.label, group, wallH + 2);
|
| 983 |
const badge = createOccupantBadge(group, wallH + 1);
|
|
|
|
| 988 |
function createTower(id, locData) {
|
| 989 |
const group = new THREE.Group();
|
| 990 |
const w = 4.5, d = 4.5, wallH = 14;
|
| 991 |
+
const towerMat = mat(BLDG_COLORS.tower, { roughness: 0.25, metalness: 0.35 });
|
| 992 |
+
const frameMat = mat(0x445566, { metalness: 0.5, roughness: 0.3 });
|
| 993 |
+
const glassMat = mat(BLDG_COLORS.window, { roughness: 0.1, metalness: 0.5, opacity: 0.5, transparent: true });
|
| 994 |
+
|
| 995 |
+
const base = new THREE.Mesh(new THREE.BoxGeometry(w + 1.5, 3.5, d + 1.5), mat(BLDG_COLORS.tower - 0x101010));
|
| 996 |
+
base.position.y = 1.75; base.castShadow = true; base.receiveShadow = true;
|
| 997 |
+
addEdges(base); group.add(base);
|
| 998 |
+
const baseLobby = new THREE.Mesh(new THREE.BoxGeometry(2.5, 2.8, 0.8),
|
| 999 |
+
mat(0x88bbdd, { transparent: true, opacity: 0.4, roughness: 0.1 }));
|
| 1000 |
+
baseLobby.position.set(0, 1.4, (d + 1.5) / 2 + 0.3);
|
| 1001 |
+
baseLobby.userData.isWindow = true; group.add(baseLobby);
|
| 1002 |
+
|
| 1003 |
+
const body = new THREE.Mesh(new THREE.BoxGeometry(w, wallH - 3, d), towerMat);
|
| 1004 |
+
body.position.y = 3.5 + (wallH - 3) / 2; body.castShadow = true; body.receiveShadow = true;
|
| 1005 |
+
addEdges(body); group.add(body);
|
| 1006 |
+
|
| 1007 |
+
for (let face = 0; face < 4; face++) {
|
| 1008 |
+
const fw = w, fd = d;
|
| 1009 |
+
const faceGrp = new THREE.Group();
|
| 1010 |
+
for (let f = 0; f < 5; f++) {
|
| 1011 |
+
const fy = 4.5 + f * 2;
|
| 1012 |
+
const strip = new THREE.Mesh(new THREE.BoxGeometry(fw * 0.85, 1.3, 0.08), glassMat);
|
| 1013 |
+
strip.position.set(0, fy, fd / 2 + 0.04);
|
| 1014 |
+
strip.userData.isWindow = true; faceGrp.add(strip);
|
| 1015 |
+
for (let mx = -1; mx <= 1; mx++) {
|
| 1016 |
+
const mull = new THREE.Mesh(new THREE.BoxGeometry(0.06, 1.4, 0.06), frameMat);
|
| 1017 |
+
mull.position.set(mx * (fw * 0.28), fy, fd / 2 + 0.05); faceGrp.add(mull);
|
| 1018 |
+
}
|
| 1019 |
+
const hBar = new THREE.Mesh(new THREE.BoxGeometry(fw * 0.88, 0.06, 0.06), frameMat);
|
| 1020 |
+
hBar.position.set(0, fy + 0.65, fd / 2 + 0.05); faceGrp.add(hBar);
|
| 1021 |
+
}
|
| 1022 |
+
faceGrp.rotation.y = (face * Math.PI) / 2;
|
| 1023 |
+
group.add(faceGrp);
|
| 1024 |
+
}
|
| 1025 |
|
| 1026 |
+
const deckY = wallH - 0.5;
|
| 1027 |
+
const deckW = w + 1.2, deckD = d + 1.2;
|
| 1028 |
+
const deckFloor = new THREE.Mesh(new THREE.BoxGeometry(deckW, 0.2, deckD), frameMat);
|
| 1029 |
+
deckFloor.position.y = deckY; group.add(deckFloor);
|
| 1030 |
+
const railMat = mat(0x888888, { metalness: 0.4 });
|
| 1031 |
+
for (let side of [-1, 1]) {
|
| 1032 |
+
const railH = new THREE.Mesh(new THREE.BoxGeometry(deckW, 0.06, 0.06), railMat);
|
| 1033 |
+
railH.position.set(0, deckY + 0.8, side * deckD / 2); group.add(railH);
|
| 1034 |
+
const railS = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.06, deckD), railMat);
|
| 1035 |
+
railS.position.set(side * deckW / 2, deckY + 0.8, 0); group.add(railS);
|
| 1036 |
+
for (let i = -3; i <= 3; i++) {
|
| 1037 |
+
const post = new THREE.Mesh(new THREE.BoxGeometry(0.04, 0.8, 0.04), railMat);
|
| 1038 |
+
post.position.set(i * 0.8, deckY + 0.4, side * deckD / 2); group.add(post);
|
| 1039 |
+
}
|
| 1040 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1041 |
|
| 1042 |
+
const crownH = 3;
|
| 1043 |
+
const crownW = w * 0.7, crownD = d * 0.7;
|
| 1044 |
+
const crown = new THREE.Mesh(new THREE.BoxGeometry(crownW, crownH, crownD), towerMat);
|
| 1045 |
+
crown.position.y = wallH + crownH / 2; crown.castShadow = true;
|
| 1046 |
+
addEdges(crown); group.add(crown);
|
| 1047 |
+
for (let face = 0; face < 4; face++) {
|
| 1048 |
+
const cfGrp = new THREE.Group();
|
| 1049 |
+
const cg = new THREE.Mesh(new THREE.BoxGeometry(crownW * 0.7, crownH * 0.7, 0.06), glassMat);
|
| 1050 |
+
cg.position.set(0, wallH + crownH / 2, crownD / 2 + 0.03);
|
| 1051 |
+
cg.userData.isWindow = true; cfGrp.add(cg);
|
| 1052 |
+
cfGrp.rotation.y = (face * Math.PI) / 2;
|
| 1053 |
+
group.add(cfGrp);
|
| 1054 |
+
}
|
| 1055 |
+
|
| 1056 |
+
const spireH = 4;
|
| 1057 |
+
const spire = new THREE.Mesh(new THREE.ConeGeometry(0.4, spireH, 8), mat(0x888899, { metalness: 0.6 }));
|
| 1058 |
+
spire.position.y = wallH + crownH + spireH / 2; group.add(spire);
|
| 1059 |
+
const beacon = new THREE.Mesh(new THREE.SphereGeometry(0.15, 8, 6),
|
| 1060 |
+
mat(0xff4444, { emissive: 0xff2222, emissiveIntensity: 0.6 }));
|
| 1061 |
+
beacon.position.y = wallH + crownH + spireH + 0.15; group.add(beacon);
|
| 1062 |
+
|
| 1063 |
+
const label = createLabel(locData.label, group, wallH + crownH + spireH + 2);
|
| 1064 |
+
const badge = createOccupantBadge(group, wallH + crownH + spireH + 1);
|
| 1065 |
group.userData = { id, type: 'tower', label, badge, locData };
|
| 1066 |
return group;
|
| 1067 |
}
|
| 1068 |
|
| 1069 |
function createHospital(id, locData) {
|
| 1070 |
const group = new THREE.Group();
|
| 1071 |
+
const w = 7, d = 5, wallH = 5;
|
| 1072 |
|
| 1073 |
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), mat(BLDG_COLORS.hospital));
|
| 1074 |
+
walls.position.y = wallH / 2; walls.castShadow = true; walls.receiveShadow = true;
|
| 1075 |
+
addEdges(walls); group.add(walls);
|
| 1076 |
+
|
| 1077 |
+
const wingW = 3, wingD = 3, wingH = 3.5;
|
| 1078 |
+
const wing = new THREE.Mesh(new THREE.BoxGeometry(wingW, wingH, wingD), mat(0xe8e8e8));
|
| 1079 |
+
wing.position.set(-w / 2 + wingW / 2 - 0.5, wingH / 2, -d / 2 - wingD / 2 + 0.5);
|
| 1080 |
+
wing.castShadow = true; wing.receiveShadow = true; addEdges(wing); group.add(wing);
|
| 1081 |
+
|
| 1082 |
+
const winMat = mat(isNight ? BLDG_COLORS.windowLit : BLDG_COLORS.window,
|
| 1083 |
+
isNight ? { emissive: BLDG_COLORS.windowLit, emissiveIntensity: 0.3 } : {});
|
| 1084 |
+
for (let f = 0; f < 2; f++) {
|
| 1085 |
+
for (let wx = -2; wx <= 2; wx++) {
|
| 1086 |
+
const win = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.6, 0.1), winMat);
|
| 1087 |
+
win.position.set(wx * 1.2, 1.5 + f * 2, d / 2 + 0.05);
|
| 1088 |
+
win.userData.isWindow = true; group.add(win);
|
| 1089 |
+
}
|
| 1090 |
+
}
|
| 1091 |
|
|
|
|
| 1092 |
const crossMat = mat(0xcc2222);
|
| 1093 |
const crossH = new THREE.Mesh(new THREE.BoxGeometry(1.5, 0.4, 0.1), crossMat);
|
| 1094 |
+
crossH.position.set(0, wallH - 0.5, d / 2 + 0.06); group.add(crossH);
|
|
|
|
| 1095 |
const crossV = new THREE.Mesh(new THREE.BoxGeometry(0.4, 1.5, 0.1), crossMat);
|
| 1096 |
+
crossV.position.set(0, wallH - 0.5, d / 2 + 0.06); group.add(crossV);
|
| 1097 |
+
|
| 1098 |
+
const canopyW = 4, canopyD = 2.5;
|
| 1099 |
+
const canopy = new THREE.Mesh(new THREE.BoxGeometry(canopyW, 0.12, canopyD), mat(0xdddddd));
|
| 1100 |
+
canopy.position.set(0, 3, d / 2 + canopyD / 2); canopy.castShadow = true; group.add(canopy);
|
| 1101 |
+
for (let s of [-1, 1]) {
|
| 1102 |
+
const pole = new THREE.Mesh(new THREE.CylinderGeometry(0.08, 0.08, 3, 6), mat(0x888888));
|
| 1103 |
+
pole.position.set(s * (canopyW / 2 - 0.3), 1.5, d / 2 + canopyD - 0.3); group.add(pole);
|
| 1104 |
+
}
|
| 1105 |
+
const emergSign = new THREE.Mesh(new THREE.BoxGeometry(2, 0.4, 0.08),
|
| 1106 |
+
mat(0xcc2222, { emissive: 0xaa1111, emissiveIntensity: 0.5 }));
|
| 1107 |
+
emergSign.position.set(0, 3.3, d / 2 + canopyD / 2); group.add(emergSign);
|
| 1108 |
+
|
| 1109 |
+
const roofCap = new THREE.Mesh(new THREE.BoxGeometry(w + 0.3, 0.25, d + 0.3), mat(0xcccccc));
|
| 1110 |
+
roofCap.position.y = wallH; group.add(roofCap);
|
| 1111 |
+
const heliR = 1.5;
|
| 1112 |
+
const heliPad = new THREE.Mesh(new THREE.CylinderGeometry(heliR, heliR, 0.05, 16), mat(0xaaaaaa));
|
| 1113 |
+
heliPad.position.set(w / 4, wallH + 0.15, 0); group.add(heliPad);
|
| 1114 |
+
const heliCircle = new THREE.Mesh(
|
| 1115 |
+
new THREE.RingGeometry(heliR - 0.15, heliR, 16),
|
| 1116 |
+
mat(0xeeee22, { side: THREE.DoubleSide }));
|
| 1117 |
+
heliCircle.rotation.x = -Math.PI / 2;
|
| 1118 |
+
heliCircle.position.set(w / 4, wallH + 0.2, 0); group.add(heliCircle);
|
| 1119 |
+
const hBar = new THREE.Mesh(new THREE.BoxGeometry(1.2, 0.04, 0.15), mat(0xeeee22));
|
| 1120 |
+
hBar.position.set(w / 4, wallH + 0.2, 0); group.add(hBar);
|
| 1121 |
+
const hBar2 = new THREE.Mesh(new THREE.BoxGeometry(0.15, 0.04, 1.2), mat(0xeeee22));
|
| 1122 |
+
hBar2.position.set(w / 4, wallH + 0.2, 0); group.add(hBar2);
|
| 1123 |
|
| 1124 |
const label = createLabel(locData.label, group, wallH + 2);
|
| 1125 |
const badge = createOccupantBadge(group, wallH + 1);
|
|
|
|
| 1129 |
|
| 1130 |
function createChurch(id, locData) {
|
| 1131 |
const group = new THREE.Group();
|
| 1132 |
+
const w = 4, d = 6, wallH = 4.5;
|
| 1133 |
+
const stoneMat = mat(BLDG_COLORS.church);
|
| 1134 |
|
| 1135 |
+
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), stoneMat);
|
| 1136 |
+
walls.position.y = wallH / 2; walls.castShadow = true; walls.receiveShadow = true;
|
| 1137 |
+
addEdges(walls); group.add(walls);
|
|
|
|
|
|
|
|
|
|
| 1138 |
|
|
|
|
| 1139 |
const roofGeo = new THREE.BufferGeometry();
|
| 1140 |
+
const hw = w / 2 + 0.2, hd = d / 2 + 0.2, rh = 2.2;
|
| 1141 |
+
const v = new Float32Array([
|
| 1142 |
+
-hw,wallH,-hd, hw,wallH,-hd, 0,wallH+rh,-hd,
|
| 1143 |
+
-hw,wallH,hd, hw,wallH,hd, 0,wallH+rh,hd,
|
| 1144 |
+
-hw,wallH,-hd, -hw,wallH,hd, 0,wallH+rh,-hd, 0,wallH+rh,hd,
|
| 1145 |
+
hw,wallH,-hd, hw,wallH,hd, 0,wallH+rh,-hd, 0,wallH+rh,hd,
|
|
|
|
| 1146 |
]);
|
| 1147 |
+
roofGeo.setAttribute('position', new THREE.BufferAttribute(v, 3));
|
| 1148 |
+
roofGeo.setIndex([0,1,2, 3,5,4, 2,3,0, 2,5,3, 1,4,5, 1,5,2]);
|
|
|
|
| 1149 |
roofGeo.computeVertexNormals();
|
| 1150 |
const roof = new THREE.Mesh(roofGeo, mat(BLDG_COLORS.roof[0]));
|
| 1151 |
+
roof.castShadow = true; group.add(roof);
|
| 1152 |
+
|
| 1153 |
+
for (let bz = -1.5; bz <= 1.5; bz += 1.5) {
|
| 1154 |
+
for (let side of [-1, 1]) {
|
| 1155 |
+
const buttress = new THREE.Mesh(new THREE.BoxGeometry(0.3, wallH * 0.8, 0.5), stoneMat);
|
| 1156 |
+
buttress.position.set(side * (w / 2 + 0.15), wallH * 0.4, bz);
|
| 1157 |
+
buttress.castShadow = true; group.add(buttress);
|
| 1158 |
+
const buttTop = new THREE.Mesh(new THREE.ConeGeometry(0.25, 0.6, 4), stoneMat);
|
| 1159 |
+
buttTop.position.set(side * (w / 2 + 0.15), wallH * 0.8 + 0.3, bz);
|
| 1160 |
+
buttTop.rotation.y = Math.PI / 4; group.add(buttTop);
|
| 1161 |
+
}
|
| 1162 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1163 |
|
| 1164 |
+
const roseR = 0.7;
|
| 1165 |
+
const roseFrame = new THREE.Mesh(new THREE.TorusGeometry(roseR, 0.06, 8, 16),
|
| 1166 |
+
mat(0x888878, { metalness: 0.3 }));
|
| 1167 |
+
roseFrame.position.set(0, wallH * 0.7, d / 2 + 0.06); group.add(roseFrame);
|
| 1168 |
+
const roseGlass = new THREE.Mesh(new THREE.CircleGeometry(roseR - 0.05, 16),
|
| 1169 |
+
mat(0x4466aa, { transparent: true, opacity: 0.6, emissive: 0x223355, emissiveIntensity: 0.2, side: THREE.DoubleSide }));
|
| 1170 |
+
roseGlass.position.set(0, wallH * 0.7, d / 2 + 0.07);
|
| 1171 |
+
roseGlass.userData.isWindow = true; group.add(roseGlass);
|
| 1172 |
+
for (let i = 0; i < 8; i++) {
|
| 1173 |
+
const ang = (i / 8) * Math.PI * 2;
|
| 1174 |
+
const spoke = new THREE.Mesh(new THREE.BoxGeometry(0.03, roseR * 1.6, 0.03), mat(0x888878));
|
| 1175 |
+
spoke.position.set(0, wallH * 0.7, d / 2 + 0.065);
|
| 1176 |
+
spoke.rotation.z = ang; group.add(spoke);
|
| 1177 |
+
}
|
| 1178 |
+
|
| 1179 |
+
const doorW = 1.2, doorH = 2;
|
| 1180 |
+
const doorArch = new THREE.Mesh(new THREE.BoxGeometry(doorW, doorH, 0.15), mat(BLDG_COLORS.door));
|
| 1181 |
+
doorArch.position.set(0, doorH / 2, d / 2 + 0.05); doorArch.userData.isDoor = true; group.add(doorArch);
|
| 1182 |
+
const archTop = new THREE.Mesh(new THREE.CylinderGeometry(doorW / 2, doorW / 2, 0.15, 12, 1, false, 0, Math.PI),
|
| 1183 |
+
mat(BLDG_COLORS.door));
|
| 1184 |
+
archTop.rotation.x = Math.PI / 2; archTop.rotation.z = Math.PI;
|
| 1185 |
+
archTop.position.set(0, doorH, d / 2 + 0.05); group.add(archTop);
|
| 1186 |
+
|
| 1187 |
+
const stBase = new THREE.Mesh(new THREE.BoxGeometry(1.8, 2.5, 1.8), stoneMat);
|
| 1188 |
+
stBase.position.set(0, wallH + 1.25, -d / 2 + 1);
|
| 1189 |
+
stBase.castShadow = true; group.add(stBase);
|
| 1190 |
+
for (let corner of [[-0.7,-0.7],[0.7,-0.7],[-0.7,0.7],[0.7,0.7]]) {
|
| 1191 |
+
const pin = new THREE.Mesh(new THREE.ConeGeometry(0.15, 0.8, 4), mat(BLDG_COLORS.roof[1]));
|
| 1192 |
+
pin.position.set(corner[0], wallH + 2.9, -d / 2 + 1 + corner[1]);
|
| 1193 |
+
pin.rotation.y = Math.PI / 4; group.add(pin);
|
| 1194 |
+
}
|
| 1195 |
+
const steeple = new THREE.Mesh(new THREE.ConeGeometry(0.7, 4.5, 4), mat(BLDG_COLORS.roof[1]));
|
| 1196 |
+
steeple.position.set(0, wallH + rh + 2.5, -d / 2 + 1);
|
| 1197 |
+
steeple.rotation.y = Math.PI / 4;
|
| 1198 |
+
steeple.castShadow = true; group.add(steeple);
|
| 1199 |
+
const crossBar1 = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.8, 0.06), mat(0xccaa44, { metalness: 0.5 }));
|
| 1200 |
+
crossBar1.position.set(0, wallH + rh + 5.1, -d / 2 + 1); group.add(crossBar1);
|
| 1201 |
+
const crossBar2 = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.06, 0.06), mat(0xccaa44, { metalness: 0.5 }));
|
| 1202 |
+
crossBar2.position.set(0, wallH + rh + 5.2, -d / 2 + 1); group.add(crossBar2);
|
| 1203 |
+
|
| 1204 |
+
const label = createLabel(locData.label, group, wallH + rh + 6);
|
| 1205 |
+
const badge = createOccupantBadge(group, wallH + rh + 5);
|
| 1206 |
group.userData = { id, type: 'church', label, badge, locData };
|
| 1207 |
return group;
|
| 1208 |
}
|
|
|
|
| 1212 |
const w = 6, d = 5, wallH = 3.5;
|
| 1213 |
|
| 1214 |
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), mat(BLDG_COLORS.school));
|
| 1215 |
+
walls.position.y = wallH / 2; walls.castShadow = true; walls.receiveShadow = true;
|
| 1216 |
+
addEdges(walls); group.add(walls);
|
|
|
|
|
|
|
|
|
|
| 1217 |
|
| 1218 |
+
const entW = 2, entH = wallH + 1.5, entD = 0.6;
|
| 1219 |
+
const entrance = new THREE.Mesh(new THREE.BoxGeometry(entW, entH, entD), mat(BLDG_COLORS.school - 0x101010));
|
| 1220 |
+
entrance.position.set(0, entH / 2, d / 2 + entD / 2 - 0.1);
|
| 1221 |
+
entrance.castShadow = true; group.add(entrance);
|
| 1222 |
+
const pediment = new THREE.Mesh(new THREE.ConeGeometry(entW * 0.6, 1, 3), mat(BLDG_COLORS.school - 0x101010));
|
| 1223 |
+
pediment.position.set(0, entH + 0.3, d / 2 + entD / 2 - 0.1);
|
| 1224 |
+
pediment.scale.z = 0.3; pediment.rotation.y = Math.PI / 6; group.add(pediment);
|
| 1225 |
+
|
| 1226 |
+
const door = new THREE.Mesh(new THREE.BoxGeometry(1.2, 2, 0.1), mat(BLDG_COLORS.door));
|
| 1227 |
+
door.position.set(0, 1, d / 2 + entD - 0.05); door.userData.isDoor = true; group.add(door);
|
| 1228 |
+
|
| 1229 |
+
const clockR = 0.4;
|
| 1230 |
+
const clockFace = new THREE.Mesh(new THREE.CircleGeometry(clockR, 16),
|
| 1231 |
+
mat(0xf8f8f0, { side: THREE.DoubleSide }));
|
| 1232 |
+
clockFace.position.set(0, entH - 0.5, d / 2 + entD + 0.01); group.add(clockFace);
|
| 1233 |
+
const clockRim = new THREE.Mesh(new THREE.TorusGeometry(clockR, 0.04, 8, 16), mat(0x444444));
|
| 1234 |
+
clockRim.position.set(0, entH - 0.5, d / 2 + entD + 0.01); group.add(clockRim);
|
| 1235 |
+
const hourHand = new THREE.Mesh(new THREE.BoxGeometry(0.04, clockR * 0.5, 0.02), mat(0x222222));
|
| 1236 |
+
hourHand.position.set(0, entH - 0.5 + clockR * 0.2, d / 2 + entD + 0.02); group.add(hourHand);
|
| 1237 |
+
const minHand = new THREE.Mesh(new THREE.BoxGeometry(0.03, clockR * 0.7, 0.02), mat(0x222222));
|
| 1238 |
+
minHand.position.set(clockR * 0.15, entH - 0.5, d / 2 + entD + 0.02);
|
| 1239 |
+
minHand.rotation.z = -Math.PI / 3; group.add(minHand);
|
| 1240 |
+
|
| 1241 |
+
const winMat = mat(isNight ? BLDG_COLORS.windowLit : BLDG_COLORS.window,
|
| 1242 |
+
isNight ? { emissive: BLDG_COLORS.windowLit, emissiveIntensity: 0.3 } : {});
|
| 1243 |
for (let i = -2; i <= 2; i++) {
|
| 1244 |
+
if (Math.abs(i) < 1) continue;
|
| 1245 |
const win = new THREE.Mesh(new THREE.BoxGeometry(0.6, 0.8, 0.1), winMat);
|
| 1246 |
win.position.set(i * 1.1, wallH * 0.6, d / 2 + 0.05);
|
| 1247 |
+
win.userData.isWindow = true; group.add(win);
|
| 1248 |
+
const sill = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.06, 0.12), mat(0xaa6848));
|
| 1249 |
+
sill.position.set(i * 1.1, wallH * 0.6 - 0.43, d / 2 + 0.08); group.add(sill);
|
| 1250 |
+
}
|
| 1251 |
+
for (let side of [-1, 1]) {
|
| 1252 |
+
for (let sz = -1; sz <= 1; sz++) {
|
| 1253 |
+
const sWin = new THREE.Mesh(new THREE.BoxGeometry(0.1, 0.7, 0.5), winMat);
|
| 1254 |
+
sWin.position.set(side * (w / 2 + 0.05), wallH * 0.6, sz * 1.3);
|
| 1255 |
+
sWin.userData.isWindow = true; group.add(sWin);
|
| 1256 |
+
}
|
| 1257 |
}
|
| 1258 |
|
| 1259 |
+
const pole = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.05, 4, 6), mat(0x888888));
|
| 1260 |
+
pole.position.set(w / 2 + 0.8, 2, d / 2 - 1); group.add(pole);
|
|
|
|
|
|
|
|
|
|
| 1261 |
const flag = new THREE.Mesh(new THREE.PlaneGeometry(1, 0.6), mat(0xcc3333, { side: THREE.DoubleSide }));
|
| 1262 |
+
flag.position.set(w / 2 + 0.8 + 0.5, 3.5, d / 2 - 1); group.add(flag);
|
| 1263 |
+
|
| 1264 |
+
const roofCap = new THREE.Mesh(new THREE.BoxGeometry(w + 0.3, 0.2, d + 0.3), mat(BLDG_COLORS.school - 0x151515));
|
| 1265 |
+
roofCap.position.y = wallH; group.add(roofCap);
|
| 1266 |
|
| 1267 |
+
const label = createLabel(locData.label, group, entH + 2);
|
| 1268 |
+
const badge = createOccupantBadge(group, entH + 1);
|
| 1269 |
group.userData = { id, type: 'school', label, badge, locData };
|
| 1270 |
return group;
|
| 1271 |
}
|
|
|
|
| 1273 |
function createFactory(id, locData) {
|
| 1274 |
const group = new THREE.Group();
|
| 1275 |
const w = 7, d = 5, wallH = 4.5;
|
| 1276 |
+
const factMat = mat(BLDG_COLORS.factory);
|
| 1277 |
+
const metalMat = mat(0x666666, { metalness: 0.4, roughness: 0.4 });
|
| 1278 |
|
| 1279 |
+
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), factMat);
|
| 1280 |
+
walls.position.y = wallH / 2; walls.castShadow = true; walls.receiveShadow = true;
|
| 1281 |
+
addEdges(walls); group.add(walls);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1282 |
|
| 1283 |
+
const sawTeeth = 3, toothW = w / sawTeeth, toothH = 1.2;
|
| 1284 |
+
for (let i = 0; i < sawTeeth; i++) {
|
| 1285 |
+
const tx = -w / 2 + toothW / 2 + i * toothW;
|
| 1286 |
+
const geo = new THREE.BufferGeometry();
|
| 1287 |
+
const hw = toothW / 2, hd = d / 2;
|
| 1288 |
+
const v = new Float32Array([
|
| 1289 |
+
-hw, wallH, -hd, hw, wallH, -hd, hw, wallH, hd, -hw, wallH, hd,
|
| 1290 |
+
-hw, wallH + toothH, -hd, hw, wallH + toothH, -hd,
|
| 1291 |
+
-hw, wallH + toothH, hd, hw, wallH, hd,
|
| 1292 |
+
]);
|
| 1293 |
+
geo.setAttribute('position', new THREE.BufferAttribute(v, 3));
|
| 1294 |
+
geo.setIndex([0,1,5,0,5,4, 3,6,4,3,4,0, 1,2,7,1,7,5, 4,5,7,4,7,6, 3,2,1,3,1,0]);
|
| 1295 |
+
geo.computeVertexNormals();
|
| 1296 |
+
const tooth = new THREE.Mesh(geo, metalMat);
|
| 1297 |
+
tooth.position.x = tx; tooth.castShadow = true; group.add(tooth);
|
| 1298 |
+
const skylight = new THREE.Mesh(new THREE.PlaneGeometry(toothW * 0.6, d * 0.6),
|
| 1299 |
+
mat(0x88bbdd, { transparent: true, opacity: 0.4, side: THREE.DoubleSide }));
|
| 1300 |
+
skylight.position.set(tx, wallH + toothH * 0.5 + 0.01, 0);
|
| 1301 |
+
skylight.rotation.x = -Math.PI / 2 + 0.3;
|
| 1302 |
+
skylight.userData.isWindow = true; group.add(skylight);
|
| 1303 |
+
}
|
| 1304 |
+
|
| 1305 |
+
const chimney = new THREE.Mesh(new THREE.CylinderGeometry(0.5, 0.65, 5, 8), mat(0x555555));
|
| 1306 |
+
chimney.position.set(w / 2 - 1, wallH + 2.5, -d / 2 + 1);
|
| 1307 |
+
chimney.castShadow = true; group.add(chimney);
|
| 1308 |
+
const chimRing1 = new THREE.Mesh(new THREE.TorusGeometry(0.55, 0.06, 6, 12), metalMat);
|
| 1309 |
+
chimRing1.position.set(w / 2 - 1, wallH + 4.5, -d / 2 + 1);
|
| 1310 |
+
chimRing1.rotation.x = Math.PI / 2; group.add(chimRing1);
|
| 1311 |
+
const chimney2 = new THREE.Mesh(new THREE.CylinderGeometry(0.4, 0.5, 4, 8), mat(0x555555));
|
| 1312 |
+
chimney2.position.set(w / 2 - 2.8, wallH + 2, -d / 2 + 1);
|
| 1313 |
+
chimney2.castShadow = true; group.add(chimney2);
|
| 1314 |
+
|
| 1315 |
+
const dockW = 3, dockD = 1.5;
|
| 1316 |
+
const dockFloor = new THREE.Mesh(new THREE.BoxGeometry(dockW, 0.8, dockD), mat(0x606060));
|
| 1317 |
+
dockFloor.position.set(-w / 4, 0.4, d / 2 + dockD / 2); group.add(dockFloor);
|
| 1318 |
+
const dockRoof = new THREE.Mesh(new THREE.BoxGeometry(dockW + 0.5, 0.1, dockD + 0.5), metalMat);
|
| 1319 |
+
dockRoof.position.set(-w / 4, 3.5, d / 2 + dockD / 2); dockRoof.castShadow = true; group.add(dockRoof);
|
| 1320 |
+
for (let s of [-1, 1]) {
|
| 1321 |
+
const dPole = new THREE.Mesh(new THREE.CylinderGeometry(0.06, 0.06, 3, 6), metalMat);
|
| 1322 |
+
dPole.position.set(-w / 4 + s * dockW / 2, 2, d / 2 + dockD); group.add(dPole);
|
| 1323 |
+
}
|
| 1324 |
+
const rollDoor = new THREE.Mesh(new THREE.BoxGeometry(2.5, 2.8, 0.1), mat(0x888888, { metalness: 0.3 }));
|
| 1325 |
+
rollDoor.position.set(-w / 4, 2.2, d / 2 + 0.05); rollDoor.userData.isDoor = true; group.add(rollDoor);
|
| 1326 |
+
|
| 1327 |
+
const pipeMat = mat(0x999900, { metalness: 0.3 });
|
| 1328 |
+
const pipe1 = new THREE.Mesh(new THREE.CylinderGeometry(0.12, 0.12, wallH + 1, 8), pipeMat);
|
| 1329 |
+
pipe1.position.set(-w / 2 + 0.3, wallH / 2, -d / 2 + 0.3); group.add(pipe1);
|
| 1330 |
+
const pipe2 = new THREE.Mesh(new THREE.CylinderGeometry(0.1, 0.1, 3, 8), pipeMat);
|
| 1331 |
+
pipe2.rotation.z = Math.PI / 2;
|
| 1332 |
+
pipe2.position.set(-w / 2 + 1.8, wallH * 0.7, -d / 2 + 0.3); group.add(pipe2);
|
| 1333 |
+
|
| 1334 |
+
const label = createLabel(locData.label, group, wallH + toothH + 5);
|
| 1335 |
+
const badge = createOccupantBadge(group, wallH + toothH + 4);
|
| 1336 |
group.userData = { id, type: 'factory', label, badge, locData };
|
| 1337 |
return group;
|
| 1338 |
}
|
|
|
|
| 1342 |
const w = 5.5, d = 5, wallH = 5;
|
| 1343 |
|
| 1344 |
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), mat(BLDG_COLORS.cinema));
|
| 1345 |
+
walls.position.y = wallH / 2; walls.castShadow = true; walls.receiveShadow = true;
|
| 1346 |
+
addEdges(walls); group.add(walls);
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1347 |
|
| 1348 |
+
const facadeH = 2;
|
| 1349 |
+
const facade = new THREE.Mesh(new THREE.BoxGeometry(w + 0.8, facadeH, 0.3), mat(0x6b2040));
|
| 1350 |
+
facade.position.set(0, wallH + facadeH / 2, d / 2); facade.castShadow = true; group.add(facade);
|
| 1351 |
+
|
| 1352 |
+
const marquee = new THREE.Mesh(new THREE.BoxGeometry(w + 1.2, 1, 1),
|
| 1353 |
+
mat(0xddcc44, { emissive: 0xaa8833, emissiveIntensity: 0.4 }));
|
| 1354 |
+
marquee.position.set(0, wallH * 0.75, d / 2 + 0.5); group.add(marquee);
|
| 1355 |
+
const marqueeBottom = new THREE.Mesh(new THREE.BoxGeometry(w + 1.3, 0.08, 1.1),
|
| 1356 |
+
mat(0xccbb33, { emissive: 0x887722, emissiveIntensity: 0.3 }));
|
| 1357 |
+
marqueeBottom.position.set(0, wallH * 0.75 - 0.5, d / 2 + 0.5); group.add(marqueeBottom);
|
| 1358 |
+
|
| 1359 |
+
const bulbMat = mat(0xffee88, { emissive: 0xffdd44, emissiveIntensity: 0.6 });
|
| 1360 |
+
for (let bx = -w / 2 - 0.3; bx <= w / 2 + 0.3; bx += 0.5) {
|
| 1361 |
+
const bulb = new THREE.Mesh(new THREE.SphereGeometry(0.06, 4, 4), bulbMat);
|
| 1362 |
+
bulb.position.set(bx, wallH * 0.75 + 0.52, d / 2 + 1.02); group.add(bulb);
|
| 1363 |
+
const bulb2 = new THREE.Mesh(new THREE.SphereGeometry(0.06, 4, 4), bulbMat);
|
| 1364 |
+
bulb2.position.set(bx, wallH * 0.75 - 0.52, d / 2 + 1.02); group.add(bulb2);
|
| 1365 |
+
}
|
| 1366 |
+
|
| 1367 |
+
const doors = new THREE.Mesh(new THREE.BoxGeometry(2.5, 2.5, 0.1), mat(0x4a2030));
|
| 1368 |
+
doors.position.set(0, 1.25, d / 2 + 0.06); doors.userData.isDoor = true; group.add(doors);
|
| 1369 |
+
for (let dx of [-0.5, 0.5]) {
|
| 1370 |
+
const dGlass = new THREE.Mesh(new THREE.BoxGeometry(0.7, 1.8, 0.06),
|
| 1371 |
+
mat(BLDG_COLORS.window, { transparent: true, opacity: 0.4 }));
|
| 1372 |
+
dGlass.position.set(dx, 1.4, d / 2 + 0.09); dGlass.userData.isWindow = true; group.add(dGlass);
|
| 1373 |
+
}
|
| 1374 |
+
|
| 1375 |
+
const boothW = 1, boothH = 2.2, boothD = 1;
|
| 1376 |
+
const booth = new THREE.Mesh(new THREE.BoxGeometry(boothW, boothH, boothD), mat(0x8b3050));
|
| 1377 |
+
booth.position.set(w / 2 + boothW / 2 + 0.3, boothH / 2, d / 2 - 0.5);
|
| 1378 |
+
booth.castShadow = true; group.add(booth);
|
| 1379 |
+
const boothWin = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.5, 0.6),
|
| 1380 |
+
mat(BLDG_COLORS.window, { transparent: true, opacity: 0.5 }));
|
| 1381 |
+
boothWin.position.set(w / 2 + 0.3, boothH * 0.6, d / 2 - 0.5);
|
| 1382 |
+
boothWin.userData.isWindow = true; group.add(boothWin);
|
| 1383 |
+
const boothRoof = new THREE.Mesh(new THREE.BoxGeometry(boothW + 0.2, 0.1, boothD + 0.2), mat(0x6b2040));
|
| 1384 |
+
boothRoof.position.set(w / 2 + boothW / 2 + 0.3, boothH + 0.05, d / 2 - 0.5); group.add(boothRoof);
|
| 1385 |
+
|
| 1386 |
+
const label = createLabel(locData.label, group, wallH + facadeH + 2);
|
| 1387 |
+
const badge = createOccupantBadge(group, wallH + facadeH + 1);
|
| 1388 |
group.userData = { id, type: 'cinema', label, badge, locData };
|
| 1389 |
return group;
|
| 1390 |
}
|
| 1391 |
|
| 1392 |
function createPark(id, locData) {
|
| 1393 |
const group = new THREE.Group();
|
| 1394 |
+
const benchMat = mat(0x8b6040);
|
| 1395 |
+
|
| 1396 |
+
const parkGround = new THREE.Mesh(new THREE.BoxGeometry(8, 0.1, 6), mat(BLDG_COLORS.park));
|
| 1397 |
+
parkGround.position.y = 0.05; parkGround.receiveShadow = true; group.add(parkGround);
|
| 1398 |
+
|
| 1399 |
+
const pathMat = mat(0xccbbaa);
|
| 1400 |
+
const mainPath = new THREE.Mesh(new THREE.BoxGeometry(1, 0.12, 6), pathMat);
|
| 1401 |
+
mainPath.position.y = 0.06; group.add(mainPath);
|
| 1402 |
+
const crossPath = new THREE.Mesh(new THREE.BoxGeometry(8, 0.12, 0.8), pathMat);
|
| 1403 |
+
crossPath.position.y = 0.06; group.add(crossPath);
|
| 1404 |
+
|
| 1405 |
+
const gazR = 1.2, gazH = 2.8, gazPoles = 6;
|
| 1406 |
+
const gazebo = new THREE.Group();
|
| 1407 |
+
const gazFloor = new THREE.Mesh(new THREE.CylinderGeometry(gazR + 0.1, gazR + 0.1, 0.12, gazPoles), mat(0xc0b090));
|
| 1408 |
+
gazFloor.position.y = 0.2; gazebo.add(gazFloor);
|
| 1409 |
+
for (let i = 0; i < gazPoles; i++) {
|
| 1410 |
+
const ang = (i / gazPoles) * Math.PI * 2;
|
| 1411 |
+
const pole = new THREE.Mesh(new THREE.CylinderGeometry(0.05, 0.06, gazH, 6), mat(0xf0f0f0));
|
| 1412 |
+
pole.position.set(Math.cos(ang) * gazR, gazH / 2 + 0.2, Math.sin(ang) * gazR);
|
| 1413 |
+
gazebo.add(pole);
|
| 1414 |
+
}
|
| 1415 |
+
const gazRoof = new THREE.Mesh(new THREE.ConeGeometry(gazR + 0.4, 1.2, gazPoles), mat(0x885540));
|
| 1416 |
+
gazRoof.position.y = gazH + 0.8; gazRoof.castShadow = true; gazebo.add(gazRoof);
|
| 1417 |
+
const gazRail = new THREE.Mesh(new THREE.TorusGeometry(gazR, 0.04, 4, gazPoles), mat(0xf0f0f0));
|
| 1418 |
+
gazRail.rotation.x = Math.PI / 2; gazRail.position.y = 0.7; gazebo.add(gazRail);
|
| 1419 |
+
gazebo.position.set(-2.5, 0, 0); group.add(gazebo);
|
| 1420 |
+
|
| 1421 |
+
const flowerColors = [0xff6688, 0xffaa44, 0xcc66cc, 0xff4444, 0xffdd44];
|
| 1422 |
+
for (let fb = 0; fb < 2; fb++) {
|
| 1423 |
+
const fbz = fb === 0 ? -2 : 2;
|
| 1424 |
+
const bed = new THREE.Mesh(new THREE.BoxGeometry(2, 0.15, 0.8), mat(0x3a6a2a));
|
| 1425 |
+
bed.position.set(2.5, 0.12, fbz); group.add(bed);
|
| 1426 |
+
const border = new THREE.Mesh(new THREE.BoxGeometry(2.1, 0.2, 0.9), mat(0x8b7050));
|
| 1427 |
+
border.position.set(2.5, 0.06, fbz); group.add(border);
|
| 1428 |
+
for (let fx = 0; fx < 6; fx++) {
|
| 1429 |
+
const fc = flowerColors[(fb * 6 + fx) % flowerColors.length];
|
| 1430 |
+
const flower = new THREE.Mesh(new THREE.SphereGeometry(0.08, 4, 4), mat(fc));
|
| 1431 |
+
flower.position.set(2.5 + (fx - 2.5) * 0.35, 0.28, fbz + (Math.random() - 0.5) * 0.4);
|
| 1432 |
+
group.add(flower);
|
| 1433 |
+
const stem = new THREE.Mesh(new THREE.CylinderGeometry(0.015, 0.015, 0.12, 4), mat(0x2a6a2a));
|
| 1434 |
+
stem.position.set(flower.position.x, 0.2, flower.position.z); group.add(stem);
|
| 1435 |
+
}
|
| 1436 |
+
}
|
| 1437 |
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1438 |
for (let i = 0; i < 5; i++) {
|
| 1439 |
const tx = (Math.random() - 0.5) * 6;
|
| 1440 |
const tz = (Math.random() - 0.5) * 4;
|
| 1441 |
+
if (Math.abs(tx) > 1 && Math.abs(tx + 2.5) > 1.5) {
|
| 1442 |
const t = createTree(0, 0, 0.35 + Math.random() * 0.25);
|
| 1443 |
t.position.set(tx, 0, tz);
|
| 1444 |
+
scene.remove(t); group.add(t);
|
|
|
|
| 1445 |
}
|
| 1446 |
}
|
| 1447 |
|
| 1448 |
+
for (let bPos of [[1.5, 1.2], [1.5, -1.2]]) {
|
| 1449 |
+
const bGroup = new THREE.Group();
|
| 1450 |
+
const seat = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.08, 0.45), benchMat);
|
| 1451 |
+
seat.position.y = 0.45; bGroup.add(seat);
|
| 1452 |
+
const back = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.5, 0.06), benchMat);
|
| 1453 |
+
back.position.set(0, 0.65, -0.2); bGroup.add(back);
|
| 1454 |
+
for (let lx of [-0.6, 0.6]) {
|
| 1455 |
+
const leg = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.45, 0.35), mat(0x444444));
|
| 1456 |
+
leg.position.set(lx, 0.22, 0); bGroup.add(leg);
|
| 1457 |
+
}
|
| 1458 |
+
bGroup.position.set(bPos[0], 0, bPos[1]);
|
| 1459 |
+
group.add(bGroup);
|
| 1460 |
+
}
|
| 1461 |
|
| 1462 |
const label = createLabel(locData.label, group, 5);
|
| 1463 |
const badge = createOccupantBadge(group, 4);
|
|
|
|
| 1504 |
function createPolice(id, locData) {
|
| 1505 |
const group = new THREE.Group();
|
| 1506 |
const w = 5, d = 4, wallH = 3.5;
|
| 1507 |
+
const policeMat = mat(BLDG_COLORS.police);
|
| 1508 |
+
|
| 1509 |
+
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), policeMat);
|
| 1510 |
walls.position.y = wallH / 2; walls.castShadow = true; walls.receiveShadow = true;
|
| 1511 |
addEdges(walls); group.add(walls);
|
| 1512 |
+
|
| 1513 |
const roof = new THREE.Mesh(new THREE.BoxGeometry(w + 0.4, 0.25, d + 0.4), mat(0x2a4a6a));
|
| 1514 |
roof.position.y = wallH; group.add(roof);
|
| 1515 |
+
const parapet = new THREE.Mesh(new THREE.BoxGeometry(w + 0.5, 0.4, 0.15), mat(0x2a4a6a));
|
| 1516 |
+
parapet.position.set(0, wallH + 0.2, d / 2 + 0.1); group.add(parapet);
|
| 1517 |
+
|
| 1518 |
+
const stripe = new THREE.Mesh(new THREE.BoxGeometry(w + 0.1, 0.5, 0.1), mat(0x4488cc));
|
| 1519 |
+
stripe.position.set(0, wallH * 0.88, d / 2 + 0.06); group.add(stripe);
|
| 1520 |
+
const lowerStripe = new THREE.Mesh(new THREE.BoxGeometry(w + 0.1, 0.15, 0.1), mat(0x4488cc));
|
| 1521 |
+
lowerStripe.position.set(0, 0.5, d / 2 + 0.06); group.add(lowerStripe);
|
| 1522 |
+
|
| 1523 |
+
const entW = 1.8;
|
| 1524 |
+
const canopy = new THREE.Mesh(new THREE.BoxGeometry(entW + 1, 0.1, 1.2), mat(0x2a4a6a));
|
| 1525 |
+
canopy.position.set(0, 2.5, d / 2 + 0.6); canopy.castShadow = true; group.add(canopy);
|
| 1526 |
+
const door = new THREE.Mesh(new THREE.BoxGeometry(entW, 2.2, 0.1), mat(0x2a3a5a));
|
| 1527 |
+
door.position.set(0, 1.1, d / 2 + 0.06); door.userData.isDoor = true; group.add(door);
|
| 1528 |
+
const dGlass = new THREE.Mesh(new THREE.BoxGeometry(entW * 0.7, 1.5, 0.06),
|
| 1529 |
+
mat(BLDG_COLORS.window, { transparent: true, opacity: 0.4 }));
|
| 1530 |
+
dGlass.position.set(0, 1.3, d / 2 + 0.09); dGlass.userData.isWindow = true; group.add(dGlass);
|
| 1531 |
+
|
| 1532 |
+
for (let sx of [-1.8, 1.8]) {
|
| 1533 |
+
const win = new THREE.Mesh(new THREE.BoxGeometry(0.7, 0.8, 0.1), mat(BLDG_COLORS.window, { roughness: 0.3 }));
|
| 1534 |
win.position.set(sx, wallH * 0.6, d / 2 + 0.06); win.userData.isWindow = true; group.add(win);
|
| 1535 |
+
const bars = new THREE.Group();
|
| 1536 |
+
for (let bx = -0.25; bx <= 0.25; bx += 0.12) {
|
| 1537 |
+
const bar = new THREE.Mesh(new THREE.BoxGeometry(0.02, 0.8, 0.02), mat(0x444444));
|
| 1538 |
+
bar.position.set(bx, 0, 0.06); bars.add(bar);
|
| 1539 |
+
}
|
| 1540 |
+
bars.position.set(sx, wallH * 0.6, d / 2 + 0.06); group.add(bars);
|
| 1541 |
}
|
| 1542 |
+
|
| 1543 |
+
const badgeR = 0.5;
|
| 1544 |
+
const shield = new THREE.Mesh(new THREE.CircleGeometry(badgeR, 6),
|
| 1545 |
+
mat(0xccaa44, { metalness: 0.5, side: THREE.DoubleSide }));
|
| 1546 |
+
shield.position.set(0, wallH * 0.65, d / 2 + 0.12); group.add(shield);
|
| 1547 |
+
const star = new THREE.Mesh(new THREE.CircleGeometry(badgeR * 0.5, 5),
|
| 1548 |
+
mat(0x4488cc, { side: THREE.DoubleSide }));
|
| 1549 |
+
star.position.set(0, wallH * 0.65, d / 2 + 0.13); group.add(star);
|
| 1550 |
+
|
| 1551 |
const label = createLabel(locData.label, group, wallH + 2);
|
| 1552 |
const badge = createOccupantBadge(group, wallH + 1);
|
| 1553 |
group.userData = { id, type: 'police', label, badge, locData };
|
|
|
|
| 1577 |
|
| 1578 |
function createMuseum(id, locData) {
|
| 1579 |
const group = new THREE.Group();
|
| 1580 |
+
const w = 6, d = 5, wallH = 4.5;
|
| 1581 |
+
const museumMat = mat(BLDG_COLORS.museum);
|
| 1582 |
+
const colMat = mat(0xd8c898);
|
| 1583 |
+
|
| 1584 |
+
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), museumMat);
|
| 1585 |
walls.position.y = wallH / 2; walls.castShadow = true; walls.receiveShadow = true;
|
| 1586 |
addEdges(walls); group.add(walls);
|
| 1587 |
+
|
| 1588 |
+
const porticoD = 1.5, porticoH = wallH + 0.5;
|
| 1589 |
+
const porticoRoof = new THREE.Mesh(new THREE.BoxGeometry(w + 0.8, 0.25, porticoD + 0.3), museumMat);
|
| 1590 |
+
porticoRoof.position.set(0, porticoH, d / 2 + porticoD / 2); porticoRoof.castShadow = true; group.add(porticoRoof);
|
| 1591 |
+
|
| 1592 |
+
const pedGeo = new THREE.BufferGeometry();
|
| 1593 |
+
const pw = w / 2 + 0.4, ph = 1.8;
|
| 1594 |
+
const pv = new Float32Array([
|
| 1595 |
+
-pw, porticoH + 0.25, d / 2, pw, porticoH + 0.25, d / 2, 0, porticoH + 0.25 + ph, d / 2,
|
| 1596 |
+
-pw, porticoH + 0.25, d / 2 + porticoD + 0.3, pw, porticoH + 0.25, d / 2 + porticoD + 0.3, 0, porticoH + 0.25 + ph, d / 2 + porticoD + 0.3,
|
| 1597 |
+
]);
|
| 1598 |
+
pedGeo.setAttribute('position', new THREE.BufferAttribute(pv, 3));
|
| 1599 |
+
pedGeo.setIndex([0,1,2, 3,5,4, 0,2,5,0,5,3, 1,4,5,1,5,2]);
|
| 1600 |
+
pedGeo.computeVertexNormals();
|
| 1601 |
+
const pediment = new THREE.Mesh(pedGeo, museumMat);
|
| 1602 |
+
pediment.castShadow = true; group.add(pediment);
|
| 1603 |
+
|
| 1604 |
+
const cols = 5;
|
| 1605 |
+
for (let i = 0; i < cols; i++) {
|
| 1606 |
+
const cx = (i - (cols - 1) / 2) * (w / (cols - 1));
|
| 1607 |
+
const col = new THREE.Group();
|
| 1608 |
+
const colBase = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.3, 0.5), colMat);
|
| 1609 |
+
colBase.position.y = 0.5; col.add(colBase);
|
| 1610 |
+
const shaft = new THREE.Mesh(new THREE.CylinderGeometry(0.18, 0.22, porticoH - 0.8, 12), colMat);
|
| 1611 |
+
shaft.position.y = porticoH / 2 + 0.2; col.add(shaft);
|
| 1612 |
+
const capital = new THREE.Mesh(new THREE.BoxGeometry(0.5, 0.25, 0.5), colMat);
|
| 1613 |
+
capital.position.y = porticoH - 0.15; col.add(capital);
|
| 1614 |
+
col.position.set(cx, 0, d / 2 + porticoD - 0.2);
|
| 1615 |
+
col.castShadow = true; group.add(col);
|
| 1616 |
+
}
|
| 1617 |
+
|
| 1618 |
+
for (let s = 0; s < 3; s++) {
|
| 1619 |
+
const stepW = w + 1.5 - s * 0.3, stepD = 0.5;
|
| 1620 |
+
const step = new THREE.Mesh(new THREE.BoxGeometry(stepW, 0.2, stepD), mat(0xc8c0a0));
|
| 1621 |
+
step.position.set(0, 0.1 + s * 0.2, d / 2 + porticoD + 0.5 - s * 0.3);
|
| 1622 |
+
step.receiveShadow = true; group.add(step);
|
| 1623 |
+
}
|
| 1624 |
+
|
| 1625 |
+
const door = new THREE.Mesh(new THREE.BoxGeometry(1.5, 2.5, 0.1), mat(BLDG_COLORS.door));
|
| 1626 |
+
door.position.set(0, 1.55, d / 2 + 0.05); door.userData.isDoor = true; group.add(door);
|
| 1627 |
+
|
| 1628 |
+
const winMat = mat(isNight ? BLDG_COLORS.windowLit : BLDG_COLORS.window,
|
| 1629 |
+
isNight ? { emissive: BLDG_COLORS.windowLit, emissiveIntensity: 0.3 } : {});
|
| 1630 |
+
for (let side of [-1, 1]) {
|
| 1631 |
+
for (let sz = -1; sz <= 1; sz++) {
|
| 1632 |
+
const sWin = new THREE.Mesh(new THREE.BoxGeometry(0.1, 1.2, 0.6), winMat);
|
| 1633 |
+
sWin.position.set(side * (w / 2 + 0.05), wallH * 0.55, sz * 1.3);
|
| 1634 |
+
sWin.userData.isWindow = true; group.add(sWin);
|
| 1635 |
+
}
|
| 1636 |
+
}
|
| 1637 |
+
|
| 1638 |
+
const roofCap = new THREE.Mesh(new THREE.BoxGeometry(w + 0.3, 0.2, d + 0.3), mat(0xc0b890));
|
| 1639 |
+
roofCap.position.y = wallH; group.add(roofCap);
|
| 1640 |
+
const domeR = 1.2;
|
| 1641 |
+
const dome = new THREE.Mesh(new THREE.SphereGeometry(domeR, 16, 10, 0, Math.PI * 2, 0, Math.PI / 2),
|
| 1642 |
+
mat(0xa8b8a0, { metalness: 0.2 }));
|
| 1643 |
+
dome.position.y = wallH + 0.2; dome.castShadow = true; group.add(dome);
|
| 1644 |
+
|
| 1645 |
+
const label = createLabel(locData.label, group, porticoH + ph + 2);
|
| 1646 |
+
const badge = createOccupantBadge(group, porticoH + ph + 1);
|
| 1647 |
group.userData = { id, type: 'museum', label, badge, locData };
|
| 1648 |
return group;
|
| 1649 |
}
|
|
|
|
| 1653 |
const h = hash(id);
|
| 1654 |
const w = 8, d = 6, wallH = 5;
|
| 1655 |
const clr = Array.isArray(BLDG_COLORS.mall) ? BLDG_COLORS.mall[h % BLDG_COLORS.mall.length] : BLDG_COLORS.mall;
|
| 1656 |
+
const frameMat = mat(0x666666, { metalness: 0.3 });
|
| 1657 |
+
|
| 1658 |
const walls = new THREE.Mesh(new THREE.BoxGeometry(w, wallH, d), mat(clr));
|
| 1659 |
walls.position.y = wallH / 2; walls.castShadow = true; walls.receiveShadow = true;
|
| 1660 |
addEdges(walls); group.add(walls);
|
| 1661 |
+
|
| 1662 |
+
const roof = new THREE.Mesh(new THREE.BoxGeometry(w + 0.5, 0.3, d + 0.5), frameMat);
|
| 1663 |
roof.position.y = wallH; group.add(roof);
|
| 1664 |
+
|
| 1665 |
+
const atriumW = 3, atriumH = 2.5, atriumD = 2;
|
| 1666 |
+
const atriumGlass = mat(0x88ccee, { transparent: true, opacity: 0.35, roughness: 0.1, metalness: 0.3 });
|
| 1667 |
+
const atrium = new THREE.Mesh(new THREE.BoxGeometry(atriumW, atriumH, atriumD), atriumGlass);
|
| 1668 |
+
atrium.position.set(0, wallH + atriumH / 2, 0);
|
| 1669 |
+
atrium.userData.isWindow = true; group.add(atrium);
|
| 1670 |
+
const atriumFrame = new THREE.Mesh(new THREE.BoxGeometry(atriumW + 0.1, 0.08, atriumD + 0.1), frameMat);
|
| 1671 |
+
atriumFrame.position.set(0, wallH + atriumH, 0); group.add(atriumFrame);
|
| 1672 |
+
for (let mx of [-1, 0, 1]) {
|
| 1673 |
+
const mull = new THREE.Mesh(new THREE.BoxGeometry(0.05, atriumH, 0.05), frameMat);
|
| 1674 |
+
mull.position.set(mx * (atriumW / 3), wallH + atriumH / 2, atriumD / 2 + 0.02); group.add(mull);
|
| 1675 |
+
const mullB = mull.clone();
|
| 1676 |
+
mullB.position.z = -atriumD / 2 - 0.02; group.add(mullB);
|
| 1677 |
}
|
| 1678 |
+
|
| 1679 |
+
const glassMat = mat(BLDG_COLORS.window, { roughness: 0.15, metalness: 0.2, transparent: true, opacity: 0.6 });
|
| 1680 |
+
const glass = new THREE.Mesh(new THREE.BoxGeometry(w * 0.85, wallH * 0.55, 0.08), glassMat);
|
| 1681 |
+
glass.position.set(0, wallH * 0.4, d / 2 + 0.05); glass.userData.isWindow = true; group.add(glass);
|
| 1682 |
+
for (let mx = -3; mx <= 3; mx++) {
|
| 1683 |
+
const mull = new THREE.Mesh(new THREE.BoxGeometry(0.05, wallH * 0.6, 0.05), frameMat);
|
| 1684 |
+
mull.position.set(mx * (w * 0.12), wallH * 0.4, d / 2 + 0.06); group.add(mull);
|
| 1685 |
+
}
|
| 1686 |
+
|
| 1687 |
+
const entrW = 3, entrH = 3;
|
| 1688 |
+
const canopy = new THREE.Mesh(new THREE.BoxGeometry(entrW + 1, 0.1, 2), frameMat);
|
| 1689 |
+
canopy.position.set(0, entrH, d / 2 + 1); canopy.castShadow = true; group.add(canopy);
|
| 1690 |
+
const doors = new THREE.Mesh(new THREE.BoxGeometry(entrW, entrH, 0.1), mat(0x3a3a4a));
|
| 1691 |
+
doors.position.set(0, entrH / 2, d / 2 + 0.05); doors.userData.isDoor = true; group.add(doors);
|
| 1692 |
+
for (let dx of [-0.8, 0, 0.8]) {
|
| 1693 |
+
const dGlass = new THREE.Mesh(new THREE.BoxGeometry(0.6, entrH * 0.7, 0.06), glassMat);
|
| 1694 |
+
dGlass.position.set(dx, entrH * 0.45, d / 2 + 0.08);
|
| 1695 |
+
dGlass.userData.isWindow = true; group.add(dGlass);
|
| 1696 |
+
}
|
| 1697 |
+
|
| 1698 |
+
for (let sx of [-2.8, 2.8]) {
|
| 1699 |
+
const awning = new THREE.Mesh(new THREE.BoxGeometry(1.8, 0.08, 1), mat(0xcc8844, { roughness: 0.6 }));
|
| 1700 |
+
awning.position.set(sx, wallH * 0.65, d / 2 + 0.5); awning.castShadow = true; group.add(awning);
|
| 1701 |
+
}
|
| 1702 |
+
|
| 1703 |
+
for (let side of [-1, 1]) {
|
| 1704 |
+
for (let sz = -1; sz <= 1; sz++) {
|
| 1705 |
+
const sWin = new THREE.Mesh(new THREE.BoxGeometry(0.08, 1.2, 0.8), glassMat);
|
| 1706 |
+
sWin.position.set(side * (w / 2 + 0.04), wallH * 0.5, sz * 1.5);
|
| 1707 |
+
sWin.userData.isWindow = true; group.add(sWin);
|
| 1708 |
+
}
|
| 1709 |
+
}
|
| 1710 |
+
|
| 1711 |
+
const label = createLabel(locData.label, group, wallH + atriumH + 2);
|
| 1712 |
+
const badge = createOccupantBadge(group, wallH + atriumH + 1);
|
| 1713 |
group.userData = { id, type: 'mall', label, badge, locData };
|
| 1714 |
return group;
|
| 1715 |
}
|
|
|
|
| 1768 |
|
| 1769 |
function createSquare(id, locData) {
|
| 1770 |
const group = new THREE.Group();
|
| 1771 |
+
const stoneMat = mat(0x999988);
|
| 1772 |
+
|
| 1773 |
+
const plaza = new THREE.Mesh(new THREE.BoxGeometry(7, 0.1, 7), mat(BLDG_COLORS.square));
|
| 1774 |
+
plaza.position.y = 0.05; plaza.receiveShadow = true; group.add(plaza);
|
| 1775 |
+
for (let ring = 0; ring < 2; ring++) {
|
| 1776 |
+
const r = 2.5 + ring * 1.2;
|
| 1777 |
+
const border = new THREE.Mesh(new THREE.TorusGeometry(r, 0.06, 4, 24), mat(0x888878));
|
| 1778 |
+
border.rotation.x = Math.PI / 2; border.position.y = 0.12; group.add(border);
|
| 1779 |
+
}
|
| 1780 |
+
|
| 1781 |
+
const basinOuter = new THREE.Mesh(new THREE.CylinderGeometry(1.5, 1.7, 0.6, 16), stoneMat);
|
| 1782 |
+
basinOuter.position.y = 0.3; group.add(basinOuter);
|
| 1783 |
+
const basinInner = new THREE.Mesh(new THREE.CylinderGeometry(1.3, 1.3, 0.3, 16), mat(0x5588aa, { roughness: 0.2 }));
|
| 1784 |
+
basinInner.position.y = 0.45; group.add(basinInner);
|
| 1785 |
+
const waterTop = new THREE.Mesh(new THREE.CylinderGeometry(1.25, 1.25, 0.05, 16),
|
| 1786 |
+
mat(PALETTE.water, { roughness: 0.15, metalness: 0.15 }));
|
| 1787 |
+
waterTop.position.y = 0.55; group.add(waterTop);
|
| 1788 |
+
|
| 1789 |
+
const pedestal = new THREE.Mesh(new THREE.CylinderGeometry(0.25, 0.35, 1.5, 8), stoneMat);
|
| 1790 |
+
pedestal.position.y = 1.3; group.add(pedestal);
|
| 1791 |
+
const upperBasin = new THREE.Mesh(new THREE.CylinderGeometry(0.6, 0.5, 0.25, 12), stoneMat);
|
| 1792 |
+
upperBasin.position.y = 2.15; group.add(upperBasin);
|
| 1793 |
+
const upperWater = new THREE.Mesh(new THREE.CylinderGeometry(0.5, 0.5, 0.04, 12),
|
| 1794 |
+
mat(PALETTE.water, { roughness: 0.15, metalness: 0.15 }));
|
| 1795 |
+
upperWater.position.y = 2.3; group.add(upperWater);
|
| 1796 |
+
const finial = new THREE.Mesh(new THREE.SphereGeometry(0.15, 8, 6), stoneMat);
|
| 1797 |
+
finial.position.y = 2.55; group.add(finial);
|
| 1798 |
+
|
| 1799 |
+
const benchMat = mat(0x8b6040);
|
| 1800 |
+
for (let i = 0; i < 4; i++) {
|
| 1801 |
+
const ang = (i / 4) * Math.PI * 2 + Math.PI / 4;
|
| 1802 |
+
const br = 2.8;
|
| 1803 |
+
const bGroup = new THREE.Group();
|
| 1804 |
+
const seat = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.08, 0.45), benchMat);
|
| 1805 |
+
seat.position.y = 0.45; bGroup.add(seat);
|
| 1806 |
+
const back = new THREE.Mesh(new THREE.BoxGeometry(1.4, 0.5, 0.06), benchMat);
|
| 1807 |
+
back.position.set(0, 0.65, -0.2); bGroup.add(back);
|
| 1808 |
+
for (let lx of [-0.6, 0.6]) {
|
| 1809 |
+
const leg = new THREE.Mesh(new THREE.BoxGeometry(0.06, 0.45, 0.4), mat(0x444444));
|
| 1810 |
+
leg.position.set(lx, 0.22, 0); bGroup.add(leg);
|
| 1811 |
+
}
|
| 1812 |
+
bGroup.position.set(Math.cos(ang) * br, 0, Math.sin(ang) * br);
|
| 1813 |
+
bGroup.rotation.y = -ang + Math.PI / 2;
|
| 1814 |
+
group.add(bGroup);
|
| 1815 |
+
}
|
| 1816 |
|
| 1817 |
+
for (let i = 0; i < 4; i++) {
|
| 1818 |
+
const ang = (i / 4) * Math.PI * 2;
|
| 1819 |
+
const lamp = new THREE.Group();
|
| 1820 |
+
const lPole = new THREE.Mesh(new THREE.CylinderGeometry(0.04, 0.05, 2.5, 6), mat(0x444444));
|
| 1821 |
+
lPole.position.y = 1.25; lamp.add(lPole);
|
| 1822 |
+
const lTop = new THREE.Mesh(new THREE.SphereGeometry(0.1, 6, 4),
|
| 1823 |
+
mat(0xffeedd, { emissive: 0xffd860, emissiveIntensity: isNight ? 0.6 : 0 }));
|
| 1824 |
+
lTop.position.y = 2.5; lamp.add(lTop);
|
| 1825 |
+
lamp.position.set(Math.cos(ang) * 2, 0, Math.sin(ang) * 2);
|
| 1826 |
+
group.add(lamp);
|
| 1827 |
+
}
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
| 1828 |
|
| 1829 |
+
const label = createLabel(locData.label, group, 5);
|
| 1830 |
+
const badge = createOccupantBadge(group, 4);
|
| 1831 |
group.userData = { id, type: 'square', label, badge, locData };
|
| 1832 |
return group;
|
| 1833 |
}
|