Add Claude Haiku 4.5 artifacts

Co-authored-by: Codex <codex@openai.com>
This commit is contained in:
Nagi-ovoandCodex committed 2026-06-14 06:54:22 +01:00
1 parent 00226c3e70
commit 2b85907eb3
5 files changed
+860

No files matched your search

+1
View File
@@ -35,6 +35,7 @@ One-shot LLM eval cases by NAGI STUDIO: same prompt, different model x harness x
| Claude Opus 4.6 | Anthropic | Cursor · Max | 02 |
| Claude Opus 4.5 | Anthropic | Cursor · Thinking | 02 |
| Claude Sonnet 4.6 | Anthropic | Claude Code · Max<br>Cursor · High | 03 |
| Claude Haiku 4.5 | Anthropic | Claude Code · Default | 02 |
| GPT-5.5 Pro | OpenAI | ChatGPT Web · Extended Pro | 02 |
| GPT-5.5 | OpenAI | Codex CLI · xhigh | 02 |
| GPT-5.4 | OpenAI | Cursor · xhigh<br>Codex CLI · xhigh | 05 |
+1
View File
@@ -35,6 +35,7 @@ NAGI STUDIO 的 LLM 测评案例集:同一段提示词,不同「模型 × Ha
| Claude Opus 4.6 | Anthropic | Cursor · Max | 02 |
| Claude Opus 4.5 | Anthropic | Cursor · Thinking | 02 |
| Claude Sonnet 4.6 | Anthropic | Claude Code · Max<br>Cursor · High | 03 |
| Claude Haiku 4.5 | Anthropic | Claude Code · Default | 02 |
| GPT-5.5 Pro | OpenAI | ChatGPT Web · Extended Pro | 02 |
| GPT-5.5 | OpenAI | Codex CLI · xhigh | 02 |
| GPT-5.4 | OpenAI | Cursor · xhigh<br>Codex CLI · xhigh | 05 |
+26
View File
@@ -0,0 +1,26 @@
{
"label": "Claude Haiku 4.5",
"vendor": "Anthropic",
"harness": "Claude Code",
"effort": "Default",
"artifactDir": "claude-haiku-4-5/claude-code-default",
"order": 19.5,
"runs": {
"mythos-craft": {
"file": "minecraft-clone.html",
"note": {
"zh": "在 Claude Code 中以 Claude Haiku 4.5(Default 思考配额)一次生成。原始 HTML 因 Three.js 异步加载、渲染 canvas 绑定和 world 变量接线问题无法正常打开;Codex 仅修复启动接线以让原始实现可运行,未改动玩法、地形、视觉设计或控制逻辑。",
"en": "Generated in one shot by Claude Haiku 4.5 (Default effort) inside Claude Code. The original HTML could not open correctly due to Three.js async loading, render-canvas binding, and world variable wiring issues; Codex only fixed the startup wiring so the original implementation can run, without changing gameplay, terrain, visual design, or control logic."
},
"contributor": "Nagi-ovo"
},
"pelican-cycling": {
"file": "pelican-bike.svg",
"note": {
"zh": "在 Claude Code 中以 Claude Haiku 4.5(Default 思考配额)一次生成;本页为未经修改的原始 SVG 产出。",
"en": "Generated in one shot by Claude Haiku 4.5 (Default effort) inside Claude Code; unmodified original SVG output."
},
"contributor": "Nagi-ovo"
}
}
}
@@ -0,0 +1,712 @@
<!DOCTYPE html>
<html lang="zh-CN">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>魔幻世界 - Minecraft克隆版</title>
<style>
* {
margin: 0;
padding: 0;
box-sizing: border-box;
}
body {
width: 100%;
height: 100vh;
font-family: 'Arial', sans-serif;
background: #000;
overflow: hidden;
}
canvas {
display: block;
width: 100%;
height: 100%;
}
#info {
position: absolute;
top: 10px;
left: 10px;
color: #fff;
background: rgba(0, 0, 0, 0.7);
padding: 15px;
border-radius: 8px;
font-size: 14px;
z-index: 100;
max-width: 300px;
}
#info p {
margin: 5px 0;
}
#crosshair {
position: absolute;
top: 50%;
left: 50%;
transform: translate(-50%, -50%);
width: 20px;
height: 20px;
border: 2px solid rgba(255, 255, 255, 0.7);
border-radius: 50%;
pointer-events: none;
}
#crosshair::before,
#crosshair::after {
content: '';
position: absolute;
background: rgba(255, 255, 255, 0.7);
}
#crosshair::before {
width: 8px;
height: 2px;
left: 6px;
top: 9px;
}
#crosshair::after {
width: 2px;
height: 8px;
left: 9px;
top: 6px;
}
</style>
</head>
<body>
<canvas id="canvas"></canvas>
<div id="crosshair"></div>
<div id="info">
<p><strong>魔幻世界</strong></p>
<p>移动: WASD | 跳跃: 空格</p>
<p>鼠标: 环视 | 左键: 破坏 | 右键: 放置</p>
<p id="pos">位置: (0, 0, 0)</p>
<p id="block">方块: 无</p>
<p id="fps">FPS: 0</p>
</div>
<script src="https://cdnjs.cloudflare.com/ajax/libs/three.js/r128/three.min.js"></script>
<script>
// ==================== 柏林噪声实现 ====================
class PerlinNoise {
constructor(seed = 0) {
this.seed = seed;
this.permutation = this.buildPermutation(seed);
this.p = [...this.permutation, ...this.permutation];
}
buildPermutation(seed) {
const p = Array.from({ length: 256 }, (_, i) => i);
// Fisher-Yates shuffle with seed
for (let i = 255; i > 0; i--) {
const j = Math.floor((Math.sin(seed + i) * 10000 % 1) * (i + 1));
[p[i], p[j]] = [p[j], p[i]];
}
return p;
}
fade(t) {
return t * t * t * (t * (t * 6 - 15) + 10);
}
lerp(t, a, b) {
return a + t * (b - a);
}
grad(hash, x, y, z) {
const h = hash & 15;
const u = h < 8 ? x : y;
const v = h < 8 ? y : z;
return ((h & 1) === 0 ? u : -u) + ((h & 2) === 0 ? v : -v);
}
noise(x, y, z) {
const xi = Math.floor(x) & 255;
const yi = Math.floor(y) & 255;
const zi = Math.floor(z) & 255;
const xf = x - Math.floor(x);
const yf = y - Math.floor(y);
const zf = z - Math.floor(z);
const u = this.fade(xf);
const v = this.fade(yf);
const w = this.fade(zf);
const aaa = this.p[this.p[this.p[xi] + yi] + zi];
const aba = this.p[this.p[this.p[xi] + (yi + 1)] + zi];
const aab = this.p[this.p[this.p[xi] + yi] + (zi + 1)];
const abb = this.p[this.p[this.p[xi] + (yi + 1)] + (zi + 1)];
const baa = this.p[this.p[this.p[(xi + 1)] + yi] + zi];
const bba = this.p[this.p[this.p[(xi + 1)] + (yi + 1)] + zi];
const bab = this.p[this.p[this.p[(xi + 1)] + yi] + (zi + 1)];
const bbb = this.p[this.p[this.p[(xi + 1)] + (yi + 1)] + (zi + 1)];
const ga = this.grad(aaa, xf, yf, zf);
const gb = this.grad(baa, xf - 1, yf, zf);
const gc = this.grad(aba, xf, yf - 1, zf);
const gd = this.grad(bba, xf - 1, yf - 1, zf);
const ge = this.grad(aab, xf, yf, zf - 1);
const gf = this.grad(bab, xf - 1, yf, zf - 1);
const gg = this.grad(abb, xf, yf - 1, zf - 1);
const gh = this.grad(bbb, xf - 1, yf - 1, zf - 1);
const p0 = this.lerp(u, ga, gb);
const p1 = this.lerp(u, gc, gd);
const p2 = this.lerp(u, ge, gf);
const p3 = this.lerp(u, gg, gh);
const p4 = this.lerp(v, p0, p1);
const p5 = this.lerp(v, p2, p3);
return this.lerp(w, p4, p5);
}
}
// ==================== 块状世界管理 ====================
const CHUNK_SIZE = 16;
const CHUNK_HEIGHT = 256;
const WORLD_HEIGHT = 64;
const LOAD_DISTANCE = 3;
const BlockType = {
AIR: 0,
GRASS: 1,
DIRT: 2,
STONE: 3,
OBSIDIAN: 4,
CRYSTAL: 5
};
const BlockColors = {
[BlockType.GRASS]: new THREE.Color(0x7cb342),
[BlockType.DIRT]: new THREE.Color(0x8b6f47),
[BlockType.STONE]: new THREE.Color(0x757575),
[BlockType.OBSIDIAN]: new THREE.Color(0x1a1a2e),
[BlockType.CRYSTAL]: new THREE.Color(0x00bcd4)
};
class World {
constructor() {
this.perlin = new PerlinNoise(42);
this.chunks = new Map();
this.scene = null;
this.meshes = new Map();
}
getBlockType(x, y, z) {
const h = this.getHeight(x, z);
if (y > h) return BlockType.AIR;
if (y === h) return BlockType.GRASS;
if (y > h - 3) return BlockType.DIRT;
return BlockType.STONE;
}
getHeight(x, z) {
let height = 20;
let scale = 1;
let amplitude = 1;
let maxAmplitude = 0;
for (let i = 0; i < 4; i++) {
const n = this.perlin.noise(x * scale * 0.05, 0, z * scale * 0.05);
height += (n * 0.5 + 0.5) * 15 * amplitude;
maxAmplitude += amplitude;
scale *= 2;
amplitude *= 0.5;
}
return Math.floor(height / maxAmplitude);
}
addMythicStructures(x, z, chunk) {
const chunkX = Math.floor(x / CHUNK_SIZE);
const chunkZ = Math.floor(z / CHUNK_SIZE);
// 魔法塔
if (chunkX === 2 && chunkZ === 2) {
this.buildMagicTower(x + 5, z + 5, chunk);
}
// 古代神庙
if (chunkX === -1 && chunkZ === 1) {
this.buildAncientTemple(x + 8, z + 8, chunk);
}
// 水晶洞
if (chunkX === 1 && chunkZ === -1) {
this.buildCrystalCave(x + 7, z + 7, chunk);
}
// 悬浮岛
if (chunkX === -2 && chunkZ === -2) {
this.buildFloatingIsland(x + 10, z + 10, chunk);
}
}
buildMagicTower(baseX, baseZ, chunk) {
const baseHeight = this.getHeight(baseX, baseZ) + 1;
// 塔底
for (let dx = -2; dx <= 2; dx++) {
for (let dz = -2; dz <= 2; dz++) {
const x = Math.floor(baseX + dx);
const z = Math.floor(baseZ + dz);
if (this.isInChunk(x, z, chunk)) {
const idx = this.getBlockIndex(x, z, baseHeight, chunk);
if (idx >= 0) chunk[idx] = BlockType.OBSIDIAN;
}
}
}
// 塔身
for (let h = 1; h < 15; h++) {
for (let dx = -1; dx <= 1; dx++) {
for (let dz = -1; dz <= 1; dz++) {
if (Math.abs(dx) === 1 || Math.abs(dz) === 1) {
const x = Math.floor(baseX + dx);
const z = Math.floor(baseZ + dz);
if (this.isInChunk(x, z, chunk)) {
const idx = this.getBlockIndex(x, z, baseHeight + h, chunk);
if (idx >= 0) chunk[idx] = h % 2 === 0 ? BlockType.OBSIDIAN : BlockType.CRYSTAL;
}
}
}
}
}
}
buildAncientTemple(baseX, baseZ, chunk) {
const baseHeight = this.getHeight(baseX, baseZ) + 1;
// 主体
for (let dx = -3; dx <= 3; dx++) {
for (let dz = -3; dz <= 3; dz++) {
const x = Math.floor(baseX + dx);
const z = Math.floor(baseZ + dz);
if (this.isInChunk(x, z, chunk)) {
const idx = this.getBlockIndex(x, z, baseHeight, chunk);
if (idx >= 0) chunk[idx] = BlockType.STONE;
}
}
}
// 柱子
for (let h = 1; h < 8; h++) {
const positions = [[-3, -3], [3, -3], [-3, 3], [3, 3]];
positions.forEach(([dx, dz]) => {
const x = Math.floor(baseX + dx);
const z = Math.floor(baseZ + dz);
if (this.isInChunk(x, z, chunk)) {
const idx = this.getBlockIndex(x, z, baseHeight + h, chunk);
if (idx >= 0) chunk[idx] = BlockType.STONE;
}
});
}
}
buildCrystalCave(baseX, baseZ, chunk) {
const baseHeight = this.getHeight(baseX, baseZ) - 5;
for (let dx = -4; dx <= 4; dx++) {
for (let dz = -4; dz <= 4; dz++) {
for (let h = 0; h < 8; h++) {
if (Math.random() < 0.3) {
const x = Math.floor(baseX + dx);
const z = Math.floor(baseZ + dz);
if (this.isInChunk(x, z, chunk)) {
const idx = this.getBlockIndex(x, z, baseHeight + h, chunk);
if (idx >= 0) chunk[idx] = BlockType.CRYSTAL;
}
}
}
}
}
}
buildFloatingIsland(baseX, baseZ, chunk) {
const baseHeight = this.getHeight(baseX, baseZ) + 30;
for (let dx = -5; dx <= 5; dx++) {
for (let dz = -5; dz <= 5; dz++) {
const dist = Math.sqrt(dx * dx + dz * dz);
if (dist < 5) {
const x = Math.floor(baseX + dx);
const z = Math.floor(baseZ + dz);
if (this.isInChunk(x, z, chunk)) {
const idx = this.getBlockIndex(x, z, baseHeight, chunk);
if (idx >= 0) chunk[idx] = BlockType.GRASS;
}
}
}
}
}
isInChunk(x, z, chunk) {
return x >= 0 && x < CHUNK_SIZE && z >= 0 && z < CHUNK_SIZE;
}
getBlockIndex(x, z, y, chunk) {
if (y < 0 || y >= CHUNK_HEIGHT) return -1;
return y * CHUNK_SIZE * CHUNK_SIZE + z * CHUNK_SIZE + x;
}
getChunk(cx, cz) {
const key = `${cx},${cz}`;
if (!this.chunks.has(key)) {
const chunk = new Uint8Array(CHUNK_SIZE * CHUNK_SIZE * CHUNK_HEIGHT);
for (let x = 0; x < CHUNK_SIZE; x++) {
for (let z = 0; z < CHUNK_SIZE; z++) {
for (let y = 0; y < CHUNK_HEIGHT; y++) {
const wx = cx * CHUNK_SIZE + x;
const wz = cz * CHUNK_SIZE + z;
const blockType = this.getBlockType(wx, y, wz);
const idx = y * CHUNK_SIZE * CHUNK_SIZE + z * CHUNK_SIZE + x;
chunk[idx] = blockType;
}
}
}
this.addMythicStructures(cx * CHUNK_SIZE, cz * CHUNK_SIZE, chunk);
this.chunks.set(key, chunk);
}
return this.chunks.get(key);
}
getBlock(x, y, z) {
const cx = Math.floor(x / CHUNK_SIZE);
const cz = Math.floor(z / CHUNK_SIZE);
const lx = ((x % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE;
const lz = ((z % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE;
const chunk = this.getChunk(cx, cz);
if (y < 0 || y >= CHUNK_HEIGHT) return BlockType.AIR;
return chunk[y * CHUNK_SIZE * CHUNK_SIZE + lz * CHUNK_SIZE + lx];
}
setBlock(x, y, z, type) {
const cx = Math.floor(x / CHUNK_SIZE);
const cz = Math.floor(z / CHUNK_SIZE);
const lx = ((x % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE;
const lz = ((z % CHUNK_SIZE) + CHUNK_SIZE) % CHUNK_SIZE;
if (y < 0 || y >= CHUNK_HEIGHT) return;
const chunk = this.getChunk(cx, cz);
chunk[y * CHUNK_SIZE * CHUNK_SIZE + lz * CHUNK_SIZE + lx] = type;
this.updateChunkMesh(cx, cz);
// 更新相邻块
if (lx === 0) this.updateChunkMesh(cx - 1, cz);
if (lx === CHUNK_SIZE - 1) this.updateChunkMesh(cx + 1, cz);
if (lz === 0) this.updateChunkMesh(cx, cz - 1);
if (lz === CHUNK_SIZE - 1) this.updateChunkMesh(cx, cz + 1);
}
updateChunkMesh(cx, cz) {
const key = `${cx},${cz}`;
if (this.meshes.has(key)) {
this.scene.remove(this.meshes.get(key));
}
const mesh = this.createChunkMesh(cx, cz);
if (mesh) {
this.scene.add(mesh);
this.meshes.set(key, mesh);
}
}
createChunkMesh(cx, cz) {
const geometry = new THREE.BufferGeometry();
const vertices = [];
const colors = [];
const indices = [];
let indexOffset = 0;
for (let x = 0; x < CHUNK_SIZE; x++) {
for (let z = 0; z < CHUNK_SIZE; z++) {
for (let y = 0; y < CHUNK_HEIGHT; y++) {
const wx = cx * CHUNK_SIZE + x;
const wz = cz * CHUNK_SIZE + z;
const block = this.getBlock(wx, y, wz);
if (block === BlockType.AIR) continue;
const color = BlockColors[block];
let facesAdded = 0;
// 检查六个面
const faces = [
{ dir: [0, 1, 0], norm: [0, 1, 0] },
{ dir: [0, -1, 0], norm: [0, -1, 0] },
{ dir: [1, 0, 0], norm: [1, 0, 0] },
{ dir: [-1, 0, 0], norm: [-1, 0, 0] },
{ dir: [0, 0, 1], norm: [0, 0, 1] },
{ dir: [0, 0, -1], norm: [0, 0, -1] }
];
faces.forEach(face => {
if (this.getBlock(wx + face.dir[0], y + face.dir[1], wz + face.dir[2]) === BlockType.AIR) {
const v = this.getFaceVertices(x, y, z, face.dir);
const startIdx = indexOffset;
v.forEach(vertex => {
vertices.push(...vertex);
colors.push(color.r, color.g, color.b);
});
indices.push(
startIdx, startIdx + 1, startIdx + 2,
startIdx + 2, startIdx + 3, startIdx
);
indexOffset += 4;
}
});
}
}
}
if (vertices.length === 0) return null;
geometry.setAttribute('position', new THREE.BufferAttribute(new Float32Array(vertices), 3));
geometry.setAttribute('color', new THREE.BufferAttribute(new Float32Array(colors), 3));
geometry.setIndex(new THREE.BufferAttribute(new Uint32Array(indices), 1));
const material = new THREE.MeshLambertMaterial({
vertexColors: true,
side: THREE.DoubleSide,
flatShading: true
});
const mesh = new THREE.Mesh(geometry, material);
mesh.position.set(cx * CHUNK_SIZE, 0, cz * CHUNK_SIZE);
return mesh;
}
getFaceVertices(x, y, z, dir) {
const vertices = [
[x, y, z], [x + 1, y, z], [x + 1, y + 1, z], [x, y + 1, z]
];
if (dir[0] === 1) return [[x + 1, y, z], [x + 1, y, z + 1], [x + 1, y + 1, z + 1], [x + 1, y + 1, z]];
if (dir[0] === -1) return [[x, y, z + 1], [x, y, z], [x, y + 1, z], [x, y + 1, z + 1]];
if (dir[2] === 1) return [[x, y, z + 1], [x + 1, y, z + 1], [x + 1, y + 1, z + 1], [x, y + 1, z + 1]];
if (dir[2] === -1) return [[x + 1, y, z], [x, y, z], [x, y + 1, z], [x + 1, y + 1, z]];
if (dir[1] === 1) return [[x, y + 1, z], [x + 1, y + 1, z], [x + 1, y + 1, z + 1], [x, y + 1, z + 1]];
if (dir[1] === -1) return [[x, y, z + 1], [x + 1, y, z + 1], [x + 1, y, z], [x, y, z]];
return vertices;
}
updateChunks(playerX, playerZ) {
const cx = Math.floor(playerX / CHUNK_SIZE);
const cz = Math.floor(playerZ / CHUNK_SIZE);
for (let dx = -LOAD_DISTANCE; dx <= LOAD_DISTANCE; dx++) {
for (let dz = -LOAD_DISTANCE; dz <= LOAD_DISTANCE; dz++) {
const key = `${cx + dx},${cz + dz}`;
if (!this.meshes.has(key)) {
const mesh = this.createChunkMesh(cx + dx, cz + dz);
if (mesh) {
this.scene.add(mesh);
this.meshes.set(key, mesh);
}
}
}
}
// 卸载远处的块
for (const [key, mesh] of this.meshes.entries()) {
const [mcx, mcz] = key.split(',').map(Number);
if (Math.abs(mcx - cx) > LOAD_DISTANCE + 1 || Math.abs(mcz - cz) > LOAD_DISTANCE + 1) {
this.scene.remove(mesh);
this.meshes.delete(key);
}
}
}
}
// ==================== 玩家控制 ====================
class Player {
constructor(camera) {
this.camera = camera;
this.velocity = new THREE.Vector3();
this.keys = {};
this.euler = new THREE.Euler(0, 0, 0, 'YXZ');
this.raycaster = new THREE.Raycaster();
this.selectedBlock = null;
this.selectedFace = null;
document.addEventListener('keydown', (e) => this.keys[e.key.toLowerCase()] = true);
document.addEventListener('keyup', (e) => this.keys[e.key.toLowerCase()] = false);
document.addEventListener('mousemove', (e) => this.onMouseMove(e));
document.addEventListener('mousedown', (e) => this.onMouseDown(e));
document.addEventListener('mouseup', (e) => this.onMouseUp(e));
document.addEventListener('click', () => {
if (document.pointerLockElement === document.documentElement) return;
document.documentElement.requestPointerLock();
});
}
onMouseMove(e) {
if (document.pointerLockElement !== document.documentElement) return;
this.euler.setFromQuaternion(this.camera.quaternion);
this.euler.rotateY(-e.movementX * 0.005);
this.euler.rotateX(-e.movementY * 0.005);
this.euler.x = Math.max(-Math.PI / 2, Math.min(Math.PI / 2, this.euler.x));
this.camera.quaternion.setFromEuler(this.euler);
}
onMouseDown(e) {
if (!this.selectedBlock) return;
if (e.button === 0) this.destroyBlock();
if (e.button === 2) this.placeBlock();
}
onMouseUp(e) {}
destroyBlock() {
if (this.selectedBlock) {
const pos = this.selectedBlock;
window.world.setBlock(pos.x, pos.y, pos.z, BlockType.AIR);
}
}
placeBlock() {
if (!this.selectedBlock || !this.selectedFace) return;
const pos = this.selectedBlock;
const face = this.selectedFace;
const newX = pos.x + face[0];
const newY = pos.y + face[1];
const newZ = pos.z + face[2];
window.world.setBlock(newX, newY, newZ, BlockType.DIRT);
}
update(world, delta) {
// 移动
const speed = 6;
const forward = new THREE.Vector3();
const right = new THREE.Vector3();
this.camera.getWorldDirection(forward);
forward.y = 0;
forward.normalize();
right.crossVectors(forward, new THREE.Vector3(0, 1, 0));
this.velocity.x = 0;
this.velocity.z = 0;
if (this.keys['w']) this.velocity.add(forward.multiplyScalar(speed));
if (this.keys['s']) this.velocity.sub(forward.multiplyScalar(speed));
if (this.keys['a']) this.velocity.sub(right.multiplyScalar(speed));
if (this.keys['d']) this.velocity.add(right.multiplyScalar(speed));
// 重力
this.velocity.y -= 9.8 * delta;
// 碰撞检测
const nextPos = this.camera.position.clone().add(this.velocity.clone().multiplyScalar(delta));
const below = Math.floor(nextPos.y - 1.6);
const blockBelow = world.getBlock(Math.floor(nextPos.x), below, Math.floor(nextPos.z));
if (blockBelow !== BlockType.AIR && this.velocity.y < 0) {
this.velocity.y = 0;
nextPos.y = below + 1.6;
if (this.keys[' ']) this.velocity.y = 8;
}
this.camera.position.copy(nextPos);
// 射线检测方块
this.raycaster.setFromCamera(new THREE.Vector2(0, 0), this.camera);
const intersects = this.raycaster.intersectObjects(world.scene.children);
this.selectedBlock = null;
this.selectedFace = null;
for (let intersection of intersects) {
const point = intersection.point;
const normal = intersection.face.normal;
const blockPos = {
x: Math.floor(point.x - normal.x * 0.1),
y: Math.floor(point.y - normal.y * 0.1),
z: Math.floor(point.z - normal.z * 0.1)
};
if (world.getBlock(blockPos.x, blockPos.y, blockPos.z) !== BlockType.AIR) {
this.selectedBlock = blockPos;
this.selectedFace = [normal.x, normal.y, normal.z];
break;
}
}
// 更新块加载
world.updateChunks(this.camera.position.x, this.camera.position.z);
// 更新UI
const pos = this.camera.position;
document.getElementById('pos').textContent = `位置: (${Math.floor(pos.x)}, ${Math.floor(pos.y)}, ${Math.floor(pos.z)})`;
if (this.selectedBlock) {
const block = this.selectedBlock;
const type = world.getBlock(block.x, block.y, block.z);
const typeNames = { 1: '草地', 2: '泥土', 3: '石头', 4: '黑曜石', 5: '魔法水晶' };
document.getElementById('block').textContent = `方块: ${typeNames[type] || '未知'}`;
}
}
}
// ==================== 初始化 ====================
let camera, scene, renderer, world, player;
let lastTime = Date.now();
let frameCount = 0;
let lastFpsUpdate = Date.now();
function init() {
// 场景
scene = new THREE.Scene();
scene.background = new THREE.Color(0x87ceeb);
scene.fog = new THREE.Fog(0x87ceeb, 200, 300);
// 相机
camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
camera.position.set(0, 50, 0);
// 渲染器
renderer = new THREE.WebGLRenderer({ canvas: document.getElementById('canvas'), antialias: true });
renderer.setSize(window.innerWidth, window.innerHeight);
renderer.shadowMap.enabled = true;
// 光源
const light = new THREE.DirectionalLight(0xffffff, 1);
light.position.set(50, 100, 50);
light.castShadow = true;
light.shadow.camera.far = 500;
light.shadow.camera.left = -200;
light.shadow.camera.right = 200;
light.shadow.camera.top = 200;
light.shadow.camera.bottom = -200;
scene.add(light);
const ambientLight = new THREE.AmbientLight(0xffffff, 0.5);
scene.add(ambientLight);
// 世界
world = new World();
window.world = world;
world.scene = scene;
// 玩家
player = new Player(camera);
window.addEventListener('resize', () => {
camera.aspect = window.innerWidth / window.innerHeight;
camera.updateProjectionMatrix();
renderer.setSize(window.innerWidth, window.innerHeight);
});
animate();
}
function animate() {
requestAnimationFrame(animate);
const now = Date.now();
const delta = Math.min((now - lastTime) / 1000, 0.016);
lastTime = now;
player.update(world, delta);
renderer.render(scene, camera);
frameCount++;
if (now - lastFpsUpdate >= 1000) {
document.getElementById('fps').textContent = `FPS: ${frameCount}`;
frameCount = 0;
lastFpsUpdate = now;
}
}
window.addEventListener('DOMContentLoaded', init);
window.addEventListener('contextmenu', (e) => e.preventDefault());
</script>
</body>
</html>
@@ -0,0 +1,120 @@
<svg viewBox="0 0 1200 800" xmlns="http://www.w3.org/2000/svg">
<!-- Sky gradient -->
<defs>
<linearGradient id="skyGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#87CEEB;stop-opacity:1" />
<stop offset="100%" style="stop-color:#E0F6FF;stop-opacity:1" />
</linearGradient>
<linearGradient id="oceanGrad" x1="0%" y1="0%" x2="0%" y2="100%">
<stop offset="0%" style="stop-color:#1E90FF;stop-opacity:1" />
<stop offset="100%" style="stop-color:#0047AB;stop-opacity:1" />
</linearGradient>
</defs>
<!-- Background -->
<rect width="1200" height="800" fill="url(#skyGrad)"/>
<!-- Ocean -->
<rect y="450" width="1200" height="350" fill="url(#oceanGrad)"/>
<!-- Sand -->
<ellipse cx="600" cy="480" rx="600" ry="80" fill="#F4A460"/>
<!-- Waves -->
<path d="M 0 450 Q 150 430 300 450 T 600 450 T 900 450 T 1200 450" stroke="#ffffff" stroke-width="2" fill="none" opacity="0.6"/>
<path d="M 0 500 Q 150 480 300 500 T 600 500 T 900 500 T 1200 500" stroke="#ffffff" stroke-width="2" fill="none" opacity="0.4"/>
<!-- Sun -->
<circle cx="1000" cy="100" r="60" fill="#FFD700" opacity="0.8"/>
<!-- Clouds -->
<ellipse cx="200" cy="120" rx="80" ry="40" fill="#ffffff" opacity="0.7"/>
<ellipse cx="250" cy="100" rx="60" ry="35" fill="#ffffff" opacity="0.7"/>
<!-- Bicycle frame -->
<g id="bike">
<!-- Frame -->
<line x1="450" y1="280" x2="550" y2="350" stroke="#FF4500" stroke-width="8" stroke-linecap="round"/>
<line x1="450" y1="280" x2="600" y2="280" stroke="#FF4500" stroke-width="8" stroke-linecap="round"/>
<line x1="600" y1="280" x2="550" y2="350" stroke="#FF4500" stroke-width="8" stroke-linecap="round"/>
<!-- Seat -->
<ellipse cx="480" cy="260" rx="35" ry="15" fill="#333333"/>
<!-- Handlebars -->
<line x1="450" y1="280" x2="420" y2="250" stroke="#FF4500" stroke-width="6"/>
<circle cx="420" cy="250" r="8" fill="#333333"/>
<circle cx="435" cy="260" r="6" fill="#333333"/>
<!-- Front wheel -->
<circle cx="600" cy="350" r="60" fill="none" stroke="#333333" stroke-width="6"/>
<circle cx="600" cy="350" r="55" fill="none" stroke="#FFD700" stroke-width="3"/>
<line x1="600" y1="290" x2="600" y2="410" stroke="#666666" stroke-width="2"/>
<line x1="540" y1="350" x2="660" y2="350" stroke="#666666" stroke-width="2"/>
<line x1="550" y1="300" x2="650" y2="400" stroke="#666666" stroke-width="1"/>
<line x1="650" y1="300" x2="550" y2="400" stroke="#666666" stroke-width="1"/>
<!-- Back wheel -->
<circle cx="450" cy="350" r="60" fill="none" stroke="#333333" stroke-width="6"/>
<circle cx="450" cy="350" r="55" fill="none" stroke="#FFD700" stroke-width="3"/>
<line x1="450" y1="290" x2="450" y2="410" stroke="#666666" stroke-width="2"/>
<line x1="390" y1="350" x2="510" y2="350" stroke="#666666" stroke-width="2"/>
<line x1="400" y1="300" x2="500" y2="400" stroke="#666666" stroke-width="1"/>
<line x1="500" y1="300" x2="400" y2="400" stroke="#666666" stroke-width="1"/>
</g>
<!-- Pelican body -->
<g id="pelican">
<!-- Body (large, round) -->
<ellipse cx="520" cy="240" rx="85" ry="100" fill="#F5F5DC"/>
<ellipse cx="520" cy="240" rx="85" ry="100" fill="none" stroke="#D3D3D3" stroke-width="2"/>
<!-- Neck -->
<path d="M 480 170 Q 470 130 480 80" stroke="#F5F5DC" stroke-width="45" fill="none" stroke-linecap="round"/>
<!-- Head -->
<ellipse cx="490" cy="50" rx="40" ry="45" fill="#F5F5DC"/>
<ellipse cx="490" cy="50" rx="40" ry="45" fill="none" stroke="#D3D3D3" stroke-width="2"/>
<!-- Eye -->
<circle cx="510" cy="35" r="6" fill="#000000"/>
<circle cx="512" cy="33" r="2" fill="#ffffff"/>
<!-- Beak pouch (iconic pelican feature) -->
<path d="M 515 55 Q 540 65 545 50 Q 540 45 520 45" fill="#FFB6C1" opacity="0.8"/>
<path d="M 515 55 Q 540 65 545 50 Q 540 45 520 45" fill="none" stroke="#FF69B4" stroke-width="1.5"/>
<!-- Beak -->
<path d="M 520 45 L 560 50 L 520 60 Z" fill="#F08080"/>
<!-- Wing -->
<ellipse cx="450" cy="200" rx="60" ry="90" fill="#E8E8E8" opacity="0.8" transform="rotate(-30 450 200)"/>
<!-- Legs -->
<line x1="510" y1="340" x2="510" y2="360" stroke="#FFB347" stroke-width="8" stroke-linecap="round"/>
<line x1="530" y1="340" x2="530" y2="360" stroke="#FFB347" stroke-width="8" stroke-linecap="round"/>
<!-- Feet (webbed) -->
<ellipse cx="510" cy="368" rx="12" ry="8" fill="#FF8C00"/>
<ellipse cx="530" cy="368" rx="12" ry="8" fill="#FF8C00"/>
</g>
<!-- Pelican gripping handlebars -->
<g id="pelican-wings-on-bars">
<path d="M 440 240 Q 420 220 415 250" stroke="#D3D3D3" stroke-width="6" fill="none" stroke-linecap="round" opacity="0.7"/>
<path d="M 600 240 Q 620 220 625 250" stroke="#D3D3D3" stroke-width="6" fill="none" stroke-linecap="round" opacity="0.7"/>
</g>
<!-- Smile/happy expression -->
<path d="M 500 65 Q 510 75 520 65" stroke="#000000" stroke-width="2" fill="none" stroke-linecap="round" opacity="0.6"/>
<!-- Sand beach details -->
<circle cx="300" cy="460" r="15" fill="#D2B48C" opacity="0.6"/>
<circle cx="950" cy="475" r="20" fill="#D2B48C" opacity="0.6"/>
<circle cx="150" cy="485" r="12" fill="#D2B48C" opacity="0.5"/>
<!-- Sparkle on water -->
<circle cx="750" cy="550" r="3" fill="#ffffff" opacity="0.8"/>
<circle cx="850" cy="600" r="2" fill="#ffffff" opacity="0.6"/>
<circle cx="400" cy="520" r="2.5" fill="#ffffff" opacity="0.7"/>
</svg>

After

Width:  |  Height:  |  Size: 5.4 KiB