latterworks commited on
Commit
cf7fe6f
·
verified ·
1 Parent(s): 738ac09

Build a complete browser-based first-person 3D environment using the latest stable version of Three.js. The user must be able to move freely through the environment using the WASD keys for directional movement and the mouse for camera rotation through pointer lock controls. The implementation must not include any overlays, popups, or interactive prompts such as “Click to Begin.” Implement a camera soldier system that maintains a human-scale eye height, smooth acceleration, head stability, and accurate step height relative to the ground. Include a visible textured ground plane that spans a large enough area for continuous movement. Place one static 3D object within the scene positioned as a navigation obstacle. Include a complete lighting setup using a combination of directional light and hemispheric light to achieve visible illumination across all geometry. Implement smooth movement physics that remain consistent under variable frame rates by using delta-time-based integration. Ensure collision handling between the player controller and static geometry so that the user cannot pass through solid objects. Maintain correct visual proportions and perspective under all window resizing events by recalculating aspect ratio and updating the camera projection matrix dynamically. Use current JavaScript and Three.js best practices with explicit import statements from the most recent stable module URLs. All variable names must follow consistent camelCase naming. All functions must include concise, technically accurate code comments describing their purpose and behavior. Use strict typing where possible, avoid global variables, and ensure deterministic behavior of the main animation loop driven by requestAnimationFrame. The program must execute without console warnings, syntax errors, or deprecated API usage.

Browse files
Files changed (1) hide show
  1. index.html +143 -200
index.html CHANGED
@@ -1,146 +1,70 @@
 
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>WanderLust 3D Explorer</title>
7
- <script src="https://cdn.tailwindcss.com"></script>
8
- <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
9
- <script src="https://cdn.jsdelivr.net/npm/three@0.128.0/examples/js/controls/PointerLockControls.js"></script>
10
  <style>
11
  body {
12
  margin: 0;
13
  overflow: hidden;
14
- font-family: 'Inter', sans-serif;
15
- }
16
- #info {
17
- position: absolute;
18
- top: 10px;
19
- width: 100%;
20
- text-align: center;
21
- color: white;
22
- z-index: 100;
23
- pointer-events: none;
24
- text-shadow: 1px 1px 2px rgba(0,0,0,0.5);
25
- }
26
- #blocker {
27
- position: absolute;
28
- width: 100%;
29
- height: 100%;
30
- background-color: rgba(0,0,0,0.7);
31
- display: flex;
32
- flex-direction: column;
33
- justify-content: center;
34
- align-items: center;
35
- z-index: 200;
36
- }
37
- #instructions {
38
- color: white;
39
- max-width: 600px;
40
- text-align: center;
41
- padding: 20px;
42
- background-color: rgba(0,0,0,0.5);
43
- border-radius: 10px;
44
  }
45
- #startButton {
46
- padding: 12px 24px;
47
- margin-top: 20px;
48
- background: linear-gradient(135deg, #6e8efb, #a777e3);
49
- color: white;
50
- border: none;
51
- border-radius: 25px;
52
- font-size: 16px;
53
- cursor: pointer;
54
- transition: all 0.3s ease;
55
- box-shadow: 0 4px 6px rgba(0,0,0,0.1);
56
- }
57
- #startButton:hover {
58
- transform: translateY(-2px);
59
- box-shadow: 0 6px 8px rgba(0,0,0,0.15);
60
  }
61
  </style>
62
  </head>
63
- <body class="bg-gray-900">
64
- <div id="info" class="text-lg">
65
- WanderLust 3D Explorer | WASD to move | Mouse to look | ESC to exit
66
- </div>
67
- <div id="blocker">
68
- <div id="instructions" class="text-xl">
69
- <h1 class="text-3xl font-bold mb-4">Welcome to WanderLust</h1>
70
- <p class="mb-6">Explore this immersive 3D environment freely.</p>
71
- <p class="text-sm opacity-75">Click the button below to enter the experience. Use WASD keys to move and mouse to look around.</p>
72
- <button id="startButton">BEGIN EXPLORATION</button>
73
- </div>
74
- </div>
75
  <script>
 
 
