Last active
April 11, 2025 11:13
-
-
Save RageshAntonyHM/20445d78bd2611ab0bf9e8c613e2d9b9 to your computer and use it in GitHub Desktop.
high school and Lake using THREE.js
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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. |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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> |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
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(); |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<!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