Spaces:
Running
Running
File size: 4,635 Bytes
b29710c |
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 |
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
import {
SCENE_BACKGROUND,
GROUND_SIZE,
GRID_CELL_SIZE,
} from "../config/gameConfig.js";
export class SceneSetup {
constructor() {
// Basic scene setup
this.scene = new THREE.Scene();
this.scene.background = new THREE.Color(SCENE_BACKGROUND);
// Camera
this.camera = new THREE.PerspectiveCamera(
60,
window.innerWidth / window.innerHeight,
0.1,
2000
);
this.camera.position.set(20, 22, 24);
// Renderer
this.renderer = new THREE.WebGLRenderer({ antialias: true });
this.renderer.setSize(window.innerWidth, window.innerHeight);
this.renderer.shadowMap.enabled = true;
document.body.appendChild(this.renderer.domElement);
// Controls
this.controls = new OrbitControls(this.camera, this.renderer.domElement);
this.controls.target.set(0, 0, 0);
this.controls.enableDamping = true;
// Camera movement config
this.CAMERA_MOVE_SPEED = 18; // units per second in world-space
// Bounds relative to ground size, keep a small margin inside edges
const half = GROUND_SIZE / 2;
this.CAMERA_MIN_X = -half + 2;
this.CAMERA_MAX_X = half - 2;
this.CAMERA_MIN_Z = -half + 2;
this.CAMERA_MAX_Z = half - 2;
// Setup lighting
this.setupLighting();
// Setup ground
this.ground = this.setupGround();
// Setup grid
this.grid = this.setupGrid();
// Handle window resize
window.addEventListener("resize", () => this.onWindowResize());
}
setupLighting() {
const hemi = new THREE.HemisphereLight(0xffffff, 0x404040, 0.6);
this.scene.add(hemi);
const dirLight = new THREE.DirectionalLight(0xffffff, 1.0);
dirLight.position.set(8, 20, 8);
dirLight.castShadow = true;
dirLight.shadow.mapSize.set(1024, 1024);
this.scene.add(dirLight);
}
setupGround() {
const groundGeo = new THREE.PlaneGeometry(GROUND_SIZE, GROUND_SIZE);
const groundMat = new THREE.MeshStandardMaterial({ color: 0x1d6e2f });
const ground = new THREE.Mesh(groundGeo, groundMat);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
ground.name = "ground";
this.scene.add(ground);
return ground;
}
setupGrid() {
const divisions = Math.floor(GROUND_SIZE / GRID_CELL_SIZE);
const grid = new THREE.GridHelper(
GROUND_SIZE,
divisions,
0x8ab4f8,
0x3a97ff
);
grid.position.y = 0.02; // avoid z-fighting with ground
grid.material.transparent = true;
grid.material.opacity = 0.35;
grid.renderOrder = 0;
this.scene.add(grid);
return grid;
}
clampToBounds(vec3) {
vec3.x = Math.min(this.CAMERA_MAX_X, Math.max(this.CAMERA_MIN_X, vec3.x));
vec3.z = Math.min(this.CAMERA_MAX_Z, Math.max(this.CAMERA_MIN_Z, vec3.z));
return vec3;
}
// Move camera and controls target horizontally in world space while keeping height
moveCamera(direction, deltaTime) {
// direction: {x: -1|0|1, z: -1|0|1}
if (!direction || (direction.x === 0 && direction.z === 0)) return;
// Compute normalized planar direction
const move = new THREE.Vector3(direction.x, 0, direction.z);
if (move.lengthSq() === 0) return;
move.normalize().multiplyScalar(this.CAMERA_MOVE_SPEED * deltaTime);
// Maintain current height
const currentY = this.camera.position.y;
// Move both camera and target so orbit feel is preserved
const newCamPos = this.camera.position.clone().add(move);
const newTarget = this.controls.target.clone().add(move);
// Clamp within bounds
this.clampToBounds(newCamPos);
this.clampToBounds(newTarget);
// Apply positions (preserve camera height)
newCamPos.y = currentY;
this.camera.position.copy(newCamPos);
this.controls.target.copy(newTarget);
// Let OrbitControls smoothing handle interpolation
}
onWindowResize() {
this.camera.aspect = window.innerWidth / window.innerHeight;
this.camera.updateProjectionMatrix();
this.renderer.setSize(window.innerWidth, window.innerHeight);
}
render(gameState) {
// Optionally pass gameState so we can update transient visuals like electric arcs
this.controls.update();
// Per-frame visual updates for towers (electric arcs fade/cleanup)
if (gameState && Array.isArray(gameState.towers)) {
const now = performance.now();
for (const t of gameState.towers) {
if (t?.updateElectricArcs) {
t.updateElectricArcs(now);
}
}
}
this.renderer.render(this.scene, this.camera);
}
}
|