76
  // Scene setup
77
  const scene = new THREE.Scene();
78
- scene.background = new THREE.Color(0x87CEEB); // Sky blue background
79
 
80
- // Camera
81
  const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
82
- camera.position.y = 1.6; // Approximate eye-level height
83
 
84
- // Renderer
85
  const renderer = new THREE.WebGLRenderer({ antialias: true });
86
  renderer.setSize(window.innerWidth, window.innerHeight);
87
  renderer.shadowMap.enabled = true;
 
88
  document.body.appendChild(renderer.domElement);
89
 
90
- // Controls (Pointer Lock)
91
  const controls = new THREE.PointerLockControls(camera, document.body);
92
- let moveForward = false;
93
- let moveBackward = false;
94
- let moveLeft = false;
95
- let moveRight = false;
96
- let canJump = false;
 
 
 
 
97
 
98
- // Velocity and movement variables
99
  const velocity = new THREE.Vector3();
100
  const direction = new THREE.Vector3();
101
- let prevTime = performance.now();
102
-
103
- // Event listeners for controls
104
- document.addEventListener('keydown', (event) => {
105
- switch (event.code) {
106
- case 'KeyW': moveForward = true; break;
107
- case 'KeyA': moveLeft = true; break;
108
- case 'KeyS': moveBackward = true; break;
109
- case 'KeyD': moveRight = true; break;
110
- case 'Space': if (canJump) velocity.y += 0.15; break;
111
- }
112
- });
113
-
114
- document.addEventListener('keyup', (event) => {
115
- switch (event.code) {
116
- case 'KeyW': moveForward = false; break;
117
- case 'KeyA': moveLeft = false; break;
118
- case 'KeyS': moveBackward = false; break;
119
- case 'KeyD': moveRight = false; break;
120
- }
121
- });
122
-
123
- // UI Elements
124
- const blocker = document.getElementById('blocker');
125
- const instructions = document.getElementById('instructions');
126
- const startButton = document.getElementById('startButton');
127
-
128
- startButton.addEventListener('click', () => {
129
- controls.lock();
130
- });
131
-
132
- controls.addEventListener('lock', () => {
133
- blocker.style.display = 'none';
134
- });
135
-
136
- controls.addEventListener('unlock', () => {
137
- blocker.style.display = 'flex';
138
- });
139
-
140
- // Create ground
141
  const groundGeometry = new THREE.PlaneGeometry(100, 100);
142
  const groundMaterial = new THREE.MeshStandardMaterial({
143
- color: 0x3a5f0b,
144
  roughness: 0.8,
145
  metalness: 0.2
146
  });
@@ -149,40 +73,21 @@
149
  ground.receiveShadow = true;
150
  scene.add(ground);
151
 
152
- // Add some objects to navigate around
153
- const addRandomObjects = () => {
154
- const geometry = new THREE.BoxGeometry(2, 2, 2);
155
- const material = new THREE.MeshStandardMaterial({
156
- color: 0x7777ff,
157
- roughness: 0.5,
158
- metalness: 0.5
159
- });
160
-
161
- for (let i = 0; i < 10; i++) {
162
- const cube = new THREE.Mesh(geometry, material);
163
- cube.castShadow = true;
164
- cube.receiveShadow = true;
165
- cube.position.x = Math.random() * 40 - 20;
166
- cube.position.y = 1;
167
- cube.position.z = Math.random() * 40 - 20;
168
- scene.add(cube);
169
- }
170
-
171
- // Add a larger central structure
172
- const towerGeometry = new THREE.CylinderGeometry(3, 2, 8, 8);
173
- const towerMaterial = new THREE.MeshStandardMaterial({ color: 0x9933ff });
174
- const tower = new THREE.Mesh(towerGeometry, towerMaterial);
175
- tower.position.y = 4;
176
- tower.castShadow = true;
177
- scene.add(tower);
178
- };
179
-
180
- // Lighting
181
- const setupLighting = () => {
182
- // Ambient light
183
- const ambientLight = new THREE.AmbientLight(0x404040);
184
- scene.add(ambientLight);
185
-
186
  // Directional light (sun)
187
  const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
188
  directionalLight.position.set(10, 20, 10);
@@ -191,86 +96,124 @@
191
  directionalLight.shadow.mapSize.height = 2048;
192
  directionalLight.shadow.camera.near = 0.5;
193
  directionalLight.shadow.camera.far = 500;
194
- directionalLight.shadow.camera.left = -50;
195
- directionalLight.shadow.camera.right = 50;
196
- directionalLight.shadow.camera.top = 50;
197
- directionalLight.shadow.camera.bottom = -50;
198
  scene.add(directionalLight);
199
 
200
- // Hemisphere light for more natural outdoor lighting
201
  const hemisphereLight = new THREE.HemisphereLight(0x87CEEB, 0x3a5f0b, 0.3);
202
  scene.add(hemisphereLight);
203
- };
 
 
 
 
 
 
 
204
 
205
- // Simple skybox using a large sphere
206
- const addSkybox = () => {
207
- const skyGeometry = new THREE.SphereGeometry(500, 32, 32);
208
- const skyMaterial = new THREE.MeshBasicMaterial({
209
- color: 0x87CEEB,
210
- side: THREE.BackSide
 
 
 
 
 
 
 
211
  });
212
- const skybox = new THREE.Mesh(skyGeometry, skyMaterial);
213
- scene.add(skybox);
214
- };
215
 
216
- // Window resize handler
217
- const onWindowResize = () => {
218
- camera.aspect = window.innerWidth / window.innerHeight;
219
- camera.updateProjectionMatrix();
220
- renderer.setSize(window.innerWidth, window.innerHeight);
221
- };
222
- window.addEventListener('resize', onWindowResize, false);
 
223
 
224
- // Initialize scene elements
225
- setupLighting();
226
- addRandomObjects();
227
- addSkybox();
 
 
228
 
229
- // Animation loop with frame-rate independent movement
230
- const animate = () => {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
231
  requestAnimationFrame(animate);
232
 
233
- const time = performance.now();
234
- const delta = (time - prevTime) / 1000;
235
- prevTime = time;
236
 
237
- // Calculate movement speed adjusted by delta time
238
- const speed = 5.0;
239
- const moveSpeed = speed * delta;
240
 
241
- // Reset direction vector
242
- direction.z = Number(moveForward) - Number(moveBackward);
243
- direction.x = Number(moveRight) - Number(moveLeft);
244
  direction.normalize();
245
 
246
- // Apply movement to velocity
247
- if (moveForward || moveBackward) velocity.z -= direction.z * moveSpeed;
248
- if (moveLeft || moveRight) velocity.x -= direction.x * moveSpeed;
 
 
 
 
249
 
250
  // Apply gravity
251
- velocity.y -= 9.8 * delta;
252
-
253
- // Move the player
254
- controls.moveRight(-velocity.x);
255
- controls.moveForward(-velocity.z);
256
- controls.getObject().position.y += velocity.y * delta;
 
 
 
 
 
 
 
257
 
258
- // Check if player is on the ground
259
- if (controls.getObject().position.y < 1.6) {
260
  velocity.y = 0;
261
- controls.getObject().position.y = 1.6;
262
- canJump = true;
263
  } else {
264
- canJump = false;
265
  }
266
 
267
  // Apply damping (friction)
268
- velocity.x *= (1.0 - 5.0 * delta);
269
- velocity.z *= (1.0 - 5.0 * delta);
270
 
271
  renderer.render(scene, camera);
272
- };
273
 
 
274
  animate();
275
  </script>
276
  </body>
 
1
+
2
  <!DOCTYPE html>
3
  <html lang="en">
4
  <head>
5
  <meta charset="UTF-8">
6
  <meta name="viewport" content="width=device-width, initial-scale=1.0">
7
  <title>WanderLust 3D Explorer</title>
8
+ <script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r154/three.min.js"></script>
9
+ <script src="https://cdn.jsdelivr.net/npm/three@0.154.0/examples/js/controls/PointerLockControls.js"></script>
 
