-
Notifications
You must be signed in to change notification settings - Fork 0
Expand file tree
/
Copy pathindex.html
More file actions
746 lines (625 loc) · 23.3 KB
/
index.html
File metadata and controls
746 lines (625 loc) · 23.3 KB
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
<!DOCTYPE html>
<html lang="en">
<head>
<title> three.js webgl - Character Controller </title>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, user-scalable=no, minimum-scale=1.0, maximum-scale=1.0">
<link type="text/css" rel="stylesheet" href="main.css">
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
}
#container {
position: absolute;
width: 100%;
height: 100%;
left: 0;
top: 0;
}
</style>
</head>
<body>
<div id="container"></div>
<script type="importmap">
{
"imports": {
"three": "../build/three.module.js",
"three/addons/": "./jsm/"
}
}
</script>
<script type="module">
import * as THREE from 'three';
import { ShadowMesh } from 'three/addons/objects/ShadowMesh.js';
import { GLTFLoader } from 'three/addons/loaders/GLTFLoader.js';
let SCREEN_WIDTH = window.innerWidth;
let SCREEN_HEIGHT = window.innerHeight;
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera( 55, SCREEN_WIDTH / SCREEN_HEIGHT, 1, 3000 );
const clock = new THREE.Clock();
const renderer = new THREE.WebGLRenderer( { stencil: true } );
const sunLight = new THREE.DirectionalLight( 'rgb(255,255,255)', 3 );
let useDirectionalLight = true;
let arrowHelper1, arrowHelper2, arrowHelper3;
const arrowDirection = new THREE.Vector3();
const arrowPosition1 = new THREE.Vector3();
const arrowPosition2 = new THREE.Vector3();
const arrowPosition3 = new THREE.Vector3();
let groundMesh;
let lightSphere, lightHolder;
let pyramid, pyramidShadow;
let sphere, sphereShadow;
let cube, cubeShadow;
let cylinder, cylinderShadow;
let torus, torusShadow;
const normalVector = new THREE.Vector3( 0, 1, 0 );
const planeConstant = 0.01; // this value must be slightly higher than the groundMesh's y position of 0.0
const groundPlane = new THREE.Plane( normalVector, planeConstant );
const lightPosition4D = new THREE.Vector4();
let verticalAngle = 0;
let horizontalAngle = 0;
let frameTime = 0;
const TWO_PI = Math.PI * 2;
// Character controller
let player;
let playerShadow;
const playerSize = 0.5;
const moveSpeed = 5;
const jumpForce = 10;
const gravity = 20;
let playerVelocity = new THREE.Vector3();
let onGround = false;
// Input handling
const keys = {
w: false,
a: false,
s: false,
d: false,
space: false
};
// Mouse controls
let mouseEnabled = false;
let cameraRotation = new THREE.Vector2(0, 0); // x: horizontal, y: vertical
const mouseSensitivity = 0.005;
const cameraDistance = 5;
const cameraHeight = 2;
const cameraTarget = new THREE.Vector3();
const PI_2 = Math.PI / 2;
// Object loading and management
const gltfLoader = new GLTFLoader();
const loadedObjects = {};
let websocket;
init();
initWebSocket();
function init() {
scene.background = new THREE.Color( 0x0096ff );
renderer.setPixelRatio( window.devicePixelRatio );
renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );
renderer.setAnimationLoop( animate );
document.getElementById( 'container' ).appendChild( renderer.domElement );
window.addEventListener( 'resize', onWindowResize );
window.addEventListener( 'keydown', handleKeyDown );
window.addEventListener( 'keyup', handleKeyUp );
window.addEventListener( 'mousemove', handleMouseMove );
window.addEventListener( 'mousedown', handleMouseDown );
window.addEventListener( 'mouseup', handleMouseUp );
camera.position.set( 0, 3, 10 );
scene.add( camera );
onWindowResize();
sunLight.position.set( 5, 7, - 1 );
sunLight.lookAt( scene.position );
scene.add( sunLight );
lightPosition4D.x = sunLight.position.x;
lightPosition4D.y = sunLight.position.y;
lightPosition4D.z = sunLight.position.z;
// amount of light-ray divergence. Ranging from:
// 0.001 = sunlight(min divergence) to 1.0 = pointlight(max divergence)
lightPosition4D.w = 0.001; // must be slightly greater than 0, due to 0 causing matrixInverse errors
// YELLOW ARROW HELPERS
arrowDirection.subVectors( scene.position, sunLight.position ).normalize();
arrowPosition1.copy( sunLight.position );
arrowHelper1 = new THREE.ArrowHelper( arrowDirection, arrowPosition1, 0.9, 0xffff00, 0.25, 0.08 );
scene.add( arrowHelper1 );
arrowPosition2.copy( sunLight.position ).add( new THREE.Vector3( 0, 0.2, 0 ) );
arrowHelper2 = new THREE.ArrowHelper( arrowDirection, arrowPosition2, 0.9, 0xffff00, 0.25, 0.08 );
scene.add( arrowHelper2 );
arrowPosition3.copy( sunLight.position ).add( new THREE.Vector3( 0, - 0.2, 0 ) );
arrowHelper3 = new THREE.ArrowHelper( arrowDirection, arrowPosition3, 0.9, 0xffff00, 0.25, 0.08 );
scene.add( arrowHelper3 );
// LIGHTBULB
const lightSphereGeometry = new THREE.SphereGeometry( 0.09 );
const lightSphereMaterial = new THREE.MeshBasicMaterial( { color: 'rgb(255,255,255)' } );
lightSphere = new THREE.Mesh( lightSphereGeometry, lightSphereMaterial );
scene.add( lightSphere );
lightSphere.visible = false;
const lightHolderGeometry = new THREE.CylinderGeometry( 0.05, 0.05, 0.13 );
const lightHolderMaterial = new THREE.MeshBasicMaterial( { color: 'rgb(75,75,75)' } );
lightHolder = new THREE.Mesh( lightHolderGeometry, lightHolderMaterial );
scene.add( lightHolder );
lightHolder.visible = false;
// GROUND
const groundGeometry = new THREE.BoxGeometry( 30, 0.01, 40 );
const groundMaterial = new THREE.MeshLambertMaterial( { color: 'rgb(0,130,0)' } );
groundMesh = new THREE.Mesh( groundGeometry, groundMaterial );
groundMesh.position.y = 0.0; //this value must be slightly lower than the planeConstant (0.01) parameter above
scene.add( groundMesh );
// PLAYER CHARACTER
const playerGeometry = new THREE.BoxGeometry( playerSize, playerSize, playerSize );
const playerMaterial = new THREE.MeshLambertMaterial( { color: 'rgb(0,100,255)', emissive: 0x000040 } );
player = new THREE.Mesh( playerGeometry, playerMaterial );
player.position.set( 0, playerSize/2 + 0.01, 0 ); // Position just above ground
scene.add( player );
playerShadow = new ShadowMesh( player );
scene.add( playerShadow );
// RED CUBE and CUBE's SHADOW
const cubeGeometry = new THREE.BoxGeometry( 1, 1, 1 );
const cubeMaterial = new THREE.MeshLambertMaterial( { color: 'rgb(255,0,0)', emissive: 0x200000 } );
cube = new THREE.Mesh( cubeGeometry, cubeMaterial );
cube.position.z = - 1;
scene.add( cube );
cubeShadow = new ShadowMesh( cube );
scene.add( cubeShadow );
// BLUE CYLINDER and CYLINDER's SHADOW
const cylinderGeometry = new THREE.CylinderGeometry( 0.3, 0.3, 2 );
const cylinderMaterial = new THREE.MeshPhongMaterial( { color: 'rgb(0,0,255)', emissive: 0x000020 } );
cylinder = new THREE.Mesh( cylinderGeometry, cylinderMaterial );
cylinder.position.z = - 2.5;
scene.add( cylinder );
cylinderShadow = new ShadowMesh( cylinder );
scene.add( cylinderShadow );
// MAGENTA TORUS and TORUS' SHADOW
const torusGeometry = new THREE.TorusGeometry( 1, 0.2, 10, 16, TWO_PI );
const torusMaterial = new THREE.MeshPhongMaterial( { color: 'rgb(255,0,255)', emissive: 0x200020 } );
torus = new THREE.Mesh( torusGeometry, torusMaterial );
torus.position.z = - 6;
scene.add( torus );
torusShadow = new ShadowMesh( torus );
scene.add( torusShadow );
// WHITE SPHERE and SPHERE'S SHADOW
const sphereGeometry = new THREE.SphereGeometry( 0.5, 20, 10 );
const sphereMaterial = new THREE.MeshPhongMaterial( { color: 'rgb(255,255,255)', emissive: 0x222222 } );
sphere = new THREE.Mesh( sphereGeometry, sphereMaterial );
sphere.position.set( 4, 0.5, 2 );
scene.add( sphere );
sphereShadow = new ShadowMesh( sphere );
scene.add( sphereShadow );
// YELLOW PYRAMID and PYRAMID'S SHADOW
const pyramidGeometry = new THREE.CylinderGeometry( 0, 0.5, 2, 4 );
const pyramidMaterial = new THREE.MeshPhongMaterial( { color: 'rgb(255,255,0)', emissive: 0x440000, flatShading: true, shininess: 0 } );
pyramid = new THREE.Mesh( pyramidGeometry, pyramidMaterial );
pyramid.position.set( - 4, 1, 2 );
scene.add( pyramid );
pyramidShadow = new ShadowMesh( pyramid );
scene.add( pyramidShadow );
// Create a button for switching light type that's small and in the corner
const lightButton = document.createElement('button');
lightButton.style.position = 'absolute';
lightButton.style.bottom = '10px';
lightButton.style.right = '10px';
lightButton.style.zIndex = '100';
lightButton.textContent = 'Switch Light';
lightButton.addEventListener('click', lightButtonHandler);
document.body.appendChild(lightButton);
}
function initWebSocket() {
// Change this URL to your WebSocket server
const wsUrl = 'ws://localhost:8080';
try {
websocket = new WebSocket(wsUrl);
websocket.onopen = function() {
console.log('WebSocket connected');
};
websocket.onclose = function() {
console.log('WebSocket disconnected');
// Try to reconnect after 5 seconds
setTimeout(initWebSocket, 5000);
};
websocket.onerror = function(error) {
console.error('WebSocket error:', error);
};
websocket.onmessage = function(event) {
handleWebSocketMessage(event.data);
};
} catch (error) {
console.error('WebSocket initialization error:', error);
}
}
function handleWebSocketMessage(message) {
try {
const data = JSON.parse(message);
// Check if this is an object loading message
if (data.type === 'load-object' && data.path && data.position) {
loadObject(
data.id || generateObjectId(),
data.path,
data.position,
data.rotation || { x: 0, y: 0, z: 0 },
data.scale || { x: 1, y: 1, z: 1 }
);
}
// Check if this is a request for object positions
else if (data.type === 'get-object-positions') {
sendObjectPositions(data.requestId);
}
// Handle other message types if needed
} catch (error) {
console.error('Error handling WebSocket message:', error);
}
}
function generateObjectId() {
return 'obj_' + Math.random().toString(36).substr(2, 9);
}
function loadObject(id, path, position, rotation, scale) {
console.log(`Loading object: ${path} at position:`, position);
// If we already have an object with this ID, remove it first
if (loadedObjects[id]) {
scene.remove(loadedObjects[id]);
if (loadedObjects[id].shadows && loadedObjects[id].shadows.length > 0) {
loadedObjects[id].shadows.forEach(shadow => {
scene.remove(shadow);
});
}
}
// Add more verbose debugging
console.log(`Starting GLTFLoader.load for ${path}`);
// Load the GLB/GLTF model
gltfLoader.load(
path,
// onLoad callback
function(gltf) {
console.log(`GLB/GLTF loaded successfully:`, gltf);
const object = gltf.scene;
// Calculate dimensions to adjust position
let treeHeight = 0;
// Compute the bounding box to get the tree height
const boundingBox = new THREE.Box3().setFromObject(object);
treeHeight = boundingBox.max.y - boundingBox.min.y;
console.log(`Tree height: ${treeHeight}`);
// Adjust Y position to place the trunk at ground level
// We assume the model's bottom is at the trunk base
const adjustedY = position.y + (treeHeight / 2) * scale.y;
// Set position with adjusted Y
object.position.set(position.x, adjustedY, position.z);
// Set rotation if provided
object.rotation.set(rotation.x, rotation.y, rotation.z);
// Set scale if provided
object.scale.set(scale.x, scale.y, scale.z);
// Store the original path for reference
object.userData.path = path;
// Add to scene
scene.add(object);
console.log(`Added object to scene at adjusted Y: ${adjustedY}`);
// Create shadow for the object
try {
// GLB/GLTF objects often contain multiple meshes in a hierarchy
// We need to handle each mesh separately for shadows
const meshes = [];
object.traverse(child => {
if (child.isMesh) {
meshes.push(child);
}
});
console.log(`Found ${meshes.length} meshes to create shadows for`);
// Store all shadows for this object
const shadows = [];
// Create shadows for each mesh
for (const mesh of meshes) {
const shadow = new ShadowMesh(mesh);
scene.add(shadow);
shadows.push(shadow);
}
// Store references
object.shadows = shadows;
loadedObjects[id] = object;
console.log(`Object loaded with ID: ${id} and ${shadows.length} shadows`);
} catch(error) {
console.error(`Error creating shadow:`, error);
// Still store the object even without a shadow
loadedObjects[id] = object;
}
},
// onProgress callback
function(xhr) {
console.log((xhr.loaded / xhr.total * 100) + '% loaded');
},
// onError callback
function(error) {
console.error('Error loading GLB/GLTF:', error);
}
);
}
function handleMouseDown(event) {
if (event.button === 0) { // Left mouse button
mouseEnabled = true;
document.getElementById('container').style.cursor = 'grabbing';
}
}
function handleMouseUp(event) {
if (event.button === 0) { // Left mouse button
mouseEnabled = false;
document.getElementById('container').style.cursor = 'grab';
}
}
function handleMouseMove(event) {
if (mouseEnabled) {
cameraRotation.x -= event.movementX * mouseSensitivity;
cameraRotation.y += event.movementY * mouseSensitivity;
// Limit vertical rotation to avoid flipping
cameraRotation.y = Math.max(-PI_2 + 0.1, Math.min(PI_2 - 0.1, cameraRotation.y));
}
}
function handleKeyDown(event) {
switch(event.code) {
case 'KeyW': keys.w = true; break;
case 'KeyA': keys.a = true; break;
case 'KeyS': keys.s = true; break;
case 'KeyD': keys.d = true; break;
case 'Space':
keys.space = true;
if (onGround) {
playerVelocity.y = jumpForce;
onGround = false;
}
break;
}
}
function handleKeyUp(event) {
switch(event.code) {
case 'KeyW': keys.w = false; break;
case 'KeyA': keys.a = false; break;
case 'KeyS': keys.s = false; break;
case 'KeyD': keys.d = false; break;
case 'Space': keys.space = false; break;
}
}
function updatePlayer(deltaTime) {
// Apply gravity
playerVelocity.y -= gravity * deltaTime;
// Handle movement
const moveDirection = new THREE.Vector3(0, 0, 0);
// Get movement direction relative to camera
if (keys.w || keys.s || keys.a || keys.d) {
// Forward/backward movement
if (keys.w) {
moveDirection.x += Math.sin(cameraRotation.x);
moveDirection.z += Math.cos(cameraRotation.x);
}
if (keys.s) {
moveDirection.x -= Math.sin(cameraRotation.x);
moveDirection.z -= Math.cos(cameraRotation.x);
}
// Left/right movement (perpendicular to forward)
if (keys.a) {
moveDirection.x += Math.sin(cameraRotation.x + Math.PI/2);
moveDirection.z += Math.cos(cameraRotation.x + Math.PI/2);
}
if (keys.d) {
moveDirection.x -= Math.sin(cameraRotation.x + Math.PI/2);
moveDirection.z -= Math.cos(cameraRotation.x + Math.PI/2);
}
}
if (moveDirection.length() > 0) {
moveDirection.normalize();
moveDirection.multiplyScalar(moveSpeed * deltaTime);
player.position.x += moveDirection.x;
player.position.z += moveDirection.z;
// Rotate player to face movement direction
if (moveDirection.length() > 0) {
player.rotation.y = Math.atan2(moveDirection.x, moveDirection.z);
}
}
// Apply vertical velocity (gravity and jumping)
player.position.y += playerVelocity.y * deltaTime;
// Check ground collision
if (player.position.y < (playerSize/2 + 0.01)) {
player.position.y = playerSize/2 + 0.01;
playerVelocity.y = 0;
onGround = true;
}
// Simple bounds checking to keep the player on the ground plane
const groundWidth = 15;
const groundDepth = 20;
player.position.x = Math.max(-groundWidth, Math.min(groundWidth, player.position.x));
player.position.z = Math.max(-groundDepth, Math.min(groundDepth, player.position.z));
// Update camera position based on player and rotation
updateCamera();
}
function updateCamera() {
// Calculate camera position based on player position and camera rotation
const horizontalDistance = cameraDistance * Math.cos(cameraRotation.y);
// Set the camera position
camera.position.x = player.position.x - horizontalDistance * Math.sin(cameraRotation.x);
camera.position.y = player.position.y + cameraHeight + cameraDistance * Math.sin(cameraRotation.y);
camera.position.z = player.position.z - horizontalDistance * Math.cos(cameraRotation.x);
// Set camera target to look at player
cameraTarget.copy(player.position);
cameraTarget.y += playerSize/2; // Look at the center of the player
camera.lookAt(cameraTarget);
}
function animate() {
frameTime = clock.getDelta();
// Update player
updatePlayer(frameTime);
cube.rotation.x += 1.0 * frameTime;
cube.rotation.y += 1.0 * frameTime;
cylinder.rotation.y += 1.0 * frameTime;
cylinder.rotation.z -= 1.0 * frameTime;
torus.rotation.x -= 1.0 * frameTime;
torus.rotation.y -= 1.0 * frameTime;
pyramid.rotation.y += 0.5 * frameTime;
horizontalAngle += 0.5 * frameTime;
if ( horizontalAngle > TWO_PI )
horizontalAngle -= TWO_PI;
cube.position.x = Math.sin( horizontalAngle ) * 4;
cylinder.position.x = Math.sin( horizontalAngle ) * - 4;
torus.position.x = Math.cos( horizontalAngle ) * 4;
verticalAngle += 1.5 * frameTime;
if ( verticalAngle > TWO_PI )
verticalAngle -= TWO_PI;
cube.position.y = Math.sin( verticalAngle ) * 2 + 2.9;
cylinder.position.y = Math.sin( verticalAngle ) * 2 + 3.1;
torus.position.y = Math.cos( verticalAngle ) * 2 + 3.3;
// update the ShadowMeshes to follow their shadow-casting objects
playerShadow.update( groundPlane, lightPosition4D );
cubeShadow.update( groundPlane, lightPosition4D );
cylinderShadow.update( groundPlane, lightPosition4D );
torusShadow.update( groundPlane, lightPosition4D );
sphereShadow.update( groundPlane, lightPosition4D );
pyramidShadow.update( groundPlane, lightPosition4D );
// Update dynamically loaded object shadows
Object.values(loadedObjects).forEach(obj => {
if (obj.shadows && obj.shadows.length > 0) {
// Update each shadow for this object
obj.shadows.forEach(shadow => {
shadow.update(groundPlane, lightPosition4D);
});
}
});
renderer.render( scene, camera );
}
function onWindowResize() {
SCREEN_WIDTH = window.innerWidth;
SCREEN_HEIGHT = window.innerHeight;
renderer.setSize( SCREEN_WIDTH, SCREEN_HEIGHT );
camera.aspect = SCREEN_WIDTH / SCREEN_HEIGHT;
camera.updateProjectionMatrix();
}
function lightButtonHandler() {
useDirectionalLight = !useDirectionalLight;
if (useDirectionalLight) {
scene.background.setHex(0x0096ff);
groundMesh.material.color.setHex(0x008200);
sunLight.position.set(5, 7, -1);
sunLight.lookAt(scene.position);
lightPosition4D.x = sunLight.position.x;
lightPosition4D.y = sunLight.position.y;
lightPosition4D.z = sunLight.position.z;
lightPosition4D.w = 0.001; // more of a directional Light value
arrowHelper1.visible = true;
arrowHelper2.visible = true;
arrowHelper3.visible = true;
lightSphere.visible = false;
lightHolder.visible = false;
} else {
scene.background.setHex(0x000000);
groundMesh.material.color.setHex(0x969696);
sunLight.position.set(0, 6, -2);
sunLight.lookAt(scene.position);
lightSphere.position.copy(sunLight.position);
lightHolder.position.copy(lightSphere.position);
lightHolder.position.y += 0.12;
lightPosition4D.x = sunLight.position.x;
lightPosition4D.y = sunLight.position.y;
lightPosition4D.z = sunLight.position.z;
lightPosition4D.w = 0.9; // more of a point Light value
arrowHelper1.visible = false;
arrowHelper2.visible = false;
arrowHelper3.visible = false;
lightSphere.visible = true;
lightHolder.visible = true;
}
}
// Function to collect and send object positions back to the server
function sendObjectPositions(requestId) {
// Create an object to hold all the position data
const positionsData = {
type: 'object-positions',
requestId: requestId,
timestamp: Date.now(),
player: {
id: 'player',
position: {
x: player.position.x,
y: player.position.y,
z: player.position.z
},
rotation: {
y: player.rotation.y
}
},
objects: {}
};
// Add position data for all loaded objects
Object.entries(loadedObjects).forEach(([id, object]) => {
positionsData.objects[id] = {
position: {
x: object.position.x,
y: object.position.y,
z: object.position.z
},
rotation: {
x: object.rotation.x,
y: object.rotation.y,
z: object.rotation.z
},
scale: {
x: object.scale.x,
y: object.scale.y,
z: object.scale.z
},
// Include bounding box information
boundingBox: getBoundingBoxForObject(object)
};
});
// Add built-in scene objects
const sceneObjects = {
'cube': cube,
'cylinder': cylinder,
'torus': torus,
'sphere': sphere,
'pyramid': pyramid
};
Object.entries(sceneObjects).forEach(([id, object]) => {
positionsData.objects[id] = {
position: {
x: object.position.x,
y: object.position.y,
z: object.position.z
},
rotation: {
x: object.rotation.x,
y: object.rotation.y,
z: object.rotation.z
},
scale: {
x: object.scale.x,
y: object.scale.y,
z: object.scale.z
},
boundingBox: getBoundingBoxForObject(object)
};
});
// Send the data back to the WebSocket server
if (websocket && websocket.readyState === WebSocket.OPEN) {
websocket.send(JSON.stringify(positionsData));
console.log('Sent object positions to server', positionsData);
} else {
console.warn('WebSocket not connected, cannot send object positions');
}
}
// Helper function to get object bounding box
function getBoundingBoxForObject(object) {
const boundingBox = new THREE.Box3().setFromObject(object);
return {
min: {
x: boundingBox.min.x,
y: boundingBox.min.y,
z: boundingBox.min.z
},
max: {
x: boundingBox.max.x,
y: boundingBox.max.y,
z: boundingBox.max.z
},
size: {
x: boundingBox.max.x - boundingBox.min.x,
y: boundingBox.max.y - boundingBox.min.y,
z: boundingBox.max.z - boundingBox.min.z
}
};
}
</script>
</body>
</html>