GPT-6 Luna
by OpenAIReasoningClean
Running live in a sandboxed frame. Click inside it before using the keyboard.
How it went
- Total time
- 7 min 13 s
- Cost
- $0.016
- Output tokens
- 31,806
- Spent thinking
- 23,168 tokens
- Speed
- 75 tok/s
- Console errors
- 0
- First token after
- 9.5 s
- First visible text after
- 5 min 53 s
- Prompt tokens
- 555
- Code size
- 28.1 KB
- Finish reason
- stop
- Served by
- OpenAI
- Model id
openai/gpt-6-luna- Reasoning sent
{"effort":"xhigh"}- Max output
- 64,000 tokens
- Generated
- Nov 10, 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 · 890 lines
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8" />
<meta name="viewport" content="width=device-width, initial-scale=1.0" />
<title>The Quiet Ember — Night Study</title>
<style>
:root {
color-scheme: dark;
--ivory: #eee9dc;
--muted: #abb2ac;
--ember: #f0a45b;
}
* { box-sizing: border-box; }
html, body {
width: 100%;
height: 100%;
margin: 0;
overflow: hidden;
background: #080f12;
}
body {
font-family: Inter, ui-sans-serif, system-ui, -apple-system, BlinkMacSystemFont, "Segoe UI", sans-serif;
}
canvas {
position: fixed;
inset: 0;
display: block;
width: 100%;
height: 100%;
}
.vignette {
position: fixed;
inset: 0;
pointer-events: none;
background:
linear-gradient(180deg, rgba(4, 8, 13, .42) 0%, transparent 22%, transparent 62%, rgba(4, 8, 10, .62) 100%),
radial-gradient(ellipse at center, transparent 42%, rgba(3, 8, 12, .25) 100%);
}
.hud {
position: fixed;
inset: 0;
z-index: 2;
color: var(--ivory);
pointer-events: none;
user-select: none;
text-shadow: 0 2px 14px rgba(0, 0, 0, .7);
}
.topbar {
position: absolute;
top: 28px;
left: 34px;
right: 34px;
display: flex;
align-items: center;
justify-content: space-between;
}
.identity {
display: flex;
align-items: center;
gap: 12px;
}
.mark {
width: 32px;
height: 32px;
display: grid;
place-items: center;
border: 1px solid rgba(238, 233, 220, .34);
border-radius: 50%;
position: relative;
}
.mark::before {
content: "";
width: 8px;
height: 8px;
border-radius: 50%;
background: var(--ember);
box-shadow: 0 0 14px rgba(240, 164, 91, .85);
}
.identity-copy {
display: flex;
flex-direction: column;
gap: 3px;
}
.identity-copy strong,
.status,
.eyebrow,
.instructions {
font-size: 9px;
font-weight: 600;
letter-spacing: .19em;
text-transform: uppercase;
}
.identity-copy strong {
font-size: 10px;
letter-spacing: .2em;
}
.identity-copy span {
color: rgba(238, 233, 220, .56);
font-size: 8px;
letter-spacing: .18em;
text-transform: uppercase;
}
.status {
display: flex;
align-items: center;
gap: 9px;
color: rgba(238, 233, 220, .78);
}
.status-dot {
width: 6px;
height: 6px;
border-radius: 50%;
background: #ed9d58;
box-shadow: 0 0 10px rgba(237, 157, 88, .85);
}
.status-divider {
color: rgba(238, 233, 220, .32);
}
.bottom {
position: absolute;
left: 42px;
right: 42px;
bottom: 34px;
display: flex;
align-items: flex-end;
justify-content: space-between;
}
.eyebrow {
margin: 0 0 10px;
color: var(--ember);
font-size: 9px;
letter-spacing: .24em;
}
h1 {
margin: 0;
color: #f2eee3;
font-family: Georgia, "Times New Roman", serif;
font-size: clamp(34px, 4.3vw, 54px);
line-height: .98;
font-weight: 400;
letter-spacing: -.045em;
}
.subtitle {
margin: 11px 0 0;
color: rgba(238, 233, 220, .7);
font-family: Georgia, "Times New Roman", serif;
font-size: 14px;
font-style: italic;
letter-spacing: .01em;
}
.instructions {
display: flex;
align-items: center;
gap: 10px;
padding-bottom: 5px;
color: rgba(238, 233, 220, .7);
font-size: 8px;
letter-spacing: .16em;
}
.instruction-line {
width: 28px;
height: 1px;
background: rgba(240, 164, 91, .75);
}
@media (max-width: 600px) {
.topbar { top: 20px; left: 20px; right: 20px; }
.bottom { left: 22px; right: 22px; bottom: 25px; }
.status { font-size: 8px; letter-spacing: .12em; }
.status-divider, .status-study { display: none; }
.instructions { font-size: 7px; letter-spacing: .12em; gap: 7px; }
.instruction-line { width: 16px; }
.subtitle { max-width: 250px; font-size: 13px; }
}
@media (max-width: 390px) {
.instructions { max-width: 92px; line-height: 1.7; }
}
</style>
<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>
</head>
<body>
<div class="vignette"></div>
<div class="hud">
<div class="topbar">
<div class="identity">
<div class="mark"></div>
<div class="identity-copy">
<strong>Field Journal · 07</strong>
<span>Wilderness archive</span>
</div>
</div>
<div class="status">
<span class="status-dot"></span>
<span>Night watch</span>
<span class="status-divider">/</span>
<span class="status-study">Pine ridge</span>
</div>
</div>
<div class="bottom">
<div>
<p class="eyebrow">After dark · 12:48 AM</p>
<h1>The Quiet Ember</h1>
<p class="subtitle">A small warmth against an endless forest.</p>
</div>
<div class="instructions">
<span class="instruction-line"></span>
<span>Drag to orbit · Scroll to zoom</span>
</div>
</div>
</div>
<script type="module">
import * as THREE from 'three';
import { OrbitControls } from 'three/addons/controls/OrbitControls.js';
let seed = 730217;
function rand() {
seed = (seed * 1664525 + 1013904223) >>> 0;
return seed / 4294967296;
}
const between = (a, b) => a + (b - a) * rand();
const scene = new THREE.Scene();
scene.background = new THREE.Color(0x080f15);
scene.fog = new THREE.FogExp2(0x081118, 0.018);
const camera = new THREE.PerspectiveCamera(58, innerWidth / innerHeight, 0.1, 240);
camera.position.set(16, 10.5, 21);
const renderer = new THREE.WebGLRenderer({ antialias: true, alpha: false });
renderer.setPixelRatio(Math.min(window.devicePixelRatio || 1, 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;
document.body.appendChild(renderer.domElement);
const controls = new OrbitControls(camera, renderer.domElement);
controls.target.set(0, 1.2, 0);
controls.enableDamping = true;
controls.dampingFactor = 0.055;
controls.enablePan = false;
controls.minDistance = 12;
controls.maxDistance = 76;
controls.minPolarAngle = 0.16;
controls.maxPolarAngle = Math.PI / 2 - 0.035;
controls.autoRotate = true;
controls.autoRotateSpeed = 0.28;
controls.update();
const hemi = new THREE.HemisphereLight(0x8296bd, 0x11170f, 0.82);
scene.add(hemi);
const ambient = new THREE.AmbientLight(0x39443b, 0.38);
scene.add(ambient);
const moonlight = new THREE.DirectionalLight(0x8ca6d2, 0.68);
moonlight.position.set(-32, 43, -20);
moonlight.target.position.set(0, 0, 0);
moonlight.castShadow = true;
moonlight.shadow.mapSize.set(1024, 1024);
moonlight.shadow.camera.left = -48;
moonlight.shadow.camera.right = 48;
moonlight.shadow.camera.top = 48;
moonlight.shadow.camera.bottom = -48;
moonlight.shadow.camera.near = 1;
moonlight.shadow.camera.far = 110;
moonlight.shadow.bias = -0.00035;
moonlight.shadow.normalBias = 0.035;
scene.add(moonlight, moonlight.target);
// A softly graded, low-poly night dome.
const skyGeo = new THREE.SphereGeometry(170, 40, 24);
const skyPos = skyGeo.attributes.position;
const skyColors = new Float32Array(skyPos.count * 3);
const horizonColor = new THREE.Color(0x15202b);
const zenithColor = new THREE.Color(0x050914);
const nadirColor = new THREE.Color(0x080d13);
for (let i = 0; i < skyPos.count; i++) {
const y = skyPos.getY(i) / 170;
const c = y >= 0
? horizonColor.clone().lerp(zenithColor, Math.pow(y, 0.66))
: horizonColor.clone().lerp(nadirColor, Math.min(1, -y * 1.5));
skyColors[i * 3] = c.r;
skyColors[i * 3 + 1] = c.g;
skyColors[i * 3 + 2] = c.b;
}
skyGeo.setAttribute('color', new THREE.BufferAttribute(skyColors, 3));
const sky = new THREE.Mesh(
skyGeo,
new THREE.MeshBasicMaterial({
vertexColors: true,
side: THREE.BackSide,
depthWrite: false,
fog: false,
toneMapped: false
})
);
sky.renderOrder = -100;
scene.add(sky);
// Procedural soft circular texture shared by the moon glow and embers.
function radialTexture() {
const canvas = document.createElement('canvas');
canvas.width = canvas.height = 64;
const ctx = canvas.getContext('2d');
const gradient = ctx.createRadialGradient(32, 32, 1, 32, 32, 32);
gradient.addColorStop(0, 'rgba(255,255,255,1)');
gradient.addColorStop(0.14, 'rgba(255,255,255,.88)');
gradient.addColorStop(0.43, 'rgba(255,255,255,.3)');
gradient.addColorStop(1, 'rgba(255,255,255,0)');
ctx.fillStyle = gradient;
ctx.fillRect(0, 0, 64, 64);
const texture = new THREE.CanvasTexture(canvas);
texture.colorSpace = THREE.SRGBColorSpace;
return texture;
}
const glowTexture = radialTexture();
// Stars sit on a broad upper hemisphere, with a few brighter points.
function makeStars(count, material, radiusMin, radiusMax) {
const positions = new Float32Array(count * 3);
const colors = new Float32Array(count * 3);
const palette = [
new THREE.Color(0xc3d4ff),
new THREE.Color(0xffe5c4),
new THREE.Color(0xe4edff),
new THREE.Color(0x91add8)
];
for (let i = 0; i < count; i++) {
const y = 0.025 + rand() * 0.975;
const angle = rand() * Math.PI * 2;
const r = between(radiusMin, radiusMax);
const horizontal = Math.sqrt(1 - y * y) * r;
positions[i * 3] = Math.cos(angle) * horizontal;
positions[i * 3 + 1] = y * r;
positions[i * 3 + 2] = Math.sin(angle) * horizontal;
const c = palette[Math.floor(rand() * palette.length)];
colors[i * 3] = c.r;
colors[i * 3 + 1] = c.g;
colors[i * 3 + 2] = c.b;
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.BufferAttribute(positions, 3));
geometry.setAttribute('color', new THREE.BufferAttribute(colors, 3));
const points = new THREE.Points(geometry, material);
points.frustumCulled = false;
scene.add(points);
}
const starMaterial = new THREE.PointsMaterial({
size: 0.36,
map: glowTexture,
vertexColors: true,
transparent: true,
opacity: 0.82,
depthWrite: false,
alphaTest: 0.01,
sizeAttenuation: true,
fog: false,
toneMapped: false
});
const brightStarMaterial = new THREE.PointsMaterial({
size: 0.66,
map: glowTexture,
vertexColors: true,
transparent: true,
opacity: 0.92,
depthWrite: false,
alphaTest: 0.01,
sizeAttenuation: true,
fog: false,
toneMapped: false
});
makeStars(440, starMaterial, 100, 125);
makeStars(44, brightStarMaterial, 96, 118);
const moonPosition = new THREE.Vector3(-28, 18, -37);
const moonGlow = new THREE.Sprite(new THREE.SpriteMaterial({
map: glowTexture,
color: 0x91a9da,
transparent: true,
opacity: 0.34,
blending: THREE.AdditiveBlending,
depthWrite: false,
fog: false,
toneMapped: false
}));
moonGlow.position.copy(moonPosition);
moonGlow.scale.set(13, 13, 1);
scene.add(moonGlow);
const moon = new THREE.Mesh(
new THREE.IcosahedronGeometry(1.55, 2),
new THREE.MeshBasicMaterial({
color: 0xd9e3f4,
fog: false,
toneMapped: false
})
);
moon.position.copy(moonPosition);
moon.renderOrder = 1;
scene.add(moon);
// Forest floor and the slightly lighter clearing.
const ground = new THREE.Mesh(
new THREE.PlaneGeometry(190, 190),
new THREE.MeshStandardMaterial({ color: 0x111915, roughness: 1, flatShading: true })
);
ground.rotation.x = -Math.PI / 2;
ground.position.y = -0.06;
ground.receiveShadow = true;
scene.add(ground);
const clearing = new THREE.Mesh(
new THREE.CircleGeometry(12.1, 48),
new THREE.MeshStandardMaterial({ color: 0x24251e, roughness: 1, flatShading: true })
);
clearing.rotation.x = -Math.PI / 2;
clearing.position.y = 0.005;
clearing.receiveShadow = true;
scene.add(clearing);
const patchMats = [
0x171e19, 0x1b211b, 0x1b201c, 0x20231d, 0x161d19
].map(color => new THREE.MeshStandardMaterial({
color, roughness: 1, flatShading: true, side: THREE.DoubleSide
}));
for (let i = 0; i < 54; i++) {
const angle = rand() * Math.PI * 2;
const radius = between(8.5, 66);
const size = between(1.3, 4.1);
const patch = new THREE.Mesh(
new THREE.CircleGeometry(1, 5 + Math.floor(rand() * 4)),
patchMats[Math.floor(rand() * patchMats.length)]
);
patch.rotation.set(-Math.PI / 2, rand() * Math.PI * 2, 0);
patch.position.set(Math.sin(angle) * radius, 0.008 + i * 0.00001, Math.cos(angle) * radius);
patch.scale.set(size * between(0.7, 1.5), size * between(0.7, 1.3), 1);
patch.receiveShadow = true;
scene.add(patch);
}
// Reusable low-poly tree and undergrowth geometry.
const trunkGeometry = new THREE.CylinderGeometry(0.29, 0.4, 1, 5, 1);
const coneGeometry = new THREE.ConeGeometry(1, 1, 6, 1);
const trunkMaterials = [
0x382b20, 0x443328, 0x30291f
].map(color => new THREE.MeshStandardMaterial({ color, roughness: 1, flatShading: true }));
const foliageMaterials = [
0x17352c, 0x1d3c31, 0x204134, 0x254637, 0x19372e, 0x294638
].map(color => new THREE.MeshStandardMaterial({ color, roughness: 0.96, flatShading: true }));
const shrubGeometry = new THREE.IcosahedronGeometry(1, 0);
function addTree(x, z, height) {
const group = new THREE.Group();
group.position.set(x, 0, z);
group.rotation.y = rand() * Math.PI * 2;
scene.add(group);
const trunkHeight = height * between(0.39, 0.46);
const trunk = new THREE.Mesh(trunkGeometry, trunkMaterials[Math.floor(rand() * trunkMaterials.length)]);
trunk.scale.y = trunkHeight;
trunk.position.y = trunkHeight * 0.5;
trunk.castShadow = true;
trunk.receiveShadow = true;
group.add(trunk);
const baseIndex = Math.floor(rand() * foliageMaterials.length);
const tiers = [
{ y: height * 0.39, h: height * 0.49, r: height * between(0.18, 0.23) },
{ y: height * 0.56, h: height * 0.43, r: height * between(0.135, 0.17) },
{ y: height * 0.72, h: height * 0.38, r: height * between(0.095, 0.13) }
];
for (let i = 0; i < tiers.length; i++) {
const tier = tiers[i];
const crown = new THREE.Mesh(coneGeometry, foliageMaterials[(baseIndex + i) % foliageMaterials.length]);
crown.scale.set(tier.r, tier.h, tier.r);
crown.position.set(between(-0.12, 0.12), tier.y, between(-0.12, 0.12));
crown.rotation.y = rand() * Math.PI * 2;
crown.castShadow = true;
crown.receiveShadow = true;
group.add(crown);
}
}
const viewAngle = Math.atan2(camera.position.x, camera.position.z);
let treeCount = 0;
let attempts = 0;
while (treeCount < 62 && attempts < 1200) {
attempts++;
const angle = rand() * Math.PI * 2;
const radius = between(12.7, 43);
const angleDifference = Math.atan2(Math.sin(angle - viewAngle), Math.cos(angle - viewAngle));
// Leave a natural sightline from the camera to the fire.
if (radius < 22 && Math.abs(angleDifference) < 0.43) continue;
const x = Math.sin(angle) * radius;
const z = Math.cos(angle) * radius;
addTree(x, z, between(6.3, 12.4));
treeCount++;
}
// Small clusters of faceted undergrowth soften the tree line.
for (let i = 0; i < 26; i++) {
const angle = rand() * Math.PI * 2;
const radius = between(9.5, 31);
const x = Math.sin(angle) * radius;
const z = Math.cos(angle) * radius;
for (let j = 0; j < 3; j++) {
const scale = between(0.35, 0.82);
const shrub = new THREE.Mesh(
shrubGeometry,
foliageMaterials[Math.floor(rand() * foliageMaterials.length)]
);
shrub.scale.set(scale * between(0.9, 1.5), scale * between(0.7, 1.15), scale);
shrub.position.set(x + between(-0.7, 0.7), shrub.scale.y * 0.68, z + between(-0.7, 0.7));
shrub.rotation.set(rand() * 0.5, rand() * Math.PI * 2, rand() * 0.35);
shrub.castShadow = true;
shrub.receiveShadow = true;
scene.add(shrub);
}
}
// Irregular stones around the fire and scattered through the clearing.
const rockGeometry = new THREE.IcosahedronGeometry(1, 0);
const rockMaterials = [
0x3b403c, 0x4b4b43, 0x343b39, 0x514b40
].map(color => new THREE.MeshStandardMaterial({
color, roughness: 0.96, flatShading: true
}));
function addRock(x, z, sx, sy, sz, rotation = null) {
const rock = new THREE.Mesh(
rockGeometry,
rockMaterials[Math.floor(rand() * rockMaterials.length)]
);
rock.scale.set(sx, sy, sz);
rock.position.set(x, sy * 0.72, z);
if (rotation) rock.rotation.set(rotation[0], rotation[1], rotation[2]);
else rock.rotation.set(rand() * 1.4, rand() * Math.PI, rand() * 1.4);
rock.castShadow = true;
rock.receiveShadow = true;
scene.add(rock);
return rock;
}
for (let i = 0; i < 12; i++) {
const angle = (i / 12) * Math.PI * 2 + between(-0.12, 0.12);
const radius = between(2.0, 2.65);
addRock(
Math.sin(angle) * radius,
Math.cos(angle) * radius,
between(0.34, 0.62),
between(0.27, 0.52),
between(0.32, 0.57)
);
}
for (let i = 0; i < 16; i++) {
const angle = rand() * Math.PI * 2;
const radius = between(7.2, 12.8);
addRock(
Math.sin(angle) * radius,
Math.cos(angle) * radius,
between(0.22, 0.62),
between(0.17, 0.44),
between(0.25, 0.64)
);
}
// Charred logs with lighter cut ends.
const logBark = new THREE.MeshStandardMaterial({
color: 0x493426, roughness: 0.96, flatShading: true
});
const logEnd = new THREE.MeshStandardMaterial({
color: 0x76553a, roughness: 1, flatShading: true
});
function addLog(angle, length, radius, xOffset, zOffset) {
const direction = new THREE.Vector3(Math.sin(angle), 0, Math.cos(angle));
const quaternion = new THREE.Quaternion().setFromUnitVectors(
new THREE.Vector3(0, 1, 0), direction
);
const center = new THREE.Vector3(xOffset, radius * 0.72, zOffset);
const body = new THREE.Mesh(
new THREE.CylinderGeometry(radius * 0.78, radius, length, 7, 1),
logBark
);
body.position.copy(center);
body.quaternion.copy(quaternion);
body.castShadow = true;
body.receiveShadow = true;
scene.add(body);
for (const sign of [-1, 1]) {
const cap = new THREE.Mesh(
new THREE.CylinderGeometry(radius * 0.72, radius * 0.72, 0.045, 7, 1),
logEnd
);
cap.position.copy(center).addScaledVector(direction, sign * (length * 0.5 - 0.025));
cap.quaternion.copy(quaternion);
cap.castShadow = true;
cap.receiveShadow = true;
scene.add(cap);
}
}
addLog(-0.92, 3.7, 0.30, 0.00, 0.02);
addLog(0.30, 3.25, 0.27, -0.04, 0.08);
addLog(1.47, 2.95, 0.25, 0.04, -0.06);
// Warm pool of light on the forest floor.
const fireGlow = new THREE.Mesh(
new THREE.PlaneGeometry(7.4, 7.4),
new THREE.MeshBasicMaterial({
map: glowTexture,
color: 0xff5818,
transparent: true,
opacity: 0.42,
blending: THREE.AdditiveBlending,
depthWrite: false,
side: THREE.DoubleSide,
fog: false,
toneMapped: false
})
);
fireGlow.rotation.x = -Math.PI / 2;
fireGlow.position.y = 0.035;
scene.add(fireGlow);
// Faceted, vertex-colored flame tongues.
function makeFlameGeometry(height, radius, colors) {
const radialSegments = 6;
const rings = [
{ y: 0, r: radius * 0.36, x: 0, z: 0 },
{ y: height * 0.19, r: radius, x: 0, z: 0 },
{ y: height * 0.53, r: radius * 0.72, x: radius * 0.08, z: -radius * 0.05 },
{ y: height * 0.81, r: radius * 0.34, x: -radius * 0.11, z: radius * 0.1 }
];
const vertices = [];
const colorValues = [];
const indices = [];
const palette = colors.map(value => new THREE.Color(value));
for (let r = 0; r < rings.length; r++) {
const ring = rings[r];
const c = palette[r];
for (let j = 0; j < radialSegments; j++) {
const angle = (j / radialSegments) * Math.PI * 2 + 0.18;
vertices.push(
ring.x + Math.cos(angle) * ring.r,
ring.y,
ring.z + Math.sin(angle) * ring.r
);
colorValues.push(c.r, c.g, c.b);
}
}
const tipIndex = vertices.length / 3;
vertices.push(radius * 0.2, height, radius * 0.11);
const tipColor = palette[palette.length - 1];
colorValues.push(tipColor.r, tipColor.g, tipColor.b);
for (let r = 0; r < rings.length - 1; r++) {
for (let j = 0; j < radialSegments; j++) {
const a = r * radialSegments + j;
const b = r * radialSegments + (j + 1) % radialSegments;
const c = (r + 1) * radialSegments + j;
const d = (r + 1) * radialSegments + (j + 1) % radialSegments;
indices.push(a, b, c, b, d, c);
}
}
const lastRing = (rings.length - 1) * radialSegments;
for (let j = 0; j < radialSegments; j++) {
indices.push(lastRing + j, lastRing + (j + 1) % radialSegments, tipIndex);
}
const geometry = new THREE.BufferGeometry();
geometry.setAttribute('position', new THREE.Float32BufferAttribute(vertices, 3));
geometry.setAttribute('color', new THREE.Float32BufferAttribute(colorValues, 3));
geometry.setIndex(indices);
geometry.computeVertexNormals();
return geometry;
}
const outerFlameMaterial = new THREE.MeshBasicMaterial({
vertexColors: true,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.94,
depthWrite: false,
fog: false,
toneMapped: false
});
const innerFlameMaterial = new THREE.MeshBasicMaterial({
vertexColors: true,
side: THREE.DoubleSide,
transparent: true,
opacity: 0.96,
depthWrite: false,
fog: false,
toneMapped: false
});
const flameParts = [];
function addFlame(x, z, height, radius, inner, phase) {
const colors = inner
? [0xffcf42, 0xffffb0, 0xffd22c, 0xff7610]
: [0xff6714, 0xff8218, 0xf64a0a, 0xd82d09];
const mesh = new THREE.Mesh(
makeFlameGeometry(height, radius, colors),
inner ? innerFlameMaterial : outerFlameMaterial
);
mesh.position.set(x, 0.29, z);
mesh.rotation.y = rand() * Math.PI * 2;
mesh.renderOrder = 2;
scene.add(mesh);
flameParts.push({
mesh, x, z, phase,
rate: between(1.9, 3.1),
width: between(0.91, 1.09)
});
}
addFlame(0, 0, 1.82, 0.56, false, 0.1);
addFlame(0.34, -0.11, 1.46, 0.45, false, 1.7);
addFlame(-0.35, 0.15, 1.63, 0.48, false, 3.2);
addFlame(0.08, 0.36, 1.31, 0.41, false, 4.8);
addFlame(-0.08, -0.04, 1.12, 0.32, true, 0.9);
addFlame(0.21, 0.16, 0.96, 0.29, true, 2.8);
const fireLight = new THREE.PointLight(0xff8738, 58, 31, 2);
fireLight.position.set(0, 2.15, 0);
fireLight.castShadow = true;
fireLight.shadow.mapSize.set(512, 512);
fireLight.shadow.camera.near = 0.5;
fireLight.shadow.camera.far = 32;
fireLight.shadow.bias = -0.0002;
fireLight.shadow.normalBias = 0.035;
fireLight.shadow.radius = 4;
scene.add(fireLight);
// Rising sparks, recycled as a small animated point-particle system.
const sparkCount = 86;
const sparkPositions = new Float32Array(sparkCount * 3);
const sparkColors = new Float32Array(sparkCount * 3);
const sparkStates = [];
const sparkPalette = [
new THREE.Color(0xffb453),
new THREE.Color(0xff7b2e),
new THREE.Color(0xffd17a)
];
for (let i = 0; i < sparkCount; i++) {
const life = between(1.25, 3.15);
const speed = between(0.65, 1.42);
const age = rand() * life;
const state = {
age,
life,
speed,
x: between(-0.5, 0.5),
z: between(-0.45, 0.45),
driftX: between(-0.18, 0.18),
driftZ: between(-0.18, 0.18)
};
sparkStates.push(state);
sparkPositions[i * 3] = state.x;
sparkPositions[i * 3 + 1] = 0.32 + age * speed;
sparkPositions[i * 3 + 2] = state.z;
const c = sparkPalette[Math.floor(rand() * sparkPalette.length)];
sparkColors[i * 3] = c.r;
sparkColors[i * 3 + 1] = c.g;
sparkColors[i * 3 + 2] = c.b;
}
const sparkGeometry = new THREE.BufferGeometry();
sparkGeometry.setAttribute('position', new THREE.BufferAttribute(sparkPositions, 3));
sparkGeometry.setAttribute('color', new THREE.BufferAttribute(sparkColors, 3));
const sparks = new THREE.Points(
sparkGeometry,
new THREE.PointsMaterial({
size: 0.29,
map: glowTexture,
vertexColors: true,
transparent: true,
opacity: 0.92,
blending: THREE.AdditiveBlending,
depthWrite: false,
alphaTest: 0.01,
sizeAttenuation: true,
fog: false,
toneMapped: false
})
);
sparks.renderOrder = 3;
scene.add(sparks);
const clock = new THREE.Clock();
const positionAttribute = sparkGeometry.attributes.position;
function animate() {
requestAnimationFrame(animate);
const dt = Math.min(clock.getDelta(), 0.05);
const time = clock.elapsedTime;
for (const part of flameParts) {
const t = time * part.rate + part.phase;
part.mesh.scale.set(
part.width * (0.94 + 0.08 * Math.sin(t * 1.23)),
0.86 + 0.15 * Math.sin(t),
part.width * (0.93 + 0.08 * Math.cos(t * 1.14))
);
part.mesh.position.x = part.x + Math.sin(t * 0.72) * 0.055;
part.mesh.position.z = part.z + Math.cos(t * 0.61) * 0.045;
part.mesh.rotation.z = Math.sin(t * 0.8) * 0.045;
part.mesh.rotation.x = Math.cos(t * 0.72) * 0.035;
}
fireLight.intensity = 56 + Math.sin(time * 11.4) * 7 + Math.sin(time * 23.2) * 3.5;
fireLight.position.y = 2.12 + Math.sin(time * 8.1) * 0.08;
fireGlow.material.opacity = 0.38 + 0.06 * Math.sin(time * 9.5);
fireGlow.scale.setScalar(1 + 0.025 * Math.sin(time * 5.7));
for (let i = 0; i < sparkCount; i++) {
const p = sparkStates[i];
p.age += dt;
if (p.age >= p.life) {
p.age = 0;
p.life = between(1.25, 3.15);
p.speed = between(0.65, 1.42);
p.x = between(-0.48, 0.48);
p.z = between(-0.42, 0.42);
p.driftX = between(-0.18, 0.18);
p.driftZ = between(-0.18, 0.18);
} else {
p.x += p.driftX * dt;
p.z += p.driftZ * dt;
}
sparkPositions[i * 3] = p.x;
sparkPositions[i * 3 + 1] = 0.32 + p.age * p.speed;
sparkPositions[i * 3 + 2] = p.z;
}
positionAttribute.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(window.devicePixelRatio || 1, 2));
});
</script>
</body>
</html>Reasoning trace (23,168 tokens)
**Designing flame simulation** I’m thinking about how to compose the scene with a full-screen canvas. I want a translucent overlay at the lower-left for titles and a controls pill in the top-right—keeping UI minimal. I’ll focus on creating a dynamic flame at the center, needing to import map modules and use custom geometries for low poly flame shapes. I’ll animate the flames with layered tapered blobs and explore color materials. Overall, I'd aim for an engaging fire simulation.**Creating flame tongue geometry** I'm working on designing the flame tongues as cone-like shapes with various ring levels—at 0, .3, .8, and 1.2, leading to the tip. I need to determine the vertex colors, transitioning from yellow at the base to orange and red towards the top. Should I go for smooth triangles? Enabling flat shading may help with the polygon appearance. I also want to ensure a warm point light is included for flickering effects and to cast shadows effectively.**Creating a forest scene** I’m considering the shadow map size for the PointLight, weighing between 512 and 1024, since three point lights need six faces. I need to ensure the scene objects cast and receive shadows effectively, especially with a directional moonlight setup that's low and ambient. I'll set the shadow camera settings at a near of .5 and a far of 45. My goal is to create a variety of trees, ensuring they’re not too close to rocks or logs, and I want to keep the ground clear around the fire. I aim for a balanced layout within the radial area.**Designing tree properties** I'm planning each tree to root at y=0, with pine heights varying randomly between 5-11 units. The trunk will be a cylinder, and the foliage will have 2 or 3 cone-shaped layers. For the leaves, I’ll set ConeGeometry with 5-7 radial segments, with an arbitrary rotation. I want to ensure the colors are dark forest greens, so they stand out against the sky. I’ll position 40-50 trees randomly within an annulus of radius 10 to 40 while avoiding clutter in the front composition. The scene camera will be placed at (16,13,20), pointing toward the center. I’ll add a moon and a starry background, using fog effects but no giant sky sphere.**Adding moon glow effect** I'm thinking about adding a glow effect around the moon, possibly using a sprite created with a CanvasTexture and a radial gradient. I want to ensure it’s built in, so no external resources are needed. The SpriteMaterial will be transparent with depth writing turned off for better effect. The camera will face the origin from the position (15,13,20), capturing the view direction of [-.5,-.35,-.8]. I’m considering placing the moon at coordinates (-28,35,-38) for a nice relative view with the x-axis and upward orientation.**Evaluating object position** I'm considering an object’s position in a scene. It's near me, with a horizontal framing at ground level and the sky extending up for 12 units. The moon is at an elevation of 35 degrees, placed 55 units away. The vertical offset is 32, which positions it quite prominently. The horizontal angle is about 45 degrees. Overall, it looks like I’m focusing on how everything aligns in the visual direction.**Designing fire layout** I'm thinking about creating irregular small ground patches around a fire, like polygon platforms for rocks. I want to scatter stones with irregular icosahedron scales and have a ring of ten stones around the fire. There should be a few rocks and logs too. I need to use CylinderGeometry to represent the logs, keeping in mind their horizontal orientation along the xz axis. I’ll adjust the geometry's height by rotating it as needed while ensuring it sits properly on the ground.**Evaluating lighting and shadows** I'm looking into how to set up shadows for trees with a warm point light source. It seems that enabling cast shadows and adjusting settings is important, particularly ensuring the renderer has shadow mapping enabled. I’ll need to manage the point light’s flickering by updating shadows every frame. I have to set specific distances and biases to optimize the appearance. I might also consider adjustments for better interaction with orbit controls. Testing all this will help refine the lighting setup.**Considering tree layout** I need to ensure the inner clearing has a radius of less than 12, while the tree trunks should be in the foreground along the path. The trees should be arranged in rings, with their centers having a radius of at least 13. The lower branches of the trees can range from 3 to 5 units tall, and I’d like to position a fire at the center. I might use a deterministic pseudo-random seeded layout for reproducibility and randomize the position of these rings.**Designing lighting effects** I'm considering the lighting setup for a scene. I want to keep it simple with no additional scene lighting, focusing instead on using vibrant colors. DepthWrite and toneMapping will be set to false. I wonder if using MeshBasicMaterial with additive blending will make the flames appear more luminous. This could enhance the visual impact, giving it that fiery glow I'm hoping for. It's all about finding the right balance here!**Adjusting camera angles** I'm configuring the camera angle with a tilt of -23 degrees, aiming for a top view at 22 degrees. I think that's good since the moon appears centered at the top. However, I need to watch out for UI overlap, with the title at the top left. The field of view (FOV) is 52 degrees, and the image composition is set at 1280x800. I'm checking various distances and heights to ensure the fire and surrounding forest appear balanced and visually appealing. Overall, it seems like I’m getting closer to the look I want!**Positioning the moon** I’m adjusting the field of view (FOV) to 48 degrees, which gives a vertical half-angle of 24 degrees, placing the moon in a candidate position near the top. I calculated its vector from the camera’s position to the moon. The angle adjustments need careful attention; moving the moon affects its visibility above the tree canopies. I need to ensure it stays visible in the background while considering the camera's direction and the screen placement to achieve an optimal view. Finding the right balance in positioning is key!**Setting target position** I'm figuring out how to place the target relative to the camera at distance D. I think using the camera's forward direction combined with the up vector multiplied by the tangent of 18 degrees times the distance will help. For a distance of 60, I’ll need to calculate it precisely. The camera's direction is set at [-0.57, -0.4, -0.72], and the up vector is [0.22, 0.91, -0.31]. The target position is at (0, 1.5, 0). This setup should create the desired positioning!**Analyzing camera angle and view** I'm figuring out the right angles for the camera in relation to the horizon. I need more visible sky, as the current setup emphasizes the ground. By adjusting the camera to reduce the downward pitch to about 10°, I can improve the view. The initial camera position and target angles help me understand the field of view, but I want to ensure that the scene captures enough sky while focusing on the ground. It's a balancing act!**Optimizing 3D Rendering** I'm considering how the stars might be hidden behind the trees due to material occlusion and depth. Next, I’m thinking about adding a fire effect for ground reflection, but I’m unsure about lens adjustments. I plan to set the renderer size according to the viewport and ensure the controls and camera aspect ratio update properly. Should I create multiple shared geometries for the trees and rocks, especially for efficiency? It seems reusing the geometry could cut down on the number of meshes. I’ll also experiment with different materials to create a visual palette.**Setting Up Materials for Trees** I'm thinking about the light variations depending on the materials. I need to create a bark material palette with three options and a foliage palette for each tree type. Each tree will have a unique material base with slight variations per layer. Using low poly normals with default geometry and flat shading seems like the way to go. I'll use `ConeGeometry(1, 1, 6, 1)`, ensuring to adjust for mesh scaling and include yaw rotation. Also, I need to distribute tree positions while keeping a minimum rejection distance.**Exploring geometry for terrain** I'm thinking about patch positions around y=.012 and using a clearing disk at y=.02. The ground plane should be 200; it's flat, containing 40 patches. I want to avoid the center radius r<4, ensuring the patches have a dark ground for a low poly look. The large circular disk could have a radius of 12 and keep polygons inside it. For the terrain, I might use a distorted PlaneGeometry and displace the vertices carefully. I'll also add controls to orbit around.**Configuring responsive canvas** I’m considering how to ensure the canvas is automatically responsive. I need to import `OrbitControls` accurately. It's essential to pay attention to the exact specifications for the map tag content. They mentioned using `<script type=...>` to include the script in a certain way, so I’ll follow that closely. I want to make sure everything is set up properly!**Evaluating moon observation conditions** I'm considering the moon's position, which is about 10 degrees above the horizon, and it’s at the top of my view. The ground has trees and a canopy filling the space. I think using a field of view (FOV) of 55 could be beneficial, but I need to focus on the terrain. It seems I don't have a full view of the sky, which limits my observations. I'll keep that in mind as I gather more information!**Evaluating tree placement** I'm thinking about how to set trees in an annular area while avoiding a specific cone angle that aligns with the camera direction. I want to create a path from the fire to the camera, while ensuring the foreground looks realistic. The placement should involve around 50 trees while avoiding areas close to the camera. I'll focus on radial and angular randomness, ensuring trees don’t block the view of the fire, especially for the lower perspective. It’s a delicate balance!**Refining tree rejection criteria** I need to establish some rejection criteria for tree placement, specifically for any radius less than 21 and angles close to the camera direction. I’m using the atan2 function to determine angles from the origin to the camera’s position in the x/z plane. By rejecting angular differences less than 0.48 radians, I can create a nice 55° opening around the viewer while allowing trees at radius 21 to project wider. At least 50 trees will be placed randomly. Sounds good!**Considering shader implementation** I’m thinking about using `ShaderMaterial` with both the `vertexShader` and fragment shaders, including the colorspace fragment. This feels like a solid approach for managing my materials. I wonder if I need to consider any additional attributes or settings in the material. It's crucial to ensure that colors are rendered correctly in the final output. I should double-check my shader syntax and make sure everything is aligned properly for optimal performance.**Positioning celestial objects** I'm working on positioning craters, adding three small flat spheres around the side, but I'm unsure about the camera side. I'm considering using a 0.2 scale mesh for the craters with a slightly darker material, placing them in relation to the moon's actual center. The moon is supposed to be a bright pale disc, visible amidst the stars. I need to ensure the moon's position makes sense within the given camera coordinates and perspective. The calculations seem solid.**Setting up the scene** I need to adjust the moon's position based on the center and offset values to avoid clipping. The camera perspective at y17 seems to work, keeping the tree height in mind for a realistic canopy. I'm aiming for an appropriate field of view, with both vertical and horizontal settings aligning properly. For lighting, I'm using HemisphereLight with a blue tone and adjusting the intensity of various lights to create a balanced scene. I also need to incorporate shadows and manage lighting flickers for a more dynamic effect.**Considering geometry transformations** I'm thinking about how the default plane at z=0 extends across x and y, and rotating around the x-axis by -90 degrees maps the local y-axis to a new z position. It looks like this transforms the plane's normal to local +z. When I create CircleGeometry, using a rotation of -PI/2 positions it correctly. I'll adjust the ground, keeping in mind the placement of various patches and their potential overlaps. Everything seems to be falling into place with these adjustments!**Adjusting scene lighting** I want to brighten the scene a bit. I'll use standard material with a flat setup, adjusting hemispherical intensity to .9, ambient to .38, and directional lighting to .85, giving a total directional luminosity around 1.8. It’s a nighttime scene, but I still want some dark colors. For the moonlight, I’ll set its position at (-35, 45, -20) with an intensity of .55, in a blue hue, but no shadows. The trees should be uniformly lit with a hemisphere intensity of .85, while dark foliage remains below.**Investigating HUD Issues** I'm looking into the HUD screen’s no interaction issue. It seems there might be a problem with the controls. One concern is whether automated screenshots from the front-end are blocking the external CDN, which should be explicitly allowed. I'll need to explore this further to find out if there's a connection between these components affecting functionality. Let's see what I can discover!