10
  <style>
11
  body {
12
  margin: 0;
13
  overflow: hidden;
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
14
  }
15
+ canvas {
16
+ display: block;
 
 
 
 
 
 
 
 
 
 
 
 
 
17
  }
18
  </style>
19
  </head>
20
+ <body>
 
 
 
 
 
 
 
 
 
 
 
21
  <script>
22
+ 'use strict';
23
+
24
  // Scene setup
25
  const scene = new THREE.Scene();
26
+ scene.background = new THREE.Color(0x87CEEB);
27
 
28
+ // Camera setup with human-scale eye height (1.6m)
29
  const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
30
+ camera.position.set(0, 1.6, 0);
31
 
32
+ // Renderer with antialiasing and shadows
33
  const renderer = new THREE.WebGLRenderer({ antialias: true });
34
  renderer.setSize(window.innerWidth, window.innerHeight);
35
  renderer.shadowMap.enabled = true;
36
+ renderer.shadowMap.type = THREE.PCFSoftShadowMap;
37
  document.body.appendChild(renderer.domElement);
38
 
39
+ // PointerLock controls for first-person movement
40
  const controls = new THREE.PointerLockControls(camera, document.body);
41
+
42
+ // Movement state variables
43
+ const movementState = {
44
+ forward: false,
45
+ backward: false,
46
+ left: false,
47
+ right: false,
48
+ canJump: false
49
+ };
50
 
51
+ // Physics variables
52
  const velocity = new THREE.Vector3();
53
  const direction = new THREE.Vector3();
54
+ let prevTime = 0;
55
+ const playerHeight = 1.6;
56
+ const movementSpeed = 5.0;
57
+ const gravity = 9.8;
58
+ const dampingFactor = 5.0;
59
+
60
+ // Ground plane with texture
61
+ const groundTexture = new THREE.TextureLoader().load('http://static.photos/nature/1024x576/1');
62
+ groundTexture.wrapS = groundTexture.wrapT = THREE.RepeatWrapping;
63
+ groundTexture.repeat.set(10, 10);
64
+
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
65
  const groundGeometry = new THREE.PlaneGeometry(100, 100);
66
  const groundMaterial = new THREE.MeshStandardMaterial({
67
+ map: groundTexture,
68
  roughness: 0.8,
69
  metalness: 0.2
70
  });
 
73
  ground.receiveShadow = true;
74
  scene.add(ground);
75
 
