victor HF Staff commited on
Commit
180b798
·
verified ·
1 Parent(s): 5e245a3

Upload folder using huggingface_hub

Browse files
Files changed (7) hide show
  1. .gitattributes +1 -35
  2. .gitignore +1 -0
  3. README.md +11 -0
  4. index.html +83 -0
  5. ping.mp3 +3 -0
  6. script.js +1027 -0
  7. soundtrack.mp3 +3 -0
.gitattributes CHANGED
@@ -1,35 +1 @@
1
- *.7z filter=lfs diff=lfs merge=lfs -text
2
- *.arrow filter=lfs diff=lfs merge=lfs -text
3
- *.bin filter=lfs diff=lfs merge=lfs -text
4
- *.bz2 filter=lfs diff=lfs merge=lfs -text
5
- *.ckpt filter=lfs diff=lfs merge=lfs -text
6
- *.ftz filter=lfs diff=lfs merge=lfs -text
7
- *.gz filter=lfs diff=lfs merge=lfs -text
8
- *.h5 filter=lfs diff=lfs merge=lfs -text
9
- *.joblib filter=lfs diff=lfs merge=lfs -text
10
- *.lfs.* filter=lfs diff=lfs merge=lfs -text
11
- *.mlmodel filter=lfs diff=lfs merge=lfs -text
12
- *.model filter=lfs diff=lfs merge=lfs -text
13
- *.msgpack filter=lfs diff=lfs merge=lfs -text
14
- *.npy filter=lfs diff=lfs merge=lfs -text
15
- *.npz filter=lfs diff=lfs merge=lfs -text
16
- *.onnx filter=lfs diff=lfs merge=lfs -text
17
- *.ot filter=lfs diff=lfs merge=lfs -text
18
- *.parquet filter=lfs diff=lfs merge=lfs -text
19
- *.pb filter=lfs diff=lfs merge=lfs -text
20
- *.pickle filter=lfs diff=lfs merge=lfs -text
21
- *.pkl filter=lfs diff=lfs merge=lfs -text
22
- *.pt filter=lfs diff=lfs merge=lfs -text
23
- *.pth filter=lfs diff=lfs merge=lfs -text
24
- *.rar filter=lfs diff=lfs merge=lfs -text
25
- *.safetensors filter=lfs diff=lfs merge=lfs -text
26
- saved_model/**/* filter=lfs diff=lfs merge=lfs -text
27
- *.tar.* filter=lfs diff=lfs merge=lfs -text
28
- *.tar filter=lfs diff=lfs merge=lfs -text
29
- *.tflite filter=lfs diff=lfs merge=lfs -text
30
- *.tgz filter=lfs diff=lfs merge=lfs -text
31
- *.wasm filter=lfs diff=lfs merge=lfs -text
32
- *.xz filter=lfs diff=lfs merge=lfs -text
33
- *.zip filter=lfs diff=lfs merge=lfs -text
34
- *.zst filter=lfs diff=lfs merge=lfs -text
35
- *tfevents* filter=lfs diff=lfs merge=lfs -text
 
