LFM3-1.7B (free)
by LiquidAIreasoning defaultRan out of tokens
How it went
- Total time
- 35.3 s
- Cost
- $0
- Output tokens
- 8,192
- Spent thinking
- 8,192 tokens
- Speed
- — tok/s
- Console errors
- —
- First token after
- 402 ms
- First visible text after
- —
- Prompt tokens
- 592
- Code size
- 0 B
- Finish reason
- length
- Served by
- Liquid
- Model id
liquid/lfm-3-1.7b:free- Reasoning sent
model default- Max output
- 8,192 tokens
- Generated
- Nov 9, 2026
What it was asked
The prompt
Build a low-poly 3D scene of a campfire in a forest clearing at night, with Three.js in a single HTML file. Load Three.js only through this import map (do not use any other URL or version): <script type="importmap">{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"}}</script> Requirements: - A ground with a few low-poly rocks and logs around a campfire in the center. - At least 30 low-poly trees placed randomly around the clearing (not on the fire). - The campfire has animated flames and a warm point light that flickers and casts shadows. - Sparks rising from the fire as a simple particle system. - A dark night sky with stars and a moon, plus fog that fades the distant trees. - OrbitControls to look around, with limits so the camera stays above the ground. - The renderer fills the window and handles resizing correctly.
System prompt, sent with every prompt
You are generating a self-contained web demo. It will be rendered automatically in a sandboxed iframe and compared side by side with the output of other AI models given the exact same task. Output rules: - Reply with exactly ONE fenced code block containing the complete file: ```html (or ```svg when the task asks for an SVG). No text before or after the code block. - Everything must be inline: CSS in <style>, JavaScript in <script>. No external files, images, fonts or network requests, except the CDN URLs explicitly allowed by the task. - Do not use localStorage, sessionStorage, cookies, IndexedDB, alert(), confirm(), prompt() or window.open(): they are blocked in the sandbox and will throw. - The page must look right in a 1280×800 viewport and adapt gracefully down to 375px wide. No horizontal scrollbar. - Something meaningful must be visible on first paint: a screenshot is taken automatically about 3 seconds after load, without any interaction. - Everything that makes the demo interesting must be visible within the first seconds: nothing hidden behind a high score, a later level or a long play session. - Games must start in a self-playing demo mode (a simple AI plays) behind a "Click or press a key to start" message, so the game can be judged in action without playing. - The iframe only receives keyboard events after the user clicks it: if the demo uses the keyboard, display a clear "Click or press a key to start" message. - The code must run with zero errors in the console of the latest Chrome.
Reasoning trace (8,192 tokens)
We need to create a single HTML file that uses Three.js via import map. The scene: low-poly 3D campfire in forest clearing at night.
Requirements:
- Ground with few low-poly rocks and logs around the campfire.
- At least 30 low-poly trees placed randomly (not on fire).
- Campfire has animated flames and warm point light that flickers and casts shadows.
- Sparks rising from fire as simple particle system.
- Dark night sky with stars and moon, plus fog that fades distant trees.
- OrbitControls with limits so camera stays above ground.
- Renderer fills window and handles resizing correctly.
- Must be self-contained, no external files except CDN imports given.
- Use only allowed imports: three module and addons orbit controls.
Also must not use localStorage etc. No network requests besides importmap.
Need to ensure first paint shows something meaningful within seconds; we can have some static elements like background stars, maybe a faint glow.
The page should adapt for viewport width down to 375px, no horizontal scrollbar.
Let's plan structure:
HTML skeleton:
<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8">
<title>Campfire Night Scene</title>
<style>
body { margin:0; overflow:hidden; background:#000; }
canvas { display:block; } // or just let Three.js render
</style>
</head>
<body>
<script type="importmap">
{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/controls/OrbitControls.js"}}
</script>
<script type="module">
// code here
</script>
</body>
</html>
But note: In import map we also need "three/addons/orbitControls"? Actually OrbitControls is under examples/jsm/controls/OrbitControls.js. So we include that path.
Now implement:
- Initialize scene, camera, renderer.
- Resize handling: set renderer.setSize(window.innerWidth, window.innerHeight) and update camera position accordingly? Usually Three.js automatically resizes but we can handle window resize event.
- Ground: plane geometry with low poly texture? We'll just make a mesh of low-poly rocks and logs. Could generate random points and shapes. Simpler: one large flat ground plane with some rock pieces and log pieces scattered. But requirement: "ground with a few low-poly rocks and logs around a campfire". So we can create a plane for ground, then add some cubes/boxes for rocks and logs.
- Fire: a circle shape (maybe using sphere or cone) for flame, with point light inside. Animated flames: rotate positions over time, change color. Also a warm point light casting shadows.
- Particles: spark particles rising from fire: use Points or BufferGeometry with many small spheres moving upward.
- Trees: 30 low-poly tree meshes placed randomly around clearing (e.g., around radius). Each tree: trunk cylinder + branches (triangles). Keep them away from fire location.
- Sky: dark background, stars: many small dots, moon: sphere with texture or simple mesh.
- Fog: use PointCloudFog or simply add a Fog object with opacity decreasing with distance.
- OrbitControls: limit camera up so it doesn't go below ground. We can set min height of camera to e.g., 2 units above ground.
Implementation details:
Three.js version 0.170.0.
We'll write code in script tag.
Plan steps:
1. Setup:
- const scene = new THREE.Scene();
- const camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 1000);
- const renderer = new THREE.WebGLRenderer({ antialias: true });
- renderer.setSize(window.innerWidth, window.innerHeight);
- document.body.appendChild(renderer.domElement);
2. Add lights:
- Ambient light: weak?
- Warm point light for fire: create a point light at fire center, color orange/yellow, intensity moderate, castShadow: true.
- Maybe an ambient light for overall illumination.
3. Ground:
- Plane: new THREE.PlaneGeometry(50, 50). Then shift up by 2 units? Actually ground at y=0. Let's place ground at z=0 plane. Then add rocks and logs.
4. Rocks & Logs:
- Create array of objects: each with geometry (BoxGeometry or BrickGeometry), material (MeshStandardMaterial with low poly look). Random positions near ground.
5. Fire:
- Circle: use THREE.MeshBasicMaterial? For flame shape, perhaps a torus or cone. Let's do a sphere for fire base, and then add multiple circles for flames around it. Or use a Mesh with a texture? Simpler: a plane with emissive material? I think easiest: a cone shape representing fire, with a point light at its tip. But we need animated flames: we can have several small circles rotating around the cone.
Given complexity constraints, we can approximate: A central point light at (cx, cy, cz) with intensity. Then a mesh for fire: a small sphere or a cone. Flames: small circles drawn as lines? Not needed too much. Since it's a demo, we can produce a simple flame effect using a sprite? But limited to basic materials.
Simplify: Represent fire as a glowing area: a plane with emissive material, and animate its rotation and brightness. And add point light at center.
Better: Use THREE.Points with a buffer geometry for sparks.
I'll design:
- FireMesh: a small sphere (or box) centered at (0,0,0) relative to world coordinates. Place it at (0,0,-1) maybe. Light at same location.
- Flames: an array of circles (like rings) around fire, each with a small circle mesh (thin ring) rotated slowly.
But this may become heavy. However we need at least 30 trees, which is okay.
Given time constraints, we can keep fire relatively simple: a point light and a small emissive sphere.
Sparks: use a Points system: create a BufferGeometry with many points arranged radially outward, with velocity upward and decaying amplitude.
Let's outline step-by-step coding.
First, define constants:
- CAMERA_POSITION = [0, 0, 10] maybe above ground.
- GROUND_Y = 0.
- FIRE_RADIUS = 0.5.
- FLAME_COLORS = ['#ffaa00', '#ff8800', ...].
- SPARK_COUNT = 20.
- TREE_COUNT = 30.
- STARS_COUNT = 200.
- MOON_SIZE = 1.5.
- FOG_DISTANCE = 40.
- FOG_COLOR = 0x050510 (dark blue-gray).
Implement:
```js
import * as THREE from 'three';
import { OrbitControls } from 'https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/controls/OrbitControls.js'; // but import map includes three/addons/... path. Need correct URL.
```
Import map:
{"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"}}
Thus we can import OrbitControls from that path: "three/addons/controls/OrbitControls.js". So in script we can do `import { OrbitControls } from 'three/addons/controls/OrbitControls';`. That works if import map matches exactly.
Check: The example jsm path is https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/. So subpath "controls/OrbitControls.js". Good.
So we can import OrbitControls.
Now code:
```js
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a14); // night sky
const camera = new THREE.PerspectiveCamera(45, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 15, 12); // start above ground
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
// OrbitControls with limits
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.target.set(0, 0, 0); // aim at origin? maybe center of fire.
// Lights
const ambientLight = new THREE.AmbientLight(0x404040, 0.6);
scene.add(ambientLight);
const firePointLight = new THREE.PointLight(0xffaa00, 1.5, 5, 1); // warm orange
firePointLight.castShadow = true;
firePointLight.shadow.mapSize.width = 1024;
firePointLight.shadow.mapSize.height = 1024;
firePointLight.shadow.camera.left = -20;
firePointLight.shadow.camera.right = 20;
firePointLight.shadow.camera.top = 20;
firePointLight.shadow.camera.bottom = -20;
firePointLight.shadow.bias = -0.0001;
// Position fire point light at fire center (0,0,-1)
firePointLight.position.set(0, 0, -1);
// Add fire mesh
const fireGeo = new THREE.CylinderGeometry(0.3, 0.3, 0.5, 32);
const fireMat = new THREE.MeshStandardMaterial({ color: 0xffaa00, emissive: 0xffaa00, emissiveIntensity: 1.5, roughness: 0.9 });
const fireMesh = new THREE.Mesh(fireGeo, fireMat);
fireMesh.position.y = 0.5; // slightly above ground
scene.add(fireMesh);
// Flames: small circles around fire
function createFlameRings() {
const count = 8;
for(let i=0;i<count;i++){
const angle = (i/count)*Math.PI*2;
const radius = 0.2 + Math.random()*0.1;
const mat = new THREE.MeshBasicMaterial({ color: 0xffaa00, transparent: true, opacity: 0.7 });
const ringGeo = new THREE.TorusGeometry(radius, 0.05, 16, 64);
const ringMesh = new THREE.Mesh(ringGeo, mat);
ringMesh.position.set(Math.cos(angle)*radius, 0.5, Math.sin(angle)*radius);
ringMesh.quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0), new THREE.Vector3(0,0,1));
scene.add(ringMesh);
}
}
createFlameRings();
// Point light for fire? Already firePointLight.
// Sparks particles
const sparkCount = 30;
const sparkGeo = new THREE.BufferGeometry().setFromPoints();
for(let i=0;i<sparkCount;i++){
const theta = (i/sparkCount)*Math.PI*2;
const r = 0.5 + Math.random()*0.5;
const speed = 2 + Math.random()*2;
const pos = new THREE.Vector3(
Math.cos(theta)*r,
0.5,
Math.sin(theta)*r
);
sparkGeo.setAttribute('position', new THREE.Float32BufferAttribute([pos.x, pos.y, pos.z], 3));
sparkGeo.setAttribute('velocity', new THREE.Float32BufferAttribute([0, 0, speed], 1));
}
const sparkMat = new THREE.PointsMaterial({ color: 0xffd700, size: 0.2, transparent: true, opacity: 0.8 });
const sparkMeshes = new THREE.Points(sparkGeo, sparkMat);
scene.add(sparkMeshes);
// Ground
const groundGeo = new THREE.PlaneGeometry(60, 60);
const groundMat = new THREE.MeshStandardMaterial({ color: 0x222233, side: THREE.DoubleSide });
const groundMesh = new THREE.Mesh(groundGeo, groundMat);
groundMesh.rotation.x = -Math.PI/2;
groundMesh.receiveShadow = true;
groundMesh.position.y = -0.1; // raise slightly
scene.add(groundMesh);
// Add rocks and logs around ground
function addGroundObjects(count=5){
for(let i=0;i<count;i++){
const x = (Math.random()-0.5)*10;
const z = (Math.random()-0.5)*10;
const h = 0.5 + Math.random()*1.5;
const geo = new THREE.BoxGeometry(1, h, 1);
const mat = new THREE.MeshStandardMaterial({ color: 0x555555, metalness: 0.2, roughness: 0.7 });
const obj = new THREE.Mesh(geo, mat);
obj.position.set(x, h, z);
groundMesh.add(obj); // add to existing ground mesh? Better separate.
}
}
// Instead, create separate arrays for rocks and logs.
let rocks = [];
let logs = [];
for(let i=0;i<5;i++){
const x = (Math.random()-0.5)*8;
const z = (Math.random()-0.5)*8;
const h = 0.5 + Math.random()*1.5;
const geoRock = new THREE.BoxGeometry(1.5, h, 1.5);
const matRock = new THREE.MeshStandardMaterial({ color: 0x888888, roughness: 0.8 });
const rock = new THREE.Mesh(geoRock, matRock);
rock.position.set(x, h, z);
rocks.push(rock);
}
for(let i=0;i<3;i++){
const x = (Math.random()-0.5)*6;
const z = (Math.random()-0.5)*6;
const geoLog = new THREE.BoxGeometry(0.5, 0.2, 0.5);
const matLog = new THREE.MeshStandardMaterial({ color: 0x999999, roughness: 0.7 });
const log = new THREE.Mesh(geoLog, matLog);
log.position.set(x, 0.1, z);
logs.push(log);
}
groundMesh.addAll(rocks);
groundMesh.addAll(logs);
// Trees
function createTree(position, angle, length){
// Trunk
const trunkGeo = new THREE.CylinderGeometry(0.1, 0.1, length, 8);
const trunkMat = new THREE.MeshStandardMaterial({ color: 0x1b4332, roughness: 0.8 });
const trunk = new THREE.Mesh(trunkGeo, trunkMat);
trunk.position.copy(position);
scene.add(trunk);
// Branches: simple triangles
const branchLen = 1.5;
const branchGeo = new THREE.PlaneGeometry(0.3, 0.3);
const branchMat = new THREE.MeshStandardMaterial({ color: 0x2e86ab, roughness: 0.6 });
for(let b=0;b<4;b++){
const bx = Math.cos(angle+b*0.8)*branchLen;
const bz = Math.sin(angle+b*0.8)*branchLen;
const bg = new THREE.Mesh(branchGeo, branchMat);
bg.position.set(bx, 0.3, bz);
bg.lookAt(trunk.position);
scene.add(bg);
}
}
// Generate 30 trees around fire (center at (0,0,-1)? Actually fire at (0,0,-1) but we want trees not on fire. Choose radius ~5 m.
for(let i=0;i<TREE_COUNT;i++){
const radius = 5 + Math.random()*3;
const phi = Math.random()*Math.PI*2;
const theta = Math.acos(0.5 + Math.random()*0.5); // cos between 0 and 1 => latitudes
const x = radius*Math.sin(phi)*Math.sin(theta);
const y = radius*Math.sin(phi)*Math.cos(theta);
const z = radius*Math.sin(phi);
// fire center is (0,0,-1). So we offset z+1? Actually fire at (0,0,-1). So we want trees at positive z > -1? Let's put fire at (0,0,-1). Then trees at z = -1 + radius*something? Eh.
// Simpler: place fire at (0,0,0) and adjust later.
// Let's move fire to (0,0,0) for simplicity.
// Reassign fireMesh.position to (0,0,0). Then adjust.
// We'll set fireMesh.position = new THREE.Vector3(0,0.5,0);
// Anyway, ignore now.
createTree({
x: x,
y: y,
z: z
}, phi, theta);
}
// Actually we haven't defined createTree signature properly. Let's restructure.
Given the complexity, better to simplify drastically: Use fewer objects but still meet requirements.
Given time constraints, I will craft a concise but functional demo.
Key aspects:
- Single HTML file.
- Import map provided.
- Use Three.js 0.170.0.
Will try to keep code manageable.
Outline final script:
```js
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
// Basic setup
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a14);
const camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 100);
camera.position.set(0, 12, 8);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.target.set(0, 0, 0);
// Lights
const ambient = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambient);
const fireLight = new THREE.PointLight(0xffaa00, 1.5, 5, 1);
fireLight.castShadow = true;
fireLight.shadow.mapSize.width = 512;
fireLight.shadow.mapSize.height = 512;
fireLight.shadow.camera.left = -15;
fireLight.shadow.camera.right = 15;
fireLight.shadow.camera.top = 15;
fireLight.shadow.camera.bottom = -15;
fireLight.shadow.bias = -0.001;
fireLight.position.set(0, 0, -1); // fire point light at origin
// Fire mesh: a small sphere
const fireGeo = new THREE.SphereGeometry(0.3, 32, 32);
const fireMat = new THREE.MeshStandardMaterial({ color: 0xffaa00, emissive: 0xffaa00, emissiveIntensity: 2 });
const fireMesh = new THREE.Mesh(fireGeo, fireMat);
fireMesh.position.y = 0.5;
scene.add(fireMesh);
// Flames: simple rings around fire
function addFlames(){
const count = 6;
for(let i=0;i<count;i++){
const angle = (i/count)*Math.PI*2;
const ringRadius = 0.25 + Math.random()*0.2;
const mat = new THREE.MeshBasicMaterial({ color: 0xffaa00, transparent: true, opacity: 0.6 });
const ringGeo = new THREE.TorusGeometry(ringRadius, 0.02, 16, 64);
const ringMesh = new THREE.Mesh(ringGeo, mat);
ringMesh.position.set(Math.cos(angle)*ringRadius, 0.5, Math.sin(angle)*ringRadius);
ringMesh.quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0), new THREE.Vector3(0,0,1));
scene.add(ringMesh);
}
}
addFlames();
// Sparks particles
const sparkCount = 20;
const sparkPosGeo = new THREE.BufferGeometry();
const sparkVelGeo = new THREE.BufferGeometry();
const positions = new Float32Array(sparkCount*3);
const velocities = new Float32Array(sparkCount*1);
for(let i=0;i<sparkCount;i++){
const theta = (i/sparkCount)*Math.PI*2;
const r = 0.5 + Math.random()*0.5;
positions[i*3] = Math.cos(theta)*r;
positions[i*3+1] = 0.5;
positions[i*3+2] = Math.sin(theta)*r;
velocities[i] = 0;
velocities[i+1] = 0;
velocities[i+2] = 2 + Math.random()*2;
}
sparkPosGeo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
sparkVelGeo.setAttribute('velocity', new THREE.Float32BufferAttribute(velocities, 1));
const sparkMat = new THREE.PointsMaterial({ size: 0.3, color: 0xffd700, transparent: true, opacity: 0.8 });
const sparkMeshes = new THREE.Points(sparkPosGeo, sparkMat);
scene.add(sparkMeshes);
// Ground plane
const groundGeo = new THREE.PlaneGeometry(80, 80);
const groundMat = new THREE.MeshStandardMaterial({ color: 0x222233 });
const groundMesh = new THREE.Mesh(groundGeo, groundMat);
groundMesh.rotation.x = -Math.PI/2;
groundMesh.receiveShadow = true;
groundMesh.position.y = -0.1;
scene.add(groundMesh);
// Rocks and logs around ground
function addGroundObjs(){
// rocks
for(let i=0;i<5;i++){
const x = (Math.random()-0.5)*10;
const z = (Math.random()-0.5)*10;
const geo = new THREE.BoxGeometry(1.5, 0.5, 1.5);
const mat = new THREE.MeshStandardMaterial({ color: 0x555555, roughness: 0.7 });
const rock = new THREE.Mesh(geo, mat);
rock.position.set(x, 0.5, z);
groundMesh.add(rock);
}
// logs
for(let i=0;i<3;i++){
const x = (Math.random()-0.5)*6;
const z = (Math.random()-0.5)*6;
const geo = new THREE.BoxGeometry(0.5, 0.2, 0.5);
const mat = new THREE.MeshStandardMaterial({ color: 0x999999, roughness: 0.7 });
const log = new THREE.Mesh(geo, mat);
log.position.set(x, 0.1, z);
groundMesh.add(log);
}
}
addGroundObjs();
// Trees (at least 30)
function addTrees(){
const count = 30;
for(let i=0;i<count;i++){
// pick random angle around fire (z axis)
const radius = 5 + Math.random()*3;
const phi = Math.random()*Math.PI*2;
const theta = Math.acos(0.5 + Math.random()*0.5); // latitude
const x = radius*Math.sin(phi)*Math.sin(theta);
const y = radius*Math.sin(phi)*Math.cos(theta);
const z = -1 + radius*Math.sin(theta); // fire at (0,0,-1) -> ground level? hmm.
// Actually fire at (0,0,-1). So we want trees above ground (y?). This is messy.
// Simplify: place trees in xy-plane roughly around origin, ignoring vertical.
// Just random positions in XY plane.
const px = (Math.random()-0.5)*8;
const py = (Math.random()-0.5)*8;
const pz = 0.5; // half height
// trunk
const trunkGeo = new THREE.CylinderGeometry(0.1, 0.1, 0.5, 8);
const trunkMat = new THREE.MeshStandardMaterial({ color: 0x1b4332, roughness: 0.8 });
const trunk = new THREE.Mesh(trunkGeo, trunkMat);
trunk.position.set(px, py, pz);
scene.add(trunk);
// branches
const branchGeo = new THREE.PlaneGeometry(0.3, 0.3);
const branchMat = new THREE.MeshStandardMaterial({ color: 0x2e86ab, roughness: 0.6 });
for(let b=0;b<4;b++){
const bx = Math.cos(b*0.8)*0.3;
const bz = Math.sin(b*0.8)*0.3;
const bg = new THREE.Mesh(branchGeo, branchMat);
bg.position.set(bx, py+0.3, bz);
bg.lookAt(trunk.position);
scene.add(bg);
}
}
}
addTrees();
// Stars and Moon
const starCount = 150;
const starPositions = new Float32Array(starCount*3);
for(let i=0;i<starCount;i++){
const theta = (i/starCount)*Math.PI*2;
const r = 30 + Math.random()*30;
starPositions[i*3] = Math.cos(theta)*r;
starPositions[i*3+1] = Math.sin(theta)*r;
starPositions[i*3+2] = 0.5;
}
const starsGeo = new THREE.BufferGeometry();
starsGeo.setAttribute('position', new THREE.Float32BufferAttribute(starPositions, 3));
const starsMat = new THREE.PointsMaterial({ color: 0xffffff, size: 0.5, transparent: true, opacity: 0.6 });
const stars = new THREE.Points(starsGeo, starsMat);
scene.add(stars);
// Moon
const moonGeo = new THREE.SphereGeometry(1.5, 16, 16);
const moonMat = new THREE.MeshStandardMaterial({ color: 0xd0efaf, metalness: 0.5, roughness: 0.5 });
const moon = new THREE.Mesh(moonGeo, moonMat);
moon.position.set(0, 2, 0);
scene.add(moon);
// Fog
const fog = new THREE.FogExp2(0x0a0a14, 0.2);
scene.fog = fog;
// Controls
controls.enableDamping = true;
controls.minDistance = 5;
controls.maxDistance = 15;
controls.maxPolarAngle = Math.PI/2 - 0.2; // keep camera above ground
// Animation loop
function animate(){
requestAnimationFrame(animate);
// Update fire light flicker
fireLight.intensity = 1 + Math.sin(Date.now()*0.002)*0.1;
// Animate sparks
sparkMeshes.visible = true; // always visible
// Update camera position occasionally? Not needed.
controls.update();
renderer.render(scene, camera);
}
animate();
// Resize handler
window.addEventListener('resize', () => {
const w = window.innerWidth;
const h = window.innerHeight;
renderer.setSize(w, h);
camera.aspect = w/h;
camera.updateProjectionMatrix();
});
```
But there are many issues: missing references to variables after creation, like adding objects to scene incorrectly, missing loops for trees, etc. Also need to ensure no errors.
Given the complexity, I'd rather write a more compact but complete script focusing on essential parts and ensuring everything runs without error. Let me rewrite cleanly.
I'll break into sections:
- Setup scene, camera, renderer.
- Add lights.
- Add fire mesh and point light.
- Add flame rings.
- Add sparks.
- Add ground plane.
- Add rocks and logs.
- Add trees (loop 30 times).
- Add stars and moon.
- Add fog.
- Add OrbitControls with limits.
- Add animation loop.
Make sure to call `scene.add` for all meshes.
Potential pitfalls: Using `new THREE.CylinderGeometry` vs BoxGeometry. Ensure proper orientation.
Also need to avoid using `receiveShadow` property? It exists in modern Three.js.
Let's write final code carefully.
### Step 1: Imports
```html
<script type="importmap">
{
"imports": {
"three": "https://cdn.jsdelivr.net/npm/three@0.170.0/build/three.module.js",
"three/addons/*": "https://cdn.jsdelivr.net/npm/three@0.170.0/examples/jsm/"
}
}
</script>
```
In script tag:
```js
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
```
### Step 2: Scene
```js
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x0a0a14);
```
### Step 3: Camera
```js
const camera = new THREE.PerspectiveCamera(45, window.innerWidth/window.innerHeight, 0.1, 100);
camera.position.set(0, 12, 8);
```
### Step 4: Renderer
```js
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
document.body.appendChild(renderer.domElement);
```
### Step 5: OrbitControls
```js
const controls = new OrbitControls(camera, renderer.domElement);
controls.enableDamping = true;
controls.target.set(0, 0, 0);
controls.minDistance = 5;
controls.maxDistance = 15;
controls.maxPolarAngle = Math.PI/2 - 0.2; // keep above ground
```
### Step 6: Ambient Light
```js
const ambient = new THREE.AmbientLight(0x404040, 0.5);
scene.add(ambient);
```
### Step 7: Fire Point Light
```js
const fireLight = new THREE.PointLight(0xffaa00, 1.5, 5, 1);
fireLight.castShadow = true;
fireLight.shadow.mapSize.width = 512;
fireLight.shadow.mapSize.height = 512;
fireLight.shadow.camera.left = -15;
fireLight.shadow.camera.right = 15;
fireLight.shadow.camera.top = 15;
fireLight.shadow.camera.bottom = -15;
fireLight.shadow.bias = -0.001;
fireLight.position.set(0, 0, -1); // fire at origin below ground
```
### Step 8: Fire Mesh
```js
const fireGeo = new THREE.SphereGeometry(0.35, 32, 32);
const fireMat = new THREE.MeshStandardMaterial({ color: 0xffaa00, emissive: 0xffaa00, emissiveIntensity: 2 });
const fireMesh = new THREE.Mesh(fireGeo, fireMat);
fireMesh.position.y = 0.5;
scene.add(fireMesh);
```
### Step 9: Flames (rings)
```js
function addFlames(){
const count = 6;
for(let i=0;i<count;i++){
const angle = (i/count)*Math.PI*2;
const ringRadius = 0.22 + Math.random()*0.18;
const mat = new THREE.MeshBasicMaterial({ color: 0xffaa00, transparent: true, opacity: 0.6 });
const ringGeo = new THREE.TorusGeometry(ringRadius, 0.03, 16, 64);
const ringMesh = new THREE.Mesh(ringGeo, mat);
ringMesh.position.set(Math.cos(angle)*ringRadius, 0.5, Math.sin(angle)*ringRadius);
ringMesh.quaternion.setFromUnitVectors(new THREE.Vector3(0,1,0), new THREE.Vector3(0,0,1));
scene.add(ringMesh);
}
}
addFlames();
```
### Step 10: Sparks
Create buffer geometries for positions and velocities.
```js
const sparkCount = 20;
const positions = new Float32Array(sparkCount*3);
const velocities = new Float32Array(sparkCount*1);
for(let i=0;i<sparkCount;i++){
const theta = (i/sparkCount)*Math.PI*2;
const r = 0.5 + Math.random()*0.5;
positions[i*3] = Math.cos(theta)*r;
positions[i*3+1] = 0.5;
positions[i*3+2] = Math.sin(theta)*r;
velocities[i] = 0;
velocities[i+1] = 0;
velocities[i+2] = 2 + Math.random()*2;
}
const posGeo = new THREE.BufferGeometry();
posGeo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
const velGeo = new THREE.BufferGeometry();
velGeo.setAttribute('velocity', new THREE.Float32BufferAttribute(velocities, 1));
const sparkMat = new THREE.PointsMaterial({ size: 0.3, color: 0xffd700, transparent: true, opacity: 0.8 });
const sparkMeshes = new THREE.Points(posGeo, sparkMat);
scene.add(sparkMeshes);
```
### Step 11: Ground Plane
```js
const groundGeo = new THREE.PlaneGeometry(80, 80);
const groundMat = new THREE.MeshStandardMaterial({ color: 0x222233 });
const groundMesh = new THREE.Mesh(groundGeo, groundMat);
groundMesh.rotation.x = -Math.PI/2;
groundMesh.receiveShadow = true;
groundMesh.position.y = -0.1;
scene.add(groundMesh);
```
### Step 12: Rocks and Logs
Add 5 rocks and 3 logs.
```js
// Rocks
for(let i=0;i<5;i++){
const x = (Math.random()-0.5)*10;
const z = (Math.random()-0.5)*10;
const geo = new THREE.BoxGeometry(1.5, 0.5, 1.5);
const mat = new THREE.MeshStandardMaterial({ color: 0x555555, roughness: 0.7 });
const rock = new THREE.Mesh(geo, mat);
rock.position.set(x, 0.5, z);
groundMesh.add(rock);
}
// Logs
for(let i=0;i<3;i++){
const x = (Math.random()-0.5)*6;
const z = (Math.random()-0.5)*6;
const geo = new THREE.BoxGeometry(0.5, 0.2, 0.5);
const mat = new THREE.MeshStandardMaterial({ color: 0x999999, roughness: 0.7 });
const log = new THREE.Mesh(geo, mat);
log.position.set(x, 0.1, z);
groundMesh.add(log);
}
```
### Step 13: Trees (30)
Loop 30 times:
```js
function addTrees(){
const count = 30;
for(let i=0;i<count;i++){
// random position in XY plane
const px = (Math.random()-0.5)*8;
const py = (Math.random()-0.5)*8;
const pz = 0.5;
// trunk
const trunkGeo = new THREE.CylinderGeometry(0.1, 0.1, 0.5, 8);
const trunkMat = new THREE.MeshStandardMaterial({ color: 0x1b4332, roughness: 0.8 });
const trunk = new THREE.Mesh(trunkGeo, trunkMat);
trunk.position.set(px, py, pz);
scene.add(trunk);
// branches
const branchGeo =