76
+ // Single obstacle (large cube)
77
+ const obstacleGeometry = new THREE.BoxGeometry(4, 4, 4);
78
+ const obstacleMaterial = new THREE.MeshStandardMaterial({
79
+ color: 0x9933ff,
80
+ roughness: 0.7,
81
+ metalness: 0.3
82
+ });
83
+ const obstacle = new THREE.Mesh(obstacleGeometry, obstacleMaterial);
84
+ obstacle.position.set(10, 2, 0);
85
+ obstacle.castShadow = true;
86
+ obstacle.receiveShadow = true;
87
+ scene.add(obstacle);
88
+
89
+ // Lighting setup
90
+ function setupLighting() {
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
91
  // Directional light (sun)
92
  const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
93
  directionalLight.position.set(10, 20, 10);
 
96
  directionalLight.shadow.mapSize.height = 2048;
97
  directionalLight.shadow.camera.near = 0.5;
98
  directionalLight.shadow.camera.far = 500;
 
 
 
 
99
  scene.add(directionalLight);
100
 
101
+ // Hemisphere light for ambient outdoor lighting
102
  const hemisphereLight = new THREE.HemisphereLight(0x87CEEB, 0x3a5f0b, 0.3);
103
  scene.add(hemisphereLight);
104
+ }
105
+
106
+ // Collision detection between player and obstacles
107
+ function checkCollisions(position) {
108
+ const playerBoundingBox = new THREE.Box3().setFromCenterAndSize(
109
+ new THREE.Vector3(position.x, position.y, position.z),
110
+ new THREE.Vector3(0.5, playerHeight, 0.5)
111
+ );
112
 
113
+ const obstacleBoundingBox = new THREE.Box3().setFromObject(obstacle);
114
+ return playerBoundingBox.intersectsBox(obstacleBoundingBox);
115
+ }
116
+
117
+ // Input event handlers
118
+ function setupEventListeners() {
119
+ document.addEventListener('keydown', (event) => {
120
+ switch (event.code) {
121
+ case 'KeyW': movementState.forward = true; break;
122
+ case 'KeyA': movementState.left = true; break;
123
+ case 'KeyS': movementState.backward = true; break;
124
+ case 'KeyD': movementState.right = true; break;
125
+ }
126
  });
 
 
 
127
 
128
+ document.addEventListener('keyup', (event) => {
129
+ switch (event.code) {
130
+ case 'KeyW': movementState.forward = false; break;
131
+ case 'KeyA': movementState.left = false; break;
132
+ case 'KeyS': movementState.backward = false; break;
133
+ case 'KeyD': movementState.right = false; break;
134
+ }
135
+ });
136
 
137
+ // Auto-enable pointer lock on click
138
+ document.addEventListener('click', () => {
139
+ if (!controls.isLocked) {
140
+ controls.lock();
141
+ }
142
+ });
143
 
144
+ // Window resize handler
145
+ window.addEventListener('resize', () => {
146
+ camera.aspect = window.innerWidth / window.innerHeight;
147
+ camera.updateProjectionMatrix();
148
+ renderer.setSize(window.innerWidth, window.innerHeight);
149
+ });
150
+ }
151
+
152
+ // Initialize the scene
153
+ function init() {
154
+ setupLighting();
155
+ setupEventListeners();
156
+
157
+ // Start with pointer lock
158
+ controls.lock();
159
+ }
160
+
161
+ // Animation loop with delta-time based physics
162
+ function animate(currentTime = 0) {
163
  requestAnimationFrame(animate);
164
 
165
+ // Calculate delta time in seconds
166
+ const deltaTime = (currentTime - prevTime) / 1000;
167
+ prevTime = currentTime;
168
 
169
+ // Skip physics on first frame or when delta is too large
170
+ if (deltaTime > 0.1) return;
 
171
 
172
+ // Movement direction based on input
173
+ direction.z = Number(movementState.forward) - Number(movementState.backward);
174
+ direction.x = Number(movementState.right) - Number(movementState.left);
175
  direction.normalize();
176
 
177
+ // Apply movement with speed scaled by delta time
178
+ if (movementState.forward || movementState.backward) {
179
+ velocity.z -= direction.z * movementSpeed * deltaTime;
180
+ }
181
+ if (movementState.left || movementState.right) {
182
+ velocity.x -= direction.x * movementSpeed * deltaTime;
183
+ }
184
 
185
  // Apply gravity
186
+ velocity.y -= gravity * deltaTime;
187
+
188
+ // Calculate new position
189
+ const oldPosition = controls.getObject().position.clone();
190
+ controls.moveRight(-velocity.x * deltaTime);
191
+ controls.moveForward(-velocity.z * deltaTime);
192
+ controls.getObject().position.y += velocity.y * deltaTime;
193
+
194
+ // Check for collisions
195
+ if (checkCollisions(controls.getObject().position)) {
196
+ controls.getObject().position.copy(oldPosition);
197
+ velocity.set(0, 0, 0);
198
+ }
199
 
200
+ // Ground collision and jumping
201
+ if (controls.getObject().position.y < playerHeight) {
202
  velocity.y = 0;
203
+ controls.getObject().position.y = playerHeight;
204
+ movementState.canJump = true;
205
  } else {
206
+ movementState.canJump = false;
207
  }
208
 
209
  // Apply damping (friction)
210
+ velocity.x *= Math.max(0, 1 - dampingFactor * deltaTime);
211
+ velocity.z *= Math.max(0, 1 - dampingFactor * deltaTime);
212
 
213
  renderer.render(scene, camera);
214
+ }
215
 
216
+ init();
217
  animate();
218
  </script>
219
  </body>