kimhyunwoo commited on
Commit
cb562cc
·
verified ·
1 Parent(s): 113aff4

Update index.html

Browse files
Files changed (1) hide show
  1. index.html +1141 -19
index.html CHANGED
@@ -1,19 +1,1141 @@
1
- <!doctype html>
2
- <html>
3
- <head>
4
- <meta charset="utf-8" />
5
- <meta name="viewport" content="width=device-width" />
6
- <title>My static Space</title>
7
- <link rel="stylesheet" href="style.css" />
8
- </head>
9
- <body>
10
- <div class="card">
11
- <h1>Welcome to your static Space!</h1>
12
- <p>You can modify this app directly by editing <i>index.html</i> in the Files and versions tab.</p>
13
- <p>
14
- Also don't forget to check the
15
- <a href="https://huggingface.co/docs/hub/spaces" target="_blank">Spaces documentation</a>.
16
- </p>
17
- </div>
18
- </body>
19
- </html>
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
+ <!DOCTYPE html>
2
+ <html lang="ko">
3
+ <head>
4
+ <meta charset="UTF-8">
5
+ <meta name="viewport" content="width=device-width, initial-scale=1.0">
6
+ <title>Tesla 3D 무한맵 운전 게임</title>
7
+ <style>
8
+ * {
9
+ margin: 0;
10
+ padding: 0;
11
+ box-sizing: border-box;
12
+ }
13
+
14
+ body {
15
+ overflow: hidden;
16
+ font-family: -apple-system, BlinkMacSystemFont, 'Segoe UI', Roboto, 'Helvetica Neue', Arial, sans-serif;
17
+ background: #000;
18
+ }
19
+
20
+ #gameContainer {
21
+ position: relative;
22
+ width: 100vw;
23
+ height: 100vh;
24
+ }
25
+
26
+ #minimap {
27
+ position: absolute;
28
+ top: 20px;
29
+ right: 20px;
30
+ width: 200px;
31
+ height: 200px;
32
+ background: rgba(0, 0, 0, 0.8);
33
+ border: 2px solid #00ff00;
34
+ border-radius: 15px;
35
+ z-index: 100;
36
+ box-shadow: 0 0 20px rgba(0, 255, 0, 0.3);
37
+ }
38
+
39
+ #speedometer {
40
+ position: absolute;
41
+ bottom: 30px;
42
+ right: 30px;
43
+ font-size: 20px;
44
+ color: white;
45
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
46
+ z-index: 100;
47
+ background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), rgba(20, 20, 20, 0.8));
48
+ padding: 20px;
49
+ border-radius: 15px;
50
+ min-width: 200px;
51
+ border: 1px solid rgba(255, 255, 255, 0.1);
52
+ backdrop-filter: blur(10px);
53
+ }
54
+
55
+ #speedometer .speed-display {
56
+ font-size: 48px;
57
+ font-weight: 300;
58
+ color: #00ff88;
59
+ text-align: center;
60
+ margin: 10px 0;
61
+ }
62
+
63
+ #controls {
64
+ position: absolute;
65
+ top: 20px;
66
+ left: 20px;
67
+ color: white;
68
+ background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), rgba(20, 20, 20, 0.8));
69
+ padding: 20px;
70
+ border-radius: 15px;
71
+ z-index: 100;
72
+ font-size: 14px;
73
+ line-height: 1.8;
74
+ border: 1px solid rgba(255, 255, 255, 0.1);
75
+ backdrop-filter: blur(10px);
76
+ }
77
+
78
+ #score {
79
+ position: absolute;
80
+ top: 20px;
81
+ left: 50%;
82
+ transform: translateX(-50%);
83
+ color: white;
84
+ font-size: 24px;
85
+ font-weight: 300;
86
+ text-shadow: 2px 2px 4px rgba(0, 0, 0, 0.8);
87
+ z-index: 100;
88
+ background: linear-gradient(135deg, rgba(0, 0, 0, 0.8), rgba(20, 20, 20, 0.8));
89
+ padding: 15px 30px;
90
+ border-radius: 15px;
91
+ border: 1px solid rgba(255, 255, 255, 0.1);
92
+ backdrop-filter: blur(10px);
93
+ }
94
+
95
+ .gear-indicator {
96
+ text-align: center;
97
+ font-size: 32px;
98
+ margin-top: 10px;
99
+ font-weight: bold;
100
+ }
101
+
102
+ .gear-p { color: #ff4444; }
103
+ .gear-r { color: #ffaa44; }
104
+ .gear-n { color: #ffffff; }
105
+ .gear-d { color: #00ff88; }
106
+
107
+ canvas {
108
+ display: block;
109
+ }
110
+ </style>
111
+ </head>
112
+ <body>
113
+ <div id="gameContainer">
114
+ <div id="controls">
115
+ <div><strong>🎮 Tesla 운전 시뮬레이터</strong></div>
116
+ <div>W/↑ - 가속</div>
117
+ <div>S/↓ - 브레이크/후진</div>
118
+ <div>A/← - 좌회전</div>
119
+ <div>D/→ - 우회전</div>
120
+ <div>Space - 핸드브레이크</div>
121
+ <div>C - 카메라 변경</div>
122
+ <div>L - 헤드라이트</div>
123
+ </div>
124
+ <div id="score">주행 거리: 0 km</div>
125
+ <canvas id="minimap"></canvas>
126
+ <div id="speedometer">
127
+ <div style="text-align: center; color: #888; font-size: 12px;">TESLA</div>
128
+ <div class="speed-display"><span id="speed">0</span></div>
129
+ <div style="text-align: center; color: #888; font-size: 14px;">km/h</div>
130
+ <div class="gear-indicator gear-n" id="gear">P</div>
131
+ </div>
132
+ </div>
133
+
134
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
135
+ <script>
136
+ // Game variables
137
+ let scene, camera, renderer;
138
+ let car, carSpeed = 0, carRotation = 0;
139
+ let keys = {};
140
+ let buildings = [];
141
+ let roads = [];
142
+ let props = [];
143
+ let trees = [];
144
+ let distance = 0;
145
+ let cameraMode = 0;
146
+ let minimapCanvas, minimapCtx;
147
+ let clock = new THREE.Clock();
148
+ let headlightsOn = false;
149
+ let leftHeadlight, rightHeadlight;
150
+ let wheels = [];
151
+
152
+ const MAX_SPEED = 3;
153
+ const ACCELERATION = 0.08;
154
+ const DECELERATION = 0.04;
155
+ const TURN_SPEED = 0.04;
156
+ const BRAKE_DECELERATION = 0.12;
157
+ const WORLD_SIZE = 1000;
158
+ const CHUNK_SIZE = 150;
159
+
160
+ // Initialize game
161
+ function init() {
162
+ // Scene setup
163
+ scene = new THREE.Scene();
164
+ scene.fog = new THREE.Fog(0x8EAEC0, 200, 800);
165
+
166
+ // Camera setup
167
+ camera = new THREE.PerspectiveCamera(
168
+ 75,
169
+ window.innerWidth / window.innerHeight,
170
+ 0.1,
171
+ 2000
172
+ );
173
+
174
+ // Renderer setup
175
+ renderer = new THREE.WebGLRenderer({ antialias: true, alpha: true });
176
+ renderer.setSize(window.innerWidth, window.innerHeight);
177
+ renderer.shadowMap.enabled = true;
178
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
179
+ renderer.toneMapping = THREE.ACESFilmicToneMapping;
180
+ renderer.toneMappingExposure = 0.8;
181
+ document.getElementById('gameContainer').appendChild(renderer.domElement);
182
+
183
+ // Sky
184
+ createSky();
185
+
186
+ // Lighting
187
+ setupLighting();
188
+
189
+ // Create Tesla car
190
+ createTeslaCar();
191
+
192
+ // Create initial world
193
+ createWorld();
194
+
195
+ // Setup minimap
196
+ setupMinimap();
197
+
198
+ // Event listeners
199
+ window.addEventListener('keydown', handleKeyDown);
200
+ window.addEventListener('keyup', handleKeyUp);
201
+ window.addEventListener('resize', onWindowResize);
202
+
203
+ // Start game loop
204
+ animate();
205
+ }
206
+
207
+ function createSky() {
208
+ // Create gradient sky
209
+ const skyGeometry = new THREE.SphereGeometry(1500, 32, 15);
210
+ const skyMaterial = new THREE.ShaderMaterial({
211
+ uniforms: {
212
+ topColor: { value: new THREE.Color(0x0077be) },
213
+ bottomColor: { value: new THREE.Color(0xffffff) },
214
+ offset: { value: 33 },
215
+ exponent: { value: 0.6 }
216
+ },
217
+ vertexShader: `
218
+ varying vec3 vWorldPosition;
219
+ void main() {
220
+ vec4 worldPosition = modelMatrix * vec4(position, 1.0);
221
+ vWorldPosition = worldPosition.xyz;
222
+ gl_Position = projectionMatrix * modelViewMatrix * vec4(position, 1.0);
223
+ }
224
+ `,
225
+ fragmentShader: `
226
+ uniform vec3 topColor;
227
+ uniform vec3 bottomColor;
228
+ uniform float offset;
229
+ uniform float exponent;
230
+ varying vec3 vWorldPosition;
231
+ void main() {
232
+ float h = normalize(vWorldPosition + offset).y;
233
+ gl_FragColor = vec4(mix(bottomColor, topColor, max(pow(max(h, 0.0), exponent), 0.0)), 1.0);
234
+ }
235
+ `,
236
+ side: THREE.BackSide
237
+ });
238
+
239
+ const sky = new THREE.Mesh(skyGeometry, skyMaterial);
240
+ scene.add(sky);
241
+
242
+ // Add clouds
243
+ const cloudGeometry = new THREE.PlaneGeometry(100, 100);
244
+ const cloudMaterial = new THREE.MeshBasicMaterial({
245
+ color: 0xffffff,
246
+ transparent: true,
247
+ opacity: 0.4,
248
+ side: THREE.DoubleSide
249
+ });
250
+
251
+ for(let i = 0; i < 20; i++) {
252
+ const cloud = new THREE.Mesh(cloudGeometry, cloudMaterial);
253
+ cloud.position.set(
254
+ (Math.random() - 0.5) * 1000,
255
+ 200 + Math.random() * 200,
256
+ (Math.random() - 0.5) * 1000
257
+ );
258
+ cloud.rotation.x = Math.PI / 2;
259
+ cloud.scale.setScalar(Math.random() * 2 + 1);
260
+ scene.add(cloud);
261
+ }
262
+ }
263
+
264
+ function setupLighting() {
265
+ // Ambient light
266
+ const ambientLight = new THREE.AmbientLight(0xffffff, 0.4);
267
+ scene.add(ambientLight);
268
+
269
+ // Sun light
270
+ const sunLight = new THREE.DirectionalLight(0xffd4a3, 1);
271
+ sunLight.position.set(100, 200, 100);
272
+ sunLight.castShadow = true;
273
+ sunLight.shadow.camera.left = -150;
274
+ sunLight.shadow.camera.right = 150;
275
+ sunLight.shadow.camera.top = 150;
276
+ sunLight.shadow.camera.bottom = -150;
277
+ sunLight.shadow.camera.near = 0.1;
278
+ sunLight.shadow.camera.far = 500;
279
+ sunLight.shadow.mapSize.width = 2048;
280
+ sunLight.shadow.mapSize.height = 2048;
281
+ scene.add(sunLight);
282
+
283
+ // Hemisphere light for better ambient
284
+ const hemiLight = new THREE.HemisphereLight(0x87CEEB, 0x8B7355, 0.3);
285
+ scene.add(hemiLight);
286
+ }
287
+
288
+ function createTeslaCar() {
289
+ car = new THREE.Group();
290
+
291
+ // Tesla-like car body
292
+ const bodyGroup = new THREE.Group();
293
+
294
+ // Main body
295
+ const bodyGeometry = new THREE.BoxGeometry(2.2, 0.8, 4.8);
296
+ const bodyMaterial = new THREE.MeshPhongMaterial({
297
+ color: 0x1a1a1a,
298
+ metalness: 0.8,
299
+ roughness: 0.2
300
+ });
301
+ const carBody = new THREE.Mesh(bodyGeometry, bodyMaterial);
302
+ carBody.position.y = 0.6;
303
+ carBody.castShadow = true;
304
+ bodyGroup.add(carBody);
305
+
306
+ // Sleek roof (Tesla-like curve)
307
+ const roofGeometry = new THREE.BoxGeometry(2, 0.6, 3);
308
+ const roofMaterial = new THREE.MeshPhongMaterial({
309
+ color: 0x0a0a0a,
310
+ metalness: 0.9,
311
+ roughness: 0.1
312
+ });
313
+ const carRoof = new THREE.Mesh(roofGeometry, roofMaterial);
314
+ carRoof.position.y = 1.2;
315
+ carRoof.position.z = -0.3;
316
+
317
+ // Round the edges
318
+ carRoof.scale.set(0.95, 1, 1);
319
+ bodyGroup.add(carRoof);
320
+
321
+ // Glass windows
322
+ const glassGeometry = new THREE.BoxGeometry(1.9, 0.5, 2.8);
323
+ const glassMaterial = new THREE.MeshPhongMaterial({
324
+ color: 0x222244,
325
+ transparent: true,
326
+ opacity: 0.3,
327
+ metalness: 0.9,
328
+ roughness: 0.1
329
+ });
330
+ const glass = new THREE.Mesh(glassGeometry, glassMaterial);
331
+ glass.position.y = 1.2;
332
+ glass.position.z = -0.3;
333
+ bodyGroup.add(glass);
334
+
335
+ // Front hood
336
+ const hoodGeometry = new THREE.BoxGeometry(2.1, 0.2, 1.5);
337
+ const hood = new THREE.Mesh(hoodGeometry, bodyMaterial);
338
+ hood.position.y = 0.8;
339
+ hood.position.z = 2.2;
340
+ hood.rotation.x = -0.1;
341
+ bodyGroup.add(hood);
342
+
343
+ // Wheels - Tesla style
344
+ const wheelGeometry = new THREE.CylinderGeometry(0.35, 0.35, 0.25, 32);
345
+ const wheelMaterial = new THREE.MeshPhongMaterial({
346
+ color: 0x1a1a1a,
347
+ metalness: 0.8,
348
+ roughness: 0.3
349
+ });
350
+
351
+ // Wheel rims
352
+ const rimGeometry = new THREE.CylinderGeometry(0.32, 0.32, 0.26, 8);
353
+ const rimMaterial = new THREE.MeshPhongMaterial({
354
+ color: 0x888888,
355
+ metalness: 0.9,
356
+ roughness: 0.2
357
+ });
358
+
359
+ const wheelPositions = [
360
+ { x: -0.95, y: 0.1, z: 1.6 },
361
+ { x: 0.95, y: 0.1, z: 1.6 },
362
+ { x: -0.95, y: 0.1, z: -1.6 },
363
+ { x: 0.95, y: 0.1, z: -1.6 }
364
+ ];
365
+
366
+ wheelPositions.forEach(pos => {
367
+ const wheel = new THREE.Mesh(wheelGeometry, wheelMaterial);
368
+ wheel.rotation.z = Math.PI / 2;
369
+ wheel.position.set(pos.x, pos.y, pos.z);
370
+ wheel.castShadow = true;
371
+ bodyGroup.add(wheel);
372
+ wheels.push(wheel);
373
+
374
+ const rim = new THREE.Mesh(rimGeometry, rimMaterial);
375
+ rim.rotation.z = Math.PI / 2;
376
+ rim.position.set(pos.x * 1.01, pos.y, pos.z);
377
+ bodyGroup.add(rim);
378
+ });
379
+
380
+ // Headlights - Tesla style LED
381
+ const headlightGeometry = new THREE.BoxGeometry(0.4, 0.2, 0.1);
382
+ const headlightMaterial = new THREE.MeshPhongMaterial({
383
+ color: 0xffffff,
384
+ emissive: 0xffffff,
385
+ emissiveIntensity: 0.2
386
+ });
387
+
388
+ const headlight1 = new THREE.Mesh(headlightGeometry, headlightMaterial);
389
+ headlight1.position.set(-0.7, 0.6, 2.4);
390
+ bodyGroup.add(headlight1);
391
+
392
+ const headlight2 = new THREE.Mesh(headlightGeometry, headlightMaterial);
393
+ headlight2.position.set(0.7, 0.6, 2.4);
394
+ bodyGroup.add(headlight2);
395
+
396
+ // LED tail lights
397
+ const taillightGeometry = new THREE.BoxGeometry(0.5, 0.15, 0.1);
398
+ const taillightMaterial = new THREE.MeshPhongMaterial({
399
+ color: 0xff0000,
400
+ emissive: 0xff0000,
401
+ emissiveIntensity: 0.3
402
+ });
403
+
404
+ const taillight1 = new THREE.Mesh(taillightGeometry, taillightMaterial);
405
+ taillight1.position.set(-0.8, 0.7, -2.4);
406
+ bodyGroup.add(taillight1);
407
+
408
+ const taillight2 = new THREE.Mesh(taillightGeometry, taillightMaterial);
409
+ taillight2.position.set(0.8, 0.7, -2.4);
410
+ bodyGroup.add(taillight2);
411
+
412
+ // Door handles (flush like Tesla)
413
+ const handleGeometry = new THREE.BoxGeometry(0.02, 0.1, 0.4);
414
+ const handleMaterial = new THREE.MeshPhongMaterial({
415
+ color: 0x444444,
416
+ metalness: 0.9
417
+ });
418
+
419
+ [-1.1, 1.1].forEach(x => {
420
+ [-0.5, 0.8].forEach(z => {
421
+ const handle = new THREE.Mesh(handleGeometry, handleMaterial);
422
+ handle.position.set(x, 0.8, z);
423
+ bodyGroup.add(handle);
424
+ });
425
+ });
426
+
427
+ // Tesla logo/emblem
428
+ const logoGeometry = new THREE.PlaneGeometry(0.3, 0.3);
429
+ const logoMaterial = new THREE.MeshPhongMaterial({
430
+ color: 0xff0000,
431
+ emissive: 0xff0000,
432
+ emissiveIntensity: 0.2
433
+ });
434
+ const logo = new THREE.Mesh(logoGeometry, logoMaterial);
435
+ logo.position.set(0, 0.8, 2.41);
436
+ bodyGroup.add(logo);
437
+
438
+ car.add(bodyGroup);
439
+
440
+ // Add headlight spotlights
441
+ leftHeadlight = new THREE.SpotLight(0xffffff, 0, 50, Math.PI / 4, 0.5, 2);
442
+ leftHeadlight.position.set(-0.7, 0.6, 2.4);
443
+ leftHeadlight.target.position.set(-0.7, 0, 10);
444
+ car.add(leftHeadlight);
445
+ car.add(leftHeadlight.target);
446
+
447
+ rightHeadlight = new THREE.SpotLight(0xffffff, 0, 50, Math.PI / 4, 0.5, 2);
448
+ rightHeadlight.position.set(0.7, 0.6, 2.4);
449
+ rightHeadlight.target.position.set(0.7, 0, 10);
450
+ car.add(rightHeadlight);
451
+ car.add(rightHeadlight.target);
452
+
453
+ car.position.y = 0.3;
454
+ scene.add(car);
455
+ }
456
+
457
+ function createWorld() {
458
+ // Better ground texture
459
+ const groundGeometry = new THREE.PlaneGeometry(WORLD_SIZE * 4, WORLD_SIZE * 4);
460
+ const groundMaterial = new THREE.MeshLambertMaterial({
461
+ color: 0x4a5d3a
462
+ });
463
+ const ground = new THREE.Mesh(groundGeometry, groundMaterial);
464
+ ground.rotation.x = -Math.PI / 2;
465
+ ground.position.y = -0.1;
466
+ ground.receiveShadow = true;
467
+ scene.add(ground);
468
+
469
+ // Create initial city blocks
470
+ for (let x = -WORLD_SIZE/2; x < WORLD_SIZE/2; x += CHUNK_SIZE) {
471
+ for (let z = -WORLD_SIZE/2; z < WORLD_SIZE/2; z += CHUNK_SIZE) {
472
+ createCityBlock(x, z);
473
+ }
474
+ }
475
+ }
476
+
477
+ function createCityBlock(centerX, centerZ) {
478
+ // Asphalt roads with better textures
479
+ const roadWidth = 12;
480
+ const roadMaterial = new THREE.MeshLambertMaterial({
481
+ color: 0x2a2a2a
482
+ });
483
+
484
+ // Main roads
485
+ const hRoadGeometry = new THREE.PlaneGeometry(CHUNK_SIZE, roadWidth);
486
+ const hRoad = new THREE.Mesh(hRoadGeometry, roadMaterial);
487
+ hRoad.rotation.x = -Math.PI / 2;
488
+ hRoad.position.set(centerX, 0.01, centerZ);
489
+ hRoad.receiveShadow = true;
490
+ scene.add(hRoad);
491
+ roads.push(hRoad);
492
+
493
+ const vRoadGeometry = new THREE.PlaneGeometry(roadWidth, CHUNK_SIZE);
494
+ const vRoad = new THREE.Mesh(vRoadGeometry, roadMaterial);
495
+ vRoad.rotation.x = -Math.PI / 2;
496
+ vRoad.position.set(centerX, 0.01, centerZ);
497
+ vRoad.receiveShadow = true;
498
+ scene.add(vRoad);
499
+ roads.push(vRoad);
500
+
501
+ // Road lines
502
+ const lineMaterial = new THREE.MeshBasicMaterial({ color: 0xffff00 });
503
+ for(let i = -CHUNK_SIZE/2; i < CHUNK_SIZE/2; i += 10) {
504
+ const lineGeometry = new THREE.PlaneGeometry(0.2, 4);
505
+ const line = new THREE.Mesh(lineGeometry, lineMaterial);
506
+ line.rotation.x = -Math.PI / 2;
507
+ line.position.set(centerX, 0.02, centerZ + i);
508
+ scene.add(line);
509
+ }
510
+
511
+ // Sidewalks
512
+ const sidewalkMaterial = new THREE.MeshLambertMaterial({ color: 0x808080 });
513
+ const sidewalkGeometry = new THREE.BoxGeometry(CHUNK_SIZE, 0.2, 2);
514
+
515
+ [-7, 7].forEach(offset => {
516
+ const sidewalk = new THREE.Mesh(sidewalkGeometry, sidewalkMaterial);
517
+ sidewalk.position.set(centerX, 0.1, centerZ + offset);
518
+ scene.add(sidewalk);
519
+ });
520
+
521
+ // Buildings in quadrants
522
+ const quadrants = [
523
+ { x: centerX - CHUNK_SIZE/3, z: centerZ - CHUNK_SIZE/3 },
524
+ { x: centerX + CHUNK_SIZE/3, z: centerZ - CHUNK_SIZE/3 },
525
+ { x: centerX - CHUNK_SIZE/3, z: centerZ + CHUNK_SIZE/3 },
526
+ { x: centerX + CHUNK_SIZE/3, z: centerZ + CHUNK_SIZE/3 }
527
+ ];
528
+
529
+ quadrants.forEach(quad => {
530
+ const rand = Math.random();
531
+ if (rand > 0.3) {
532
+ createModernBuildings(quad.x, quad.z, CHUNK_SIZE/4);
533
+ } else {
534
+ createPark(quad.x, quad.z, CHUNK_SIZE/4);
535
+ }
536
+ });
537
+
538
+ // Street furniture
539
+ createStreetFurniture(centerX, centerZ);
540
+ }
541
+
542
+ function createModernBuildings(centerX, centerZ, size) {
543
+ const buildingCount = Math.floor(Math.random() * 3) + 2;
544
+
545
+ for (let i = 0; i < buildingCount; i++) {
546
+ const width = Math.random() * 20 + 15;
547
+ const depth = Math.random() * 20 + 15;
548
+ const height = Math.random() * 60 + 30;
549
+ const floors = Math.floor(height / 3);
550
+
551
+ const buildingGroup = new THREE.Group();
552
+
553
+ // Building base
554
+ const buildingGeometry = new THREE.BoxGeometry(width, height, depth);
555
+ const buildingColor = new THREE.Color(
556
+ 0.3 + Math.random() * 0.3,
557
+ 0.3 + Math.random() * 0.3,
558
+ 0.3 + Math.random() * 0.4
559
+ );
560
+ const buildingMaterial = new THREE.MeshPhongMaterial({
561
+ color: buildingColor,
562
+ metalness: 0.5,
563
+ roughness: 0.5
564
+ });
565
+
566
+ const building = new THREE.Mesh(buildingGeometry, buildingMaterial);
567
+ building.position.y = height / 2;
568
+ building.castShadow = true;
569
+ building.receiveShadow = true;
570
+ buildingGroup.add(building);
571
+
572
+ // Windows
573
+ for(let floor = 0; floor < floors; floor++) {
574
+ for(let side = 0; side < 4; side++) {
575
+ const windowCount = Math.floor(width / 3);
576
+ for(let w = 0; w < windowCount; w++) {
577
+ const windowGeometry = new THREE.BoxGeometry(1.5, 2, 0.1);
578
+ const windowMaterial = new THREE.MeshPhongMaterial({
579
+ color: 0x87CEEB,
580
+ emissive: 0x444466,
581
+ emissiveIntensity: 0.3,
582
+ metalness: 0.9,
583
+ roughness: 0.1
584
+ });
585
+ const window = new THREE.Mesh(windowGeometry, windowMaterial);
586
+
587
+ const angle = (side * Math.PI) / 2;
588
+ const radius = (side % 2 === 0) ? depth/2 : width/2;
589
+
590
+ window.position.set(
591
+ Math.sin(angle) * radius * 1.01,
592
+ floor * 3 + 2,
593
+ Math.cos(angle) * radius * 1.01
594
+ );
595
+ window.rotation.y = angle;
596
+
597
+ if(side < 2) {
598
+ window.position.x += (w - windowCount/2) * 2.5;
599
+ } else {
600
+ window.position.z += (w - windowCount/2) * 2.5;
601
+ }
602
+
603
+ buildingGroup.add(window);
604
+ }
605
+ }
606
+ }
607
+
608
+ buildingGroup.position.set(
609
+ centerX + (Math.random() - 0.5) * size,
610
+ 0,
611
+ centerZ + (Math.random() - 0.5) * size
612
+ );
613
+
614
+ scene.add(buildingGroup);
615
+ buildings.push(buildingGroup);
616
+ }
617
+ }
618
+
619
+ function createPark(centerX, centerZ, size) {
620
+ // Grass ground
621
+ const parkGeometry = new THREE.PlaneGeometry(size, size);
622
+ const parkMaterial = new THREE.MeshLambertMaterial({
623
+ color: 0x3a7d3a
624
+ });
625
+ const park = new THREE.Mesh(parkGeometry, parkMaterial);
626
+ park.rotation.x = -Math.PI / 2;
627
+ park.position.set(centerX, 0.02, centerZ);
628
+ park.receiveShadow = true;
629
+ scene.add(park);
630
+
631
+ // Walking paths
632
+ const pathMaterial = new THREE.MeshLambertMaterial({ color: 0x8B7355 });
633
+ const pathGeometry = new THREE.PlaneGeometry(2, size);
634
+
635
+ const path1 = new THREE.Mesh(pathGeometry, pathMaterial);
636
+ path1.rotation.x = -Math.PI / 2;
637
+ path1.position.set(centerX, 0.03, centerZ);
638
+ scene.add(path1);
639
+
640
+ const path2 = new THREE.Mesh(new THREE.PlaneGeometry(size, 2), pathMaterial);
641
+ path2.rotation.x = -Math.PI / 2;
642
+ path2.position.set(centerX, 0.03, centerZ);
643
+ scene.add(path2);
644
+
645
+ // Trees
646
+ const treeCount = Math.floor(Math.random() * 8) + 5;
647
+ for (let i = 0; i < treeCount; i++) {
648
+ const x = centerX + (Math.random() - 0.5) * size * 0.8;
649
+ const z = centerZ + (Math.random() - 0.5) * size * 0.8;
650
+
651
+ // Avoid paths
652
+ if(Math.abs(x - centerX) > 2 && Math.abs(z - centerZ) > 2) {
653
+ createRealisticTree(x, z);
654
+ }
655
+ }
656
+
657
+ // Park benches
658
+ for(let i = 0; i < 3; i++) {
659
+ createBench(
660
+ centerX + (Math.random() - 0.5) * size * 0.6,
661
+ centerZ + (Math.random() - 0.5) * size * 0.6
662
+ );
663
+ }
664
+ }
665
+
666
+ function createRealisticTree(x, z) {
667
+ const tree = new THREE.Group();
668
+
669
+ // Trunk
670
+ const trunkGeometry = new THREE.CylinderGeometry(0.8, 1, 6, 8);
671
+ const trunkMaterial = new THREE.MeshPhongMaterial({
672
+ color: 0x4a3c28,
673
+ roughness: 0.8
674
+ });
675
+ const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
676
+ trunk.position.y = 3;
677
+ trunk.castShadow = true;
678
+ tree.add(trunk);
679
+
680
+ // Foliage layers
681
+ const foliageColors = [0x2d5a2d, 0x3a6b3a, 0x4a7c4a];
682
+ const foliageSizes = [5, 4, 3];
683
+ const foliageHeights = [6, 8, 10];
684
+
685
+ foliageColors.forEach((color, i) => {
686
+ const foliageGeometry = new THREE.SphereGeometry(foliageSizes[i], 8, 6);
687
+ const foliageMaterial = new THREE.MeshPhongMaterial({
688
+ color: color,
689
+ roughness: 0.8
690
+ });
691
+ const foliage = new THREE.Mesh(foliageGeometry, foliageMaterial);
692
+ foliage.position.y = foliageHeights[i];
693
+ foliage.castShadow = true;
694
+ tree.add(foliage);
695
+ });
696
+
697
+ tree.position.set(x, 0, z);
698
+ scene.add(tree);
699
+ trees.push(tree);
700
+ }
701
+
702
+ function createStreetFurniture(centerX, centerZ) {
703
+ // Modern street lights
704
+ const positions = [
705
+ { x: centerX - 20, z: centerZ - 20 },
706
+ { x: centerX + 20, z: centerZ - 20 },
707
+ { x: centerX - 20, z: centerZ + 20 },
708
+ { x: centerX + 20, z: centerZ + 20 }
709
+ ];
710
+
711
+ positions.forEach(pos => {
712
+ const pole = new THREE.Group();
713
+
714
+ // Pole
715
+ const poleGeometry = new THREE.CylinderGeometry(0.15, 0.2, 10);
716
+ const poleMaterial = new THREE.MeshPhongMaterial({
717
+ color: 0x333333,
718
+ metalness: 0.8,
719
+ roughness: 0.3
720
+ });
721
+ const poleMesh = new THREE.Mesh(poleGeometry, poleMaterial);
722
+ poleMesh.position.y = 5;
723
+ poleMesh.castShadow = true;
724
+ pole.add(poleMesh);
725
+
726
+ // LED Light fixture
727
+ const lightGeometry = new THREE.BoxGeometry(2, 0.3, 0.8);
728
+ const lightMaterial = new THREE.MeshPhongMaterial({
729
+ color: 0xffffff,
730
+ emissive: 0xffffaa,
731
+ emissiveIntensity: 0.5
732
+ });
733
+ const lightMesh = new THREE.Mesh(lightGeometry, lightMaterial);
734
+ lightMesh.position.y = 10;
735
+ pole.add(lightMesh);
736
+
737
+ // Add actual light
738
+ const streetLight = new THREE.PointLight(0xffffaa, 0.5, 30);
739
+ streetLight.position.y = 9.5;
740
+ pole.add(streetLight);
741
+
742
+ pole.position.set(pos.x, 0, pos.z);
743
+ scene.add(pole);
744
+ props.push(pole);
745
+ });
746
+
747
+ // Traffic lights
748
+ if(Math.random() > 0.5) {
749
+ createTrafficLight(centerX + 8, centerZ + 8);
750
+ }
751
+
752
+ // Fire hydrants
753
+ if(Math.random() > 0.6) {
754
+ createFireHydrant(centerX + 15, centerZ - 15);
755
+ }
756
+ }
757
+
758
+ function createTrafficLight(x, z) {
759
+ const trafficLight = new THREE.Group();
760
+
761
+ // Pole
762
+ const poleGeometry = new THREE.CylinderGeometry(0.1, 0.1, 8);
763
+ const poleMaterial = new THREE.MeshPhongMaterial({ color: 0x333333 });
764
+ const pole = new THREE.Mesh(poleGeometry, poleMaterial);
765
+ pole.position.y = 4;
766
+ trafficLight.add(pole);
767
+
768
+ // Light box
769
+ const boxGeometry = new THREE.BoxGeometry(0.8, 2.4, 0.8);
770
+ const boxMaterial = new THREE.MeshPhongMaterial({ color: 0x1a1a1a });
771
+ const box = new THREE.Mesh(boxGeometry, boxMaterial);
772
+ box.position.y = 8;
773
+ trafficLight.add(box);
774
+
775
+ // Lights
776
+ const colors = [0xff0000, 0xffff00, 0x00ff00];
777
+ colors.forEach((color, i) => {
778
+ const lightGeometry = new THREE.SphereGeometry(0.3);
779
+ const lightMaterial = new THREE.MeshPhongMaterial({
780
+ color: color,
781
+ emissive: color,
782
+ emissiveIntensity: i === 2 ? 0.8 : 0.1
783
+ });
784
+ const light = new THREE.Mesh(lightGeometry, lightMaterial);
785
+ light.position.y = 8.8 - i * 0.8;
786
+ light.position.z = 0.41;
787
+ trafficLight.add(light);
788
+ });
789
+
790
+ trafficLight.position.set(x, 0, z);
791
+ scene.add(trafficLight);
792
+ props.push(trafficLight);
793
+ }
794
+
795
+ function createFireHydrant(x, z) {
796
+ const hydrant = new THREE.Group();
797
+
798
+ const bodyGeometry = new THREE.CylinderGeometry(0.3, 0.35, 1.5);
799
+ const bodyMaterial = new THREE.MeshPhongMaterial({
800
+ color: 0xff0000,
801
+ metalness: 0.5,
802
+ roughness: 0.4
803
+ });
804
+ const body = new THREE.Mesh(bodyGeometry, bodyMaterial);
805
+ body.position.y = 0.75;
806
+ hydrant.add(body);
807
+
808
+ const topGeometry = new THREE.SphereGeometry(0.35, 8, 4);
809
+ const top = new THREE.Mesh(topGeometry, bodyMaterial);
810
+ top.position.y = 1.5;
811
+ hydrant.add(top);
812
+
813
+ hydrant.position.set(x, 0, z);
814
+ hydrant.castShadow = true;
815
+ scene.add(hydrant);
816
+ props.push(hydrant);
817
+ }
818
+
819
+ function createBench(x, z) {
820
+ const bench = new THREE.Group();
821
+
822
+ // Seat
823
+ const seatGeometry = new THREE.BoxGeometry(4, 0.2, 1.5);
824
+ const woodMaterial = new THREE.MeshPhongMaterial({
825
+ color: 0x8B4513,
826
+ roughness: 0.8
827
+ });
828
+ const seat = new THREE.Mesh(seatGeometry, woodMaterial);
829
+ seat.position.y = 0.6;
830
+ bench.add(seat);
831
+
832
+ // Back
833
+ const backGeometry = new THREE.BoxGeometry(4, 1.2, 0.2);
834
+ const back = new THREE.Mesh(backGeometry, woodMaterial);
835
+ back.position.y = 1.2;
836
+ back.position.z = -0.6;
837
+ back.rotation.x = -0.1;
838
+ bench.add(back);
839
+
840
+ // Metal legs
841
+ const legMaterial = new THREE.MeshPhongMaterial({
842
+ color: 0x444444,
843
+ metalness: 0.8
844
+ });
845
+ const legGeometry = new THREE.CylinderGeometry(0.05, 0.05, 0.6);
846
+
847
+ [-1.8, 1.8].forEach(xOffset => {
848
+ [-0.5, 0.5].forEach(zOffset => {
849
+ const leg = new THREE.Mesh(legGeometry, legMaterial);
850
+ leg.position.set(xOffset, 0.3, zOffset);
851
+ bench.add(leg);
852
+ });
853
+ });
854
+
855
+ bench.position.set(x, 0, z);
856
+ bench.rotation.y = Math.random() * Math.PI;
857
+ bench.castShadow = true;
858
+ scene.add(bench);
859
+ props.push(bench);
860
+ }
861
+
862
+ function setupMinimap() {
863
+ minimapCanvas = document.getElementById('minimap');
864
+ minimapCanvas.width = 200;
865
+ minimapCanvas.height = 200;
866
+ minimapCtx = minimapCanvas.getContext('2d');
867
+ }
868
+
869
+ function updateMinimap() {
870
+ // Clear
871
+ minimapCtx.fillStyle = 'rgba(0, 0, 0, 0.9)';
872
+ minimapCtx.fillRect(0, 0, 200, 200);
873
+
874
+ // Draw grid
875
+ minimapCtx.strokeStyle = '#2a2a2a';
876
+ minimapCtx.lineWidth = 1;
877
+
878
+ const scale = 0.4;
879
+ const offsetX = 100 - car.position.x * scale;
880
+ const offsetZ = 100 - car.position.z * scale;
881
+
882
+ // Grid lines
883
+ for (let x = -WORLD_SIZE; x <= WORLD_SIZE; x += CHUNK_SIZE) {
884
+ minimapCtx.beginPath();
885
+ minimapCtx.moveTo(x * scale + offsetX, 0);
886
+ minimapCtx.lineTo(x * scale + offsetX, 200);
887
+ minimapCtx.stroke();
888
+ }
889
+
890
+ for (let z = -WORLD_SIZE; z <= WORLD_SIZE; z += CHUNK_SIZE) {
891
+ minimapCtx.beginPath();
892
+ minimapCtx.moveTo(0, z * scale + offsetZ);
893
+ minimapCtx.lineTo(200, z * scale + offsetZ);
894
+ minimapCtx.stroke();
895
+ }
896
+
897
+ // Draw buildings
898
+ buildings.forEach(building => {
899
+ const bx = building.position.x * scale + offsetX;
900
+ const bz = building.position.z * scale + offsetZ;
901
+
902
+ if (bx > -20 && bx < 220 && bz > -20 && bz < 220) {
903
+ minimapCtx.fillStyle = '#444';
904
+ minimapCtx.fillRect(bx - 4, bz - 4, 8, 8);
905
+ }
906
+ });
907
+
908
+ // Draw trees
909
+ trees.forEach(tree => {
910
+ const tx = tree.position.x * scale + offsetX;
911
+ const tz = tree.position.z * scale + offsetZ;
912
+
913
+ if (tx > -10 && tx < 210 && tz > -10 && tz < 210) {
914
+ minimapCtx.fillStyle = '#2a5a2a';
915
+ minimapCtx.beginPath();
916
+ minimapCtx.arc(tx, tz, 2, 0, Math.PI * 2);
917
+ minimapCtx.fill();
918
+ }
919
+ });
920
+
921
+ // Draw car
922
+ minimapCtx.save();
923
+ minimapCtx.translate(100, 100);
924
+ minimapCtx.rotate(-car.rotation.y);
925
+
926
+ // Car body
927
+ minimapCtx.fillStyle = '#00ff88';
928
+ minimapCtx.fillRect(-4, -7, 8, 14);
929
+
930
+ // Direction indicator
931
+ minimapCtx.strokeStyle = '#00ffff';
932
+ minimapCtx.lineWidth = 2;
933
+ minimapCtx.beginPath();
934
+ minimapCtx.moveTo(0, 0);
935
+ minimapCtx.lineTo(0, -15);
936
+ minimapCtx.stroke();
937
+
938
+ minimapCtx.restore();
939
+ }
940
+
941
+ function handleKeyDown(e) {
942
+ keys[e.key.toLowerCase()] = true;
943
+
944
+ if (e.key.toLowerCase() === 'l') {
945
+ headlightsOn = !headlightsOn;
946
+ leftHeadlight.intensity = headlightsOn ? 2 : 0;
947
+ rightHeadlight.intensity = headlightsOn ? 2 : 0;
948
+ }
949
+ }
950
+
951
+ function handleKeyUp(e) {
952
+ keys[e.key.toLowerCase()] = false;
953
+ }
954
+
955
+ function updateCar() {
956
+ const delta = clock.getDelta();
957
+
958
+ // Handle input
959
+ if (keys['w'] || keys['arrowup']) {
960
+ carSpeed = Math.min(carSpeed + ACCELERATION, MAX_SPEED);
961
+ } else if (keys['s'] || keys['arrowdown']) {
962
+ if (carSpeed > 0) {
963
+ carSpeed = Math.max(carSpeed - BRAKE_DECELERATION, 0);
964
+ } else {
965
+ carSpeed = Math.max(carSpeed - ACCELERATION, -MAX_SPEED / 2);
966
+ }
967
+ } else {
968
+ if (carSpeed > 0) {
969
+ carSpeed = Math.max(carSpeed - DECELERATION, 0);
970
+ } else if (carSpeed < 0) {
971
+ carSpeed = Math.min(carSpeed + DECELERATION, 0);
972
+ }
973
+ }
974
+
975
+ // Turning
976
+ if (Math.abs(carSpeed) > 0.01) {
977
+ let turnAmount = TURN_SPEED;
978
+ if(Math.abs(carSpeed) < 0.5) turnAmount *= Math.abs(carSpeed) * 2;
979
+
980
+ if (keys['a'] || keys['arrowleft']) {
981
+ carRotation += turnAmount * (carSpeed > 0 ? 1 : -1);
982
+ }
983
+ if (keys['d'] || keys['arrowright']) {
984
+ carRotation -= turnAmount * (carSpeed > 0 ? 1 : -1);
985
+ }
986
+ }
987
+
988
+ // Handbrake
989
+ if (keys[' '] && Math.abs(carSpeed) > 0.5) {
990
+ carSpeed *= 0.92;
991
+ if (keys['a'] || keys['arrowleft']) {
992
+ carRotation += TURN_SPEED * 2;
993
+ }
994
+ if (keys['d'] || keys['arrowright']) {
995
+ carRotation -= TURN_SPEED * 2;
996
+ }
997
+ }
998
+
999
+ // Update position
1000
+ car.rotation.y = carRotation;
1001
+ car.position.x += Math.sin(carRotation) * carSpeed;
1002
+ car.position.z += Math.cos(carRotation) * carSpeed;
1003
+
1004
+ // Rotate wheels
1005
+ wheels.forEach(wheel => {
1006
+ wheel.rotation.x += carSpeed * 0.5;
1007
+ });
1008
+
1009
+ // Update camera
1010
+ updateCamera();
1011
+
1012
+ // Generate world
1013
+ checkWorldGeneration();
1014
+
1015
+ // Update UI
1016
+ updateUI();
1017
+
1018
+ // Update distance
1019
+ distance += Math.abs(carSpeed) * 0.05;
1020
+ document.getElementById('score').textContent = `주행 거리: ${distance.toFixed(1)} km`;
1021
+ }
1022
+
1023
+ function updateCamera() {
1024
+ if (keys['c']) {
1025
+ keys['c'] = false;
1026
+ cameraMode = (cameraMode + 1) % 3;
1027
+ }
1028
+
1029
+ const smoothness = 0.1;
1030
+ let targetX, targetY, targetZ;
1031
+ let lookX, lookY, lookZ;
1032
+
1033
+ switch(cameraMode) {
1034
+ case 0: // Third person
1035
+ targetX = car.position.x - Math.sin(carRotation) * 20;
1036
+ targetY = car.position.y + 10;
1037
+ targetZ = car.position.z - Math.cos(carRotation) * 20;
1038
+ lookX = car.position.x;
1039
+ lookY = car.position.y + 2;
1040
+ lookZ = car.position.z;
1041
+ break;
1042
+ case 1: // Hood cam
1043
+ targetX = car.position.x + Math.sin(carRotation) * 2;
1044
+ targetY = car.position.y + 3;
1045
+ targetZ = car.position.z + Math.cos(carRotation) * 2;
1046
+ lookX = car.position.x + Math.sin(carRotation) * 20;
1047
+ lookY = car.position.y + 2;
1048
+ lookZ = car.position.z + Math.cos(carRotation) * 20;
1049
+ break;
1050
+ case 2: // Drone view
1051
+ targetX = car.position.x;
1052
+ targetY = car.position.y + 50;
1053
+ targetZ = car.position.z - 10;
1054
+ lookX = car.position.x;
1055
+ lookY = car.position.y;
1056
+ lookZ = car.position.z;
1057
+ break;
1058
+ }
1059
+
1060
+ camera.position.x += (targetX - camera.position.x) * smoothness;
1061
+ camera.position.y += (targetY - camera.position.y) * smoothness;
1062
+ camera.position.z += (targetZ - camera.position.z) * smoothness;
1063
+ camera.lookAt(lookX, lookY, lookZ);
1064
+ }
1065
+
1066
+ function checkWorldGeneration() {
1067
+ const carChunkX = Math.floor(car.position.x / CHUNK_SIZE) * CHUNK_SIZE;
1068
+ const carChunkZ = Math.floor(car.position.z / CHUNK_SIZE) * CHUNK_SIZE;
1069
+
1070
+ // Generate new chunks
1071
+ for (let x = carChunkX - CHUNK_SIZE * 3; x <= carChunkX + CHUNK_SIZE * 3; x += CHUNK_SIZE) {
1072
+ for (let z = carChunkZ - CHUNK_SIZE * 3; z <= carChunkZ + CHUNK_SIZE * 3; z += CHUNK_SIZE) {
1073
+ const chunkKey = `${x}_${z}`;
1074
+ if (!window.generatedChunks) window.generatedChunks = new Set();
1075
+
1076
+ if (!window.generatedChunks.has(chunkKey)) {
1077
+ createCityBlock(x, z);
1078
+ window.generatedChunks.add(chunkKey);
1079
+ }
1080
+ }
1081
+ }
1082
+
1083
+ // Remove far objects
1084
+ const maxDistance = CHUNK_SIZE * 4;
1085
+ [...buildings, ...props, ...roads, ...trees].forEach(obj => {
1086
+ if (obj.position) {
1087
+ const distance = obj.position.distanceTo(car.position);
1088
+ if (distance > maxDistance) {
1089
+ scene.remove(obj);
1090
+ buildings = buildings.filter(b => b !== obj);
1091
+ props = props.filter(p => p !== obj);
1092
+ roads = roads.filter(r => r !== obj);
1093
+ trees = trees.filter(t => t !== obj);
1094
+ }
1095
+ }
1096
+ });
1097
+ }
1098
+
1099
+ function updateUI() {
1100
+ const speedKmh = Math.floor(Math.abs(carSpeed) * 60);
1101
+ document.getElementById('speed').textContent = speedKmh;
1102
+
1103
+ const gearElement = document.getElementById('gear');
1104
+ let gear = 'P';
1105
+ let gearClass = 'gear-p';
1106
+
1107
+ if (Math.abs(carSpeed) < 0.01) {
1108
+ gear = 'P';
1109
+ gearClass = 'gear-p';
1110
+ } else if (carSpeed > 0.01) {
1111
+ gear = 'D';
1112
+ gearClass = 'gear-d';
1113
+ } else if (carSpeed < -0.01) {
1114
+ gear = 'R';
1115
+ gearClass = 'gear-r';
1116
+ }
1117
+
1118
+ gearElement.textContent = gear;
1119
+ gearElement.className = `gear-indicator ${gearClass}`;
1120
+ }
1121
+
1122
+ function onWindowResize() {
1123
+ camera.aspect = window.innerWidth / window.innerHeight;
1124
+ camera.updateProjectionMatrix();
1125
+ renderer.setSize(window.innerWidth, window.innerHeight);
1126
+ }
1127
+
1128
+ function animate() {
1129
+ requestAnimationFrame(animate);
1130
+
1131
+ updateCar();
1132
+ updateMinimap();
1133
+
1134
+ renderer.render(scene, camera);
1135
+ }
1136
+
1137
+ // Start the game
1138
+ init();
1139
+ </script>
1140
+ </body>
1141
+ </html>