GPT-6 Luna Pro
by OpenAIreasoning defaultClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 2 min 41 s
- Cost
- $0.012
- Output tokens
- 20,263
- Spent thinking
- 8,445 tokens
- Speed
- 580.2 tok/s
- Console errors
- 0
- First token after
- 2 min 6 s
- First visible text after
- 2 min 40 s
- Prompt tokens
- 22,028
- Code size
- 20.1 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6-luna-pro- Reasoning sent
model default- Max output
- 128,000 tokens
- Generated
- Nov 13, 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 · 497 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Stillwater — A Forest Campfire</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; background: #081018; }
body { font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif; color: #f2ead8; }
#scene { position: fixed; inset: 0; }
#scene canvas { display: block; width: 100%; height: 100%; }
.hud {
position: fixed; z-index: 2; inset: 0; pointer-events: none;
padding: 30px 34px 26px;
display: flex; flex-direction: column; justify-content: space-between;
background: linear-gradient(180deg, rgba(5,12,17,.48), transparent 24%, transparent 70%, rgba(5,12,17,.28));
}
.top { display: flex; align-items: flex-start; justify-content: space-between; gap: 16px; }
.brand { display: flex; align-items: flex-start; gap: 13px; }
.mark {
width: 34px; height: 34px; border: 1px solid rgba(237,184,112,.56);
border-radius: 50%; display: grid; place-items: center; color: #e5a969;
font-size: 17px; line-height: 1; box-shadow: 0 0 22px rgba(231,144,67,.12);
}
.eyebrow { font-size: 9px; letter-spacing: .25em; text-transform: uppercase; color: #d1a775; margin: 1px 0 7px; }
h1 { font-weight: 400; font-size: 21px; letter-spacing: .015em; line-height: 1; margin: 0; }
.location {
display: flex; align-items: center; gap: 8px; margin-top: 4px; padding: 9px 12px;
border: 1px solid rgba(226,232,226,.13); border-radius: 20px;
background: rgba(11,19,23,.36); color: rgba(236,238,226,.76);
font-size: 9px; letter-spacing: .14em; text-transform: uppercase; backdrop-filter: blur(8px);
}
.live-dot { width: 6px; height: 6px; border-radius: 50%; background: #e6a25d; box-shadow: 0 0 9px #e6a25d; }
.bottom { display: flex; align-items: flex-end; justify-content: space-between; gap: 16px; }
.caption { max-width: 310px; }
.caption-title { color: #e6bd8c; font-size: 9px; letter-spacing: .22em; text-transform: uppercase; margin-bottom: 6px; }
.caption p { margin: 0; color: rgba(230,233,224,.68); font-size: 12px; line-height: 1.55; }
.controls {
padding: 9px 13px; border-radius: 18px; background: rgba(9,17,21,.46);
border: 1px solid rgba(226,232,226,.12); color: rgba(235,235,222,.72);
font-size: 9px; letter-spacing: .12em; text-transform: uppercase; backdrop-filter: blur(7px);
}
@media (max-width: 600px) {
.hud { padding: 21px 18px 19px; }
.mark { width: 30px; height: 30px; }
h1 { font-size: 18px; }
.location { font-size: 8px; padding: 8px 10px; }
.caption { max-width: 235px; }
.caption p { font-size: 11px; }
.controls { font-size: 8px; padding: 8px 10px; text-align: center; }
}
@media (max-width: 410px) {
.hud { padding: 17px 14px 16px; }
.location { letter-spacing: .08em; gap: 6px; }
.bottom { align-items: flex-end; }
.caption { max-width: 190px; }
.controls { max-width: 130px; line-height: 1.6; }
}
</style>
</head>
<body>
<div id="scene"></div>
<div class="hud">
<div class="top">
<div class="brand">
<div class="mark">✳</div>
<div>
<div class="eyebrow">A little wilderness</div>
<h1>Stillwater Camp</h1>
</div>
</div>
<div class="location"><span class="live-dot"></span> Nightfall <span style="opacity:.38">/</span> North woods</div>
</div>
<div class="bottom">
<div class="caption">
<div class="caption-title">The last warm light</div>
<p>Quiet trees, ember-soft air, and a sky full of stars.</p>
</div>
<div class="controls">Drag to look · Scroll to wander</div>
</div>
</div>
<script type="module">
import * as THREE from "three";
import { OrbitControls } from "three/addons/controls/OrbitControls.js";
const host = document.getElementById("scene");
const scene = new THREE.Scene();
const night = new THREE.Color(0x09121a);
scene.background = night;
scene.fog = new THREE.FogExp2(night, 0.017);
const camera = new THREE.PerspectiveCamera(46, innerWidth / innerHeight, 0.1, 180);
camera.position.set(13.5, 10.5, 17.5);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
renderer.setSize(innerWidth, innerHeight);
renderer.outputColorSpace = THREE.SRGBColorSpace;
renderer.toneMapping = THREE.ACESFilmicToneMapping;
renderer.toneMappingExposure = 1.12;
renderer.shadowMap.enabled = true;
renderer.shadowMap.type = THREE.PCFSoftShadowMap;
host.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 1.05, 0);
controls.enableDamping = true;
controls.dampingFactor = 0.055;
controls.minDistance = 7;
controls.maxDistance = 34;
controls.minPolarAngle = 0.16;
controls.maxPolarAngle = 1.48;
controls.autoRotate = true;
controls.autoRotateSpeed = 0.24;
controls.addEventListener("start", () => { controls.autoRotate = false; });
scene.add(new THREE.HemisphereLight(0x829bb6, 0x182018, 0.78));
const moonLight = new THREE.DirectionalLight(0x9eb9e2, 1.05);
moonLight.position.set(-18, 28, -20);
moonLight.target.position.set(0, 0, 0);
scene.add(moonLight, moonLight.target);
moonLight.castShadow = true;
moonLight.shadow.mapSize.set(1024, 1024);
moonLight.shadow.camera.left = -38;
moonLight.shadow.camera.right = 38;
moonLight.shadow.camera.top = 38;
moonLight.shadow.camera.bottom = -38;
moonLight.shadow.bias = -0.0004;
moonLight.shadow.normalBias = 0.035;
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(180, 180),
new THREE.MeshStandardMaterial({ color: 0x17201b, roughness: 1, metalness: 0 })
);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -0.04;
ground.receiveShadow = true;
scene.add(ground);
// A soft, earthy clearing that disappears gently into the forest floor.
const patchCanvas = document.createElement("canvas");
patchCanvas.width = patchCanvas.height = 256;
const patchCtx = patchCanvas.getContext("2d");
const patchGradient = patchCtx.createRadialGradient(128, 128, 12, 128, 128, 128);
patchGradient.addColorStop(0, "rgba(85,69,43,0.46)");
patchGradient.addColorStop(0.55, "rgba(70,62,41,0.32)");
patchGradient.addColorStop(1, "rgba(50,52,36,0)");
patchCtx.fillStyle = patchGradient;
patchCtx.fillRect(0, 0, 256, 256);
const clearing = new THREE.Mesh(
new THREE.CircleGeometry(10, 48),
new THREE.MeshBasicMaterial({ map: new THREE.CanvasTexture(patchCanvas), transparent: true, depthWrite: false })
);
clearing.rotation.x = -Math.PI / 2;
clearing.position.y = 0.006;
scene.add(clearing);
// Small polygonal patches of ground cover soften the edge of the open clearing.
const tuftMat = new THREE.MeshStandardMaterial({ color: 0x34402b, roughness: 1, flatShading: true });
const tuftGeo = new THREE.ConeGeometry(0.14, 0.52, 4);
for (let i = 0; i < 72; i++) {
const a = Math.random() * Math.PI * 2;
const r = 7.2 + Math.random() * 5.5;
const tuft = new THREE.Mesh(tuftGeo, tuftMat);
tuft.position.set(Math.cos(a) * r, 0.19, Math.sin(a) * r);
tuft.rotation.y = Math.random() * Math.PI;
tuft.scale.set(0.5 + Math.random() * 0.8, 0.45 + Math.random() * 0.8, 0.5 + Math.random() * 0.8);
tuft.castShadow = true;
scene.add(tuft);
}
// Forest: a broad, irregular ring leaves the fire and its clearing unobstructed.
const trunkGeo = new THREE.CylinderGeometry(0.72, 1, 1, 6);
const coneGeo = new THREE.ConeGeometry(1, 1, 7, 1);
const trunkMats = [0x392b22, 0x443126, 0x3b3026].map(c =>
new THREE.MeshStandardMaterial({ color: c, roughness: 1, flatShading: true })
);
const pineMats = [0x14271f, 0x1b3025, 0x203629, 0x182a23, 0x24382a].map(c =>
new THREE.MeshStandardMaterial({ color: c, roughness: 1, flatShading: true })
);
let placed = [];
for (let i = 0; i < 58; i++) {
let x, z, r, tries = 0;
do {
const a = Math.random() * Math.PI * 2;
r = 12.2 + Math.random() * 26;
x = Math.cos(a) * r;
z = Math.sin(a) * r;
tries++;
} while (tries < 20 && placed.some(p => Math.hypot(p.x - x, p.z - z) < 2.8));
placed.push({ x, z });
const h = 5.2 + Math.random() * 4.6;
const tree = new THREE.Group();
tree.position.set(x, 0, z);
tree.rotation.y = Math.random() * Math.PI * 2;
const trunkHeight = h * 0.34;
const trunk = new THREE.Mesh(trunkGeo, trunkMats[Math.floor(Math.random() * trunkMats.length)]);
trunk.position.y = trunkHeight * 0.5;
trunk.scale.set(0.13 + h * 0.018, trunkHeight, 0.13 + h * 0.018);
trunk.castShadow = true;
trunk.receiveShadow = true;
tree.add(trunk);
const tiers = [
{ y: h * 0.49, height: h * 0.57, radius: h * 0.285 },
{ y: h * 0.68, height: h * 0.48, radius: h * 0.215 },
{ y: h * 0.84, height: h * 0.38, radius: h * 0.15 }
];
for (const t of tiers) {
const crown = new THREE.Mesh(coneGeo, pineMats[Math.floor(Math.random() * pineMats.length)]);
crown.position.y = t.y;
crown.scale.set(t.radius, t.height, t.radius);
crown.rotation.y = Math.random() * 0.22;
crown.castShadow = true;
crown.receiveShadow = true;
tree.add(crown);
}
scene.add(tree);
}
// A few faceted stones around the fire ring.
const rockGeo = new THREE.DodecahedronGeometry(1, 0);
const rockMats = [0x4c5049, 0x56574e, 0x414942, 0x626056].map(c =>
new THREE.MeshStandardMaterial({ color: c, roughness: 1, flatShading: true })
);
for (let i = 0; i < 15; i++) {
const a = (i / 15) * Math.PI * 2 + (Math.random() - 0.5) * 0.22;
const r = 1.65 + Math.random() * 1.15;
const rock = new THREE.Mesh(rockGeo, rockMats[Math.floor(Math.random() * rockMats.length)]);
rock.position.set(Math.cos(a) * r, 0.16 + Math.random() * 0.12, Math.sin(a) * r);
rock.scale.set(0.28 + Math.random() * 0.34, 0.22 + Math.random() * 0.24, 0.27 + Math.random() * 0.34);
rock.rotation.set(Math.random() * 0.5, Math.random() * 3, Math.random() * 0.4);
rock.castShadow = true;
rock.receiveShadow = true;
scene.add(rock);
}
// Charred logs arranged in a loose star around the heart of the fire.
const logGeo = new THREE.CylinderGeometry(1, 1, 1, 7, 1);
const cutGeo = new THREE.CylinderGeometry(1, 1, 1, 8, 1);
const logMat = new THREE.MeshStandardMaterial({ color: 0x463024, roughness: 1, flatShading: true });
const cutMat = new THREE.MeshStandardMaterial({ color: 0x92704b, roughness: 1, flatShading: true });
for (let i = 0; i < 4; i++) {
const a = i * Math.PI / 4 + 0.2;
const axis = new THREE.Vector3(Math.cos(a), 0, Math.sin(a));
const q = new THREE.Quaternion().setFromUnitVectors(new THREE.Vector3(0, 1, 0), axis);
const center = new THREE.Vector3(-axis.z * 0.14, 0.22, axis.x * 0.14);
const log = new THREE.Mesh(logGeo, logMat);
log.position.copy(center);
log.quaternion.copy(q);
log.scale.set(0.22 + Math.random() * 0.035, 1.16, 0.22 + Math.random() * 0.035);
log.castShadow = true;
log.receiveShadow = true;
scene.add(log);
for (const end of [-1, 1]) {
const cap = new THREE.Mesh(cutGeo, cutMat);
cap.position.copy(center).addScaledVector(axis, end * 1.14);
cap.quaternion.copy(q);
cap.scale.set(0.205, 0.035, 0.205);
cap.castShadow = true;
scene.add(cap);
}
}
// Procedural faceted flames, with vertex colors from ember-red to hot gold.
function makeFlameGeometry(seed = 1) {
const sides = 7;
const rings = [
{ y: 0, r: 0.44 },
{ y: 0.35, r: 0.37 },
{ y: 0.82, r: 0.24 },
{ y: 1.27, r: 0.12 }
];
const positions = [], colors = [], indices = [];
const palette = [
new THREE.Color(0xc32608),
new THREE.Color(0xff4c08),
new THREE.Color(0xffa21b),
new THREE.Color(0xffdf68)
];
for (let j = 0; j < rings.length; j++) {
for (let i = 0; i < sides; i++) {
const a = (i / sides) * Math.PI * 2 + (seed * 0.73);
const wobble = 0.84 + 0.16 * Math.sin(i * 7.3 + seed * 4 + j * 2.2);
positions.push(Math.cos(a) * rings[j].r * wobble, rings[j].y,
Math.sin(a) * rings[j].r * wobble);
const t = Math.min(0.999, j / (rings.length - 0.4));
const color = t < 0.55
? palette[0].clone().lerp(palette[1], t / 0.55)
: palette[1].clone().lerp(palette[3], (t - 0.55) / 0.45);
colors.push(color.r, color.g, color.b);
}
}
const tip = positions.length / 3;
positions.push(0.045 * Math.sin(seed * 8), 1.76, 0.04 * Math.cos(seed * 3));
colors.push(1, 0.77, 0.31);
for (let j = 0; j < rings.length - 1; j++) {
for (let i = 0; i < sides; i++) {
const a = j * sides + i, b = j * sides + (i + 1) % sides;
const c = (j + 1) * sides + i, d = (j + 1) * sides + (i + 1) % sides;
indices.push(a, c, b, b, c, d);
}
}
const last = (rings.length - 1) * sides;
for (let i = 0; i < sides; i++) indices.push(last + i, tip, last + (i + 1) % sides);
const geo = new THREE.BufferGeometry();
geo.setAttribute("position", new THREE.Float32BufferAttribute(positions, 3));
geo.setAttribute("color", new THREE.Float32BufferAttribute(colors, 3));
geo.setIndex(indices);
geo.computeVertexNormals();
return geo;
}
const flameMat = new THREE.MeshStandardMaterial({
vertexColors: true, color: 0xffffff, emissive: 0xff4b08, emissiveIntensity: 0.72,
roughness: 0.75, flatShading: true, side: THREE.DoubleSide
});
const flameSpecs = [
{ x: 0, z: 0, s: 1.0 },
{ x: -0.19, z: 0.06, s: 0.73 },
{ x: 0.2, z: -0.08, s: 0.67 },
{ x: 0.02, z: 0.19, s: 0.56 }
];
const flames = [];
flameSpecs.forEach((spec, i) => {
const mesh = new THREE.Mesh(makeFlameGeometry(i + 1), flameMat);
mesh.position.set(spec.x, 0.27, spec.z);
mesh.scale.set(spec.s, spec.s, spec.s);
mesh.rotation.y = i * 1.73;
mesh.castShadow = true;
scene.add(mesh);
flames.push({ mesh, spec, phase: i * 1.8 });
});
// A hot ember bed beneath the dancing flames.
const ember = new THREE.Mesh(
new THREE.IcosahedronGeometry(0.36, 1),
new THREE.MeshStandardMaterial({ color: 0x7f250d, emissive: 0xff3206, emissiveIntensity: 1.1, flatShading: true, roughness: 1 })
);
ember.position.set(0, 0.27, 0);
ember.scale.set(1.35, 0.45, 1.1);
scene.add(ember);
const fireLight = new THREE.PointLight(0xff8b37, 48, 19, 2);
fireLight.position.set(0, 2.1, 0);
fireLight.castShadow = true;
fireLight.shadow.mapSize.set(512, 512);
fireLight.shadow.camera.near = 0.15;
fireLight.shadow.bias = -0.001;
fireLight.shadow.normalBias = 0.025;
scene.add(fireLight);
// Simple rising ember particle system.
const sparkCount = 82;
const sparkPositions = new Float32Array(sparkCount * 3);
const sparkColors = new Float32Array(sparkCount * 3);
const sparkVelocity = [];
const sparkLife = [];
for (let i = 0; i < sparkCount; i++) {
sparkPositions[i * 3] = (Math.random() - 0.5) * 0.8;
sparkPositions[i * 3 + 1] = Math.random() * 2.8;
sparkPositions[i * 3 + 2] = (Math.random() - 0.5) * 0.8;
const c = new THREE.Color().setHSL(0.075 + Math.random() * 0.04, 0.95, 0.55 + Math.random() * 0.2);
sparkColors[i * 3] = c.r; sparkColors[i * 3 + 1] = c.g; sparkColors[i * 3 + 2] = c.b;
sparkVelocity.push({ x: (Math.random() - 0.5) * 0.35, y: 0.5 + Math.random() * 1.25, z: (Math.random() - 0.5) * 0.35 });
sparkLife.push(Math.random() * 3.3);
}
const sparkGeo = new THREE.BufferGeometry();
sparkGeo.setAttribute("position", new THREE.BufferAttribute(sparkPositions, 3));
sparkGeo.setAttribute("color", new THREE.BufferAttribute(sparkColors, 3));
const sparkCanvas = document.createElement("canvas");
sparkCanvas.width = sparkCanvas.height = 64;
const sparkCtx = sparkCanvas.getContext("2d");
const sparkGrad = sparkCtx.createRadialGradient(32, 32, 0, 32, 32, 32);
sparkGrad.addColorStop(0, "rgba(255,255,220,1)");
sparkGrad.addColorStop(0.22, "rgba(255,193,70,.95)");
sparkGrad.addColorStop(1, "rgba(255,92,14,0)");
sparkCtx.fillStyle = sparkGrad;
sparkCtx.fillRect(0, 0, 64, 64);
const sparks = new THREE.Points(sparkGeo, new THREE.PointsMaterial({
size: 0.2, map: new THREE.CanvasTexture(sparkCanvas), vertexColors: true,
transparent: true, depthWrite: false, blending: THREE.AdditiveBlending, sizeAttenuation: true
}));
sparks.frustumCulled = false;
scene.add(sparks);
// Moon and its subtle halo.
const moon = new THREE.Mesh(
new THREE.SphereGeometry(1.72, 16, 12),
new THREE.MeshStandardMaterial({ color: 0xd7e2f0, emissive: 0x9bbbe8, emissiveIntensity: 1.15, roughness: 1, flatShading: true })
);
moon.position.set(-27, 25, -38);
scene.add(moon);
const haloCanvas = document.createElement("canvas");
haloCanvas.width = haloCanvas.height = 128;
const haloCtx = haloCanvas.getContext("2d");
const haloGrad = haloCtx.createRadialGradient(64, 64, 8, 64, 64, 64);
haloGrad.addColorStop(0, "rgba(182,207,245,.24)");
haloGrad.addColorStop(0.38, "rgba(144,180,226,.10)");
haloGrad.addColorStop(1, "rgba(116,151,205,0)");
haloCtx.fillStyle = haloGrad;
haloCtx.fillRect(0, 0, 128, 128);
const halo = new THREE.Sprite(new THREE.SpriteMaterial({
map: new THREE.CanvasTexture(haloCanvas), transparent: true, depthWrite: false,
blending: THREE.AdditiveBlending, opacity: 0.9
}));
halo.position.copy(moon.position);
halo.scale.set(9.5, 9.5, 1);
scene.add(halo);
// Star field, placed on a distant celestial shell.
const starPositions = [];
for (let i = 0; i < 650; i++) {
const y = Math.random() * 0.94 + 0.06;
const a = Math.random() * Math.PI * 2;
const r = 78 + Math.random() * 18;
const horizontal = Math.sqrt(1 - y * y);
starPositions.push(Math.cos(a) * horizontal * r, y * r, Math.sin(a) * horizontal * r);
}
const starGeo = new THREE.BufferGeometry();
starGeo.setAttribute("position", new THREE.Float32BufferAttribute(starPositions, 3));
const starMat = new THREE.PointsMaterial({
color: 0xcbd8ed, size: 0.17, sizeAttenuation: true, transparent: true,
opacity: 0.86, depthWrite: false, fog: false
});
scene.add(new THREE.Points(starGeo, starMat));
// A few larger pinprick stars give the sky some depth.
const brightPositions = [];
for (let i = 0; i < 44; i++) {
const y = Math.random() * 0.88 + 0.12;
const a = Math.random() * Math.PI * 2;
const r = 75 + Math.random() * 14;
const h = Math.sqrt(1 - y * y);
brightPositions.push(Math.cos(a) * h * r, y * r, Math.sin(a) * h * r);
}
const brightGeo = new THREE.BufferGeometry();
brightGeo.setAttribute("position", new THREE.Float32BufferAttribute(brightPositions, 3));
scene.add(new THREE.Points(brightGeo, new THREE.PointsMaterial({
color: 0xf4dfb9, size: 0.28, sizeAttenuation: true, transparent: true, opacity: 0.8, depthWrite: false, fog: false
})));
const clock = new THREE.Clock();
function animate() {
requestAnimationFrame(animate);
const t = clock.getElapsedTime();
controls.update();
for (const flame of flames) {
const wobble = Math.sin(t * 8.4 + flame.phase) * 0.075 + Math.sin(t * 13.1 + flame.phase * 1.7) * 0.035;
flame.mesh.scale.y = flame.spec.s * (1 + wobble);
flame.mesh.scale.x = flame.spec.s * (1 - wobble * 0.28);
flame.mesh.rotation.y += 0.004 + Math.sin(t * 3 + flame.phase) * 0.0015;
flame.mesh.position.x = flame.spec.x + Math.sin(t * 4.2 + flame.phase) * 0.025;
}
fireLight.intensity = 45 + Math.sin(t * 10.5) * 5.5 + Math.sin(t * 17.2) * 2.8;
fireLight.position.y = 2.05 + Math.sin(t * 7.5) * 0.08;
ember.material.emissiveIntensity = 0.85 + Math.sin(t * 8) * 0.18;
for (let i = 0; i < sparkCount; i++) {
const p = i * 3;
const v = sparkVelocity[i];
sparkLife[i] += 0.016;
sparkPositions[p] += v.x * 0.016 + Math.sin(t * 2 + i) * 0.0015;
sparkPositions[p + 1] += v.y * 0.016;
sparkPositions[p + 2] += v.z * 0.016 + Math.cos(t * 1.7 + i) * 0.0015;
if (sparkLife[i] > 2.4 + (i % 7) * 0.22 || sparkPositions[p + 1] > 4.7) {
sparkPositions[p] = (Math.random() - 0.5) * 0.72;
sparkPositions[p + 1] = 0.3 + Math.random() * 0.25;
sparkPositions[p + 2] = (Math.random() - 0.5) * 0.72;
v.x = (Math.random() - 0.5) * 0.35;
v.y = 0.65 + Math.random() * 1.1;
v.z = (Math.random() - 0.5) * 0.35;
sparkLife[i] = 0;
}
}
sparkGeo.attributes.position.needsUpdate = true;
starMat.opacity = 0.78 + Math.sin(t * 0.55) * 0.07;
renderer.render(scene, camera);
}
animate();
addEventListener("resize", () => {
camera.aspect = innerWidth / innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(innerWidth, innerHeight);
renderer.setPixelRatio(Math.min(devicePixelRatio, 2));
});
</script>
</body>
</html>Reasoning trace (8,445 tokens)
**Designing a forest scene** I'm visualizing a forest clearing with trees randomly seeded in a ring, approximately 10-40 feet apart. The tree trunks have low-poly cone shapes, and I need to distribute them randomly within a radius of 10 to 36 feet, possibly framing one side. There's a small fire at the center with logs and a few rocks around it. I'll also consider creating a ground plane with subdivided textures, incorporating dark green and dark brown elements, while ensuring it maintains a night aesthetic with proper lighting.**Creating flame geometry** I'm designing a flame effect using three cone geometries, applying vertex colors based on height. I'll use meshStandardMaterial to incorporate vertex colors and possibly an emissive effect. The color gradient will transition from orange at the base to yellow and then to a darker hue. I’m unsure if I should make the flame material transparent or use a polygon style. Each custom flame shape will have varying ring heights, with triangular segments and bent tips. I'll ensure to use flat shading for added depth.**Creating a night scene** I’m focusing on a scene with star points and a moon positioned at specific coordinates. It’s important to get the camera orientation right so it captures everything visible. I want to add an overlay with a subtle title at the top left for "NIGHTFALL / CAMP 07" and some tips at the bottom. I need to include controls since the scene should be more of a self-playing demo. I'll also work on light settings for true shadows and a flickering fire effect.**Setting up the fire scene** I'm considering adding a separate warm fire point to enhance the texture of the scene. The camera position should be at coordinates (13, 10, 17), with a radius of 22 and a field of view (FOV) of 48. I might aim for a target height of around 2.5. The forest should surround the area, with fog starting to appear between radii 24 to 70, and I'll keep the fire prominently in the center of the foreground. It’s crucial to maintain visibility of the trees!**Creating tree geometry** I'm working on rendering trees with distant fog. Each tree has three tiers of conical layers created using ConeGeometry with specific radius and height values, positioned so the trunk is half the height. I wonder if the trees should lean randomly, but then realize that's not necessary. The stylized pine arrangement I've planned has a total height of 5-10, with trunk height adjusted accordingly. It's important to keep the design consistent!**Building flame geometry** I'm designing custom flame geometry with radial rings. I think I’ll have 7 vertices per ring, with the base at y=0 and radius of 0.50, a middle ring at y=0.55 with radius 0.32, then another at y=1.1 with radius 0.17, and finally, a tip at y=1.75 with a radius of 0. I’ll generate just three levels of rings, using indexed triangles and vertex colors, while ensuring proper shading and color gradients.**Configuring star visuals** I'm working on a scene to display 600 stars in a hemisphere, aiming for a uniformly distributed sphere with a radius of 70 units, positioning it so that the y-axis is positive. I'll use a points material with a size of .12 and an opacity of .8 for transparency. I'm a bit unsure about whether to apply size attenuation, but it seems like a size of .13 might be better. The camera will be positioned far away to depict a high-density star field.**Refining Fire Log Animation** I think the fire logs shouldn't cover the base too much; I want sparks visible above the fire. I'm considering adding 70 points since color on points with size attenuation might have an effect. For the flames, I'm thinking about a blinking animation without relying on shaders, using custom geometry that's static with scaled dimensions and rotated positions. It seems I need to create the flames with good geometry color and ensure dynamic vertices have a conical appearance. I should also use a radial gradient for the canvas texture. Let's get coding!**Creating Circle Geometry** I’m planning to use circle geometry with 24 segments to create a shape. The CircleGeometry will have a radius of 9.7 and will be rotated flat with a color of #1a2019. To blend the disk's edge, I might try using two overlapping rings with different opacities. For the disk color, it should be dark with a hard border. Using a canvas texture for a radial gradient sounds like a good approach, ensuring I set up the materials properly with transparency and vertex colors. I need to confirm that the renderer supports vertexAlphas as true!**Designing Canvas Texture** I'm creating a canvas texture with gradient stops, using rgba(65, 55, 34, 0.48) at the center, transitioning to transparent. This should ensure that the ground below remains unaffected, which is nice! For the disk, I'm thinking about a radius of 9.5 and will set a base ground color. It feels like I’m on the right path to achieving the visual effect I want! Now, I'll move forward with implementing this design.**Considering landscape rendering settings** I'm focused on crafting a landscape ground plane with 180 dimensions, ensuring I handle the background and fog properly. I need to address the display pixel ratio (DPR) and resize accordingly. I’m opting for an sRGB output color space, using ACES for tone mapping with an exposure of 1.1 since ACES can make scenes appear darker. I'll ensure the renderer outputs in sRGB and use soft PCF shadows. Let’s get this to production!