1
+ *.mp3 filter=lfs diff=lfs merge=lfs -text
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
.gitignore ADDED
@@ -0,0 +1 @@
 
 
1
+ .aider*
README.md ADDED
@@ -0,0 +1,11 @@
 
 
 
 
 
 
 
 
 
 
 
 
1
+ ---
2
+ title: Paper Plane
3
+ emoji: 🛩
4
+ header: mini
5
+ colorFrom: blue
6
+ colorTo: indigo
7
+ sdk: static
8
+ pinned: false
9
+ ---
10
+
11
+ Check out the configuration reference at https://huggingface.co/docs/hub/spaces-config-reference
index.html ADDED
@@ -0,0 +1,83 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="en">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Paper Airplane Game</title>
7
+ <style>
8
+ body {
9
+ margin: 0;
10
+ overflow: hidden;
11
+ }
12
+ #distance {
13
+ position: absolute;
14
+ top: 10px;
15
+ left: 10px;
16
+ color: white;
17
+ font-family: Arial, sans-serif;
18
+ }
19
+ #finalScore {
20
+ position: absolute;
21
+ top: 50%;
22
+ left: 50%;
23
+ transform: translate(-50%, -50%);
24
+ color: white;
25
+ font-size: 24px;
26
+ font-family: Arial, sans-serif;
27
+ }
28
+ #instructions {
29
+ position: absolute;
30
+ top: 10px;
31
+ right: 10px;
32
+ color: white;
33
+ font-family: Arial, sans-serif;
34
+ max-width: 300px;
35
+ font-size: 12px;
36
+ }
37
+ #restart {
38
+ position: absolute;
39
+ top: 60%;
40
+ left: 50%;
41
+ transform: translate(-50%, -50%);
42
+ font-size: 18px;
43
+ padding: 10px 20px;
44
+ }
45
+ </style>
46
+ </head>
47
+ <body>
48
+ <div id="distance">Distance: 0</div>
49
+ <div id="finalScore" style="display: none;">Final Distance: 0</div>
50
+ <div id="spaceToRestart" style="position: absolute; top: 70%; left: 50%; transform: translate(-50%, -50%); color: white; font-family: Arial, sans-serif; font-size: 16px; display: none;">Press SPACE to restart</div>
51
+ <div id="instructions">
52
+ <p>Hold the space bar (or touch the screen on mobile) to build power, then release to launch the airplane at a 45-degree angle.</p>
53
+ <p>Use left and right arrow keys to steer the airplane during flight. Press up arrow to dive.</p>
54
+ <p>During flight, hold the down arrow key to boost upward (limited boost power).</p>
55
+ <p>Collect green globes to recharge your boost power.</p>
56
+ <p>Avoid buildings and fly as far as possible.</p>
57
+ </div>
58
+ <div id="powerGauge" style="position: absolute; bottom: 10px; left: 50%; transform: translateX(-50%); width: 200px; height: 20px; background-color: #ccc; display: none;">
59
+ <div style="position: absolute; bottom: 25px; width: 100%; text-align: center; color: white; font-family: Arial, sans-serif;">Hold SPACE</div>
60
+ <div id="powerBar" style="width: 0%; height: 100%; background-color: #f00;"></div>
61
+ </div>
62
+ <div id="boostGauge" style="position: absolute; bottom: 40px; left: 50%; transform: translateX(-50%); width: 200px; height: 20px; background-color: #ccc; display: none;">
63
+ <div style="position: absolute; bottom: 25px; width: 100%; text-align: center; color: white; font-family: Arial, sans-serif;">Boost power</div>
64
+ <div id="boostBar" style="width: 0%; height: 100%; background-color: #00ff00;"></div>
65
+ </div>
66
+ <button id="restart" style="display: none;">Restart</button>
67
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
68
+ <script src="https://cdn.jsdelivr.net/npm/[email protected]/examples/js/loaders/OBJLoader.js"></script>
69
+
70
+ <!-- Audio elements -->
71
+ <audio id="backgroundMusic" loop>
72
+ <source src="soundtrack.mp3" type="audio/mpeg">
73
+ Your browser does not support the audio element.
74
+ </audio>
75
+ <audio id="pingSound">
76
+ <source src="ping.mp3" type="audio/mpeg">
77
+ Your browser does not support the audio element.
78
+ </audio>
79
+
80
+ <script src="script.js"></script>
81
+
82
+ </body>
83
+ </html>
ping.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:7a7546d6444f9962f2cea3edcfae6551b7541965c77de5ab92069c679c35de64
3
+ size 122253
script.js ADDED
@@ -0,0 +1,1027 @@
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ // Helper Functions
2
+ /**
3
+ * Utility function to check if the airplane collides with the ground or a building.
4
+ */
5
+ function checkCollision(airplane, buildingBoxes) {
6
+ // Ground collision
7
+ if (airplane.position.y <= 0) {
8
+ return true;
9
+ }
10
+ // Building collisions
11
+ for (const box of buildingBoxes) {
12
+ if (
13
+ airplane.position.x > box.min.x &&
14
+ airplane.position.x < box.max.x &&
15
+ airplane.position.y > box.min.y &&
16
+ airplane.position.y < box.max.y &&
17
+ airplane.position.z > box.min.z &&
18
+ airplane.position.z < box.max.z
19
+ ) {
20
+ return true;
21
+ }
22
+ }
23
+ return false;
24
+ }
25
+
26
+ /**
27
+ * Utility function to update the camera position and orientation to follow the airplane.
28
+ */
29
+ function updateCamera(camera, airplane) {
30
+ camera.position.set(
31
+ airplane.position.x,
32
+ airplane.position.y + 5,
33
+ airplane.position.z - 10
34
+ );
35
+ camera.lookAt(airplane.position);
36
+ }
37
+
38
+ /**
39
+ * Utility function to update the distance UI element.
40
+ */
41
+ function updateDistanceDisplay(airplane, distanceElement) {
42
+ const horizontalDistance = Math.sqrt(
43
+ airplane.position.x ** 2 + airplane.position.z ** 2
44
+ );
45
+ distanceElement.innerText = `Distance: ${horizontalDistance.toFixed(2)}`;
46
+ }
47
+
48
+ // Scene Setup
49
+ const scene = new THREE.Scene();
50
+ // Create sunset gradient background
51
+ const canvas = document.createElement('canvas');
52
+ canvas.width = 1;
53
+ canvas.height = 256;
54
+ const context = canvas.getContext('2d');
55
+ const gradient = context.createLinearGradient(0, 256, 0, 0);
56
+ gradient.addColorStop(0, '#FF4500'); // Orange-red at the bottom (horizon)
57
+ gradient.addColorStop(0.4, '#4169E1'); // Royal blue in the middle
58
+ gradient.addColorStop(1, '#000000'); // Black at the top
59
+ context.fillStyle = gradient;
60
+ context.fillRect(0, 0, 1, 256);
61
+ const texture = new THREE.CanvasTexture(canvas);
62
+ // Center the texture so rotation pivots around its middle
63
+ texture.center.set(0.5, 0.5);
64
+ scene.background = texture;
65
+
66
+ // Create stars in the night sky
67
+ function createStars() {
68
+ const starsCount = 1000;
69
+ const starsGeometry = new THREE.BufferGeometry();
70
+ const starPositions = new Float32Array(starsCount * 3);
71
+
72
+ for (let i = 0; i < starsCount; i++) {
73
+ const i3 = i * 3;
74
+ // Generate stars in a large hemisphere above the scene
75
+ const radius = 500;
76
+ const theta = Math.random() * Math.PI * 2;
77
+ const phi = Math.random() * Math.PI * 0.65; // Limit to upper hemisphere
78
+
79
+ starPositions[i3] = radius * Math.sin(phi) * Math.cos(theta);
80
+ starPositions[i3 + 1] = radius * Math.cos(phi) + 100; // Lift up a bit
81
+ starPositions[i3 + 2] = radius * Math.sin(phi) * Math.sin(theta);
82
+ }
83
+
84
+ starsGeometry.setAttribute(
85
+ "position",
86
+ new THREE.BufferAttribute(starPositions, 3)
87
+ );
88
+
89
+ const starsMaterial = new THREE.PointsMaterial({
90
+ color: 0xffffff,
91
+ size: 1,
92
+ sizeAttenuation: false,
93
+ });
94
+
95
+ const stars = new THREE.Points(starsGeometry, starsMaterial);
96
+ scene.add(stars);
97
+ }
98
+
99
+ createStars();
100
+
101
+ // Camera Setup
102
+ const camera = new THREE.PerspectiveCamera(
103
+ 75,
104
+ window.innerWidth / window.innerHeight,
105
+ 0.1,
106
+ 1000
107
+ );
108
+
109
+ // Renderer Setup
110
+ const renderer = new THREE.WebGLRenderer();
111
+ renderer.setSize(window.innerWidth, window.innerHeight);
112
+ renderer.shadowMap.enabled = true;
113
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
114
+ document.body.appendChild(renderer.domElement);
115
+
116
+ // Lighting
117
+ const light = new THREE.DirectionalLight(0xfff0dd, 1.5); // Warm sunlight color
118
+ light.position.set(-100, 200, -50); // More realistic sun angle
119
+ light.castShadow = true;
120
+ light.shadow.mapSize.width = 512;
121
+ light.shadow.mapSize.height = 512;
122
+ light.shadow.camera.near = 0.1;
123
+ light.shadow.camera.far = 500;
124
+ light.shadow.camera.left = -50;
125
+ light.shadow.camera.right = 50;
126
+ light.shadow.camera.top = 50;
127
+ light.shadow.camera.bottom = -50;
128
+ light.shadow.bias = -0.0001;
129
+
130
+ const ambientLight = new THREE.AmbientLight(0x6688cc, 0.4); // Subtle blue sky light
131
+ scene.add(ambientLight);
132
+ scene.add(light);
133
+
134
+ // Paper Airplane
135
+ function createPaperAirplane() {
136
+ // Create a group to hold all paper airplane parts
137
+ const airplaneGroup = new THREE.Group();
138
+
139
+ // Main body/fuselage (triangle)
140
+ const bodyShape = new THREE.Shape();
141
+ bodyShape.moveTo(0, 0); // Nose
142
+ bodyShape.lineTo(-0.2, 0.5); // Left mid fold
143
+ bodyShape.lineTo(-0.4, 1.0); // Left back corner
144
+ bodyShape.lineTo(0.4, 1.0); // Right back corner
145
+ bodyShape.lineTo(0.2, 0.5); // Right mid fold
146
+ bodyShape.lineTo(0, 0); // Back to nose
147
+
148
+ const bodyGeometry = new THREE.ExtrudeGeometry(bodyShape, {
149
+ depth: 0.03,
150
+ bevelEnabled: false
151
+ });
152
+ const paperMaterial = new THREE.MeshLambertMaterial({
153
+ color: 0xf0f0f0,
154
+ side: THREE.DoubleSide,
155
+ });
156
+
157
+ const body = new THREE.Mesh(bodyGeometry, paperMaterial);
158
+ body.castShadow = true;
159
+ body.receiveShadow = true;
160
+
161
+ // Left wing (triangle extending outward)
162
+ const leftWingShape = new THREE.Shape();
163
+ leftWingShape.moveTo(0, 0.2); // Front connection to body
164
+ leftWingShape.lineTo(-0.3, 0.8); // Back connection to body
165
+ leftWingShape.lineTo(-0.7, 0.5); // Wing tip
166
+ leftWingShape.lineTo(0, 0.2); // Back to start
167
+
168
+ const leftWingGeometry = new THREE.ExtrudeGeometry(leftWingShape, {
169
+ depth: 0.02,
170
+ bevelEnabled: false
171
+ });
172
+ const leftWing = new THREE.Mesh(leftWingGeometry, paperMaterial);
173
+ leftWing.castShadow = true;
174
+ leftWing.receiveShadow = true;
175
+ leftWing.position.y = 0.01; // Slight offset to prevent z-fighting
176
+ leftWing.rotation.x = 0.2;
177
+
178
+ // Right wing (triangle extending outward)
179
+ const rightWingShape = new THREE.Shape();
180
+ rightWingShape.moveTo(0, 0.2); // Front connection to body
181
+ rightWingShape.lineTo(0.3, 0.8); // Back connection to body
182
+ rightWingShape.lineTo(0.7, 0.5); // Wing tip
183
+ rightWingShape.lineTo(0, 0.2); // Back to start
184
+
185
+ const rightWingGeometry = new THREE.ExtrudeGeometry(rightWingShape, {
186
+ depth: 0.02,
187
+ bevelEnabled: false
188
+ });
189
+ const rightWing = new THREE.Mesh(rightWingGeometry, paperMaterial);
190
+ rightWing.castShadow = true;
191
+ rightWing.receiveShadow = true;
192
+ rightWing.position.y = 0.02; // Slight offset to prevent z-fighting
193
+ rightWing.rotation.x = 0.2;
194
+
195
+ // Add center fold line for realism
196
+ const foldLineGeometry = new THREE.BufferGeometry();
197
+ const foldLinePoints = [
198
+ new THREE.Vector3(0, 0.03, 0), // Slightly above nose
199
+ new THREE.Vector3(0, 0.03, 1.0), // Slightly above back
200
+ ];
201
+ foldLineGeometry.setFromPoints(foldLinePoints);
202
+ const foldLineMaterial = new THREE.LineBasicMaterial({ color: 0xdddddd });
203
+ const foldLine = new THREE.Line(foldLineGeometry, foldLineMaterial);
204
+ foldLine.position.z = 0.026; // Slightly above the extruded body
205
+
206
+ // Add all parts to the group
207
+ airplaneGroup.add(body);
208
+ airplaneGroup.add(leftWing);
209
+ airplaneGroup.add(rightWing);
210
+ airplaneGroup.add(foldLine);
211
+
212
+ // Rotate and position
213
+ airplaneGroup.rotation.order = "ZXY";
214
+ airplaneGroup.rotation.x = -Math.PI / 2; // Rotate to face forward
215
+
216
+ return airplaneGroup;
217
+ }
218
+
219
+ const airplane = createPaperAirplane();
220
+ airplane.position.set(0, 10.1, 0);
221
+ scene.add(airplane);
222
+
223
+ // Cityscape Environment
224
+ const buildingGeometry = new THREE.BoxGeometry(1, 1, 1);
225
+
226
+ // Realistic building colors
227
+ const buildingColors = [
228
+ 0x8c8c8c, // Concrete gray
229
+ 0x9c5b3c, // Brick red-brown
230
+ 0x5a7d9e, // Steel blue
231
+ 0xbcbcbc, // Light gray
232
+ 0x4a4a4a, // Dark gray
233
+ ];
234
+ const buildings = [];
235
+ const buildingBoxes = [];
236
+
237
+ // Window parameters
238
+ const floorHeight = 1;
239
+ const windowWidth = 0.2;
240
+ const windowHeight = 0.3;
241
+ const horizontalSpacingMin = 0.1;
242
+ const epsilon = 0.01;
243
+ // Create two window materials - dark and lit
244
+ const darkWindowMaterial = new THREE.MeshLambertMaterial({
245
+ color: 0x0a1a2a,
246
+ transparent: true,
247
+ opacity: 0.5,
248
+ }); // Dark blue glass
249
+ const litWindowMaterial = new THREE.MeshLambertMaterial({
250
+ color: 0xffeb3b,
251
+ transparent: true,
252
+ opacity: 0.8,
253
+ emissive: 0xffeb3b,
254
+ emissiveIntensity: 0.5,
255
+ }); // Yellow lit windows
256
+
257
+ function createBuilding(x, z, height, width) {
258
+ const colorIndex = Math.floor(Math.random() * buildingColors.length);
259
+ const buildingMaterial = new THREE.MeshLambertMaterial({
260
+ color: buildingColors[colorIndex],
261
+ });
262
+ const building = new THREE.Mesh(buildingGeometry, buildingMaterial);
263
+ building.scale.set(width, height, width);
264
+ building.position.set(x, height / 2, z);
265
+ building.castShadow = true;
266
+ building.receiveShadow = true;
267
+ scene.add(building);
268
+ buildings.push(building);
269
+ buildingBoxes.push({
270
+ min: new THREE.Vector3(x - width / 2, 0, z - width / 2),
271
+ max: new THREE.Vector3(x + width / 2, height, z + width / 2),
272
+ });
273
+
274
+ // Add windows if building is sizable
275
+ const numFloors = Math.floor(height / floorHeight);
276
+ if (numFloors > 0) {
277
+ const n_horizontal = Math.floor(
278
+ (width + horizontalSpacingMin) / (windowWidth + horizontalSpacingMin)
279
+ );
280
+ if (n_horizontal > 0) {
281
+ const spacing_horizontal =
282
+ (width - n_horizontal * windowWidth) / (n_horizontal + 1);
283
+ const faces = [
284
+ {
285
+ normal: new THREE.Vector3(0, 0, 1),
286
+ offset: width / 2 + epsilon,
287
+ rotationY: 0,
288
+ }, // Front
289
+ {
290
+ normal: new THREE.Vector3(0, 0, -1),
291
+ offset: -width / 2 - epsilon,
292
+ rotationY: Math.PI,
293
+ }, // Back
294
+ {
295
+ normal: new THREE.Vector3(-1, 0, 0),
296
+ offset: -width / 2 - epsilon,
297
+ rotationY: -Math.PI / 2,
298
+ }, // Left
299
+ {
300
+ normal: new THREE.Vector3(1, 0, 0),
301
+ offset: width / 2 + epsilon,
302
+ rotationY: Math.PI / 2,
303
+ }, // Right
304
+ ];
305
+
306
+ // Create a merged BufferGeometry for all windows
307
+ const windowCount = numFloors * n_horizontal * faces.length;
308
+ const positions = new Float32Array(windowCount * 12); // 4 vertices * 3 coords per window
309
+ const indices = new Uint16Array(windowCount * 6); // 2 triangles * 3 indices per window
310
+
311
+ let posIndex = 0;
312
+ let idxIndex = 0;
313
+ let vertexOffset = 0;
314
+
315
+ for (const face of faces) {
316
+ const { offset, rotationY } = face;
317
+ const rotationMatrix = new THREE.Matrix4().makeRotationY(rotationY);
318
+
319
+ for (let k = 0; k < numFloors; k++) {
320
+ const y = (k + 0.5) * floorHeight;
321
+ for (let m = 0; m < n_horizontal; m++) {
322
+ let x_local, z_local;
323
+ if (face.normal.x !== 0) {
324
+ // Left or right face
325
+ z_local =
326
+ z -
327
+ width / 2 +
328
+ spacing_horizontal +
329
+ m * (windowWidth + spacing_horizontal) +
330
+ windowWidth / 2;
331
+ x_local = x + offset;
332
+ } else {
333
+ // Front or back face
334
+ x_local =
335
+ x -
336
+ width / 2 +
337
+ spacing_horizontal +
338
+ m * (windowWidth + spacing_horizontal) +
339
+ windowWidth / 2;
340
+ z_local = z + offset;
341
+ }
342
+ const windowPos = new THREE.Vector3(x_local, y, z_local);
343
+
344
+ // Define the four vertices of the window plane
345
+ const halfW = windowWidth / 2;
346
+ const halfH = windowHeight / 2;
347
+ const vertices = [
348
+ new THREE.Vector3(-halfW, -halfH, 0),
349
+ new THREE.Vector3(halfW, -halfH, 0),
350
+ new THREE.Vector3(halfW, halfH, 0),
351
+ new THREE.Vector3(-halfW, halfH, 0),
352
+ ];
353
+
354
+ // Apply rotation and translation
355
+ vertices.forEach((v) => {
356
+ v.applyMatrix4(rotationMatrix);
357
+ v.add(windowPos);
358
+ });
359
+
360
+ // Add positions
361
+ positions[posIndex++] = vertices[0].x;
362
+ positions[posIndex++] = vertices[0].y;
363
+ positions[posIndex++] = vertices[0].z;
364
+ positions[posIndex++] = vertices[1].x;
365
+ positions[posIndex++] = vertices[1].y;
366
+ positions[posIndex++] = vertices[1].z;
367
+ positions[posIndex++] = vertices[2].x;
368
+ positions[posIndex++] = vertices[2].y;
369
+ positions[posIndex++] = vertices[2].z;
370
+ positions[posIndex++] = vertices[3].x;
371
+ positions[posIndex++] = vertices[3].y;
372
+ positions[posIndex++] = vertices[3].z;
373
+
374
+ // Add indices (two triangles per quad)
375
+ indices[idxIndex++] = vertexOffset + 0;
376
+ indices[idxIndex++] = vertexOffset + 1;
377
+ indices[idxIndex++] = vertexOffset + 2;
378
+ indices[idxIndex++] = vertexOffset + 0;
379
+ indices[idxIndex++] = vertexOffset + 2;
380
+ indices[idxIndex++] = vertexOffset + 3;
381
+ vertexOffset += 4;
382
+ }
383
+ }
384
+ }
385
+
386
+ // Create and populate BufferGeometry
387
+ const mergedWindowGeometry = new THREE.BufferGeometry();
388
+ mergedWindowGeometry.setAttribute(
389
+ "position",
390
+ new THREE.BufferAttribute(positions, 3)
391
+ );
392
+ mergedWindowGeometry.setIndex(new THREE.BufferAttribute(indices, 1));
393
+ // Append window positions to global arrays
394
+ for (let i = 0; i < positions.length; i += 12) {
395
+ if (Math.random() < 0.2) {
396
+ // 20% chance to be lit
397
+ allLitWindowPositions.push(...positions.slice(i, i + 12));
398
+ } else {
399
+ allDarkWindowPositions.push(...positions.slice(i, i + 12));
400
+ }
401
+ }
402
+ }
403
+ }
404
+ return building;
405
+ }
406
+
407
+ function createGlobe(x, y, z) {
408
+ // Create the main sphere
409
+ const geometry = new THREE.SphereGeometry(1.0, 16, 16);
410
+ const material = new THREE.MeshBasicMaterial({
411
+ color: 0x00ff00,
412
+ });
413
+ const globe = new THREE.Mesh(geometry, material);
414
+ globe.position.set(x, y, z);
415
+
416
+ // Create outer glow sphere
417
+ const glowGeometry = new THREE.SphereGeometry(1.3, 16, 16);
418
+ const glowMaterial = new THREE.MeshBasicMaterial({
419
+ color: 0x00ff00,
420
+ transparent: true,
421
+ opacity: 0.3,
422
+ side: THREE.BackSide
423
+ });
424
+ const glow = new THREE.Mesh(glowGeometry, glowMaterial);
425
+ globe.add(glow);
426
+
427
+ // Create second outer glow for more intensity
428
+ const glow2Geometry = new THREE.SphereGeometry(1.6, 16, 16);
429
+ const glow2Material = new THREE.MeshBasicMaterial({
430
+ color: 0x00ff00,
431
+ transparent: true,
432
+ opacity: 0.15,
433
+ side: THREE.BackSide
434
+ });
435
+ const glow2 = new THREE.Mesh(glow2Geometry, glow2Material);
436
+ globe.add(glow2);
437
+
438
+ // Add a pulsing glow effect
439
+ globe.userData.pulsePhase = Math.random() * Math.PI * 2; // Random starting phase
440
+ globe.userData.glowLayers = [glow, glow2]; // Store references to glow layers
441
+
442
+ scene.add(globe);
443
+ return globe;
444
+ }
445
+
446
+ // Generate more buildings
447
+ // Arrays for merging windows
448
+ let allDarkWindowPositions = [];
449
+ let allLitWindowPositions = [];
450
+
451
+ for (let z = 20; z < 700; z += 5) {
452
+ // Start at z=20 instead of z=10 to double the gap
453
+ for (let x = -60; x <= 60; x += 5) {
454
+ let placeBuilding = Math.random() > 0.3;
455
+ let height, width;
456
+
457
+ // Calculate base height first
458
+ const baseHeight = Math.random() * 15 + 5; // Normal height range: 5 to 20
459
+
460
+ // 2% chance for a super tall building (30% higher)
461
+ if (Math.random() < 0.02) {
462
+ height = baseHeight * 1.3; // 30% higher than normal buildings
463
+ width = Math.random() * 4 + 3; // Wider base for tall buildings
464
+ } else {
465
+ height = baseHeight;
466
+ width = Math.random() * 3 + 1;
467
+ }
468
+ if (placeBuilding) {
469
+ const offsetX = (Math.random() - 0.5) * 2;
470
+ const offsetZ = (Math.random() - 0.5) * 2;
471
+ createBuilding(x + offsetX, z + offsetZ, height, width);
472
+ }
473
+ }
474
+ }
475
+
476
+ // Starting building for takeoff
477
+ const startingBuilding = createBuilding(0, 0, 10, 2);
478
+ startingBuilding.material.color.set(0x0000ff);
479
+
480
+ // Create boost recharge globes
481
+ let globes = [];
482
+
483
+ function initGlobes() {
484
+ // Remove existing globes
485
+ globes.forEach((globe) => scene.remove(globe));
486
+ globes = [];
487
+
488
+ // Define globe z-positions
489
+ const globe_z_positions = [];
490
+ for (let z = 40; z <= 680; z += 20) {
491
+ globe_z_positions.push(z);
492
+ }
493
+
494
+ // Place globes relative to buildings
495
+ globe_z_positions.forEach((z) => {
496
+ const nearbyBuildings = buildings.filter(
497
+ (b) => b.position.z >= z - 10 && b.position.z <= z + 10
498
+ );
499
+ if (nearbyBuildings.length > 0) {
500
+ const randomBuilding = nearbyBuildings[Math.floor(Math.random() * nearbyBuildings.length)];
501
+ const offsetX = (Math.random() - 0.5) * 2;
502
+ const offsetZ = (Math.random() - 0.5) * 2;
503
+ const globeX = randomBuilding.position.x + offsetX;
504
+ const globeZ = randomBuilding.position.z + offsetZ;
505
+ const globeY = randomBuilding.position.y + randomBuilding.scale.y / 2 + 5;
506
+ const globe = createGlobe(globeX, globeY, globeZ);
507
+ globes.push(globe);
508
+ }
509
+ });
510
+ }
511
+
512
+ // Initialize globes after buildings are created
513
+ initGlobes();
514
+
515
+ // Create merged window meshes
516
+ const darkGeometry = new THREE.BufferGeometry();
517
+ const darkPositionsArray = new Float32Array(allDarkWindowPositions);
518
+ darkGeometry.setAttribute(
519
+ "position",
520
+ new THREE.BufferAttribute(darkPositionsArray, 3)
521
+ );
522
+ const numDarkWindows = allDarkWindowPositions.length / 12;
523
+ const darkIndices = [];
524
+ for (let i = 0; i < numDarkWindows; i++) {
525
+ const offset = i * 4;
526
+ darkIndices.push(
527
+ offset,
528
+ offset + 1,
529
+ offset + 2,
530
+ offset,
531
+ offset + 2,
532
+ offset + 3
533
+ );
534
+ }
535
+ darkGeometry.setIndex(darkIndices);
536
+ const darkWindowsMesh = new THREE.Mesh(darkGeometry, darkWindowMaterial);
537
+ darkWindowsMesh.receiveShadow = true;
538
+ scene.add(darkWindowsMesh);
539
+
540
+ // Create lit windows mesh
541
+ const litGeometry = new THREE.BufferGeometry();
542
+ const litPositionsArray = new Float32Array(allLitWindowPositions);
543
+ litGeometry.setAttribute(
544
+ "position",
545
+ new THREE.BufferAttribute(litPositionsArray, 3)
546
+ );
547
+ const numLitWindows = allLitWindowPositions.length / 12;
548
+ const litIndices = [];
549
+ for (let i = 0; i < numLitWindows; i++) {
550
+ const offset = i * 4;
551
+ litIndices.push(
552
+ offset,
553
+ offset + 1,
554
+ offset + 2,
555
+ offset,
556
+ offset + 2,
557
+ offset + 3
558
+ );
559
+ }
560
+ litGeometry.setIndex(litIndices);
561
+ const litWindowsMesh = new THREE.Mesh(litGeometry, litWindowMaterial);
562
+ litWindowsMesh.receiveShadow = true;
563
+ scene.add(litWindowsMesh);
564
+
565
+ // Ground Plane with texture
566
+ const groundGeometry = new THREE.PlaneGeometry(2000, 2000, 100, 100);
567
+ const groundCanvas = document.createElement('canvas');
568
+ groundCanvas.width = 1024;
569
+ groundCanvas.height = 1024;
570
+ const groundContext = groundCanvas.getContext('2d');
571
+
572
+ // Fill with dark base color
573
+ groundContext.fillStyle = '#111111';
574
+ groundContext.fillRect(0, 0, 1024, 1024);
575
+
576
+ // Draw grid pattern
577
+ groundContext.strokeStyle = '#333333';
578
+ groundContext.lineWidth = 1;
579
+
580
+ // Draw major grid lines
581
+ const majorGridSize = 64;
582
+ groundContext.beginPath();
583
+ for (let i = 0; i <= 1024; i += majorGridSize) {
584
+ groundContext.moveTo(i, 0);
585
+ groundContext.lineTo(i, 1024);
586
+ groundContext.moveTo(0, i);
587
+ groundContext.lineTo(1024, i);
588
+ }
589
+ groundContext.stroke();
590
+
591
+ // Draw minor grid lines
592
+ groundContext.strokeStyle = '#222222';
593
+ groundContext.lineWidth = 0.5;
594
+ const minorGridSize = 16;
595
+ groundContext.beginPath();
596
+ for (let i = 0; i <= 1024; i += minorGridSize) {
597
+ if (i % majorGridSize !== 0) { // Skip where major lines already exist
598
+ groundContext.moveTo(i, 0);
599
+ groundContext.lineTo(i, 1024);
600
+ groundContext.moveTo(0, i);
601
+ groundContext.lineTo(1024, i);
602
+ }
603
+ }
604
+ groundContext.stroke();
605
+
606
+ // Add radial gradient for fade-out effect
607
+ const groundGradient = groundContext.createRadialGradient(512, 512, 0, 512, 512, 700);
608
+ groundGradient.addColorStop(0, 'rgba(0, 0, 0, 0)');
609
+ groundGradient.addColorStop(0.7, 'rgba(0, 0, 0, 0.3)');
610
+ groundGradient.addColorStop(1, 'rgba(0, 0, 0, 0.9)');
611
+ groundContext.fillStyle = groundGradient;
612
+ groundContext.fillRect(0, 0, 1024, 1024);
613
+
614
+ const groundTexture = new THREE.CanvasTexture(groundCanvas);
615
+ groundTexture.wrapS = THREE.RepeatWrapping;
616
+ groundTexture.wrapT = THREE.RepeatWrapping;
617
+ groundTexture.repeat.set(4, 4);
618
+
619
+ const groundMaterial = new THREE.MeshLambertMaterial({
620
+ map: groundTexture,
621
+ transparent: true,
622
+ opacity: 0.9
623
+ });
624
+
625
+ const ground = new THREE.Mesh(groundGeometry, groundMaterial);
626
+ ground.rotation.x = -Math.PI / 2;
627
+ ground.position.y = 0;
628
+ ground.receiveShadow = true;
629
+ scene.add(ground);
630
+
631
+ // Trail system for the airplane
632
+ const trailLength = 50; // Number of points in the trail
633
+ const trailPositions = new Float32Array(trailLength * 3);
634
+ const trailGeometry = new THREE.BufferGeometry();
635
+ trailGeometry.setAttribute(
636
+ "position",
637
+ new THREE.BufferAttribute(trailPositions, 3)
638
+ );
639
+
640
+ // Create gradient trail material
641
+ const trailMaterial = new THREE.LineBasicMaterial({
642
+ color: 0x88ccff,
643
+ transparent: true,
644
+ opacity: 0.7,
645
+ vertexColors: true,
646
+ linewidth: 1,
647
+ });
648
+
649
+ // Add vertex colors for gradient effect
650
+ const trailColors = new Float32Array(trailLength * 3);
651
+ for (let i = 0; i < trailLength; i++) {
652
+ // Create a gradient from light blue to darker blue
653
+ const intensity = 1 - i / trailLength;
654
+ trailColors[i * 3] = 0.4 * intensity; // R (less red for blue color)
655
+ trailColors[i * 3 + 1] = 0.7 * intensity; // G (medium green for cyan/blue)
656
+ trailColors[i * 3 + 2] = 1.0 * intensity; // B (full blue)
657
+ }
658
+ trailGeometry.setAttribute("color", new THREE.BufferAttribute(trailColors, 3));
659
+
660
+ const trail = new THREE.Line(trailGeometry, trailMaterial);
661
+ scene.add(trail);
662
+
663
+ // Function to update the trail positions
664
+ function updateTrail(newPosition) {
665
+ // Shift all positions one slot back
666
+ for (let i = trailLength - 1; i > 0; i--) {
667
+ trailPositions[i * 3] = trailPositions[(i - 1) * 3];
668
+ trailPositions[i * 3 + 1] = trailPositions[(i - 1) * 3 + 1];
669
+ trailPositions[i * 3 + 2] = trailPositions[(i - 1) * 3 + 2];
670
+ }
671
+
672
+ // Add the new position at the front
673
+ trailPositions[0] = newPosition.x;
674
+ trailPositions[1] = newPosition.y;
675
+ trailPositions[2] = newPosition.z;
676
+
677
+ // Update the geometry
678
+ trailGeometry.attributes.position.needsUpdate = true;
679
+ }
680
+
681
+ // Game State and Physics Variables
682
+ let gameState = "aiming";
683
+ let velocity = new THREE.Vector3(0, 0, 0);
684
+ const gravity = 2.5; // Increased gravity from 1 to 2.5
685
+ const acceleration = new THREE.Vector3(0, -gravity, 0);
686
+
687
+ // Power charging variables
688
+ let isCharging = false;
689
+ let currentPower = 0;
690
+ const maxPower = 10;
691
+ const powerIncreaseRate = 20; // units per second (4x faster)
692
+
693
+ // Launch controls
694
+ window.addEventListener("keydown", (event) => {
695
+ if (event.code === "Space") {
696
+ if (gameState === "aiming") {
697
+ event.preventDefault();
698
+ isCharging = true;
699
+ } else if (gameState === "ended") {
700
+ event.preventDefault();
701
+ resetGame();
702
+ }
703
+ } else if (event.key === "ArrowDown") {
704
+ if (gameState === "flying") {
705
+ event.preventDefault();
706
+ isBoosting = true;
707
+ }
708
+ } else if (event.key === "ArrowLeft") {
709
+ leftPressed = true;
710
+ } else if (event.key === "ArrowRight") {
711
+ rightPressed = true;
712
+ }
713
+ });
714
+
715
+ window.addEventListener("keyup", (event) => {
716
+ if (event.code === "Space") {
717
+ if (gameState === "aiming") {
718
+ event.preventDefault();
719
+ isCharging = false;
720
+ launchAirplane();
721
+ } else if (gameState === "ended") {
722
+ event.preventDefault();
723
+ resetGame();
724
+ }
725
+ } else if (event.key === "ArrowDown") {
726
+ if (gameState === "flying") {
727
+ event.preventDefault();
728
+ isBoosting = false;
729
+ }
730
+ } else if (event.key === "Escape") {
731
+ // Restart the game instantly when Escape key is pressed
732
+ event.preventDefault();
733
+ resetGame();
734
+ }
735
+ });
736
+
737
+ window.addEventListener(
738
+ "touchstart",
739
+ (event) => {
740
+ if (gameState === "aiming") {
741
+ event.preventDefault();
742
+ isCharging = true;
743
+ } else if (gameState === "flying") {
744
+ event.preventDefault();
745
+ // For touch screens, we'll still allow touch to boost since there's no down arrow
746
+ isBoosting = true;
747
+ }
748
+ },
749
+ { passive: false }
750
+ );
751
+
752
+ window.addEventListener(
753
+ "touchend",
754
+ (event) => {
755
+ if (gameState === "aiming") {
756
+ event.preventDefault();
757
+ isCharging = false;
758
+ launchAirplane();
759
+ } else if (gameState === "flying") {
760
+ event.preventDefault();
761
+ isBoosting = false;
762
+ } else if (gameState === "ended") {
763
+ event.preventDefault();
764
+ resetGame();
765
+ }
766
+ },
767
+ { passive: false }
768
+ );
769
+
770
+ function launchAirplane() {
771
+ const pitchAngle = Math.PI / 4; // 45 degrees
772
+ const initialVelocity = new THREE.Vector3(
773
+ 0,
774
+ Math.sin(pitchAngle) * currentPower,
775
+ Math.cos(pitchAngle) * currentPower
776
+ );
777
+ velocity.copy(initialVelocity);
778
+ gameState = "flying";
779
+ currentPower = 0;
780
+ }
781
+
782
+ // Steering Controls
783
+ let leftPressed = false;
784
+ let rightPressed = false;
785
+ let upPressed = false;
786
+ let downPressed = false;
787
+ const steeringForce = 5;
788
+ const diveForce = 8; // Force applied when diving
789
+ let currentTilt = 0;
790
+ let currentPitch = 0; // Track pitch angle
791
+
792
+ // Boost variables
793
+ const maxBoostPower = 100;
794
+ let boostPower = maxBoostPower;
795
+ const boostConsumptionRate = 20; // units per second
796
+ const boostForce = 6.0; // increased from 4.5 to 6.0 for more powerful boost
797
+ let isBoosting = false;
798
+
799
+ window.addEventListener("keydown", (event) => {
800
+ if (event.key === "ArrowLeft") leftPressed = true;
801
+ else if (event.key === "ArrowRight") rightPressed = true;
802
+ else if (event.key === "ArrowUp") upPressed = true;
803
+ else if (event.key === "ArrowDown") downPressed = true;
804
+ });
805
+
806
+ window.addEventListener("keyup", (event) => {
807
+ if (event.key === "ArrowLeft") leftPressed = false;
808
+ else if (event.key === "ArrowRight") rightPressed = false;
809
+ else if (event.key === "ArrowUp") upPressed = false;
810
+ else if (event.key === "ArrowDown") downPressed = false;
811
+ });
812
+
813
+ // Background music setup
814
+ const backgroundMusic = document.getElementById('backgroundMusic');
815
+ backgroundMusic.volume = 0.5; // Set volume to 50%
816
+
817
+ // Function to start background music
818
+ function startBackgroundMusic() {
819
+ backgroundMusic.play().catch(error => {
820
+ console.log("Audio playback failed:", error);
821
+ });
822
+ }
823
+
824
+ // Try to start music on page load
825
+ document.addEventListener('DOMContentLoaded', () => {
826
+ // Modern browsers require user interaction before playing audio
827
+ document.addEventListener('click', startBackgroundMusic, { once: true });
828
+ document.addEventListener('keydown', startBackgroundMusic, { once: true });
829
+ document.addEventListener('touchstart', startBackgroundMusic, { once: true });
830
+ });
831
+
832
+ // Animation Loop
833
+ const clock = new THREE.Clock();
834
+
835
+ function animate() {
836
+ requestAnimationFrame(animate);
837
+ const delta = clock.getDelta();
838
+
839
+ // Update UI elements
840
+ if (gameState === "aiming") {
841
+ // Always show the power gauge in aiming state
842
+ document.getElementById("powerGauge").style.display = "block";
843
+
844
+ if (isCharging) {
845
+ currentPower += powerIncreaseRate * delta;
846
+ if (currentPower > maxPower) currentPower = maxPower;
847
+ const powerPercentage = (currentPower / maxPower) * 100;
848
+ document.getElementById("powerBar").style.width = powerPercentage + "%";
849
+ } else {
850
+ document.getElementById("powerBar").style.width = "0%";
851
+ }
852
+ document.getElementById("boostGauge").style.display = "none";
853
+ } else if (gameState === "flying") {
854
+ document.getElementById("powerGauge").style.display = "none";
855
+ document.getElementById("boostGauge").style.display = "block";
856
+ const boostPercentage = (boostPower / maxBoostPower) * 100;
857
+ document.getElementById("boostBar").style.width = boostPercentage + "%";
858
+ } else if (gameState === "ended") {
859
+ document.getElementById("powerGauge").style.display = "none";
860
+ document.getElementById("boostGauge").style.display = "none";
861
+ }
862
+
863
+ if (gameState === "flying") {
864
+ velocity.add(acceleration.clone().multiplyScalar(delta));
865
+
866
+ if (upPressed) {
867
+ // Dive downward when up arrow is pressed
868
+ velocity.y -= diveForce * delta;
869
+
870
+ // Tilt the airplane's nose down when diving
871
+ currentPitch = THREE.MathUtils.lerp(currentPitch, 0.3, 0.1); // Gradually tilt nose down
872
+ } else if (isBoosting && boostPower > 0) {
873
+ velocity.y += boostForce * delta;
874
+ boostPower -= boostConsumptionRate * delta;
875
+ if (boostPower < 0) boostPower = 0;
876
+
877
+ // Tilt the airplane's nose up slightly when boosting
878
+ currentPitch = THREE.MathUtils.lerp(currentPitch, -0.2, 0.1); // Gradually tilt nose up
879
+ } else {
880
+ // Return to normal orientation when not boosting or diving
881
+ currentPitch = THREE.MathUtils.lerp(currentPitch, 0, 0.1); // Gradually return to neutral
882
+ }
883
+
884
+ // Apply the current pitch to the airplane
885
+ airplane.rotation.x = -Math.PI / 2 + currentPitch;
886
+
887
+ // Create a modified velocity vector with doubled forward (z) speed
888
+ const modifiedVelocity = velocity.clone();
889
+ modifiedVelocity.z *= 2; // Double the forward speed
890
+ airplane.position.add(modifiedVelocity.multiplyScalar(delta));
891
+
892
+ // Update the trail with the current airplane position
893
+ updateTrail(airplane.position);
894
+
895
+ if (leftPressed) velocity.x += steeringForce * delta; // Inverted: left key moves right
896
+ if (rightPressed) velocity.x -= steeringForce * delta; // Inverted: right key moves left
897
+
898
+ let targetTilt = 0;
899
+ if (leftPressed) targetTilt = -Math.PI / 6; // Inverted: negative tilt for left arrow
900
+ else if (rightPressed) targetTilt = Math.PI / 6; // Inverted: positive tilt for right arrow
901
+ currentTilt = THREE.MathUtils.lerp(currentTilt, targetTilt, 0.1);
902
+ airplane.rotation.z = currentTilt;
903
+
904
+ const collided = checkCollision(airplane, buildingBoxes);
905
+
906
+ // Check for globe collection
907
+ globes = globes.filter((globe) => {
908
+ const distance = airplane.position.distanceTo(globe.position);
909
+ if (distance < 3.0) { // Increased from 2.0 to 3.0 for larger hitbox (1.5x)
910
+ // Collected a globe - recharge boost
911
+ boostPower = maxBoostPower;
912
+ scene.remove(globe);
913
+
914
+ // Play ping sound
915
+ const pingSound = document.getElementById('pingSound');
916
+ pingSound.volume = 0.25; // Set volume to 25% (half of the default 0.5)
917
+ pingSound.currentTime = 0; // Reset sound to beginning
918
+ pingSound.play().catch(error => {
919
+ console.log("Ping sound playback failed:", error);
920
+ });
921
+
922
+ // Add visual feedback
923
+ console.log("Globe collected! Boost recharged.");
924
+ return false;
925
+ }
926
+
927
+ // Animate globe pulsing
928
+ globe.userData.pulsePhase += delta * 2;
929
+ const scale = 1 + 0.1 * Math.sin(globe.userData.pulsePhase);
930
+ globe.scale.set(scale, scale, scale);
931
+
932
+ // Animate glow layers
933
+ const glowScale = 1 + 0.2 * Math.sin(globe.userData.pulsePhase + Math.PI/4);
934
+ const glowOpacity = 0.3 + 0.1 * Math.sin(globe.userData.pulsePhase);
935
+
936
+ if (globe.userData.glowLayers) {
937
+ globe.userData.glowLayers[0].scale.set(glowScale, glowScale, glowScale);
938
+ globe.userData.glowLayers[0].material.opacity = glowOpacity;
939
+
940
+ const glow2Scale = 1 + 0.15 * Math.sin(globe.userData.pulsePhase + Math.PI/2);
941
+ const glow2Opacity = 0.15 + 0.05 * Math.sin(globe.userData.pulsePhase + Math.PI/3);
942
+ globe.userData.glowLayers[1].scale.set(glow2Scale, glow2Scale, glow2Scale);
943
+ globe.userData.glowLayers[1].material.opacity = glow2Opacity;
944
+ }
945
+
946
+ return true;
947
+ });
948
+
949
+ if (collided) {
950
+ gameState = "ended";
951
+ const finalHorizontalDistance = Math.sqrt(
952
+ airplane.position.x ** 2 + airplane.position.z ** 2
953
+ );
954
+ document.getElementById(
955
+ "finalScore"
956
+ ).innerText = `Final Distance: ${finalHorizontalDistance.toFixed(2)}`;
957
+ document.getElementById("finalScore").style.display = "block";
958
+ document.getElementById("restart").style.display = "block";
959
+ document.getElementById("spaceToRestart").style.display = "block";
960
+ }
961
+ }
962
+
963
+ // Camera follows airplane correctly
964
+ camera.position.set(
965
+ airplane.position.x,
966
+ airplane.position.y + 5,
967
+ airplane.position.z - 10
968
+ );
969
+ camera.lookAt(airplane.position);
970
+
971
+ updateDistanceDisplay(airplane, document.getElementById("distance"));
972
+
973
+ // Calculate distance and rotate background
974
+ const totalCityDistance = 700; // Adjust if needed
975
+ const horizontalDistance = Math.sqrt(
976
+ airplane.position.x ** 2 + airplane.position.z ** 2
977
+ );
978
+ const ratio = Math.min(1, horizontalDistance / totalCityDistance);
979
+ texture.rotation = ratio * (Math.PI * 0.5); // Up to 90° rotation
980
+ texture.needsUpdate = true;
981
+
982
+ renderer.render(scene, camera);
983
+ }
984
+ animate();
985
+
986
+ // Reset Game Function
987
+ function resetGame() {
988
+ airplane.position.set(0, 10.1, 0);
989
+ velocity.set(0, 0, 0);
990
+ currentTilt = 0;
991
+ currentPitch = 0;
992
+ airplane.rotation.z = 0;
993
+ airplane.rotation.x = -Math.PI / 2; // Reset pitch to default
994
+ gameState = "aiming";
995
+ currentPower = 0;
996
+ boostPower = maxBoostPower;
997
+
998
+ // Make sure music is playing
999
+ if (backgroundMusic.paused) {
1000
+ backgroundMusic.play().catch(error => {
1001
+ console.log("Audio playback failed:", error);
1002
+ });
1003
+ }
1004
+
1005
+ // Clear the trail when resetting the game
1006
+ for (let i = 0; i < trailLength * 3; i++) {
1007
+ trailPositions[i] = 0;
1008
+ }
1009
+ trailGeometry.attributes.position.needsUpdate = true;
1010
+
1011
+ document.getElementById("powerGauge").style.display = "block"; // Show power gauge on reset
1012
+ document.getElementById("finalScore").style.display = "none";
1013
+ document.getElementById("restart").style.display = "none";
1014
+ document.getElementById("spaceToRestart").style.display = "none";
1015
+
1016
+ // Reset globes
1017
+ initGlobes();
1018
+ }
1019
+
1020
+ document.getElementById("restart").addEventListener("click", resetGame);
1021
+
1022
+ // Handle Window Resize
1023
+ window.addEventListener("resize", () => {
1024
+ camera.aspect = window.innerWidth / window.innerHeight;
1025
+ camera.updateProjectionMatrix();
1026
+ renderer.setSize(window.innerWidth, window.innerHeight);
1027
+ });
soundtrack.mp3 ADDED
@@ -0,0 +1,3 @@
 
 
 
 
1
+ version https://git-lfs.github.com/spec/v1
2
+ oid sha256:a0ea1747bba738dc396b95f589176f9213cf394d39639096c72b29d337d747b6
3
+ size 5761197