Skip to content

Instantly share code, notes, and snippets.

@RageshAntonyHM
Last active April 11, 2025 11:13
Show Gist options
  • Save RageshAntonyHM/20445d78bd2611ab0bf9e8c613e2d9b9 to your computer and use it in GitHub Desktop.
Save RageshAntonyHM/20445d78bd2611ab0bf9e8c613e2d9b9 to your computer and use it in GitHub Desktop.
high school and Lake using THREE.js
1) using THREE.js create a High School cafeteria. All models should be complex and perfect and should be created using THREE.js itself without using external models.
2) using THREE.js create a forest scence with a lake and a cave nearby. All models should be complex and perfect and should be created using THREE.js itself without using external models.
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>High School Cafeteria - THREE.js</title>
<style>
body {
margin: 0;
overflow: hidden;
font-family: Arial, sans-serif;
}
canvas {
display: block;
}
#info {
position: absolute;
top: 10px;
left: 10px;
background-color: rgba(0, 0, 0, 0.5);
color: white;
padding: 10px;
border-radius: 5px;
font-size: 14px;
pointer-events: none;
}
</style>
</head>
<body>
<div id="info">
High School Cafeteria<br>
Click and drag to rotate | Right-click and drag to pan | Scroll to zoom
</div>
<script type="importmap">
{
"imports": {
"three": "https://unpkg.com/[email protected]/build/three.module.js",
"three/addons/": "https://unpkg.com/[email protected]/examples/jsm/"
}
}
</script>
<!-- Main script file -->
<script type="module" src="high_cafe.js"></script>
</body>
</html>
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
let scene, camera, renderer, controls;
let floor, walls = [], ceiling;
let tables = [];
let servingCounter;
const cafeteriaWidth = 40;
const cafeteriaLength = 60;
const cafeteriaHeight = 10;
function init() {
// --- Basic Setup ---
scene = new THREE.Scene();
scene.background = new THREE.Color(0xaaaaaa); // Neutral background
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, cafeteriaHeight * 0.8, cafeteriaLength * 0.6); // Position camera inside
renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true; // Enable shadows
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// --- Orbit Controls ---
controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true; // Smooths movement
controls.dampingFactor = 0.05;
controls.target.set(0, 2, 0); // Look towards the center floor area
controls.maxPolarAngle = Math.PI / 2 - 0.05; // Prevent looking straight down or below floor
controls.minDistance = 5;
controls.maxDistance = 50;
// --- Lighting ---
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // Soft overall light
scene.add(ambientLight);
const directionalLight = new THREE.DirectionalLight(0xffffff, 0.8);
directionalLight.position.set(-20, 30, 20);
directionalLight.castShadow = true;
directionalLight.shadow.mapSize.width = 2048;
directionalLight.shadow.mapSize.height = 2048;
directionalLight.shadow.camera.near = 0.5;
directionalLight.shadow.camera.far = 100;
directionalLight.shadow.camera.left = -cafeteriaLength / 2 - 5;
directionalLight.shadow.camera.right = cafeteriaLength / 2 + 5;
directionalLight.shadow.camera.top = cafeteriaWidth / 2 + 5;
directionalLight.shadow.camera.bottom = -cafeteriaWidth / 2 - 5;
scene.add(directionalLight);
// const shadowHelper = new THREE.CameraHelper(directionalLight.shadow.camera);
// scene.add(shadowHelper); // Uncomment to debug shadow camera
// --- Create Scene Elements ---
createRoom();
createTables();
createServingCounter();
createTrashCans();
createCeilingLights();
createWindowsAndDoors(); // Basic representations
// --- Event Listeners ---
window.addEventListener('resize', onWindowResize, false);
// --- Start Animation ---
animate();
}
// --- Geometry Creation Functions ---
function createRoom() {
// Floor
const floorGeometry = new THREE.PlaneGeometry(cafeteriaWidth, cafeteriaLength);
const floorMaterial = new THREE.MeshStandardMaterial({
color: 0xcccccc, // Light grey linoleum/tile color
roughness: 0.8,
metalness: 0.1,
side: THREE.DoubleSide // Visible from below if needed
});
floor = new THREE.Mesh(floorGeometry, floorMaterial);
floor.rotation.x = -Math.PI / 2; // Rotate flat
floor.position.y = 0;
floor.receiveShadow = true; // Floor receives shadows
scene.add(floor);
// Walls
const wallMaterial = new THREE.MeshStandardMaterial({
color: 0xddeeff, // Light blue/off-white wall color
roughness: 0.9,
metalness: 0.0
});
const wallThickness = 0.5;
// Wall Back (-z)
const wallBackGeo = new THREE.BoxGeometry(cafeteriaWidth, cafeteriaHeight, wallThickness);
const wallBack = new THREE.Mesh(wallBackGeo, wallMaterial);
wallBack.position.set(0, cafeteriaHeight / 2, -cafeteriaLength / 2);
wallBack.castShadow = true;
wallBack.receiveShadow = true;
scene.add(wallBack);
walls.push(wallBack);
// Wall Front (+z) - Often where entrance/serving might be, might be partial
const wallFrontGeo = new THREE.BoxGeometry(cafeteriaWidth, cafeteriaHeight, wallThickness);
const wallFront = new THREE.Mesh(wallFrontGeo, wallMaterial);
wallFront.position.set(0, cafeteriaHeight / 2, cafeteriaLength / 2);
wallFront.castShadow = true;
wallFront.receiveShadow = true;
scene.add(wallFront);
walls.push(wallFront);
// Wall Left (-x)
const wallLeftGeo = new THREE.BoxGeometry(wallThickness, cafeteriaHeight, cafeteriaLength);
const wallLeft = new THREE.Mesh(wallLeftGeo, wallMaterial);
wallLeft.position.set(-cafeteriaWidth / 2, cafeteriaHeight / 2, 0);
wallLeft.castShadow = true;
wallLeft.receiveShadow = true;
scene.add(wallLeft);
walls.push(wallLeft);
// Wall Right (+x)
const wallRightGeo = new THREE.BoxGeometry(wallThickness, cafeteriaHeight, cafeteriaLength);
const wallRight = new THREE.Mesh(wallLeftGeo, wallMaterial); // Re-use geometry
wallRight.position.set(cafeteriaWidth / 2, cafeteriaHeight / 2, 0);
wallRight.castShadow = true;
wallRight.receiveShadow = true;
scene.add(wallRight);
walls.push(wallRight);
// Ceiling
const ceilingGeometry = new THREE.PlaneGeometry(cafeteriaWidth, cafeteriaLength);
const ceilingMaterial = new THREE.MeshStandardMaterial({
color: 0xf0f0f0,
roughness: 0.95,
metalness: 0.0,
side: THREE.DoubleSide
});
ceiling = new THREE.Mesh(ceilingGeometry, ceilingMaterial);
ceiling.rotation.x = Math.PI / 2;
ceiling.position.y = cafeteriaHeight;
ceiling.receiveShadow = true; // Ceiling can receive shadows from lights below it if any
scene.add(ceiling);
}
function createCafeteriaTable(length = 8, width = 1.5, height = 2.5) {
const tableGroup = new THREE.Group();
// Materials
const tableTopMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa, roughness: 0.6, metalness: 0.2 }); // Laminate look
const legMaterial = new THREE.MeshStandardMaterial({ color: 0x555555, roughness: 0.4, metalness: 0.8 }); // Metal legs
const seatMaterial = new THREE.MeshStandardMaterial({ color: 0x4466aa, roughness: 0.7, metalness: 0.1 }); // Plastic seat color
// Table Top
const topGeo = new THREE.BoxGeometry(width, 0.2, length);
const topMesh = new THREE.Mesh(topGeo, tableTopMaterial);
topMesh.position.y = height;
topMesh.castShadow = true;
tableGroup.add(topMesh);
// Legs (Simple A-frame style legs common on these tables)
const legRadius = 0.1;
const legHeight = height - 0.1; // Slightly shorter than table height
const legSpread = width / 2 - legRadius * 2;
const legInset = length / 2 - 0.5; // Inset from ends
const legPositions = [
{ x: -legSpread, z: -legInset },
{ x: legSpread, z: -legInset },
{ x: -legSpread, z: legInset },
{ x: legSpread, z: legInset }
];
legPositions.forEach(pos => {
const legGeo = new THREE.CylinderGeometry(legRadius, legRadius, legHeight, 8);
const legMesh = new THREE.Mesh(legGeo, legMaterial);
legMesh.position.set(pos.x, legHeight / 2, pos.z);
legMesh.castShadow = true;
tableGroup.add(legMesh);
});
// Benches (Attached)
const benchHeight = height * 0.6;
const benchWidth = 0.4;
const benchThickness = 0.15;
const benchOffset = width / 2 + benchWidth / 2 + 0.1; // Distance from table center
const benchGeo = new THREE.BoxGeometry(benchWidth, benchThickness, length * 0.9); // Slightly shorter than table
// Bench 1
const benchMesh1 = new THREE.Mesh(benchGeo, seatMaterial);
benchMesh1.position.set(-benchOffset, benchHeight, 0);
benchMesh1.castShadow = true;
tableGroup.add(benchMesh1);
// Bench 2
const benchMesh2 = new THREE.Mesh(benchGeo, seatMaterial);
benchMesh2.position.set(benchOffset, benchHeight, 0);
benchMesh2.castShadow = true;
tableGroup.add(benchMesh2);
// Bench Supports (Simple connection to legs)
const supportGeo = new THREE.BoxGeometry(0.1, 0.1, 0.5);
const supportPositions = [
{ x: -benchOffset * 0.7, y: benchHeight - 0.1, z: -legInset },
{ x: -benchOffset * 0.7, y: benchHeight - 0.1, z: legInset },
{ x: benchOffset * 0.7, y: benchHeight - 0.1, z: -legInset },
{ x: benchOffset * 0.7, y: benchHeight - 0.1, z: legInset }
];
supportPositions.forEach(pos => {
const support = new THREE.Mesh(supportGeo, legMaterial);
support.position.set(pos.x, pos.y, pos.z);
support.rotation.z = Math.PI / 5 * (pos.x > 0 ? -1 : 1); // Angled slightly
support.castShadow = true;
tableGroup.add(support);
});
return tableGroup;
}
function createTables() {
const tableLength = 8;
const tableSpacingZ = tableLength + 4; // Space between tables longitudinally
const tableSpacingX = 5; // Space between table rows
const numRows = 3;
const numTablesPerRow = 4;
const startX = - (numRows - 1) * tableSpacingX / 2;
const startZ = - (numTablesPerRow - 1) * tableSpacingZ / 2 + 5; // Shift tables back a bit
for (let i = 0; i < numRows; i++) {
for (let j = 0; j < numTablesPerRow; j++) {
const table = createCafeteriaTable(tableLength);
const posX = startX + i * tableSpacingX;
const posZ = startZ + j * tableSpacingZ;
table.position.set(posX, 0, posZ); // Position the group
tables.push(table);
scene.add(table);
}
}
}
function createServingCounter() {
servingCounter = new THREE.Group();
const counterLength = cafeteriaWidth * 0.6;
const counterWidth = 1.5;
const counterHeight = 3.0;
const counterThickness = 0.2;
// Materials
const counterMaterial = new THREE.MeshStandardMaterial({ color: 0x999999, roughness: 0.3, metalness: 0.9 }); // Stainless steel
const traySlideMaterial = new THREE.MeshStandardMaterial({ color: 0x777777, roughness: 0.2, metalness: 1.0 });
const sneezeGuardMaterial = new THREE.MeshStandardMaterial({ color: 0xffffff, roughness: 0.1, metalness: 0.1, transparent: true, opacity: 0.4 });
const foodPanMaterial = new THREE.MeshStandardMaterial({ color: 0xaaaaaa, roughness: 0.5, metalness: 0.5 });
const warmerMaterial = new THREE.MeshStandardMaterial({color: 0x333333, roughness: 0.6, metalness: 0.4 });
// Main Counter Body
const counterBodyGeo = new THREE.BoxGeometry(counterLength, counterHeight, counterWidth);
const counterBody = new THREE.Mesh(counterBodyGeo, counterMaterial);
counterBody.position.y = counterHeight / 2;
counterBody.castShadow = true;
counterBody.receiveShadow = true;
servingCounter.add(counterBody);
// Tray Slide (Series of cylinders)
const slideLength = counterLength;
const slideWidth = 0.4;
const slideHeight = counterHeight * 0.9;
const railRadius = 0.05;
const railSpacing = railRadius * 3;
const numRails = Math.floor(slideWidth / railSpacing);
const traySlideGroup = new THREE.Group();
const railGeo = new THREE.CylinderGeometry(railRadius, railRadius, slideLength, 8);
for (let i = 0; i < numRails; i++) {
const rail = new THREE.Mesh(railGeo, traySlideMaterial);
const railZ = -slideWidth/2 + railSpacing/2 + i * railSpacing;
rail.rotation.x = Math.PI / 2; // Lay flat along counter length
rail.position.set(0, 0, railZ); // Position relative to group center
rail.castShadow = true;
traySlideGroup.add(rail);
}
traySlideGroup.position.set(0, slideHeight, -counterWidth / 2 - slideWidth / 2 - 0.1); // Position in front of counter
servingCounter.add(traySlideGroup);
// Sneeze Guard (Simplified)
const guardHeight = 1.0;
const guardWidth = 0.1;
const guardGeo = new THREE.BoxGeometry(counterLength * 0.9, guardHeight, guardWidth);
const sneezeGuard = new THREE.Mesh(guardGeo, sneezeGuardMaterial);
sneezeGuard.position.set(0, counterHeight + guardHeight / 2 - 0.2, counterWidth * 0.1); // Position above and slightly back on counter
sneezeGuard.castShadow = false; // Glass doesn't cast strong shadows typically
servingCounter.add(sneezeGuard);
// Vertical Supports for Sneeze Guard
const supportHeight = guardHeight;
const supportRadius = 0.05;
const supportGeo = new THREE.CylinderGeometry(supportRadius, supportRadius, supportHeight, 8);
const supportPositions = [-counterLength * 0.4, 0, counterLength * 0.4];
supportPositions.forEach(xPos => {
const support = new THREE.Mesh(supportGeo, counterMaterial);
support.position.set(xPos, counterHeight + supportHeight / 2 - 0.2, counterWidth * 0.1);
support.castShadow = true;
servingCounter.add(support);
});
// Food Warmers / Display Area (Abstract Boxes)
const warmerSectionLength = counterLength * 0.7;
const warmerDepth = counterWidth * 0.6;
const warmerHeight = 0.5;
const warmerGeo = new THREE.BoxGeometry(warmerSectionLength, warmerHeight, warmerDepth);
const warmerMesh = new THREE.Mesh(warmerGeo, warmerMaterial);
warmerMesh.position.set(0, counterHeight - warmerHeight / 2 - counterThickness/2, -counterWidth/2 + warmerDepth/2); // Recessed slightly
warmerMesh.castShadow = true;
warmerMesh.receiveShadow = true;
servingCounter.add(warmerMesh);
// Add some "food pans" inside the warmer area
const panLength = 1.0;
const panWidth = 0.6;
const panDepth = 0.15;
const panGeo = new THREE.BoxGeometry(panLength, panDepth, panWidth);
const panColors = [0xffddaa, 0xaaffaa, 0xffaaaa, 0xffffaa]; // Represent different foods
const numPans = 4;
for (let i = 0; i < numPans; i++) {
const panMaterial = new THREE.MeshStandardMaterial({ color: panColors[i % panColors.length], roughness: 0.6, metalness: 0.1 });
const panMesh = new THREE.Mesh(panGeo, panMaterial);
const panX = -warmerSectionLength/2 + panLength*1.5/2 + i * (panLength * 1.1); // Spaced out
panMesh.position.set(panX, counterHeight - panDepth/2 - counterThickness/2, -counterWidth/2 + warmerDepth/2);
panMesh.castShadow = true;
servingCounter.add(panMesh);
}
// Position the whole counter
servingCounter.position.set(0, 0, -cafeteriaLength / 2 + counterWidth / 2 + 1.5); // Against back wall, spaced out a bit
scene.add(servingCounter);
}
function createTrashCans() {
const canRadius = 0.7;
const canHeight = 2.5;
const canGeo = new THREE.CylinderGeometry(canRadius, canRadius * 0.9, canHeight, 32);
const canMaterial = new THREE.MeshStandardMaterial({ color: 0x666666, roughness: 0.5, metalness: 0.5 });
const canPositions = [
{ x: cafeteriaWidth / 2 - 3, z: cafeteriaLength / 2 - 5 },
{ x: -cafeteriaWidth / 2 + 3, z: cafeteriaLength / 2 - 5 },
{ x: cafeteriaWidth / 2 - 3, z: -cafeteriaLength / 2 + 15 },
];
canPositions.forEach(pos => {
const can = new THREE.Mesh(canGeo, canMaterial);
can.position.set(pos.x, canHeight / 2, pos.z);
can.castShadow = true;
scene.add(can);
// Simple lid lip (optional detail)
const lipGeo = new THREE.CylinderGeometry(canRadius * 1.05, canRadius * 1.0, 0.2, 32);
const lip = new THREE.Mesh(lipGeo, canMaterial);
lip.position.set(pos.x, canHeight - 0.1, pos.z);
lip.castShadow = true;
scene.add(lip);
});
}
function createCeilingLights() {
const lightFixtureLength = 6;
const lightFixtureWidth = 0.5;
const lightFixtureHeight = 0.15;
const lightTubeRadius = 0.05;
const fixtureGeo = new THREE.BoxGeometry(lightFixtureWidth, lightFixtureHeight, lightFixtureLength);
const fixtureMaterial = new THREE.MeshStandardMaterial({ color: 0xdddddd, roughness: 0.8, metalness: 0.2 });
// Basic emissive material for the "light" part
const lightMaterial = new THREE.MeshBasicMaterial({ color: 0xffffee }); // Basic, doesn't cast light
// const lightMaterial = new THREE.MeshStandardMaterial({ color: 0xffffee, emissive: 0xffffee, emissiveIntensity: 0.8 }); // Looks like light source
const tubeGeo = new THREE.CylinderGeometry(lightTubeRadius, lightTubeRadius, lightFixtureLength * 0.9, 8);
const numLightRows = 4;
const numLightsPerRow = 5;
const lightSpacingX = cafeteriaWidth / (numLightRows + 1);
const lightSpacingZ = cafeteriaLength / (numLightsPerRow + 1);
for (let i = 0; i < numLightRows; i++) {
for (let j = 0; j < numLightsPerRow; j++) {
const fixture = new THREE.Mesh(fixtureGeo, fixtureMaterial);
const lightX = -cafeteriaWidth / 2 + lightSpacingX * (i + 1);
const lightZ = -cafeteriaLength / 2 + lightSpacingZ * (j + 1);
fixture.position.set(lightX, cafeteriaHeight - lightFixtureHeight / 2 - 0.1, lightZ); // Attach below ceiling
fixture.castShadow = true; // Fixture itself casts a shadow
scene.add(fixture);
// Add two "tubes" per fixture
const tube1 = new THREE.Mesh(tubeGeo, lightMaterial);
tube1.rotation.x = Math.PI / 2;
tube1.position.set(lightX - lightFixtureWidth/4, cafeteriaHeight - lightFixtureHeight/2 - 0.1, lightZ);
scene.add(tube1);
const tube2 = new THREE.Mesh(tubeGeo, lightMaterial);
tube2.rotation.x = Math.PI / 2;
tube2.position.set(lightX + lightFixtureWidth/4, cafeteriaHeight - lightFixtureHeight/2 - 0.1, lightZ);
scene.add(tube2);
}
}
}
function createWindowsAndDoors() {
// Simple representations using planes or thin boxes on walls
// Windows (on the right wall +x)
const windowWidth = 4;
const windowHeight = 5;
const windowDepth = 0.1; // Slightly recessed look
const windowY = cafeteriaHeight / 2 + 1;
const windowMaterial = new THREE.MeshStandardMaterial({
color: 0x87CEEB, // Light blue for glass
roughness: 0.2,
metalness: 0.1,
transparent: true,
opacity: 0.6
});
const frameMaterial = new THREE.MeshStandardMaterial({ color: 0x666666, roughness: 0.7, metalness: 0.3 });
const frameThickness = 0.2;
const windowPositionsZ = [-cafeteriaLength * 0.3, 0, cafeteriaLength * 0.3];
windowPositionsZ.forEach(zPos => {
// Glass Pane
const windowGeo = new THREE.BoxGeometry(windowDepth, windowHeight, windowWidth);
const windowMesh = new THREE.Mesh(windowGeo, windowMaterial);
windowMesh.position.set(cafeteriaWidth / 2 - 0.25, windowY, zPos); // Slightly inside wall
windowMesh.receiveShadow = false; // Glass shouldn't receive strong shadows
scene.add(windowMesh);
// Frame (simple box outline) - More complex frames need more boxes
const frameTopGeo = new THREE.BoxGeometry(frameThickness*2, frameThickness, windowWidth + frameThickness*2);
const frameBottomGeo = new THREE.BoxGeometry(frameThickness*2, frameThickness, windowWidth + frameThickness*2);
const frameSideGeo = new THREE.BoxGeometry(frameThickness*2, windowHeight + frameThickness*2 , frameThickness);
const frameTop = new THREE.Mesh(frameTopGeo, frameMaterial);
frameTop.position.set(cafeteriaWidth/2 - 0.25, windowY + windowHeight/2 + frameThickness/2, zPos);
frameTop.castShadow = true; scene.add(frameTop);
const frameBottom = new THREE.Mesh(frameBottomGeo, frameMaterial);
frameBottom.position.set(cafeteriaWidth/2 - 0.25, windowY - windowHeight/2 - frameThickness/2, zPos);
frameBottom.castShadow = true; scene.add(frameBottom);
const frameLeft = new THREE.Mesh(frameSideGeo, frameMaterial);
frameLeft.position.set(cafeteriaWidth/2 - 0.25, windowY, zPos - windowWidth/2 - frameThickness/2);
frameLeft.castShadow = true; scene.add(frameLeft);
const frameRight = new THREE.Mesh(frameSideGeo, frameMaterial);
frameRight.position.set(cafeteriaWidth/2 - 0.25, windowY, zPos + windowWidth/2 + frameThickness/2);
frameRight.castShadow = true; scene.add(frameRight);
});
// Door (on the front wall +z)
const doorWidth = 3;
const doorHeight = 7;
const doorDepth = 0.2;
const doorMaterial = new THREE.MeshStandardMaterial({ color: 0x8B4513, roughness: 0.8, metalness: 0.1 }); // Wood color
const doorHandleMaterial = new THREE.MeshStandardMaterial({color: 0xcccccc, roughness:0.3, metalness: 0.8});
const doorGeo = new THREE.BoxGeometry(doorWidth, doorHeight, doorDepth);
const doorMesh = new THREE.Mesh(doorGeo, doorMaterial);
// Place it slightly off-center on the front wall
doorMesh.position.set(cafeteriaWidth * 0.3, doorHeight / 2, cafeteriaLength / 2 - 0.25);
doorMesh.castShadow = true;
scene.add(doorMesh);
// Simple Handle
const handleGeo = new THREE.BoxGeometry(0.15, 0.6, 0.1);
const handleMesh = new THREE.Mesh(handleGeo, doorHandleMaterial);
handleMesh.position.set(cafeteriaWidth * 0.3 + doorWidth/2 - 0.3, doorHeight/2, cafeteriaLength/2 - 0.25 + doorDepth/2 + 0.05);
handleMesh.castShadow = true;
scene.add(handleMesh);
// Add another door?
const doorMesh2 = new THREE.Mesh(doorGeo, doorMaterial);
doorMesh2.position.set(-cafeteriaWidth * 0.3, doorHeight / 2, cafeteriaLength / 2 - 0.25);
doorMesh2.castShadow = true;
scene.add(doorMesh2);
const handleMesh2 = new THREE.Mesh(handleGeo, doorHandleMaterial);
handleMesh2.position.set(-cafeteriaWidth * 0.3 - doorWidth/2 + 0.3, doorHeight/2, cafeteriaLength/2 - 0.25 + doorDepth/2 + 0.05);
handleMesh2.castShadow = true;
scene.add(handleMesh2);
}
// --- Animation Loop ---
function animate() {
requestAnimationFrame(animate);
controls.update(); // Only needed if enableDamping or autoRotate are set
renderer.render(scene, camera);
}
// --- Resize Handler ---
function onWindowResize() {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
}
// --- Run ---
init();
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>THREE.js Forest Scene with Lake and Cave</title>
<style>
body {
margin: 0;
padding: 0;
overflow: hidden;
width: 100%;
height: 100vh;
}
canvas {
display: block;
}
#info {
position: absolute;
top: 10px;
left: 10px;
background-color: rgba(0, 0, 0, 0.5);
color: white;
padding: 10px;
font-family: Arial, sans-serif;
border-radius: 5px;
pointer-events: none;
}
</style>
</head>
<body>
<div id="info">
THREE.js Forest Scene<br>
Use mouse to orbit, zoom with scroll wheel
</div>
<script type="importmap">
{
"imports": {
"three": "https://cdnjs.cloudflare.com/ajax/libs/three.js/0.161.0/three.module.min.js",
"three/examples/jsm/controls/OrbitControls.js": "https://unpkg.com/[email protected]/examples/jsm/controls/OrbitControls.js"
}
}
</script>
<script type="module">
// Paste the JavaScript code from the previous artifact here
import * as THREE from 'three';
import { OrbitControls } from 'three/examples/jsm/controls/OrbitControls.js';
// Initialize scene, camera, and renderer
const scene = new THREE.Scene();
const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.setClearColor(0x87CEEB); // Sky blue
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
document.body.appendChild(renderer.domElement);
// Add OrbitControls
const controls = new OrbitControls(camera, renderer.domElement);
camera.position.set(0, 10, 30);
controls.update();
// Add ambient light
const ambientLight = new THREE.AmbientLight(0x404040, 1.5);
scene.add(ambientLight);
// Add directional light (sun)
const sunLight = new THREE.DirectionalLight(0xffffbb, 2);
sunLight.position.set(30, 50, 30);
sunLight.castShadow = true;
sunLight.shadow.mapSize.width = 2048;
sunLight.shadow.mapSize.height = 2048;
sunLight.shadow.camera.near = 0.5;
sunLight.shadow.camera.far = 500;
sunLight.shadow.camera.left = -70;
sunLight.shadow.camera.right = 70;
sunLight.shadow.camera.top = 70;
sunLight.shadow.camera.bottom = -70;
scene.add(sunLight);
// Create ground
const groundGeometry = new THREE.PlaneGeometry(200, 200, 50, 50);
const groundMaterial = new THREE.MeshStandardMaterial({
color: 0x2d4c1e,
roughness: 0.8,
metalness: 0.1
});
const ground = new THREE.Mesh(groundGeometry, groundMaterial);
ground.rotation.x = -Math.PI / 2;
ground.receiveShadow = true;
scene.add(ground);
// Create lake
const lakeGeometry = new THREE.CircleGeometry(15, 32);
const lakeMaterial = new THREE.MeshStandardMaterial({
color: 0x1a75ff,
roughness: 0.1,
metalness: 0.8,
transparent: true,
opacity: 0.9
});
const lake = new THREE.Mesh(lakeGeometry, lakeMaterial);
lake.rotation.x = -Math.PI / 2;
lake.position.set(10, 0.1, 0);
lake.receiveShadow = true;
scene.add(lake);
// Add some ripples to the lake
const addLakeRipples = () => {
const rippleGeometry = new THREE.RingGeometry(0.5, 0.8, 32);
const rippleMaterial = new THREE.MeshBasicMaterial({
color: 0xadd8e6,
transparent: true,
opacity: 0.3,
side: THREE.DoubleSide
});
const ripple = new THREE.Mesh(rippleGeometry, rippleMaterial);
ripple.rotation.x = -Math.PI / 2;
ripple.position.set(
10 + (Math.random() - 0.5) * 20,
0.15,
(Math.random() - 0.5) * 20
);
// Only add ripples that are inside the lake radius
const distanceFromCenter = Math.sqrt(
Math.pow(ripple.position.x - 10, 2) +
Math.pow(ripple.position.z, 2)
);
if (distanceFromCenter < 14) {
scene.add(ripple);
// Animate ripple growing and fading
const maxSize = 1.5 + Math.random() * 2;
const growSpeed = 0.02 + Math.random() * 0.03;
const animateRipple = () => {
ripple.scale.x += growSpeed;
ripple.scale.y += growSpeed;
ripple.scale.z += growSpeed;
ripple.material.opacity -= 0.004;
if (ripple.scale.x < maxSize && ripple.material.opacity > 0) {
requestAnimationFrame(animateRipple);
} else {
scene.remove(ripple);
ripple.geometry.dispose();
ripple.material.dispose();
}
};
animateRipple();
} else {
ripple.geometry.dispose();
ripple.material.dispose();
}
};
// Create cave
const createCave = () => {
const caveGroup = new THREE.Group();
// Main cave opening
const caveMouthGeometry = new THREE.SphereGeometry(12, 32, 32, 0, Math.PI * 2, 0, Math.PI / 2);
const caveMaterial = new THREE.MeshStandardMaterial({
color: 0x4a4a4a,
roughness: 1.0,
metalness: 0.2
});
const caveMouth = new THREE.Mesh(caveMouthGeometry, caveMaterial);
caveMouth.scale.set(1.5, 1.2, 1);
caveMouth.position.set(-30, 0, -5);
caveMouth.rotation.y = Math.PI / 2;
caveMouth.castShadow = true;
caveMouth.receiveShadow = true;
caveGroup.add(caveMouth);
// Cave interior (darker)
const caveInteriorGeometry = new THREE.CylinderGeometry(6, 7, 20, 32, 1, true);
const caveInteriorMaterial = new THREE.MeshStandardMaterial({
color: 0x222222,
roughness: 1.0,
metalness: 0.1,
side: THREE.BackSide
});
const caveInterior = new THREE.Mesh(caveInteriorGeometry, caveInteriorMaterial);
caveInterior.position.set(-35, 6, -5);
caveInterior.rotation.z = Math.PI / 2;
caveGroup.add(caveInterior);
// Add rocks around cave entrance
for (let i = 0; i < 25; i++) {
const rockSize = 0.5 + Math.random() * 2;
const rockGeometry = new THREE.DodecahedronGeometry(rockSize, 1);
const rockMaterial = new THREE.MeshStandardMaterial({
color: 0x666666,
roughness: 0.9,
metalness: 0.1
});
const rock = new THREE.Mesh(rockGeometry, rockMaterial);
const angle = Math.random() * Math.PI;
const radius = 12 + Math.random() * 5;
rock.position.set(
-30 + Math.cos(angle) * radius,
rockSize / 2,
-5 + Math.sin(angle) * radius
);
rock.rotation.set(
Math.random() * Math.PI,
Math.random() * Math.PI,
Math.random() * Math.PI
);
rock.castShadow = true;
rock.receiveShadow = true;
caveGroup.add(rock);
}
return caveGroup;
};
const cave = createCave();
scene.add(cave);
// Create trees
const createTree = (x, z, height) => {
const treeGroup = new THREE.Group();
// Tree trunk
const trunkGeometry = new THREE.CylinderGeometry(0.5, 0.7, height, 8);
const trunkMaterial = new THREE.MeshStandardMaterial({
color: 0x8B4513,
roughness: 0.9,
metalness: 0.1
});
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterial);
trunk.position.y = height / 2;
trunk.castShadow = true;
trunk.receiveShadow = true;
treeGroup.add(trunk);
// Tree leaves (multiple cone layers)
const leavesColor = Math.random() > 0.2 ? 0x2d5f23 : 0x3d752d; // Different greens
const leavesMaterial = new THREE.MeshStandardMaterial({
color: leavesColor,
roughness: 0.8,
metalness: 0.1
});
const numLayers = 3 + Math.floor(Math.random() * 3);
const layerHeight = 2.5;
for (let i = 0; i < numLayers; i++) {
const layerSize = 2 + (numLayers - i) * 0.7;
const coneGeometry = new THREE.ConeGeometry(layerSize, layerHeight, 8);
const layer = new THREE.Mesh(coneGeometry, leavesMaterial);
layer.position.y = height - i * (layerHeight * 0.6) - layerHeight / 2;
layer.castShadow = true;
treeGroup.add(layer);
}
treeGroup.position.set(x, 0, z);
return treeGroup;
};
// Add trees (avoiding the lake and cave)
for (let i = 0; i < 80; i++) {
let x = (Math.random() - 0.5) * 180;
let z = (Math.random() - 0.5) * 180;
// Calculate distance from lake center
const distFromLake = Math.sqrt(Math.pow(x - 10, 2) + Math.pow(z, 2));
// Calculate distance from cave
const distFromCave = Math.sqrt(Math.pow(x + 30, 2) + Math.pow(z + 5, 2));
// Only place trees if they're not too close to lake or cave
if (distFromLake > 18 && distFromCave > 20) {
const height = 4 + Math.random() * 6;
const tree = createTree(x, z, height);
scene.add(tree);
}
}
// Create rocks
const createRock = (x, y, z, scale) => {
const geometry = new THREE.DodecahedronGeometry(1, 1);
const material = new THREE.MeshStandardMaterial({
color: 0x7d7d7d,
roughness: 0.8,
metalness: 0.2
});
const rock = new THREE.Mesh(geometry, material);
rock.position.set(x, y, z);
rock.scale.set(
scale * (0.8 + Math.random() * 0.4),
scale * (0.8 + Math.random() * 0.4),
scale * (0.8 + Math.random() * 0.4)
);
rock.rotation.set(
Math.random() * Math.PI,
Math.random() * Math.PI,
Math.random() * Math.PI
);
rock.castShadow = true;
rock.receiveShadow = true;
return rock;
};
// Add rocks around the lake
for (let i = 0; i < 30; i++) {
const angle = Math.random() * Math.PI * 2;
const radius = 15 + Math.random() * 2;
const x = 10 + Math.cos(angle) * radius;
const z = Math.sin(angle) * radius;
const scale = 0.5 + Math.random() * 1.5;
const rock = createRock(x, scale * 0.5, z, scale);
scene.add(rock);
}
// Add some bushes
const createBush = (x, z, scale) => {
const bushGroup = new THREE.Group();
const geometry = new THREE.SphereGeometry(1, 8, 6);
const material = new THREE.MeshStandardMaterial({
color: 0x3d752d,
roughness: 0.9,
metalness: 0.1
});
const numClusters = 3 + Math.floor(Math.random() * 3);
for (let i = 0; i < numClusters; i++) {
const cluster = new THREE.Mesh(geometry, material);
cluster.position.set(
(Math.random() - 0.5) * scale,
scale * (0.5 + Math.random() * 0.3),
(Math.random() - 0.5) * scale
);
cluster.scale.set(
scale * (0.6 + Math.random() * 0.4),
scale * (0.6 + Math.random() * 0.4),
scale * (0.6 + Math.random() * 0.4)
);
cluster.castShadow = true;
bushGroup.add(cluster);
}
bushGroup.position.set(x, 0, z);
return bushGroup;
};
// Add bushes randomly in the scene
for (let i = 0; i < 40; i++) {
let x = (Math.random() - 0.5) * 160;
let z = (Math.random() - 0.5) * 160;
// Calculate distance from lake center
const distFromLake = Math.sqrt(Math.pow(x - 10, 2) + Math.pow(z, 2));
// Calculate distance from cave
const distFromCave = Math.sqrt(Math.pow(x + 30, 2) + Math.pow(z + 5, 2));
if (distFromLake > 16 && distFromCave > 15) {
const scale = 1 + Math.random() * 1.5;
const bush = createBush(x, z, scale);
scene.add(bush);
}
}
// Add fog to the scene
scene.fog = new THREE.FogExp2(0xcccccc, 0.008);
// Animation loop
const animate = () => {
requestAnimationFrame(animate);
// Occasionally add ripples to the lake
if (Math.random() < 0.03) {
addLakeRipples();
}
// Update controls
controls.update();
renderer.render(scene, camera);
};
// Handle window resize
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
// Start animation
animate();
</script>
</body>
</html>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment