diff --git a/public/roadNetwork.json b/public/roadNetwork.json index 5e5bc80..30a4c96 100644 --- a/public/roadNetwork.json +++ b/public/roadNetwork.json @@ -4,315 +4,454 @@ "x": -4.61, "y": 7.13, "z": -2.77, - "connections": [2, 7] + "connections": [ + 2, + 7 + ] }, { "id": 2, "x": -5.61, "y": 7.08, "z": -7.84, - "connections": [1, 3] + "connections": [ + 1, + 3 + ] }, { "id": 3, "x": -2.36, "y": 6.96, "z": -13.75, - "connections": [2, 4] + "connections": [ + 2, + 4 + ] }, { "id": 4, "x": -3.35, "y": 6.48, "z": -22.71, - "connections": [3, 5, 69, 157] + "connections": [ + 3, + 5, + 69, + 157 + ] }, { "id": 5, "x": -11.24, "y": 6.45, "z": -20.45, - "connections": [4, 6] + "connections": [ + 4, + 6 + ] }, { "id": 6, "x": -19.8, "y": 6.46, "z": -12.16, - "connections": [5, 7, 8, 53] + "connections": [ + 5, + 7, + 8, + 53 + ] }, { "id": 7, "x": -10.38, "y": 6.99, "z": -7.51, - "connections": [6, 1] + "connections": [ + 6, + 1 + ] }, { "id": 8, "x": -29.38, "y": 5.69, "z": -14.1, - "connections": [6, 9, 15] + "connections": [ + 6, + 9, + 15 + ] }, { "id": 9, "x": -34.88, "y": 5.18, "z": -14.73, - "connections": [8, 10, 11] + "connections": [ + 8, + 10, + 11 + ] }, { "id": 10, "x": -43.64, "y": 4.25, "z": -16.72, - "connections": [9] + "connections": [ + 9 + ] }, { "id": 11, "x": -34.62, "y": 4.86, "z": -21.94, - "connections": [9, 12] + "connections": [ + 9, + 12 + ] }, { "id": 12, "x": -39.38, "y": 3.98, "z": -29.69, - "connections": [11, 13] + "connections": [ + 11, + 13 + ] }, { "id": 13, "x": -46.05, "y": 3.15, "z": -34.04, - "connections": [12, 14] + "connections": [ + 12, + 14 + ] }, { "id": 14, "x": -55.16, "y": 2.32, "z": -36.22, - "connections": [13] + "connections": [ + 13 + ] }, { "id": 15, "x": -35.85, "y": 5.37, "z": -3.43, - "connections": [8, 16, 18] + "connections": [ + 8, + 16, + 18 + ] }, { "id": 16, "x": -37.61, "y": 5.17, "z": 5.14, - "connections": [15, 17] + "connections": [ + 15, + 17 + ] }, { "id": 17, "x": -44.96, "y": 4.35, "z": 8.67, - "connections": [16] + "connections": [ + 16 + ] }, { "id": 18, "x": -43.46, "y": 4.57, "z": -4.93, - "connections": [15, 19] + "connections": [ + 15, + 19 + ] }, { "id": 19, "x": -52.58, "y": 3.6, "z": -5.75, - "connections": [18, 20, 23] + "connections": [ + 18, + 20, + 23 + ] }, { "id": 20, "x": -58.7, "y": 3.01, "z": -0.58, - "connections": [19, 21] + "connections": [ + 19, + 21 + ] }, { "id": 21, "x": -64.82, "y": 2.4, "z": 5, - "connections": [20, 22] + "connections": [ + 20, + 22 + ] }, { "id": 22, "x": -70.26, "y": 1.95, "z": 4.73, - "connections": [21] + "connections": [ + 21 + ] }, { "id": 23, "x": -60.47, "y": 2.73, "z": -11.6, - "connections": [19, 24, 25] + "connections": [ + 19, + 24, + 25 + ] }, { "id": 24, "x": -64.69, "y": 2.24, "z": -16.49, - "connections": [23] + "connections": [ + 23 + ] }, { "id": 25, "x": -66.78, "y": 2.17, "z": -10.64, - "connections": [23, 26] + "connections": [ + 23, + 26 + ] }, { "id": 26, "x": -76.23, "y": 1.39, "z": -15.3, - "connections": [25, 27, 29, 40] + "connections": [ + 25, + 27, + 29, + 40 + ] }, { "id": 27, "x": -82.06, "y": 0.97, "z": -20.09, - "connections": [26, 28] + "connections": [ + 26, + 28 + ] }, { "id": 28, "x": -89.41, "y": 0.54, "z": -26.27, - "connections": [27] + "connections": [ + 27 + ] }, { "id": 29, "x": -82.06, "y": 1.08, "z": -11.22, - "connections": [26, 30, 32] + "connections": [ + 26, + 30, + 32 + ] }, { "id": 30, "x": -88.59, "y": 0.74, "z": -2.59, - "connections": [29, 31] + "connections": [ + 29, + 31 + ] }, { "id": 31, "x": -95.82, "y": 0.45, "z": 1.02, - "connections": [30] + "connections": [ + 30 + ] }, { "id": 32, "x": -90.92, "y": 0.58, "z": -14.14, - "connections": [29, 33] + "connections": [ + 29, + 33 + ] }, { "id": 33, "x": -97.45, "y": 0.31, "z": -17.87, - "connections": [32, 34, 36] + "connections": [ + 32, + 34, + 36 + ] }, { "id": 34, "x": -102.82, "y": 0.14, "z": -24.4, - "connections": [33, 35] + "connections": [ + 33, + 35 + ] }, { "id": 35, "x": -108.07, "y": 0, "z": -31.52, - "connections": [34] + "connections": [ + 34 + ] }, { "id": 36, "x": -102.94, "y": 0.15, "z": -14.96, - "connections": [33, 37] + "connections": [ + 33, + 37 + ] }, { "id": 37, "x": -107.37, "y": 0.11, "z": -9.01, - "connections": [36, 38] + "connections": [ + 36, + 38 + ] }, { "id": 38, "x": -110.75, "y": 0.08, "z": -2.59, - "connections": [37, 39] + "connections": [ + 37, + 39 + ] }, { "id": 39, "x": -116.58, "y": 0, "z": -0.38, - "connections": [38] + "connections": [ + 38 + ] }, { "id": 40, "x": -76.23, "y": 1.27, "z": -22.89, - "connections": [26, 41] + "connections": [ + 26, + 41 + ] }, { "id": 41, "x": -76.81, "y": 1.09, "z": -29.53, - "connections": [40, 42, 47] + "connections": [ + 40, + 42, + 47 + ] }, { "id": 42, "x": -73.43, "y": 1.08, "z": -37.23, - "connections": [41, 43] + "connections": [ + 41, + 43 + ] }, { "id": 43, "x": -71.91, "y": 1.02, "z": -42.83, - "connections": [42, 44] + "connections": [ + 42, + 44 + ] }, { "id": 44, "x": -72.61, "y": 0.77, "z": -49.48, - "connections": [43, 45] + "connections": [ + 43, + 45 + ] }, { "id": 45, "x": -77.51, "y": 0.43, "z": -56.36, - "connections": [44] + "connections": [ + 44 + ] }, { "id": 46, @@ -326,35 +465,49 @@ "x": -82.6, "y": 0.64, "z": -38.22, - "connections": [41, 48] + "connections": [ + 41, + 48 + ] }, { "id": 48, "x": -87.73, "y": 0.32, "z": -45.38, - "connections": [47, 49] + "connections": [ + 47, + 49 + ] }, { "id": 49, "x": -88.14, "y": 0.17, "z": -54.34, - "connections": [48, 50] + "connections": [ + 48, + 50 + ] }, { "id": 50, "x": -89.12, "y": 0.09, "z": -59.31, - "connections": [49, 51] + "connections": [ + 49, + 51 + ] }, { "id": 51, "x": -92.7, "y": 0.01, "z": -63.96, - "connections": [50] + "connections": [ + 50 + ] }, { "id": 52, @@ -368,189 +521,274 @@ "x": -23.13, "y": 6.44, "z": -4.02, - "connections": [6, 54] + "connections": [ + 6, + 54 + ] }, { "id": 54, "x": -23.62, "y": 6.39, "z": 5.67, - "connections": [53, 55] + "connections": [ + 53, + 55 + ] }, { "id": 55, "x": -18.16, "y": 6.37, "z": 16.59, - "connections": [54, 56, 59, 156] + "connections": [ + 54, + 56, + 59, + 174 + ] }, { "id": 56, "x": -14.25, "y": 6.77, "z": 11.21, - "connections": [55, 57] + "connections": [ + 55, + 57 + ] }, { "id": 57, "x": -13.93, "y": 6.9, "z": 6.81, - "connections": [56, 58] + "connections": [ + 56, + 58 + ] }, { "id": 58, "x": -11, "y": 7.03, "z": 3.23, - "connections": [57] + "connections": [ + 57 + ] }, { "id": 59, "x": -10.67, "y": 6.41, "z": 21.39, - "connections": [55, 60] + "connections": [ + 55, + 60 + ] }, { "id": 60, "x": -1.71, "y": 6.38, "z": 24.33, - "connections": [59, 61] + "connections": [ + 59, + 61 + ] }, { "id": 61, "x": 8.8, "y": 6.36, "z": 23.1, - "connections": [60, 62] + "connections": [ + 60, + 62 + ] }, { "id": 62, "x": 14.42, "y": 6.42, "z": 18.95, - "connections": [61, 63, 64, 108] + "connections": [ + 61, + 64, + 108, + 173 + ] }, { "id": 63, "x": 2.44, "y": 7.13, "z": 3.31, - "connections": [62] + "connections": [ + 172 + ] }, { "id": 64, "x": 20.12, "y": 6.43, "z": 12.52, - "connections": [62, 65] + "connections": [ + 62, + 65 + ] }, { "id": 65, "x": 22.48, "y": 6.49, "z": 3.88, - "connections": [64, 66] + "connections": [ + 64, + 66 + ] }, { "id": 66, "x": 21.34, "y": 6.52, "z": -6.79, - "connections": [65, 67] + "connections": [ + 65, + 67 + ] }, { "id": 67, "x": 18.25, "y": 6.5, "z": -13.47, - "connections": [66, 68] + "connections": [ + 66, + 68 + ] }, { "id": 68, "x": 15.23, "y": 6.54, "z": -15.99, - "connections": [67, 69, 70, 71] + "connections": [ + 67, + 69, + 70, + 71 + ] }, { "id": 69, "x": 5.78, "y": 6.52, "z": -21.53, - "connections": [68, 4] + "connections": [ + 68, + 4 + ] }, { "id": 70, "x": 11.08, "y": 6.96, "z": -8.17, - "connections": [68] + "connections": [ + 68 + ] }, { "id": 71, "x": 19.39, "y": 6.07, "z": -20.63, - "connections": [68, 72, 91] + "connections": [ + 68, + 72, + 91 + ] }, { "id": 72, "x": 21.18, "y": 5.69, "z": -24.87, - "connections": [71, 73, 74] + "connections": [ + 71, + 73, + 74 + ] }, { "id": 73, "x": 22.57, "y": 4.58, "z": -37.32, - "connections": [72] + "connections": [ + 72 + ] }, { "id": 74, "x": 28.76, "y": 4.96, "z": -27.8, - "connections": [72, 75, 76] + "connections": [ + 72, + 75, + 76 + ] }, { "id": 75, "x": 36.01, "y": 4.45, "z": -26.82, - "connections": [74] + "connections": [ + 74 + ] }, { "id": 76, "x": 39.1, "y": 3.59, "z": -35.78, - "connections": [74, 77, 78] + "connections": [ + 74, + 77, + 78 + ] }, { "id": 77, "x": 51.07, "y": 2.37, "z": -40.58, - "connections": [76] + "connections": [ + 76 + ] }, { "id": 78, "x": 39.26, "y": 2.89, "z": -45.14, - "connections": [76, 79, 81] + "connections": [ + 76, + 79, + 81 + ] }, { "id": 79, "x": 37.55, "y": 2.11, "z": -57.04, - "connections": [78] + "connections": [ + 78 + ] }, { "id": 80, @@ -564,63 +802,89 @@ "x": 47.25, "y": 1.81, "z": -54.26, - "connections": [78, 82] + "connections": [ + 78, + 82 + ] }, { "id": 82, "x": 60.2, "y": 0.9, "z": -60.53, - "connections": [81, 83, 84] + "connections": [ + 81, + 83, + 84 + ] }, { "id": 83, "x": 75.6, "y": 0.3, "z": -63.79, - "connections": [82] + "connections": [ + 82 + ] }, { "id": 84, "x": 71.69, "y": 0.73, "z": -52.06, - "connections": [82, 85, 86] + "connections": [ + 82, + 85, + 86 + ] }, { "id": 85, "x": 84.72, "y": 0.41, "z": -45.14, - "connections": [84] + "connections": [ + 84 + ] }, { "id": 86, "x": 72.83, "y": 0.94, "z": -43.18, - "connections": [84, 87] + "connections": [ + 84, + 87 + ] }, { "id": 87, "x": 82.52, "y": 0.79, "z": -29.01, - "connections": [86, 88] + "connections": [ + 86, + 88 + ] }, { "id": 88, "x": 92.95, "y": 0.47, "z": -18.1, - "connections": [87, 89] + "connections": [ + 87, + 89 + ] }, { "id": 89, "x": 100.77, "y": 0.21, "z": -16.55, - "connections": [88] + "connections": [ + 88 + ] }, { "id": 90, @@ -634,21 +898,32 @@ "x": 31.21, "y": 5.49, "z": -15.42, - "connections": [71, 92, 102, 103] + "connections": [ + 71, + 92, + 103, + 166 + ] }, { "id": 92, "x": 48.06, "y": 3.69, "z": -19.81, - "connections": [91, 93, 95] + "connections": [ + 91, + 93, + 95 + ] }, { "id": 93, "x": 59.71, "y": 2.45, "z": -24.13, - "connections": [92] + "connections": [ + 92 + ] }, { "id": 94, @@ -662,42 +937,59 @@ "x": 60.85, "y": 2.78, "z": -3.28, - "connections": [92, 96, 97] + "connections": [ + 92, + 96, + 97 + ] }, { "id": 96, "x": 68.43, "y": 2.1, "z": -2.47, - "connections": [95] + "connections": [ + 95 + ] }, { "id": 97, "x": 68.51, "y": 2.04, "z": 9.18, - "connections": [95, 98, 99] + "connections": [ + 95, + 98, + 99 + ] }, { "id": 98, "x": 67.86, "y": 1.98, "z": 16.59, - "connections": [97] + "connections": [ + 97 + ] }, { "id": 99, "x": 91.56, "y": 0.56, "z": 13.82, - "connections": [97, 100] + "connections": [ + 97, + 100 + ] }, { "id": 100, "x": 97.1, "y": 0.31, "z": 18.46, - "connections": [99] + "connections": [ + 99 + ] }, { "id": 101, @@ -706,103 +998,137 @@ "z": 6.58, "connections": [] }, - { - "id": 102, - "x": 43.42, - "y": 4.52, - "z": -8.25, - "connections": [91] - }, { "id": 103, "x": 32.83, "y": 5.63, "z": -5.24, - "connections": [91, 104] + "connections": [ + 91, + 104 + ] }, { "id": 104, "x": 32.75, "y": 5.62, "z": 6.74, - "connections": [103, 105] + "connections": [ + 103, + 105 + ] }, { "id": 105, "x": 32.51, "y": 5.42, "z": 14.31, - "connections": [104, 106] + "connections": [ + 104, + 106 + ] }, { "id": 106, "x": 39.02, "y": 4.65, "z": 17.98, - "connections": [105, 107] + "connections": [ + 105, + 107 + ] }, { "id": 107, "x": 44.48, "y": 4.26, "z": 14.31, - "connections": [106] + "connections": [ + 106 + ] }, { "id": 108, "x": 19.31, "y": 5.65, "z": 26.84, - "connections": [62, 109, 131] + "connections": [ + 62, + 109, + 131, + 167 + ] }, { "id": 109, "x": 22.49, "y": 5.23, "z": 29.86, - "connections": [108, 110, 127] + "connections": [ + 108, + 110 + ] }, { "id": 110, "x": 32.91, "y": 3.51, "z": 42.4, - "connections": [109, 111, 122] + "connections": [ + 109, + 111, + 122 + ] }, { "id": 111, "x": 39.1, "y": 2.75, "z": 47.21, - "connections": [110, 112] + "connections": [ + 110, + 112 + ] }, { "id": 112, "x": 55.64, "y": 1.54, "z": 51.12, - "connections": [111, 113, 115, 117] + "connections": [ + 111, + 113, + 115, + 117 + ] }, { "id": 113, "x": 61.66, "y": 1.26, "z": 49.98, - "connections": [112, 114] + "connections": [ + 112, + 114 + ] }, { "id": 114, "x": 70.14, "y": 0.98, "z": 46.23, - "connections": [113] + "connections": [ + 113 + ] }, { "id": 115, "x": 56.45, "y": 1.31, "z": 54.86, - "connections": [112] + "connections": [ + 112 + ] }, { "id": 116, @@ -816,28 +1142,39 @@ "x": 61.42, "y": 0.86, "z": 60.39, - "connections": [112, 118] + "connections": [ + 112, + 118 + ] }, { "id": 118, "x": 60.85, "y": 0.54, "z": 70.01, - "connections": [117, 119] + "connections": [ + 117, + 119 + ] }, { "id": 119, "x": 56.7, "y": 0.37, "z": 78.56, - "connections": [118, 120] + "connections": [ + 118, + 120 + ] }, { "id": 120, "x": 57.11, "y": 0.24, "z": 83.2, - "connections": [119] + "connections": [ + 119 + ] }, { "id": 121, @@ -851,147 +1188,170 @@ "x": 31.12, "y": 2.64, "z": 54.13, - "connections": [110, 123, 124] + "connections": [ + 110, + 123, + 124 + ] }, { "id": 123, "x": 25.5, "y": 2.37, "z": 60.15, - "connections": [122] + "connections": [ + 122 + ] }, { "id": 124, "x": 32.1, "y": 1.84, "z": 64.06, - "connections": [122, 125] + "connections": [ + 122, + 125 + ] }, { "id": 125, "x": 26.07, "y": 1.22, "z": 75.79, - "connections": [124, 126] + "connections": [ + 124, + 126 + ] }, { "id": 126, "x": 26.07, "y": 1, "z": 79.54, - "connections": [125] - }, - { - "id": 127, - "x": 31.39, - "y": 4.75, - "z": 27.95, - "connections": [109, 128] - }, - { - "id": 128, - "x": 41.12, - "y": 4.07, - "z": 25.58, - "connections": [127, 129] - }, - { - "id": 129, - "x": 46.63, - "y": 3.54, - "z": 26.12, - "connections": [128, 130] - }, - { - "id": 130, - "x": 53.28, - "y": 2.65, - "z": 32.44, - "connections": [129] + "connections": [ + 125 + ] }, { "id": 131, "x": 19.13, "y": 4.92, "z": 35.57, - "connections": [108, 132] + "connections": [ + 108, + 132 + ] }, { "id": 132, "x": 16.75, "y": 4.05, "z": 45.62, - "connections": [131, 133] + "connections": [ + 131, + 133 + ] }, { "id": 133, "x": 11.18, "y": 3.33, "z": 54.32, - "connections": [132, 134, 143] + "connections": [ + 132, + 134, + 143 + ] }, { "id": 134, "x": 7.83, "y": 2.97, "z": 58.54, - "connections": [133, 135, 139] + "connections": [ + 133, + 135, + 139 + ] }, { "id": 135, "x": 2.7, "y": 2.73, "z": 61.4, - "connections": [134, 136] + "connections": [ + 134, + 136 + ] }, { "id": 136, "x": -2.22, "y": 2.81, "z": 60.59, - "connections": [135, 137] + "connections": [ + 135, + 137 + ] }, { "id": 137, "x": -2.98, "y": 3.58, "z": 52.97, - "connections": [136] + "connections": [ + 136 + ] }, { "id": 139, "x": 4.05, "y": 1.58, "z": 74.79, - "connections": [134, 140] + "connections": [ + 134, + 140 + ] }, { "id": 140, "x": -0.33, "y": 1.21, "z": 80.47, - "connections": [139, 141] + "connections": [ + 139, + 141 + ] }, { "id": 141, "x": -3.24, "y": 1.24, "z": 79.76, - "connections": [140, 142] + "connections": [ + 140, + 142 + ] }, { "id": 142, "x": -3.41, "y": 1.73, "z": 73.01, - "connections": [141] + "connections": [ + 141 + ] }, { "id": 143, "x": 11.78, "y": 0.74, "z": 87.87, - "connections": [133, 145, 149] + "connections": [ + 133, + 145, + 149 + ] }, { "id": 144, @@ -1005,63 +1365,88 @@ "x": 7.89, "y": 0.44, "z": 94.98, - "connections": [143, 146] + "connections": [ + 143, + 146 + ] }, { "id": 146, "x": 4.48, "y": 0.31, "z": 98.94, - "connections": [145, 147] + "connections": [ + 145, + 147 + ] }, { "id": 147, "x": -2.22, "y": 0.27, "z": 100.07, - "connections": [146, 148] + "connections": [ + 146, + 148 + ] }, { "id": 148, "x": -3.03, "y": 0.47, "z": 94.56, - "connections": [147] + "connections": [ + 147 + ] }, { "id": 149, "x": 11.78, "y": 0.29, "z": 98.56, - "connections": [143, 150] + "connections": [ + 143, + 150 + ] }, { "id": 150, "x": 9.61, "y": 0.05, "z": 108.88, - "connections": [149, 151] + "connections": [ + 149, + 151 + ] }, { "id": 151, "x": 5.08, "y": 0, "z": 117.04, - "connections": [150, 152] + "connections": [ + 150, + 152 + ] }, { "id": 152, "x": -0.49, "y": 0, "z": 119.26, - "connections": [151, 153] + "connections": [ + 151, + 153 + ] }, { "id": 153, "x": -5.3, "y": 0, "z": 116.93, - "connections": [152] + "connections": [ + 152 + ] }, { "id": 154, @@ -1070,67 +1455,237 @@ "z": 117.06, "connections": [] }, - { - "id": 156, - "x": -45.93, - "y": 2.77, - "z": 40.28, - "connections": [55] - }, { "id": 157, "x": -7.58, "y": 5.97, "z": -28.56, - "connections": [4, 158] + "connections": [ + 4, + 179 + ] }, { "id": 158, "x": -17.19, "y": 2.57, "z": -60.82, - "connections": [157, 159, 162] + "connections": [ + 159, + 162, + 180 + ] }, { "id": 159, "x": -2.45, "y": 2.37, "z": -65.38, - "connections": [158, 160] + "connections": [ + 158, + 160 + ] }, { "id": 160, "x": 7.4, "y": 2.46, "z": -63.99, - "connections": [159, 161] + "connections": [ + 159, + 161 + ] }, { "id": 161, "x": 24.59, "y": 2.71, "z": -56.66, - "connections": [160] + "connections": [ + 160 + ] }, { "id": 162, "x": -31.53, "y": 2.44, "z": -56.42, - "connections": [158, 163] + "connections": [ + 158, + 163 + ] }, { "id": 163, "x": -39.67, "y": 2.4, "z": -51.61, - "connections": [162, 164] + "connections": [ + 162, + 164 + ] }, { "id": 164, "x": -49.61, "y": 2.28, "z": -44.28, - "connections": [163] + "connections": [ + 163 + ] + }, + { + "id": 165, + "x": 106.01, + "y": 0.1, + "z": 1.02, + "connections": [] + }, + { + "id": 166, + "x": 43.06, + "y": 4.57, + "z": -7.87, + "connections": [ + 91 + ] + }, + { + "id": 167, + "x": 25.12, + "y": 5.18, + "z": 28.43, + "connections": [ + 108, + 168 + ] + }, + { + "id": 168, + "x": 33.68, + "y": 4.56, + "z": 28.11, + "connections": [ + 167, + 169 + ] + }, + { + "id": 169, + "x": 40.79, + "y": 4.09, + "z": 25.69, + "connections": [ + 168, + 170 + ] + }, + { + "id": 170, + "x": 47.9, + "y": 3.41, + "z": 26.49, + "connections": [ + 169, + 171 + ] + }, + { + "id": 171, + "x": 52.58, + "y": 2.73, + "z": 31.99, + "connections": [ + 170 + ] + }, + { + "id": 172, + "x": 7.42, + "y": 7.03, + "z": 8.82, + "connections": [ + 63, + 173 + ] + }, + { + "id": 173, + "x": 11, + "y": 6.74, + "z": 15.13, + "connections": [ + 172, + 62 + ] + }, + { + "id": 174, + "x": -24.16, + "y": 5.72, + "z": 21.45, + "connections": [ + 55, + 175 + ] + }, + { + "id": 175, + "x": -31.1, + "y": 4.88, + "z": 26.48, + "connections": [ + 174, + 176 + ] + }, + { + "id": 176, + "x": -35.84, + "y": 4.17, + "z": 31.18, + "connections": [ + 175, + 177 + ] + }, + { + "id": 177, + "x": -41.34, + "y": 3.37, + "z": 36.51, + "connections": [ + 176, + 178 + ] + }, + { + "id": 178, + "x": -47.11, + "y": 2.66, + "z": 40.6, + "connections": [ + 177 + ] + }, + { + "id": 179, + "x": -9.75, + "y": 5.03, + "z": -38.14, + "connections": [ + 157, + 180 + ] + }, + { + "id": 180, + "x": -14.38, + "y": 3.62, + "z": -50.73, + "connections": [ + 179, + 158 + ] } -] +] \ No newline at end of file diff --git a/src/components/ebike/Ebike.tsx b/src/components/ebike/Ebike.tsx index 6eb0455..502df8c 100644 --- a/src/components/ebike/Ebike.tsx +++ b/src/components/ebike/Ebike.tsx @@ -2,7 +2,7 @@ import { useEffect, useRef, useState, useMemo, useCallback } from "react"; import * as THREE from "three"; import { useFrame, useThree } from "@react-three/fiber"; import { EbikeGPSMap } from "@/components/ebike/EbikeGPSMap"; -import { EbikeSpeedometer } from "@/components/ebike/EbikeSpeedometer"; +import { EbikeSpeedmeter } from "@/components/ebike/EbikeSpeedmeter"; import { InteractableObject } from "@/components/three/interaction/InteractableObject"; import { useLoggedGLTF } from "@/hooks/three/useLoggedGLTF"; import { useClonedObject } from "@/hooks/three/useClonedObject"; @@ -123,11 +123,35 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { }, [movementMode, parkedPosition]); useEffect(() => { - if (model) { - const fork = model.getObjectByName("fourche"); - if (fork) { - forkRef.current = fork; + if (!model) return; + + // Full recursive search — case-insensitive so it survives export renames. + // Also tries the exact path Moto > * > Fourche as a fallback. + let forkNode: THREE.Object3D | null = null; + + model.traverse((child) => { + if (child.name.toLowerCase() === "fourche") { + forkNode = child; } + }); + + if (forkNode) { + forkRef.current = forkNode; + console.log("[Ebike] Fork found:", (forkNode as THREE.Object3D).name); + } else { + // Print the full hierarchy tree so you can read the exact node names. + const lines: string[] = []; + function printTree(obj: THREE.Object3D, indent: number): void { + lines.push(" ".repeat(indent * 2) + (obj.name || "(unnamed)")); + for (const child of obj.children) { + printTree(child, indent + 1); + } + } + printTree(model, 0); + console.warn( + '[Ebike] No node matching "fourche" (case-insensitive) found.\nFull hierarchy:\n' + + lines.join("\n"), + ); } }, [model]); @@ -156,9 +180,11 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { useFrame((_, delta) => { if (groupRef.current) { if (movementMode === "ebike") { + // Sound plays whenever the bike is actually moving (speedFactor > 5 %), + // not only while the input key is held. updateEbikeSounds({ mounted: true, - driving: window.ebikeDriveInputActive === true, + driving: (window.ebikeSpeedFactor ?? 0) > 0.05, breakdown: window.ebikeBreakdownActive === true, }); @@ -169,11 +195,11 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { ]; restingRotationRef.current = groupRef.current.rotation.y; - // Smoothly rotate the front fork ("fourche") up to 15 degrees in its own Z axis + // Smoothly rotate the front fork ("fourche") on its local Z axis const steerFactor = window.ebikeSteerFactor ?? 0; if (forkRef.current) { - // 15 degrees is 0.26 radians - const targetForkRotation = steerFactor * 0.26; + // 10 degrees = 0.175 radians + const targetForkRotation = steerFactor * 0.175; forkRef.current.rotation.z = THREE.MathUtils.lerp( forkRef.current.rotation.z, targetForkRotation, @@ -329,6 +355,9 @@ export function Ebike({ position }: EbikeProps): React.JSX.Element { scale={EBIKE_WORLD_SCALE} > + {/* radius 20 → ~7 unités monde (scale 0.35). + Sphère omnidirectionnelle pour que le raycast fonctionne + quelle que soit l'orientation de la caméra (montée ou à pied). */} - - + + - {/* Dynamic 3D GPS Dashboard Screen */} - + {/* GPS + Speedmeter – same group so they are perfectly co-localised. + GPS: full circle (Fresnel mask), renderOrder 10 000 + Speedmeter: upper-half arc overlay, renderOrder 10 001 + rotation: Math.PI/2 radians = 90° (NOT the number 90 which = ~116.6°) */} + + - - - ) : null} {showCameraPoints && !repairGameOwnsEbikeModel && ( <> - + {/* - + */} )} diff --git a/src/components/ebike/EbikeGPSMap.tsx b/src/components/ebike/EbikeGPSMap.tsx index 08fab3e..064bcc6 100644 --- a/src/components/ebike/EbikeGPSMap.tsx +++ b/src/components/ebike/EbikeGPSMap.tsx @@ -12,6 +12,28 @@ import { } from "@/pathfinding/WaypointAStar"; import type { Waypoint } from "@/pathfinding/types"; import type { Vector3Tuple } from "@/types/three/three"; + +const VERT_SHADER = /* glsl */ ` + varying vec2 vUv; + void main() { + vUv = uv; + gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0); + } +`; + +// Circular Fresnel mask: fully visible inside innerRadius, fades out to outerRadius +const FRAG_SHADER = /* glsl */ ` + uniform sampler2D map; + uniform float innerRadius; + uniform float outerRadius; + varying vec2 vUv; + void main() { + vec4 color = texture2D(map, vUv); + float dist = length(vUv - vec2(0.5)); + float mask = 1.0 - smoothstep(innerRadius, outerRadius, dist); + gl_FragColor = vec4(color.rgb, color.a * mask); + } +`; function computeImageSource( img: HTMLImageElement | HTMLCanvasElement, baseBounds: { minX: number; maxX: number; minZ: number; maxZ: number }, @@ -126,19 +148,57 @@ export const EbikeGPSMap: React.FC = ({ // eslint-disable-next-line react-hooks/exhaustive-deps -- Canvas should only be created once }, []); - // Resize the canvas whenever canvasSize changes - // Note: Modifying canvas dimensions is intentional and necessary for rendering - useEffect(() => { - // Use Object.assign to resize canvas - this is a necessary mutation for canvas rendering - Object.assign(offscreenCanvas, { width: canvasSize, height: canvasSize }); - if (textureRef.current) { - textureRef.current.needsUpdate = true; - } - }, [canvasSize, offscreenCanvas]); - - const textureRef = useRef(null); const animTimeRef = useRef(0); + // Imperative CanvasTexture — must be declared before the resize effect below + const texture = useMemo(() => { + const tex = new THREE.CanvasTexture(offscreenCanvas); + tex.format = THREE.RGBAFormat; + tex.minFilter = THREE.LinearFilter; + tex.magFilter = THREE.LinearFilter; + return tex; + }, [offscreenCanvas]); + + // ShaderMaterial with circular Fresnel mask (created once) + const shaderMat = useMemo( + () => + new THREE.ShaderMaterial({ + uniforms: { + map: { value: null }, + innerRadius: { value: 0.45 }, + outerRadius: { value: 0.5 }, + }, + vertexShader: VERT_SHADER, + fragmentShader: FRAG_SHADER, + transparent: true, + depthTest: false, + depthWrite: false, + side: THREE.DoubleSide, + toneMapped: false, + }), + [], + ); + + // Sync texture into uniform when it changes (canvas resize) + useEffect(() => { + shaderMat.uniforms.map.value = texture; + }, [shaderMat, texture]); + + // Cleanup on unmount + useEffect( + () => () => { + shaderMat.dispose(); + texture.dispose(); + }, + [shaderMat, texture], + ); + + // Resize the canvas whenever canvasSize changes (texture declared above) + useEffect(() => { + Object.assign(offscreenCanvas, { width: canvasSize, height: canvasSize }); + texture.needsUpdate = true; + }, [canvasSize, offscreenCanvas, texture]); + // Load waypoints (localStorage with /roadNetwork.json fallback) useEffect(() => { let cancelled = false; @@ -492,42 +552,20 @@ export const EbikeGPSMap: React.FC = ({ useEffect(() => { let animId: number; const tick = () => { - animTimeRef.current += 0.004; // Slow, premium sweep speed + animTimeRef.current += 0.004; if (animTimeRef.current > 1) animTimeRef.current = 0; - draw(); - - // Update texture after draw - if (textureRef.current) { - textureRef.current.needsUpdate = true; - } - + texture.needsUpdate = true; animId = requestAnimationFrame(tick); }; animId = requestAnimationFrame(tick); return () => cancelAnimationFrame(animId); - }, [draw]); + }, [draw, texture]); return ( - - - + ); }; diff --git a/src/components/ebike/EbikeSpeedmeter.tsx b/src/components/ebike/EbikeSpeedmeter.tsx new file mode 100644 index 0000000..827ebf6 --- /dev/null +++ b/src/components/ebike/EbikeSpeedmeter.tsx @@ -0,0 +1,233 @@ +import { useEffect, useRef, useMemo } from "react"; +import { useFrame } from "@react-three/fiber"; +import { useTexture } from "@react-three/drei"; +import * as THREE from "three"; +import type { Vector3Tuple } from "@/types/three/three"; +import "@/types/ebike/ebikeWindow"; + +const SPEEDOMETER_DIAL_TEXTURE = "/assets/world/gps/cadran.png"; +const SPEEDOMETER_NEEDLE_TEXTURE = "/assets/world/gps/fleche.png"; + +export interface EbikeSpeedmeterProps { + width?: number; + height?: number; + /** Local position offset within the parent group. Default: [0, 0, 0] */ + position?: Vector3Tuple; + /** + * Needle rotation.z when speedFactor = 0. + * Default: Math.PI / 2 (pointing left — 9 o'clock) + */ + minAngle?: number; + /** + * Needle rotation.z when speedFactor = 1. + * Default: -Math.PI / 2 (pointing right — 3 o'clock) + */ + maxAngle?: number; + renderOrder?: number; + /** + * Inner radius of the gauge-fill arc, as a fraction of the canvas half-width. + * Tune this to align the fill with the cadran.png track. Default: 0.33 + */ + gaugeInnerR?: number; + /** + * Outer radius of the gauge-fill arc, as a fraction of the canvas half-width. + * Tune this to align the fill with the cadran.png track. Default: 0.445 + */ + gaugeOuterR?: number; + /** + * Width of the gauge-fill plane. Defaults to `width` when omitted. + * Lets you resize the fill independently of the cadran/needle. + */ + gaugeWidth?: number; + /** + * Height of the gauge-fill plane. Defaults to `height` when omitted. + * Lets you resize the fill independently of the cadran/needle. + */ + gaugeHeight?: number; + /** + * Horizontal offset of the arc pivot from the canvas centre. + * Expressed as a fraction of the canvas size: -0.1 = shift 10 % to the left, + * +0.1 = shift 10 % to the right. Default: 0 + */ + gaugeOffsetX?: number; + /** + * Vertical offset of the arc pivot from its default position. + * Expressed as a fraction of the canvas size: -0.1 = shift upward (toward top + * of the plane), +0.1 = shift downward. Default: 0 + */ + gaugeOffsetY?: number; +} + +// The needle pivot is always at -height*0.38 in local space, +// which is always 12 % from the bottom of the plane (UV y = 0.12). +// With Three.js flipY texture convention, canvas y = (1 - 0.12) * size = 0.88 * size. +const NEEDLE_PIVOT_UV_Y = 0.12; // fraction from bottom + +export function EbikeSpeedmeter({ + width = 0.8, + height = 0.8, + position = [0, 0, 0], + minAngle = Math.PI / 2, + maxAngle = -Math.PI / 2, + renderOrder = 1000, + gaugeInnerR = 0.33, + gaugeOuterR = 0.445, + gaugeWidth, + gaugeHeight, + gaugeOffsetX = 0, + gaugeOffsetY = 0, +}: EbikeSpeedmeterProps): React.JSX.Element { + // Fall back to the main dimensions when gauge-specific ones aren't provided + const fillW = gaugeWidth ?? width; + const fillH = gaugeHeight ?? height; + const needleGroupRef = useRef(null); + const speedFactorRef = useRef(0); + + // ── Dial & needle textures ────────────────────────────────────────────────── + const [dialTexture, needleTexture] = useTexture([ + SPEEDOMETER_DIAL_TEXTURE, + SPEEDOMETER_NEEDLE_TEXTURE, + ]) as [THREE.Texture, THREE.Texture]; + + const needleWidth = width * 0.68; + const needleHeight = needleWidth / 2; + + useEffect(() => { + [dialTexture, needleTexture].forEach((tex) => { + tex.colorSpace = THREE.SRGBColorSpace; + tex.needsUpdate = true; + }); + }, [dialTexture, needleTexture]); + + // ── Gauge-fill canvas ─────────────────────────────────────────────────────── + const fillCanvas = useMemo(() => { + const c = document.createElement("canvas"); + c.width = 256; + c.height = 256; + return c; + }, []); + + const fillTexture = useMemo(() => { + const tex = new THREE.CanvasTexture(fillCanvas); + tex.format = THREE.RGBAFormat; + tex.minFilter = THREE.LinearFilter; + tex.magFilter = THREE.LinearFilter; + return tex; + }, [fillCanvas]); + + useEffect( + () => () => { + fillTexture.dispose(); + }, + [fillTexture], + ); + + // ── Frame loop ────────────────────────────────────────────────────────────── + useFrame((_, delta) => { + // 1. Smooth speed factor + const target = THREE.MathUtils.clamp(window.ebikeSpeedFactor ?? 0, 0, 1); + speedFactorRef.current = THREE.MathUtils.lerp( + speedFactorRef.current, + target, + Math.min(1, delta * 10), + ); + + // 2. Needle rotation + if (needleGroupRef.current) { + needleGroupRef.current.rotation.z = THREE.MathUtils.lerp( + minAngle, + maxAngle, + speedFactorRef.current, + ); + } + + // 3. Draw gauge fill ------------------------------------------------------- + const ctx = fillCanvas.getContext("2d", { alpha: true }); + if (!ctx) return; + + const size = fillCanvas.width; + ctx.clearRect(0, 0, size, size); + + // Default centre: horizontal middle + needle-pivot height. + // gaugeOffsetX/Y shift the pivot so the arc aligns with cadran.png. + const cx = size * (0.5 + gaugeOffsetX); + const cy = size * ((1 - NEEDLE_PIVOT_UV_Y) + gaugeOffsetY); // default ≈ 0.88 × size + + const outerR = size * gaugeOuterR; + const innerR = size * gaugeInnerR; + + // Arc sweeps clockwise from π (left) to current needle angle + const arcStart = Math.PI; + const arcEnd = Math.PI + speedFactorRef.current * Math.PI; + + if (speedFactorRef.current > 0.005) { + // Radial gradient using #3F67DD — slightly transparent at inner edge, + // fully solid at outer edge for a depth effect. + const radial = ctx.createRadialGradient(cx, cy, innerR, cx, cy, outerR); + radial.addColorStop(0, "rgba(191, 234, 255, 0)"); // inner edge + radial.addColorStop(0.7, "rgba(118, 152, 255, 0.95)"); // outer edge + + // Annular sector shape (outer arc + inner arc reversed) + ctx.beginPath(); + ctx.arc(cx, cy, outerR, arcStart, arcEnd, false); + ctx.arc(cx, cy, innerR, arcEnd, arcStart, true); + ctx.closePath(); + + ctx.fillStyle = radial; + ctx.shadowBlur = 16; + ctx.shadowColor = "#3F67DD"; + ctx.fill(); + ctx.shadowBlur = 0; + } + + fillTexture.needsUpdate = true; + }); + + return ( + + {/* Gauge fill — behind the cadran frame (size controlled by gaugeWidth/gaugeHeight) */} + + + + + + {/* Dial frame (cadran.png) */} + + + + + + {/* Needle — pivot at bottom-centre of the arc */} + + + + + + + + ); +} diff --git a/src/data/ebike/ebikeConfig.ts b/src/data/ebike/ebikeConfig.ts index 577a7e6..d68fb23 100644 --- a/src/data/ebike/ebikeConfig.ts +++ b/src/data/ebike/ebikeConfig.ts @@ -6,7 +6,7 @@ export interface CameraTransform { } export const EBIKE_CAMERA_TRANSFORM: CameraTransform = { - position: [-2.6, 4.5, 0], + position: [-1, 1, 0], rotation: [-10, -90, 0], }; diff --git a/src/world/player/PlayerController.tsx b/src/world/player/PlayerController.tsx index 563b25f..a42c7f5 100644 --- a/src/world/player/PlayerController.tsx +++ b/src/world/player/PlayerController.tsx @@ -512,14 +512,29 @@ export function PlayerController({ ); window.ebikeSteerFactor = steerFactor; + // ── Ebike camera tuning ────────────────────────────────────────────────── + // All motion effects in one place — set to 0 to fully disable each one. + /** Lateral camera drift when steering (0 = no sway) */ + const CAM_SWAY_SIDE = -0.5; + /** Vertical camera drop when steering (0 = no recoil) */ + const CAM_SWAY_VERTICAL = 0; + /** Position lerp factor. 1 = instant snap, lower = more lag/trail */ + const CAM_POS_LERP = 1; + /** FOV boost at full speed in degrees (0 = constant FOV) */ + const CAM_FOV_BOOST = 0.15; // speed × 0.15, capped at 3° → subtle speed sensation + /** How fast FOV lerps toward target (lower = slower breathing) */ + const CAM_FOV_LERP = 4; + /** Visual body lean in radians at max steer (20° = 0.349 rad) */ + const BIKE_LEAN = THREE.MathUtils.degToRad(10); + // ───────────────────────────────────────────────────────────────────────── + const speed = velocity.current.length(); - const targetFov = 60 + Math.min(speed * 0.35, 9); const perspectiveCam = camera as THREE.PerspectiveCamera; // eslint-disable-next-line react-hooks/immutability -- Three.js camera.fov must be mutated directly for dynamic FOV changes during frame updates perspectiveCam.fov = THREE.MathUtils.lerp( perspectiveCam.fov, - targetFov, - 6 * dt, + 60 + Math.min(speed * CAM_FOV_BOOST, 3), + CAM_FOV_LERP * dt, ); perspectiveCam.updateProjectionMatrix(); @@ -528,9 +543,8 @@ export function PlayerController({ ); cameraOffset.applyAxisAngle(_up, ebikeAngle.current); - const swingX = -Math.abs(steerFactor) * 1.5; - const swingZ = steerFactor > 0 ? steerFactor * 2.5 : steerFactor * 1.0; - + const swingX = -Math.abs(steerFactor) * CAM_SWAY_VERTICAL; + const swingZ = steerFactor * CAM_SWAY_SIDE; const cameraSwing = new THREE.Vector3(swingX, 0, swingZ); cameraSwing.applyAxisAngle(_up, ebikeAngle.current); cameraOffset.add(cameraSwing); @@ -539,7 +553,7 @@ export function PlayerController({ .copy(capsule.current.end) .add(cameraOffset); - camera.position.lerp(targetCamPos, 12 * dt); + camera.position.lerp(targetCamPos, CAM_POS_LERP); const pitchRad = THREE.MathUtils.degToRad( EBIKE_CAMERA_TRANSFORM.rotation[0], @@ -559,8 +573,12 @@ export function PlayerController({ capsule.current.end.y - PLAYER_EYE_HEIGHT, capsule.current.end.z, ); - const leanAngle = steerFactor * 0.26; - ebikeVisual.rotation.set(0, ebikeAngle.current, leanAngle, "YXZ"); + ebikeVisual.rotation.set( + steerFactor * -BIKE_LEAN, + ebikeAngle.current, + 0, + "YXZ", + ); } } else { camera.position.copy(capsule.current.end);