GPT-6.1 Terra
by OpenAIReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 2 min 10 s
- Cost
- $0.057
- Output tokens
- 11,217
- Spent thinking
- 4,327 tokens
- Speed
- 90.4 tok/s
- Console errors
- 0
- First token after
- 6.2 s
- First visible text after
- 1 min 14 s
- Prompt tokens
- 555
- Code size
- 19.6 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6.1-terra- Reasoning sent
{"effort":"high"}- Max output
- 128,000 tokens
- Generated
- Nov 12, 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.
Code run-1.html · 451 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>Night Camp</title>
<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>
<style>
* { box-sizing: border-box; }
html, body { width: 100%; height: 100%; margin: 0; overflow: hidden; }
body { background: #101c26; color: #e6e8de; font-family: Arial, Helvetica, sans-serif; }
canvas { display: block; }
#scene { position: fixed; inset: 0; }
.overlay { position: fixed; inset: 0; pointer-events: none; }
.top {
position: absolute; top: 34px; left: 42px; right: 42px;
display: flex; align-items: flex-start; justify-content: space-between;
}
.brand { display: flex; align-items: center; gap: 12px; }
.brand-icon {
width: 39px; height: 39px; border: 1px solid rgba(221,230,216,.38);
border-radius: 50%; display: grid; place-items: center;
}
.brand-name { font-size: 10px; line-height: 1.5; letter-spacing: .19em; font-weight: 700; }
.top-right { text-align: right; font-size: 9px; line-height: 1.7; letter-spacing: .18em; color: rgba(229,235,224,.65); }
.top-right strong { display: block; color: #e6e8de; font-size: 10px; font-weight: 600; }
.bottom {
position: absolute; left: 42px; right: 42px; bottom: 38px;
display: flex; justify-content: space-between; align-items: flex-end; gap: 20px;
}
.eyebrow { color: #e2a56d; font-size: 9px; font-weight: 700; letter-spacing: .23em; text-transform: uppercase; }
.eyebrow::before { content: ""; display: inline-block; width: 24px; height: 1px; background: #e2a56d; vertical-align: middle; margin-right: 10px; }
h1 { margin: 12px 0 8px; font-family: Georgia, "Times New Roman", serif; font-size: clamp(29px, 3.1vw, 44px); line-height: 1.05; letter-spacing: -.035em; font-weight: 400; text-shadow: 0 2px 18px rgba(0,0,0,.35); }
.caption { margin: 0; font-size: 11px; line-height: 1.6; color: rgba(229,235,224,.67); letter-spacing: .025em; }
.instructions { text-align: right; color: rgba(229,235,224,.7); font-size: 9px; letter-spacing: .14em; line-height: 1.8; text-transform: uppercase; }
.instructions span { display: block; color: #e9e9df; }
@media (max-width: 600px) {
.top { top: 23px; left: 22px; right: 22px; }
.top-right { font-size: 8px; }
.bottom { left: 22px; right: 22px; bottom: 25px; }
.brand-icon { width: 34px; height: 34px; }
.brand-name { font-size: 9px; }
.caption { max-width: 190px; font-size: 10px; }
.instructions { font-size: 8px; }
h1 { font-size: 30px; }
}
</style>
</head>
<body>
<div id="scene"></div>
<div class="overlay">
<div class="top">
<div class="brand">
<div class="brand-icon">
<svg width="23" height="23" viewBox="0 0 24 24" fill="none" aria-hidden="true">
<path d="M12 3 7.9 9h2L6.2 14h2.3L5.7 18h12.6l-2.8-4h2.3l-3.7-5h2L12 3Z" stroke="#dfe8dc" stroke-width="1" stroke-linejoin="round"/>
<path d="M12 18v3" stroke="#dfe8dc" stroke-width="1"/>
</svg>
</div>
<div class="brand-name">THE NIGHT<br>CAMP</div>
</div>
<div class="top-right"><strong>FIELD NOTES / 01</strong> SOMEWHERE IN THE WOODS</div>
</div>
<div class="bottom">
<div>
<div class="eyebrow">A moment in nature</div>
<h1>A little light<br>in the wild.</h1>
<p class="caption">Stay a while. The fire is warm and the night is young.</p>
</div>
<div class="instructions"><span>Explore the clearing</span>Drag to orbit · Scroll to zoom</div>
</div>
</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
const container = document.getElementById('scene');
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x101d29);
scene.fog = new THREE.Fog(0x101d29, 29, 72);
const camera = new THREE.PerspectiveCamera(45, innerWidth / innerHeight, 0.1, 220);
camera.position.set(13, 9, 20);
const renderer = new THREE.WebGLRenderer({ antialias: true });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.05;
container.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 1.8, 0);
controls.enableDamping = true;
controls.dampingFactor = 0.055;
controls.minDistance = 10;
controls.maxDistance = 48;
controls.minPolarAngle = 0.35;
controls.maxPolarAngle = Math.PI / 2 - 0.06;
controls.enablePan = false;
controls.update();
let seed = 86429;
function rand() {
seed = (seed * 1664525 + 1013904223) >>> 0;
return seed / 4294967296;
}
const range = (a, b) => a + rand() * (b - a);
const pick = arr => arr[Math.floor(rand() * arr.length)];
scene.add(new THREE.HemisphereLight(0x8ea9b5, 0x23312b, 1.05));
const moonlight = new THREE.DirectionalLight(0xa6bfda, 1.15);
moonlight.position.set(-12, 22, -15);
scene.add(moonlight);
const fireLight = new THREE.PointLight(0xff792f, 65, 24, 2);
fireLight.position.set(0, 1.5, 0);
fireLight.castShadow = true;
fireLight.shadow.mapSize.set(1024, 1024);
fireLight.shadow.camera.near = 0.2;
fireLight.shadow.camera.far = 25;
fireLight.shadow.bias = -0.002;
fireLight.shadow.normalBias = 0.035;
scene.add(fireLight);
// Faceted forest floor.
const groundGeometry = new THREE.PlaneGeometry(140, 140, 32, 32).toNonIndexed();
groundGeometry.rotateX(-Math.PI / 2);
const groundColors = [];
const groundBase = new THREE.Color();
for (let i = 0; i < groundGeometry.attributes.position.count; i += 3) {
groundBase.setHex(pick([0x263b36, 0x293f39, 0x2a3d37, 0x30433a, 0x263b39]));
groundBase.multiplyScalar(range(0.91, 1.09));
for (let j = 0; j < 3; j++) groundColors.push(groundBase.r, groundBase.g, groundBase.b);
}
groundGeometry.setAttribute('color', new THREE.Float32BufferAttribute(groundColors, 3));
const ground = new THREE.Mesh(groundGeometry, new THREE.MeshStandardMaterial({ vertexColors: true, roughness: 1, flatShading: true }));
ground.position.y = -0.12;
ground.receiveShadow = true;
scene.add(ground);
// An irregular earthen clearing, fading into the forest floor.
const segments = 36;
const radii = [0, 2.1, 4.2, 6.3, 8.1];
const ringColors = [0x625440, 0x5b5041, 0x494b3d, 0x35483c, 0x293f39];
const positions = [], colors = [], indices = [];
for (let r = 0; r < radii.length; r++) {
for (let i = 0; i < segments; i++) {
const angle = i / segments * Math.PI * 2;
const radius = radii[r] * (r === 0 ? 0 : range(0.93, 1.07));
positions.push(Math.cos(angle) * radius, -0.105 + r * 0.0003, Math.sin(angle) * radius);
const c = new THREE.Color(ringColors[r]).multiplyScalar(range(0.92, 1.08));
colors.push(c.r, c.g, c.b);
}
}
for (let r = 0; r < radii.length - 1; r++) {
for (let i = 0; i < segments; i++) {
const a = r * segments + i, b = r * segments + (i + 1) % segments;
const c = (r + 1) * segments + i, d = (r + 1) * segments + (i + 1) % segments;
indices.push(a, c, b, b, c, d);
}
}
const clearingGeo = new THREE.BufferGeometry();
clearingGeo.setAttribute('position', new THREE.Float32BufferAttribute(positions, 3));
clearingGeo.setAttribute('color', new THREE.Float32BufferAttribute(colors, 3));
clearingGeo.setIndex(indices);
clearingGeo.computeVertexNormals();
const clearing = new THREE.Mesh(clearingGeo, new THREE.MeshStandardMaterial({ vertexColors: true, roughness: 1 }));
clearing.receiveShadow = true;
scene.add(clearing);
// Stars distributed over the upper hemisphere.
const starPositions = [], starColors = [];
for (let i = 0; i < 420; i++) {
const a = range(0, Math.PI * 2);
const elevation = Math.asin(range(0.025, 0.98));
const radius = 135;
starPositions.push(Math.cos(a) * Math.cos(elevation) * radius, Math.sin(elevation) * radius + 2, Math.sin(a) * Math.cos(elevation) * radius);
const brightness = range(0.45, 1);
starColors.push(brightness * 0.86, brightness * 0.93, brightness);
}
const starGeo = new THREE.BufferGeometry();
starGeo.setAttribute('position', new THREE.Float32BufferAttribute(starPositions, 3));
starGeo.setAttribute('color', new THREE.Float32BufferAttribute(starColors, 3));
const stars = new THREE.Points(starGeo, new THREE.PointsMaterial({ size: 1.7, sizeAttenuation: false, vertexColors: true, transparent: true, opacity: 0.85, fog: false }));
scene.add(stars);
// Painted moon and soft halo, made locally on a canvas.
const moonCanvas = document.createElement('canvas');
moonCanvas.width = moonCanvas.height = 256;
const mc = moonCanvas.getContext('2d');
const halo = mc.createRadialGradient(128, 128, 24, 128, 128, 125);
halo.addColorStop(0, 'rgba(231,224,187,0.34)');
halo.addColorStop(0.35, 'rgba(217,223,206,0.12)');
halo.addColorStop(1, 'rgba(190,210,224,0)');
mc.fillStyle = halo;
mc.fillRect(0, 0, 256, 256);
mc.beginPath(); mc.arc(128, 128, 31, 0, Math.PI * 2); mc.fillStyle = '#e8e3c9'; mc.fill();
mc.fillStyle = 'rgba(144,151,137,.24)';
for (const [x,y,r] of [[116,118,5],[137,109,3],[141,132,6],[120,139,4],[131,124,2],[113,130,2]]) {
mc.beginPath(); mc.arc(x,y,r,0,Math.PI*2); mc.fill();
}
const moonTexture = new THREE.CanvasTexture(moonCanvas);
moonTexture.colorSpace = THREE.SRGBColorSpace;
const moon = new THREE.Sprite(new THREE.SpriteMaterial({ map: moonTexture, transparent: true, depthWrite: false, fog: false }));
moon.position.set(-17, 11, -45);
moon.scale.set(12, 12, 1);
scene.add(moon);
// Reusable low-poly trees.
const trunkGeo = new THREE.CylinderGeometry(0.72, 1, 1, 7);
const foliageGeo = new THREE.ConeGeometry(1, 1, 7);
const barkMats = [0x423a30, 0x4a4032, 0x37362e].map(c => new THREE.MeshStandardMaterial({ color: c, roughness: 1, flatShading: true }));
const foliageMats = [0x24483e, 0x2b5043, 0x1c3d38, 0x315447, 0x25443c, 0x304d43].map(c => new THREE.MeshStandardMaterial({ color: c, roughness: 1, flatShading: true }));
function addTree(x, z, scale) {
const group = new THREE.Group();
group.position.set(x, -0.1, z);
group.rotation.y = range(0, Math.PI * 2);
const trunk = new THREE.Mesh(trunkGeo, pick(barkMats));
trunk.scale.set(0.33 * scale, 4.4 * scale, 0.33 * scale);
trunk.position.y = 2.2 * scale;
trunk.castShadow = true;
trunk.receiveShadow = true;
group.add(trunk);
const mat = pick(foliageMats);
const tiers = [
[2.0, 3.2, 2.0],
[3.25, 3.25, 1.72],
[4.55, 3.0, 1.38],
[5.65, 2.65, 1.02]
];
for (let i = 0; i < tiers.length; i++) {
const [bottom, height, width] = tiers[i];
const cone = new THREE.Mesh(foliageGeo, i === 0 && rand() < 0.3 ? pick(foliageMats) : mat);
cone.position.y = (bottom + height / 2) * scale;
cone.scale.set(width * scale, height * scale, width * scale);
cone.rotation.y = i * 0.23;
cone.castShadow = true;
cone.receiveShadow = true;
group.add(cone);
}
scene.add(group);
}
let treeCount = 0, attempts = 0;
while (treeCount < 72 && attempts++ < 1000) {
const angle = range(0, Math.PI * 2);
const radius = treeCount < 28 ? range(9, 17) : range(17, 34);
const x = Math.cos(angle) * radius, z = Math.sin(angle) * radius;
// Leave an opening towards the initial camera.
const front = x * 0.545 + z * 0.839;
const across = x * 0.839 - z * 0.545;
if (front > 4 && Math.abs(across) < (radius < 19 ? 7.8 : 5.5)) continue;
addTree(x, z, range(0.82, 1.35));
treeCount++;
}
// Rock ring and scattered stones.
const stoneGeo = new THREE.IcosahedronGeometry(1, 0);
const stoneMats = [0x666a60, 0x757468, 0x5b635d, 0x858074].map(c => new THREE.MeshStandardMaterial({ color: c, roughness: 1, flatShading: true }));
function rock(x, z, sx, sy, sz) {
const mesh = new THREE.Mesh(stoneGeo, pick(stoneMats));
mesh.position.set(x, sy * 0.62 - 0.09, z);
mesh.scale.set(sx, sy, sz);
mesh.rotation.set(range(-0.2, 0.2), range(0, 6.28), range(-0.2, 0.2));
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
}
for (let i = 0; i < 12; i++) {
const a = i / 12 * Math.PI * 2 + range(-0.06, 0.06);
const r = range(1.35, 1.52);
rock(Math.cos(a) * r, Math.sin(a) * r, range(0.35, 0.48), range(0.25, 0.37), range(0.32, 0.44));
}
for (let i = 0; i < 24; i++) {
const a = range(0, Math.PI * 2), r = range(3.3, 10);
rock(Math.cos(a) * r, Math.sin(a) * r, range(0.12, 0.38), range(0.09, 0.28), range(0.12, 0.38));
}
// Logs, with visible cut ends.
const logSide = new THREE.MeshStandardMaterial({ color: 0x493126, roughness: 1, flatShading: true });
const logEnd = new THREE.MeshStandardMaterial({ color: 0x90704d, roughness: 1, flatShading: true });
const ringMat = new THREE.MeshStandardMaterial({ color: 0x604934, roughness: 1 });
function addLog(x, z, length, radius, angle, y = radius * 0.7 - 0.06) {
const axis = new THREE.Vector3(Math.cos(angle), 0, Math.sin(angle));
const geo = new THREE.CylinderGeometry(radius * 0.91, radius, length, 8);
const mesh = new THREE.Mesh(geo, [logSide, logEnd, logEnd]);
mesh.position.set(x, y, z);
mesh.quaternion.setFromUnitVectors(new THREE.Vector3(0, 1, 0), axis);
mesh.castShadow = true;
mesh.receiveShadow = true;
scene.add(mesh);
if (radius > 0.27) {
for (const sign of [-1, 1]) {
const ring = new THREE.Mesh(new THREE.TorusGeometry(radius * 0.52, 0.012, 4, 8), ringMat);
ring.position.copy(mesh.position).addScaledVector(axis, sign * (length / 2 + 0.006));
ring.quaternion.setFromUnitVectors(new THREE.Vector3(0, 0, 1), axis);
scene.add(ring);
}
}
}
for (let i = 0; i < 5; i++) addLog(range(-0.18, 0.18), range(-0.18, 0.18), range(2.0, 2.35), 0.19, i * Math.PI / 5, 0.13 + (i % 2) * 0.14);
addLog(-3.9, 1.1, 3.0, 0.38, -0.35);
addLog(4.1, -2.0, 2.7, 0.32, 0.65);
addLog(-5.1, -4.3, 2.0, 0.24, 1.15);
// Little tufts around the clearing.
const grassMat = new THREE.MeshStandardMaterial({ color: 0x52634a, roughness: 1, flatShading: true });
const bladeGeo = new THREE.ConeGeometry(0.08, 0.6, 4);
for (let i = 0; i < 90; i++) {
const a = range(0, Math.PI * 2), r = range(4.2, 12.5);
const x = Math.cos(a) * r, z = Math.sin(a) * r;
const group = new THREE.Group();
group.position.set(x, -0.08, z);
const s = range(0.45, 1.1);
for (let j = 0; j < 3; j++) {
const blade = new THREE.Mesh(bladeGeo, grassMat);
blade.position.set((j - 1) * 0.09 * s, 0.25 * s, 0);
blade.scale.setScalar(s);
blade.rotation.z = (j - 1) * 0.25;
group.add(blade);
}
group.rotation.y = range(0, 6.28);
scene.add(group);
}
// A soft circle of firelight over the ground.
const glowCanvas = document.createElement('canvas');
glowCanvas.width = glowCanvas.height = 256;
const gc = glowCanvas.getContext('2d');
const glow = gc.createRadialGradient(128, 128, 0, 128, 128, 128);
glow.addColorStop(0, 'rgba(255,161,55,.65)');
glow.addColorStop(0.23, 'rgba(240,99,29,.3)');
glow.addColorStop(1, 'rgba(255,100,20,0)');
gc.fillStyle = glow; gc.fillRect(0, 0, 256, 256);
const glowTexture = new THREE.CanvasTexture(glowCanvas);
const glowMesh = new THREE.Mesh(new THREE.PlaneGeometry(12, 12), new THREE.MeshBasicMaterial({
map: glowTexture, transparent: true, opacity: 0.5, depthWrite: false, blending: THREE.AdditiveBlending
}));
glowMesh.rotation.x = -Math.PI / 2;
glowMesh.position.y = -0.085;
scene.add(glowMesh);
// Angular, individually animated flame tongues.
function flameGeometry(width, height, palette) {
const vertices = [], cols = [];
const sides = 7;
const levels = [
[0, 0.58, 0], [0.18, 1, 0.03], [0.43, 0.75, -0.04],
[0.7, 0.43, 0.08], [1, 0, 0.2]
];
function point(level, i) {
const [y, r, offset] = levels[level];
const a = i / sides * Math.PI * 2 + level * 0.13;
return [Math.cos(a) * width * r + offset * width, y * height, Math.sin(a) * width * r];
}
function triangle(a, b, c, color) {
const col = new THREE.Color(color).multiplyScalar(range(0.88, 1.12));
for (const p of [a,b,c]) { vertices.push(...p); cols.push(col.r, col.g, col.b); }
}
for (let l = 0; l < levels.length - 1; l++) {
for (let i = 0; i < sides; i++) {
const next = (i + 1) % sides;
const col = palette[(i + l) % palette.length];
triangle(point(l,i), point(l,next), point(l+1,i), col);
if (l < levels.length - 2) triangle(point(l,next), point(l+1,next), point(l+1,i), col);
}
}
const geo = new THREE.BufferGeometry();
geo.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
geo.setAttribute('color', new THREE.Float32BufferAttribute(cols, 3));
geo.computeVertexNormals();
return geo;
}
const outerMat = new THREE.MeshStandardMaterial({ vertexColors: true, emissive: 0xff5a08, emissiveIntensity: 0.75, roughness: 1, flatShading: true, side: THREE.DoubleSide });
const innerMat = new THREE.MeshStandardMaterial({ vertexColors: true, emissive: 0xffa11c, emissiveIntensity: 1.1, roughness: 1, flatShading: true, side: THREE.DoubleSide });
const flames = [];
function addFlame(x, z, width, height, inner, phase) {
const palette = inner ? [0xffe274, 0xffc341, 0xffe99a] : [0xff6a13, 0xff8a1c, 0xffad29, 0xe94d10];
const mesh = new THREE.Mesh(flameGeometry(width, height, palette), inner ? innerMat : outerMat);
mesh.position.set(x, 0.32, z);
mesh.userData = { phase, x, z };
scene.add(mesh);
flames.push(mesh);
}
addFlame(0, 0, 0.62, 2.75, false, 0);
addFlame(-0.48, 0.05, 0.36, 1.8, false, 1.3);
addFlame(0.43, 0.1, 0.36, 2.05, false, 2.4);
addFlame(0.08, -0.38, 0.34, 2.25, false, 3.1);
addFlame(0.02, 0.2, 0.43, 1.7, true, 0.8);
addFlame(-0.32, 0.29, 0.21, 1.1, true, 2.2);
// Rising ember particles.
const sparkCount = 85;
const sparkPositions = new Float32Array(sparkCount * 3);
const sparkColors = new Float32Array(sparkCount * 3);
const sparkData = [];
for (let i = 0; i < sparkCount; i++) {
sparkData.push({ phase: rand(), speed: range(0.14, 0.29), x: range(-0.48, 0.48), z: range(-0.48, 0.48), drift: range(-0.5, 0.5) });
const c = new THREE.Color(pick([0xffa12f, 0xffd268, 0xff6b25, 0xffe1a0]));
sparkColors.set([c.r, c.g, c.b], i * 3);
}
const sparkGeo = new THREE.BufferGeometry();
sparkGeo.setAttribute('position', new THREE.BufferAttribute(sparkPositions, 3));
sparkGeo.setAttribute('color', new THREE.BufferAttribute(sparkColors, 3));
const sparks = new THREE.Points(sparkGeo, new THREE.PointsMaterial({
size: 0.095, vertexColors: true, transparent: true, opacity: 0.95,
blending: THREE.AdditiveBlending, depthWrite: false, sizeAttenuation: true
}));
scene.add(sparks);
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const t = clock.getElapsedTime();
const flicker = 0.5 + 0.5 * Math.sin(t * 9.1) * Math.sin(t * 13.7);
fireLight.intensity = 55 + flicker * 20;
fireLight.position.x = Math.sin(t * 3.2) * 0.13;
fireLight.position.z = Math.cos(t * 2.8) * 0.12;
glowMesh.material.opacity = 0.42 + flicker * 0.12;
for (const flame of flames) {
const p = flame.userData.phase;
flame.scale.y = 0.91 + 0.12 * Math.sin(t * 7 + p) + 0.05 * Math.sin(t * 13 + p);
flame.scale.x = 0.96 + 0.07 * Math.sin(t * 6 + p);
flame.rotation.z = 0.08 * Math.sin(t * 4.5 + p);
flame.rotation.y = 0.18 * Math.sin(t * 2.3 + p);
}
for (let i = 0; i < sparkCount; i++) {
const d = sparkData[i];
const life = (d.phase + t * d.speed) % 1;
sparkPositions[i * 3] = d.x * (0.6 + life) + d.drift * life + Math.sin(t * 2 + i) * life * 0.15;
sparkPositions[i * 3 + 1] = 0.65 + life * 5.4;
sparkPositions[i * 3 + 2] = d.z * (0.6 + life) + Math.cos(t * 1.7 + i) * life * 0.2;
}
sparkGeo.attributes.position.needsUpdate = true;
controls.update();
renderer.render(scene, camera);
}
animate();
window.addEventListener('resize', () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
});
</script>
</body>
</html>Reasoning trace (4,327 tokens)
**Creating a forest scene** I need to craft an HTML scene using import maps exactly as specified. The composition should be visually striking at 1280x800 and also mobile-friendly. I'm aiming for a low poly diorama of a forest clearing at night, complete with the moon, stars, animated fire sparks, and fog. I’ll use OrbitControls and focus on thoughtful UI overlay design that won't obstruct the view. Additionally, I need to ensure proper positioning of the moon and stars, create ambient lighting, maintain camera angles, and design realistic yet stylized fire.**Designing terrain and fire area** I’m focusing on creating a central fire ring made of rocks, arranged radially, with logs crisscrossed around it. I want to include ground details like rocks, logs, grass tufts, and ferns while placing 30 trees more than 6.5 units away. To avoid camera obstructions, I'm planning to position the trees strategically around the scene edges. The background will be a dark navy, and the ground a desaturated green, with a cool palette contrasting with orange flames. I'll build the terrain as an irregular mesh with vertex colors for texture.**Creating a ground patch** I’m planning to clear a central circular patch using custom geometry, featuring flattened brown colors with irregular triangular rings. The ground plane needs to accommodate trees, so I'll shape the clearing with a radius of 6.8 units and blend organic edges with multiple rings of triangular shapes in a random brown-gray palette. To ensure a seamless look, I might overlay jagged geometry onto the flat terrain without any visual gaps. Using circles for the ground triangles could also enhance the aesthetic of the rings.**Analyzing camera angles and positions** I'm considering the relative camera setup with a horizontal distance of 70 and an elevation of about 12 degrees. This means I'm looking at a view angled roughly 32 degrees above. I need to think about fire horizons and make adjustments based on moon positioning. If I lower the camera height and tweak the target, I might get a good framing of distant trees and maintain a pleasing horizon. Displaying the top edge is crucial while managing the angles.**Designing a moon appearance** I'm working on creating the moon's appearance in a 3D scene, ensuring it sits near the top of the field of view. The x position looks good, and I might need to tweak the target height to enhance visibility. To depict the moon, I'm considering using MeshBasicMaterial in muted, warm tones without dark patches. A canvas texture seems like the way to go, with a sprite featuring a gradient. I'll explore overlays of crater discs to enhance its look while following the no external image rule.**Designing a scene** I'm thinking about how to set up a moon texture with coordinates (-17, 16, -42) and a sprite size of (9, 9). The moon disk radius comes out to 1.4. I'll add some spots to the moon texture and generate stars using a random geometry sphere, ensuring they’re not occluded by the ground. The fog density needs some tweaking, and I'll also adjust the tree varieties to create a more natural look with varying heights and overlapping foliage.**Configuring the environment** I'm planning the ground setup with a radius of 7.5, positioning 54 trees on four concentric arcs in a radial layout. I want to avoid blocking the center region when viewed from the camera. I’ll set trees 10-15 units away to cast silhouettes and add more in the background while controlling their spacing. For lighting, I'll use a combination of ambient lights, directional moonlight, and a warm point light to create atmosphere. I’ll ensure the trunks are visible at the ground level.**Calculating projection factors** I'm considering the point size in the world, which is 0.08, but at the camera with 23 projection pixels, it creates a field of view factor of about 3.2. It’s working out well! Each spark has a velocity that’s deterministic. I’m updating the height to 5.2, and then there’s the flicker and motion effects. I want to add some small ember points near the fire to enhance the visual effects.**Generating geometry for rings** I'm looking to create an indexed geometry with positions and colors for a radial arrangement. I’ll be using ring radii of 0, 2, 4.5, 6.5, and 8, along with specific inner and outer colors for each ring. I plan on using 32 segments and generating faces with a consistent winding. I'll also need to compute normals, making sure the colors have slight variations, and use vertex colors for the material. Let's get started on this!**Visualizing tree structure** I'm considering a tree design with three cones positioned at different Y levels. The broad shape of the pine cone would taper slightly in between. The lower foliage starts around Y 2.4, with a tier height of 3, while the middle cone sits at Y 3.6, and the upper cone reaches Y 5.0. I need to position the centers of the cones by calculating the bottom plus height/2. The total tree height would be 8.0, with varying foliage and a trunk radius between 0.22 and 0.35.**Creating a moon texture** I’m working on adding a pale moon texture to a canvas that’s 256 pixels. The glow extends around it, and the disc radius is set to 31 pixels, which feels like a nice size. I’ve got to include craters to make it more realistic. The size is noted as 11, which translates to a diameter of approximately 2.66. This should create a nice visual effect!