feat: add GPT-5.6-Sol benchmark results (#32)
This commit is contained in:
1 parent
2192d05ac8
commit
807bdac433
4 files changed
+2826
No files matched your search
@@ -0,0 +1,488 @@
|
||||
<!doctype html>
|
||||
<html lang="zh-CN">
|
||||
<head>
|
||||
<meta charset="utf-8">
|
||||
<meta name="viewport" content="width=device-width,initial-scale=1,maximum-scale=1,user-scalable=no">
|
||||
<title>神域方块 · MYTHOS CRAFT</title>
|
||||
<style>
|
||||
:root{--gold:#f6d78b;--gold2:#b98743;--ink:#071317;--cyan:#71f4dc;--paper:#e9e0c5;--danger:#ff796c}
|
||||
*{box-sizing:border-box}html,body{width:100%;height:100%;margin:0;overflow:hidden;background:#061116;color:var(--paper);font-family:"Noto Sans SC","Microsoft YaHei",system-ui,sans-serif}
|
||||
body{user-select:none}canvas{display:block}#game{position:fixed;inset:0}
|
||||
#game:after{content:"";position:absolute;inset:0;pointer-events:none;background:radial-gradient(circle at 50% 44%,transparent 48%,rgba(2,7,10,.2) 72%,rgba(0,0,0,.58) 115%);z-index:2}
|
||||
.hidden{display:none!important}.glass{background:linear-gradient(145deg,rgba(7,22,25,.78),rgba(9,19,27,.56));border:1px solid rgba(246,215,139,.28);box-shadow:inset 0 0 28px rgba(113,244,220,.035),0 10px 34px rgba(0,0,0,.25);backdrop-filter:blur(10px)}
|
||||
#loading{position:fixed;inset:0;z-index:100;background:#030b0e;display:flex;align-items:center;justify-content:center;letter-spacing:.18em;color:var(--gold)}
|
||||
#loading .mark{width:34px;height:34px;margin-right:14px;border:1px solid var(--gold2);transform:rotate(45deg);animation:spin 2.2s linear infinite;box-shadow:0 0 24px rgba(113,244,220,.3)}
|
||||
@keyframes spin{to{transform:rotate(405deg)}}
|
||||
#veil{position:fixed;inset:0;z-index:40;display:grid;place-items:center;background:radial-gradient(circle at 50% 34%,rgba(18,56,59,.42),rgba(1,7,10,.88) 65%),linear-gradient(130deg,#06181a,#080b18);transition:opacity .3s}
|
||||
#veil:before,#veil:after{content:"";position:absolute;inset:22px;border:1px solid rgba(246,215,139,.19);pointer-events:none}#veil:after{inset:30px;border-color:rgba(113,244,220,.08)}
|
||||
.intro{position:relative;width:min(870px,calc(100vw - 42px));padding:48px 55px 38px;text-align:center;overflow:hidden}
|
||||
.intro:before{content:"ᚱ ✦ ᛉ ◇ ᛟ";position:absolute;left:0;right:0;top:15px;color:rgba(246,215,139,.3);letter-spacing:1.1em;font-size:11px}
|
||||
.eyebrow{font-size:11px;letter-spacing:.48em;color:var(--cyan);text-transform:uppercase;margin-bottom:14px}.intro h1{font-family:Georgia,"Noto Serif SC",serif;font-size:clamp(38px,7vw,76px);font-weight:500;line-height:.95;margin:0;background:linear-gradient(#fff8df 10%,#e5bd68 65%,#8e6334);-webkit-background-clip:text;color:transparent;text-shadow:0 9px 38px rgba(246,215,139,.12);letter-spacing:.08em}.en-title{font-family:Georgia,serif;font-size:12px;letter-spacing:.72em;color:rgba(233,224,197,.45);margin:14px 0 28px}.intro .lore{max-width:660px;margin:0 auto 27px;color:rgba(233,224,197,.72);line-height:1.9;font-size:14px}.intro .lore em{font-style:normal;color:var(--gold)}
|
||||
.start-btn{border:1px solid rgba(246,215,139,.65);background:linear-gradient(90deg,rgba(105,74,36,.48),rgba(25,80,75,.42),rgba(105,74,36,.48));color:#fff3cf;padding:14px 37px;min-width:210px;font:500 13px inherit;letter-spacing:.32em;cursor:pointer;clip-path:polygon(7px 0,calc(100% - 7px) 0,100% 7px,100% calc(100% - 7px),calc(100% - 7px) 100%,7px 100%,0 calc(100% - 7px),0 7px);transition:.2s}.start-btn:hover{filter:brightness(1.28);box-shadow:0 0 30px rgba(113,244,220,.16);transform:translateY(-1px)}
|
||||
.controls-row{display:flex;justify-content:center;gap:20px;flex-wrap:wrap;margin-top:28px;color:rgba(233,224,197,.48);font-size:11px;letter-spacing:.06em}.controls-row b{display:inline-grid;place-items:center;height:22px;min-width:22px;padding:0 5px;margin-right:5px;border:1px solid rgba(233,224,197,.24);color:rgba(255,255,255,.78);font-weight:500}
|
||||
#hud{position:fixed;inset:0;z-index:5;pointer-events:none}.top-left{position:absolute;left:22px;top:20px;min-width:258px;padding:13px 15px}.brand{font-family:Georgia,"Noto Serif SC",serif;color:var(--gold);font-size:15px;letter-spacing:.18em}.brand small{display:block;color:rgba(233,224,197,.38);font:8px system-ui;letter-spacing:.42em;margin-top:4px}.readout{display:flex;gap:13px;margin-top:12px;padding-top:10px;border-top:1px solid rgba(246,215,139,.12);font:10px ui-monospace,monospace;color:rgba(233,224,197,.6)}.readout strong{color:#d8f7ec;font-weight:500}
|
||||
#minimapWrap{position:absolute;right:22px;top:20px;width:170px;padding:10px}.map-title{display:flex;justify-content:space-between;align-items:center;color:rgba(246,215,139,.7);font:9px ui-monospace,monospace;letter-spacing:.18em;margin-bottom:7px}#minimap{width:148px;height:148px;border-radius:50%;border:1px solid rgba(246,215,139,.3);box-shadow:inset 0 0 24px rgba(0,0,0,.9)}
|
||||
#objective{position:absolute;right:22px;top:226px;width:210px;padding:13px 14px}.obj-kicker{color:var(--cyan);font-size:8px;letter-spacing:.3em}.obj-name{margin:6px 0 5px;font-family:Georgia,"Noto Serif SC",serif;color:#fff0c6;font-size:14px}.obj-copy{font-size:10px;color:rgba(233,224,197,.52);line-height:1.65}.obj-distance{margin-top:8px;color:rgba(113,244,220,.78);font:9px ui-monospace,monospace}
|
||||
#crosshair{position:absolute;left:50%;top:50%;width:20px;height:20px;transform:translate(-50%,-50%)}#crosshair:before,#crosshair:after{content:"";position:absolute;background:rgba(255,247,218,.9);box-shadow:0 0 4px rgba(0,0,0,.8)}#crosshair:before{left:9px;top:2px;width:1px;height:16px}#crosshair:after{left:2px;top:9px;width:16px;height:1px}#crosshair i{position:absolute;inset:6px;border:1px solid rgba(113,244,220,.5);transform:rotate(45deg)}
|
||||
#targetText{position:absolute;left:50%;top:calc(50% + 23px);transform:translateX(-50%);white-space:nowrap;color:rgba(255,247,218,.74);font-size:9px;letter-spacing:.12em;text-shadow:0 1px 3px #000}
|
||||
#toast{position:absolute;left:50%;top:23%;transform:translate(-50%,-8px);padding:9px 17px;border:1px solid rgba(246,215,139,.25);background:rgba(5,15,18,.74);font-size:11px;letter-spacing:.16em;color:#fff0c9;opacity:0;transition:.25s}#toast.show{opacity:1;transform:translate(-50%,0)}
|
||||
#hotbar{position:absolute;left:50%;bottom:24px;transform:translateX(-50%);display:flex;gap:6px;padding:7px}.slot{position:relative;width:54px;height:54px;border:1px solid rgba(233,224,197,.17);background:rgba(1,8,11,.63);display:grid;place-items:center;transition:.14s}.slot.active{border-color:var(--gold);background:rgba(78,63,34,.72);transform:translateY(-5px);box-shadow:0 0 20px rgba(246,215,139,.16)}.slot .key{position:absolute;left:4px;top:2px;color:rgba(233,224,197,.4);font:8px ui-monospace}.slot .cube{width:25px;height:25px;border:1px solid rgba(255,255,255,.14);box-shadow:inset -6px -6px 0 rgba(0,0,0,.2),inset 3px 3px 0 rgba(255,255,255,.08);image-rendering:pixelated}.slot .name{position:absolute;bottom:2px;left:0;right:0;text-align:center;font-size:7px;color:rgba(255,255,255,.62)}
|
||||
#meters{position:absolute;left:22px;bottom:26px;width:230px}.meter-label{display:flex;justify-content:space-between;color:rgba(233,224,197,.45);font:8px ui-monospace;margin-bottom:5px}.meter{height:3px;background:rgba(255,255,255,.1);overflow:hidden}.meter>i{display:block;height:100%;width:100%;background:linear-gradient(90deg,var(--gold2),var(--gold));transition:width .2s}.hints{margin-top:10px;color:rgba(233,224,197,.34);font-size:9px;line-height:1.8}
|
||||
#debug{position:absolute;left:50%;top:16px;transform:translateX(-50%);font:9px ui-monospace;color:rgba(233,224,197,.5);letter-spacing:.06em}
|
||||
@media(max-width:760px){#objective,#meters{display:none}.top-left{left:10px;top:10px;transform:scale(.86);transform-origin:top left}#minimapWrap{right:10px;top:10px;transform:scale(.78);transform-origin:top right}.slot{width:38px;height:44px}.slot .cube{width:19px;height:19px}.intro{padding:42px 22px 28px}.controls-row{gap:8px}.en-title{letter-spacing:.42em}}
|
||||
</style>
|
||||
</head>
|
||||
<body>
|
||||
<div id="loading"><div class="mark"></div><span>正在唤醒远古世界</span></div>
|
||||
<main id="game"></main>
|
||||
<section id="veil">
|
||||
<div class="intro glass">
|
||||
<div class="eyebrow">程序生成 · 无限神话世界</div>
|
||||
<h1>神域方块</h1><div class="en-title">MYTHOS CRAFT</div>
|
||||
<p class="lore">诸神离去后,九种原初方块仍在梦中生长。穿过<em>日冕圣门</em>,寻找倒悬天岛、月神仪庭与星骸巨龙;你破坏或放置的每一块石头,都会留在这个种子世界里。</p>
|
||||
<button class="start-btn" id="startBtn">踏 入 神 域</button>
|
||||
<div class="controls-row"><span><b>WASD</b>移动</span><span><b>鼠标</b>环视</span><span><b>左键</b>破坏</span><span><b>右键</b>放置</span><span><b>SPACE</b>跳跃</span><span><b>1—9</b>选方块</span></div>
|
||||
</div>
|
||||
</section>
|
||||
<div id="hud" class="hidden">
|
||||
<div class="top-left glass"><div class="brand">神域方块<small>MYTHOS CRAFT · SEED 9C4F</small></div><div class="readout"><span>坐标 <strong id="coords">0 / 0 / 0</strong></span><span>地域 <strong id="biome">翡翠荒原</strong></span><span>时刻 <strong id="daytime">晨曦</strong></span></div></div>
|
||||
<div id="minimapWrap" class="glass"><div class="map-title"><span>星盘地志</span><span id="heading">N</span></div><canvas id="minimap" width="296" height="296"></canvas></div>
|
||||
<div id="objective" class="glass"><div class="obj-kicker">最近的神话遗迹</div><div class="obj-name" id="objName">日冕圣门</div><div class="obj-copy" id="objCopy">旧日太阳神用鎏金符石铸成的天空之钥。</div><div class="obj-distance" id="objDistance">距离 0 格</div></div>
|
||||
<div id="crosshair"><i></i></div><div id="targetText">凝视方块以采集</div><div id="toast"></div><div id="debug"></div>
|
||||
<div id="meters"><div class="meter-label"><span>神行者体力</span><span id="staminaText">100</span></div><div class="meter"><i id="stamina"></i></div><div class="hints">SHIFT 疾跑 · SPACE 跳跃 · R 返回星印<br>滚轮切换方块 · ESC 释放鼠标</div></div>
|
||||
<div id="hotbar" class="glass"></div>
|
||||
</div>
|
||||
<script src="https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.min.js"></script>
|
||||
<script>
|
||||
'use strict';
|
||||
(() => {
|
||||
if (!window.THREE) {
|
||||
document.getElementById('loading').innerHTML = '无法载入 Three.js,请检查网络后刷新';
|
||||
return;
|
||||
}
|
||||
|
||||
const $ = (q) => document.querySelector(q);
|
||||
const game = $('#game'), loading = $('#loading'), veil = $('#veil'), hud = $('#hud');
|
||||
const coordsEl = $('#coords'), biomeEl = $('#biome'), daytimeEl = $('#daytime');
|
||||
const targetText = $('#targetText'), hotbar = $('#hotbar'), toastEl = $('#toast');
|
||||
const mapCanvas = $('#minimap'), mapCtx = mapCanvas.getContext('2d');
|
||||
const SEED = 0x9c4f21, CHUNK = 16, WORLD_H = 64, VIEW_RADIUS = 3;
|
||||
const AIR = 0, GRASS = 1, DIRT = 2, STONE = 3, RUNE = 4, OBSIDIAN = 5;
|
||||
const GOLD = 6, CRYSTAL = 7, WOOD = 8, LEAVES = 9;
|
||||
const BLOCKS = [
|
||||
null,
|
||||
{name:'神域草', short:'草地', side:1, top:0, bottom:2, css:'#4f9b68', tint:[.92,1,.94]},
|
||||
{name:'沃土', short:'泥土', side:2, top:2, bottom:2, css:'#845a3b', tint:[1,.94,.88]},
|
||||
{name:'远古岩', short:'石头', side:3, top:3, bottom:3, css:'#7f8784', tint:[.94,.97,.96]},
|
||||
{name:'符文石', short:'符石', side:4, top:4, bottom:3, css:'#3c776f', tint:[.79,1,.94]},
|
||||
{name:'夜曜石', short:'夜曜', side:5, top:5, bottom:5, css:'#302845', tint:[.86,.79,1]},
|
||||
{name:'鎏金砖', short:'鎏金', side:6, top:6, bottom:6, css:'#c28a35', tint:[1,.91,.65]},
|
||||
{name:'星辉晶', short:'星晶', side:7, top:7, bottom:7, css:'#55cdd1', tint:[.72,1,1]},
|
||||
{name:'世界木', short:'神木', side:9, top:8, bottom:8, css:'#765137', tint:[1,.91,.79]},
|
||||
{name:'永青叶', short:'永叶', side:10, top:10, bottom:10, css:'#39725c', tint:[.75,1,.82]}
|
||||
];
|
||||
|
||||
function mulberry32(a){return function(){a|=0;a=a+0x6D2B79F5|0;let t=Math.imul(a^a>>>15,1|a);t=t+Math.imul(t^t>>>7,61|t)^t;return ((t^t>>>14)>>>0)/4294967296}}
|
||||
class Perlin {
|
||||
constructor(seed){
|
||||
const rnd=mulberry32(seed), p=Array.from({length:256},(_,i)=>i);
|
||||
for(let i=255;i>0;i--){const j=Math.floor(rnd()*(i+1));[p[i],p[j]]=[p[j],p[i]]}
|
||||
this.p=new Uint16Array(512);for(let i=0;i<512;i++)this.p[i]=p[i&255];
|
||||
}
|
||||
fade(t){return t*t*t*(t*(t*6-15)+10)} lerp(a,b,t){return a+t*(b-a)}
|
||||
grad(h,x,y,z){const q=h&15,u=q<8?x:y,v=q<4?y:q===12||q===14?x:z;return ((q&1)?-u:u)+((q&2)?-v:v)}
|
||||
noise3(x,y,z){
|
||||
const X=Math.floor(x)&255,Y=Math.floor(y)&255,Z=Math.floor(z)&255;
|
||||
x-=Math.floor(x);y-=Math.floor(y);z-=Math.floor(z);
|
||||
const u=this.fade(x),v=this.fade(y),w=this.fade(z),p=this.p;
|
||||
const A=p[X]+Y,AA=p[A]+Z,AB=p[A+1]+Z,B=p[X+1]+Y,BA=p[B]+Z,BB=p[B+1]+Z;
|
||||
return this.lerp(this.lerp(this.lerp(this.grad(p[AA],x,y,z),this.grad(p[BA],x-1,y,z),u),this.lerp(this.grad(p[AB],x,y-1,z),this.grad(p[BB],x-1,y-1,z),u),v),this.lerp(this.lerp(this.grad(p[AA+1],x,y,z-1),this.grad(p[BA+1],x-1,y,z-1),u),this.lerp(this.grad(p[AB+1],x,y-1,z-1),this.grad(p[BB+1],x-1,y-1,z-1),u),v),w);
|
||||
}
|
||||
noise2(x,z){return this.noise3(x,31.416,z)}
|
||||
}
|
||||
const perlin=new Perlin(SEED), heightCache=new Map();
|
||||
function fbm(x,z,oct=4,persist=.5){let sum=0,amp=1,freq=1,norm=0;for(let i=0;i<oct;i++){sum+=perlin.noise2(x*freq,z*freq)*amp;norm+=amp;amp*=persist;freq*=2.03}return sum/norm}
|
||||
function hash2(x,z,s=SEED){let h=Math.imul(x|0,374761393)^Math.imul(z|0,668265263)^s;h=Math.imul(h^(h>>>13),1274126177);return ((h^(h>>>16))>>>0)/4294967295}
|
||||
function hash3(x,y,z){return hash2((x|0)^Math.imul(y|0,1597334677),z|0,SEED^0x51ed270b)}
|
||||
const clamp=(v,a,b)=>Math.max(a,Math.min(b,v)), floorDiv=(a,b)=>Math.floor(a/b), mod=(a,b)=>((a%b)+b)%b;
|
||||
function heightAt(x,z){
|
||||
x=Math.floor(x);z=Math.floor(z);const key=x+','+z;if(heightCache.has(key))return heightCache.get(key);
|
||||
const continent=.5+.5*fbm(x*.006,z*.006,5,.52), hills=fbm(x*.026,z*.026,4,.52);
|
||||
const ridge=1-Math.abs(perlin.noise2(x*.012-30,z*.012+18));
|
||||
const highland=Math.max(0,fbm(x*.003+70,z*.003-40,3,.55)-.05);
|
||||
let h=Math.floor(6+continent*10+hills*3+ridge*highland*15);
|
||||
h=clamp(h,4,31);heightCache.set(key,h);if(heightCache.size>240000)heightCache.clear();return h;
|
||||
}
|
||||
function biomeAt(x,z){
|
||||
const h=heightAt(x,z),m=perlin.noise2(x*.009+120,z*.009-90);
|
||||
if(h>24)return {name:'诸神脊岭',color:'#727c7d'};
|
||||
if(m>.28)return {name:'永青密林',color:'#326b55'};
|
||||
if(m<-.3)return {name:'月盐荒漠',color:'#9d8b72'};
|
||||
if(perlin.noise2(x*.004,z*.004)>.38)return {name:'星落高原',color:'#477c75'};
|
||||
return {name:'翡翠荒原',color:'#548766'};
|
||||
}
|
||||
function terrainBlockAt(x,y,z){
|
||||
if(y<0||y>=WORLD_H)return AIR;if(y===0)return OBSIDIAN;
|
||||
const h=heightAt(x,z);if(y>h)return AIR;
|
||||
if(y>3&&y<h-3){
|
||||
const cave=perlin.noise3(x*.074,y*.083,z*.074)+.36*perlin.noise3(x*.151+8,y*.143,z*.151-5);
|
||||
if(cave>.69)return AIR;
|
||||
}
|
||||
if(y===h)return h>25?STONE:GRASS;
|
||||
if(y>=h-3)return DIRT;
|
||||
const ore=hash3(x,y,z);
|
||||
if(y<20&&ore>.994)return CRYSTAL;if(y<15&&ore<.004)return GOLD;if(y<24&&ore>.987)return RUNE;
|
||||
return STONE;
|
||||
}
|
||||
|
||||
function makeAtlas(){
|
||||
const S=64,C=4,c=document.createElement('canvas');c.width=c.height=S*C;const g=c.getContext('2d');g.imageSmoothingEnabled=false;
|
||||
const rand=mulberry32(SEED^0xada5), xy=(tile)=>[(tile%C)*S,Math.floor(tile/C)*S];
|
||||
function noiseTile(tile,base,spots,amount=150){const [ox,oy]=xy(tile);g.fillStyle=base;g.fillRect(ox,oy,S,S);for(let i=0;i<amount;i++){const k=(rand()*spots.length)|0,sz=2+(rand()*5|0);g.globalAlpha=.25+rand()*.5;g.fillStyle=spots[k];g.fillRect(ox+(rand()*S|0),oy+(rand()*S|0),sz,sz)}g.globalAlpha=1}
|
||||
noiseTile(0,'#4f925d',['#82b865','#315f48','#b1c86d']);
|
||||
noiseTile(1,'#76533a',['#5a3d2d','#956b45','#3d7049']);{const[o,y]=xy(1);g.fillStyle='#4d8d59';g.fillRect(o,y,S,12);g.fillStyle='#79af63';for(let x=0;x<S;x+=6)g.fillRect(o+x,y+9,4,5+(x%12));}
|
||||
noiseTile(2,'#79543a',['#563825','#9b6a44','#45382d'],180);
|
||||
noiseTile(3,'#707876',['#929b96','#555c5b','#3f4d4e'],170);
|
||||
noiseTile(4,'#315d59',['#427d73','#183e40','#789d86'],100);{const[o,y]=xy(4);g.strokeStyle='#77d8be';g.lineWidth=4;g.shadowColor='#63e9d0';g.shadowBlur=7;g.beginPath();g.moveTo(o+17,y+12);g.lineTo(o+17,y+49);g.lineTo(o+33,y+32);g.lineTo(o+47,y+48);g.moveTo(o+17,y+31);g.lineTo(o+39,y+12);g.stroke();g.shadowBlur=0}
|
||||
noiseTile(5,'#201a31',['#3d3158','#151220','#695081'],120);{const[o,y]=xy(5);g.strokeStyle='rgba(160,114,204,.5)';g.lineWidth=2;for(let i=0;i<6;i++){g.beginPath();g.moveTo(o+rand()*S,y);g.lineTo(o+rand()*S,y+S);g.stroke()}}
|
||||
noiseTile(6,'#aa762c',['#e5bd58','#725024','#f2d87e'],100);{const[o,y]=xy(6);g.strokeStyle='#5f4221';g.lineWidth=3;g.strokeRect(o+3,y+3,S-6,S-6);g.beginPath();g.moveTo(o+6,y+32);g.lineTo(o+32,y+6);g.lineTo(o+58,y+32);g.lineTo(o+32,y+58);g.closePath();g.stroke()}
|
||||
noiseTile(7,'#43aeb2',['#a8ffff','#267c91','#6ee4d4'],90);{const[o,y]=xy(7);g.fillStyle='rgba(221,255,255,.52)';g.beginPath();g.moveTo(o+31,y+3);g.lineTo(o+58,y+28);g.lineTo(o+37,y+61);g.lineTo(o+6,y+38);g.closePath();g.fill();g.strokeStyle='#dbffff';g.lineWidth=2;g.beginPath();g.moveTo(o+31,y+3);g.lineTo(o+37,y+61);g.moveTo(o+6,y+38);g.lineTo(o+58,y+28);g.stroke()}
|
||||
noiseTile(8,'#6d4b34',['#3b2b24','#956a42'],60);{const[o,y]=xy(8);g.strokeStyle='#3d2a21';g.lineWidth=4;for(let r=8;r<31;r+=8){g.beginPath();g.arc(o+32,y+32,r,0,Math.PI*2);g.stroke()}}
|
||||
noiseTile(9,'#67472f',['#966744','#3f2d25'],80);{const[o,y]=xy(9);g.strokeStyle='#3a2821';g.lineWidth=4;for(let x=8;x<S;x+=14){g.beginPath();g.moveTo(o+x,y);g.lineTo(o+x-5,y+S);g.stroke()}}
|
||||
noiseTile(10,'#32664f',['#4f9167','#1f493f','#76a965'],220);{const[o,y]=xy(10);g.fillStyle='rgba(151,220,143,.38)';for(let i=0;i<14;i++)g.fillRect(o+(rand()*S|0),y+(rand()*S|0),4,4)}
|
||||
noiseTile(11,'#a5a69d',['#c7c7b6','#777f7c'],100);noiseTile(12,'#5f6574',['#9195a4','#363b49'],100);noiseTile(13,'#536d75',['#79a4a1','#263c46'],100);noiseTile(14,'#806951',['#ac9472','#51483d'],100);noiseTile(15,'#172f3c',['#315c66','#0b202c'],100);
|
||||
const texture=new THREE.CanvasTexture(c);texture.colorSpace=THREE.SRGBColorSpace;texture.magFilter=THREE.NearestFilter;texture.minFilter=THREE.NearestFilter;texture.generateMipmaps=false;return texture;
|
||||
}
|
||||
const atlas=makeAtlas();
|
||||
|
||||
const scene=new THREE.Scene();scene.background=new THREE.Color(0x183b49);scene.fog=new THREE.FogExp2(0x183b49,.0115);
|
||||
const camera=new THREE.PerspectiveCamera(72,innerWidth/innerHeight,.05,230);camera.rotation.order='YXZ';
|
||||
const renderer=new THREE.WebGLRenderer({antialias:false,powerPreference:'high-performance'});renderer.setPixelRatio(Math.min(devicePixelRatio,1.65));renderer.setSize(innerWidth,innerHeight);renderer.outputColorSpace=THREE.SRGBColorSpace;renderer.toneMapping=THREE.ACESFilmicToneMapping;renderer.toneMappingExposure=1.12;game.appendChild(renderer.domElement);
|
||||
const worldMaterial=new THREE.MeshStandardMaterial({map:atlas,vertexColors:true,roughness:.88,metalness:.05,side:THREE.FrontSide});
|
||||
const hemi=new THREE.HemisphereLight(0x9de8ef,0x18201b,1.32);scene.add(hemi);
|
||||
const sun=new THREE.DirectionalLight(0xffe5b1,2.15);sun.position.set(60,90,28);scene.add(sun);
|
||||
const moonGlow=new THREE.PointLight(0x73e9dc,18,55,1.8);moonGlow.position.set(38,35,24);scene.add(moonGlow);
|
||||
|
||||
function makeSkyDetails(){
|
||||
const rnd=mulberry32(SEED), starGeo=new THREE.BufferGeometry(),arr=[];
|
||||
for(let i=0;i<520;i++){const a=rnd()*Math.PI*2,b=.12+rnd()*1.18,r=145+20*rnd();arr.push(Math.sin(b)*Math.cos(a)*r,Math.cos(b)*r+18,Math.sin(b)*Math.sin(a)*r)}
|
||||
starGeo.setAttribute('position',new THREE.Float32BufferAttribute(arr,3));const stars=new THREE.Points(starGeo,new THREE.PointsMaterial({color:0xc9fff3,size:.52,transparent:true,opacity:.55,sizeAttenuation:true,depthWrite:false}));scene.add(stars);
|
||||
const sunDisc=new THREE.Mesh(new THREE.SphereGeometry(4,18,12),new THREE.MeshBasicMaterial({color:0xffe0a0,fog:false}));sunDisc.name='sunDisc';scene.add(sunDisc);
|
||||
const moonDisc=new THREE.Mesh(new THREE.SphereGeometry(2.1,16,10),new THREE.MeshBasicMaterial({color:0x9fffee,fog:false}));moonDisc.position.set(88,65,-90);scene.add(moonDisc);
|
||||
return {stars,sunDisc,moonDisc};
|
||||
}
|
||||
const sky=makeSkyDetails();
|
||||
|
||||
const chunks=new Map(), editsByChunk=new Map(), loadQueue=[];
|
||||
const chunkKey=(cx,cz)=>cx+','+cz, voxelIndex=(x,y,z)=>y*CHUNK*CHUNK+z*CHUNK+x;
|
||||
function inChunk(chunk,x,z){return x>=chunk.cx*CHUNK&&x<(chunk.cx+1)*CHUNK&&z>=chunk.cz*CHUNK&&z<(chunk.cz+1)*CHUNK}
|
||||
function put(chunk,x,y,z,type,mode='replace'){
|
||||
x=Math.floor(x);y=Math.floor(y);z=Math.floor(z);if(y<0||y>=WORLD_H||!inChunk(chunk,x,z))return;
|
||||
const i=voxelIndex(mod(x,CHUNK),y,mod(z,CHUNK)),old=chunk.data[i];
|
||||
if(mode==='air'&&old!==AIR)return;if(mode==='solid'&&old===AIR)return;chunk.data[i]=type;
|
||||
}
|
||||
function box(chunk,x0,y0,z0,x1,y1,z1,type,mode='replace'){
|
||||
for(let y=y0;y<=y1;y++)for(let z=z0;z<=z1;z++)for(let x=x0;x<=x1;x++)put(chunk,x,y,z,type,mode);
|
||||
}
|
||||
function sphere(chunk,cx,cy,cz,r,type,mode='replace'){
|
||||
const rr=r*r;for(let y=Math.floor(cy-r);y<=Math.ceil(cy+r);y++)for(let z=Math.floor(cz-r);z<=Math.ceil(cz+r);z++)for(let x=Math.floor(cx-r);x<=Math.ceil(cx+r);x++)if((x-cx)**2+(y-cy)**2+(z-cz)**2<=rr)put(chunk,x,y,z,type,mode);
|
||||
}
|
||||
function lineBlocks(chunk,a,b,type,r=.6,mode='replace'){
|
||||
const len=Math.hypot(b[0]-a[0],b[1]-a[1],b[2]-a[2]),n=Math.ceil(len*1.8);
|
||||
for(let i=0;i<=n;i++){const t=i/n;sphere(chunk,a[0]+(b[0]-a[0])*t,a[1]+(b[1]-a[1])*t,a[2]+(b[2]-a[2])*t,r,type,mode)}
|
||||
}
|
||||
function overlaps(chunk,x0,x1,z0,z1){const ax=chunk.cx*CHUNK,az=chunk.cz*CHUNK;return ax<=x1&&ax+CHUNK-1>=x0&&az<=z1&&az+CHUNK-1>=z0}
|
||||
|
||||
const LANDMARKS=[
|
||||
{name:'日冕圣门',x:0,z:-24,copy:'旧日太阳神用鎏金符石铸成的天空之钥。'},
|
||||
{name:'永恒世界树',x:-36,z:27,copy:'它的根须穿过洞窟,枝叶托举着失落的星辰。'},
|
||||
{name:'月神仪庭',x:39,z:25,copy:'潮汐祭司在此丈量月亮与梦的距离。'},
|
||||
{name:'星骸巨龙',x:31,z:-47,copy:'一头从天幕坠落的古龙,只余发光的脊骨。'},
|
||||
{name:'倒悬天岛',x:5,z:48,copy:'被神链锚定在云上的小型王国遗址。'}
|
||||
];
|
||||
const templeBase=heightAt(0,-24)+2, treeBase=heightAt(-36,27)+1, moonBase=heightAt(39,25)+2, dragonBase=heightAt(31,-47)+3;
|
||||
|
||||
function decorateNature(chunk){
|
||||
const x0=chunk.cx*CHUNK,z0=chunk.cz*CHUNK;
|
||||
// Deterministic trees. Candidate cells are evaluated beyond the chunk edge so crowns join seamlessly.
|
||||
for(let gz=floorDiv(z0-5,9);gz<=floorDiv(z0+CHUNK+5,9);gz++)for(let gx=floorDiv(x0-5,9);gx<=floorDiv(x0+CHUNK+5,9);gx++){
|
||||
const chance=hash2(gx,gz,SEED^0x4b1d);if(chance<.57)continue;
|
||||
const tx=gx*9+2+Math.floor(hash2(gx,gz,71)*6),tz=gz*9+2+Math.floor(hash2(gx,gz,109)*6),h=heightAt(tx,tz);
|
||||
const bio=biomeAt(tx,tz);if(h>24||bio.name==='月盐荒漠'||Math.abs(heightAt(tx+1,tz)-h)>1)continue;
|
||||
if(Math.hypot(tx,tz+24)<15||Math.hypot(tx+36,tz-27)<11||Math.hypot(tx-39,tz-25)<12)continue;
|
||||
const tall=3+Math.floor(hash2(gx,gz,131)*3);for(let y=h+1;y<=h+tall;y++)put(chunk,tx,y,tz,WOOD);
|
||||
sphere(chunk,tx,h+tall+1,tz,2.45,LEAVES,'air');sphere(chunk,tx+1,h+tall,tz-1,1.8,LEAVES,'air');
|
||||
}
|
||||
// Small star-crystal outcrops.
|
||||
for(let gz=floorDiv(z0-3,13);gz<=floorDiv(z0+CHUNK+3,13);gz++)for(let gx=floorDiv(x0-3,13);gx<=floorDiv(x0+CHUNK+3,13);gx++){
|
||||
if(hash2(gx,gz,991)<.78)continue;const x=gx*13+3+(hash2(gx,gz,37)*7|0),z=gz*13+3+(hash2(gx,gz,43)*7|0),y=heightAt(x,z)+1;
|
||||
const n=2+(hash2(gx,gz,59)*3|0);for(let i=0;i<n;i++){const dx=(hash2(gx+i,gz,61)*3|0)-1,dz=(hash2(gx,gz+i,67)*3|0)-1;lineBlocks(chunk,[x+dx,y,z+dz],[x+dx,y+2+(i%3),z+dz],CRYSTAL,.48,'air')}
|
||||
}
|
||||
}
|
||||
|
||||
function buildWanderingSpires(chunk){
|
||||
const x0=chunk.cx*CHUNK,z0=chunk.cz*CHUNK;
|
||||
for(let gz=floorDiv(z0-8,58);gz<=floorDiv(z0+CHUNK+8,58);gz++)for(let gx=floorDiv(x0-8,58);gx<=floorDiv(x0+CHUNK+8,58);gx++){
|
||||
if(hash2(gx,gz,0x2211)<.34)continue;const x=gx*58+10+(hash2(gx,gz,4)*38|0),z=gz*58+10+(hash2(gx,gz,8)*38|0);
|
||||
if(Math.min(...LANDMARKS.map(m=>Math.hypot(x-m.x,z-m.z)))<17)continue;
|
||||
const base=heightAt(x,z)+1,height=7+(hash2(gx,gz,12)*9|0),rad=2+(hash2(gx,gz,16)>.65?1:0);
|
||||
for(let y=base;y<base+height;y++){const rr=y>base+height-3?1:rad;for(let dz=-rr;dz<=rr;dz++)for(let dx=-rr;dx<=rr;dx++)if(dx*dx+dz*dz<=rr*rr+.2){const edge=Math.abs(dx)===rr||Math.abs(dz)===rr;put(chunk,x+dx,y,z+dz,edge&&y%4===0?RUNE:OBSIDIAN)}}
|
||||
sphere(chunk,x,base+height,z,1.35,CRYSTAL,'air');
|
||||
}
|
||||
}
|
||||
|
||||
function buildTemple(chunk){
|
||||
const X=0,Z=-24,B=templeBase;if(!overlaps(chunk,-15,15,Z-13,Z+23))return;
|
||||
// Three-tier, rounded ceremonial platform.
|
||||
for(let y=B-2;y<=B;y++){const shrink=B-y;for(let z=Z-10+shrink;z<=Z+10-shrink;z++)for(let x=-12+shrink;x<=12-shrink;x++)if(Math.abs(x)<=10||Math.abs(z-Z)<=8){const edge=Math.abs(x)>=10-shrink||Math.abs(z-Z)>=8-shrink;put(chunk,x,y,z,edge?RUNE:STONE)}}
|
||||
// Long processional stair descending toward the player spawn.
|
||||
for(let s=0;s<13;s++){const z=Z+11+s,y=B-Math.floor(s/4);for(let x=-4;x<=4;x++)put(chunk,x,y,z,(x===-4||x===4||s%4===0)?GOLD:STONE)}
|
||||
// Sun gate: paired pylons and a stepped lintel.
|
||||
for(const px of [-5,5]){box(chunk,px-1,B+1,Z+6,px+1,B+9,Z+8,RUNE);box(chunk,px,B+2,Z+7,px,B+8,Z+7,GOLD);sphere(chunk,px,B+11,Z+7,1.55,CRYSTAL,'air')}
|
||||
box(chunk,-6,B+9,Z+6,6,B+10,Z+8,GOLD);box(chunk,-4,B+11,Z+7,4,B+11,Z+7,RUNE);
|
||||
// Four sanctum towers, each capped with a luminous crystal brazier.
|
||||
for(const [px,pz] of [[-10,Z-7],[10,Z-7],[-10,Z+7],[10,Z+7]]){
|
||||
for(let y=B+1;y<=B+12;y++)for(let dz=-1;dz<=1;dz++)for(let dx=-1;dx<=1;dx++){const shell=Math.abs(dx)===1||Math.abs(dz)===1;put(chunk,px+dx,y,pz+dz,(y%4===0||!shell)?GOLD:RUNE)}
|
||||
for(let d=-3;d<=3;d++){put(chunk,px+d,B+12,pz,GOLD);put(chunk,px,B+12,pz+d,GOLD)}sphere(chunk,px,B+14,pz,1.45,CRYSTAL,'air');
|
||||
}
|
||||
// Altar, constellation mosaic and suspended halo.
|
||||
for(let r=4;r>=1;r--)for(let z=Z-r;z<=Z+r;z++)for(let x=-r;x<=r;x++)put(chunk,x,B+5-r,z,(Math.abs(x)+Math.abs(z-Z))%3===0?GOLD:RUNE);
|
||||
lineBlocks(chunk,[0,B+2,Z],[0,B+8,Z],CRYSTAL,.72);
|
||||
const hy=B+16;for(let i=0;i<64;i++){const a=i/64*Math.PI*2;put(chunk,Math.round(Math.cos(a)*7),hy+Math.round(Math.sin(a)*2),Z+Math.round(Math.sin(a)*7),i%8===0?CRYSTAL:GOLD)}
|
||||
// Rune colonnade along both sides.
|
||||
for(const sx of [-1,1])for(let p=-6;p<=6;p+=4){const x=sx*8,z=Z+p;box(chunk,x,B+1,z,x,B+7,z,RUNE);put(chunk,x,B+3,z,GOLD);put(chunk,x,B+7,z,CRYSTAL)}
|
||||
}
|
||||
|
||||
function buildWorldTree(chunk){
|
||||
const X=-36,Z=27,B=treeBase;if(!overlaps(chunk,X-15,X+15,Z-15,Z+15))return;
|
||||
// Roots crawl across the terrain before converging into a twisting trunk.
|
||||
const roots=[[11,1],[9,7],[-10,5],[-12,-2],[-7,-10],[5,-11]];
|
||||
for(const [dx,dz] of roots){const ey=heightAt(X+dx,Z+dz)+1;lineBlocks(chunk,[X,B,Z],[X+dx,ey,Z+dz],WOOD,1.0)}
|
||||
for(let y=0;y<=19;y++){const r=y<7?2.25:y<14?1.65:1.15;sphere(chunk,X+Math.sin(y*.52)*.8,B+y,Z+Math.cos(y*.43)*.7,r,WOOD)}
|
||||
const branches=[[-10,16,-4],[-8,20,7],[9,17,5],[10,21,-5],[-3,23,10],[5,24,8],[1,25,-9]];
|
||||
for(const [dx,dy,dz] of branches){lineBlocks(chunk,[X,B+12,Z],[X+dx,B+dy,Z+dz],WOOD,.85);sphere(chunk,X+dx,B+dy,Z+dz,4.1,LEAVES,'air');sphere(chunk,X+dx*.72,B+dy+2,Z+dz*.72,3.2,LEAVES,'air')}
|
||||
sphere(chunk,X,B+24,Z,5.4,LEAVES,'air');
|
||||
// A golden shrine is hidden inside the roots.
|
||||
box(chunk,X-2,B,Z-3,X+2,B,Z+1,GOLD);box(chunk,X-2,B+1,Z-2,X-2,B+4,Z,RUNE);box(chunk,X+2,B+1,Z-2,X+2,B+4,Z,RUNE);box(chunk,X-2,B+4,Z-2,X+2,B+4,Z,GOLD);put(chunk,X,B+1,Z-2,CRYSTAL);
|
||||
for(let y=B+22;y<=B+29;y+=2)sphere(chunk,X+(y%3)-1,y,Z,0.75,CRYSTAL,'air');
|
||||
}
|
||||
|
||||
function buildMoonShrine(chunk){
|
||||
const X=39,Z=25,B=moonBase;if(!overlaps(chunk,X-13,X+13,Z-13,Z+13))return;
|
||||
for(let y=B-2;y<=B;y++){const r=11-(B-y);for(let z=Z-r;z<=Z+r;z++)for(let x=X-r;x<=X+r;x++){const d=Math.hypot(x-X,z-Z);if(d<=r)put(chunk,x,y,z,d>r-1.4?CRYSTAL:(x+z)%5===0?GOLD:STONE)}}
|
||||
// Eight astronomer columns and an overhead orbit.
|
||||
for(let i=0;i<8;i++){const a=i/8*Math.PI*2,px=Math.round(X+Math.cos(a)*8),pz=Math.round(Z+Math.sin(a)*8);box(chunk,px,B+1,pz,px,B+8,pz,i%2?RUNE:STONE);put(chunk,px,B+4,pz,GOLD);sphere(chunk,px,B+9,pz,1.05,CRYSTAL,'air')}
|
||||
const oy=B+11;for(let i=0;i<72;i++){const a=i/72*Math.PI*2;put(chunk,Math.round(X+Math.cos(a)*7),oy+Math.round(Math.sin(a*3)),Math.round(Z+Math.sin(a)*7),i%9===0?CRYSTAL:GOLD)}
|
||||
// Giant crescent standing in the meridian plane.
|
||||
for(let y=-7;y<=7;y++)for(let x=-7;x<=7;x++){const outer=x*x+y*y<=49,cut=(x-3)*(x-3)+y*y<40;if(outer&&!cut)put(chunk,X+x,B+9+y,Z,GOLD)}
|
||||
sphere(chunk,X-1,B+9,Z,1.2,CRYSTAL,'air');
|
||||
// Four reflecting pools rendered as star glass mosaics.
|
||||
for(const [dx,dz] of [[-5,-5],[5,-5],[-5,5],[5,5]])for(let z=-2;z<=2;z++)for(let x=-2;x<=2;x++)if(x*x+z*z<5)put(chunk,X+dx+x,B+1,Z+dz+z,CRYSTAL);
|
||||
}
|
||||
|
||||
function buildDragon(chunk){
|
||||
const B=dragonBase;if(!overlaps(chunk,12,55,-58,-34))return;
|
||||
// Curving vertebrae and ribs form a walkable fossil across the valley.
|
||||
const spine=[];for(let i=0;i<=33;i++){const x=16+i,z=-47+Math.round(Math.sin(i*.25)*2),y=B+Math.round(Math.sin(i*.18)*2);spine.push([x,y,z]);sphere(chunk,x,y,z,.85,i%5===0?CRYSTAL:RUNE)}
|
||||
for(let i=0;i<spine.length-1;i++)lineBlocks(chunk,spine[i],spine[i+1],RUNE,.62);
|
||||
for(let i=3;i<28;i+=4){const [x,y,z]=spine[i];for(const side of [-1,1]){let prev=[x,y,z];for(let k=1;k<=7;k++){const a=k/7*Math.PI*.92,next=[x,y+Math.round(Math.sin(a)*5),z+side*Math.round(k*1.12)];lineBlocks(chunk,prev,next,k===7?GOLD:RUNE,.52);prev=next}}}
|
||||
// Horned skull at the eastern end.
|
||||
sphere(chunk,51,B+1,-46,3.3,RUNE);box(chunk,51,B,-48,56,B+2,-44,RUNE);box(chunk,53,B+1,-47,57,B+1,-45,GOLD);put(chunk,53,B+2,-48,CRYSTAL);put(chunk,53,B+2,-44,CRYSTAL);
|
||||
lineBlocks(chunk,[50,B+3,-48],[47,B+8,-53],OBSIDIAN,.65);lineBlocks(chunk,[50,B+3,-44],[47,B+8,-39],OBSIDIAN,.65);
|
||||
// Tail tapers into the ground.
|
||||
lineBlocks(chunk,[16,B,-47],[9,heightAt(9,-49)+1,-49],GOLD,.48);
|
||||
}
|
||||
|
||||
function buildFloatingIsland(chunk){
|
||||
const X=5,Z=48,Y=40;if(!overlaps(chunk,X-17,X+17,Z-14,Z+14))return;
|
||||
// Main island: grassy crown and tapered stone underside.
|
||||
for(let y=Y-11;y<=Y;y++)for(let z=Z-9;z<=Z+9;z++)for(let x=X-12;x<=X+12;x++){
|
||||
const ny=(Y-y)/11,rx=12*(1-ny*.72),rz=9*(1-ny*.72),d=((x-X)/rx)**2+((z-Z)/rz)**2;
|
||||
if(d<=1)put(chunk,x,y,z,y===Y?GRASS:y>Y-3?DIRT:(x+z+y)%11===0?RUNE:STONE);
|
||||
}
|
||||
// Chained anchors descend toward the world below.
|
||||
for(const [dx,dz] of [[-7,-5],[7,-5],[-7,5],[7,5]])for(let y=Y-10;y>=9;y--)if(y%3!==1)put(chunk,X+dx+(y%4===0?1:0),y,Z+dz,OBSIDIAN);
|
||||
// Ruined sky keep, bridge, beacon and satellite isles.
|
||||
box(chunk,X-6,Y+1,Z-5,X+6,Y+1,Z+5,GOLD);for(const [dx,dz] of [[-6,-5],[6,-5],[-6,5],[6,5]]){box(chunk,X+dx,Y+2,Z+dz,X+dx,Y+10,Z+dz,RUNE);sphere(chunk,X+dx,Y+11,Z+dz,1.2,CRYSTAL,'air')}
|
||||
box(chunk,X-6,Y+8,Z-5,X+6,Y+8,Z-5,GOLD);box(chunk,X-6,Y+8,Z+5,X+6,Y+8,Z+5,GOLD);lineBlocks(chunk,[X,Y+2,Z],[X,Y+13,Z],CRYSTAL,.62);
|
||||
for(const [dx,dz,yy,r] of [[14,1,Y-3,4],[-15,-2,Y+4,3],[2,13,Y+1,3]])for(let y=yy-4;y<=yy;y++)for(let z=-r;z<=r;z++)for(let x=-r;x<=r;x++)if(x*x+z*z<=(r-(yy-y)*.45)**2)put(chunk,X+dx+x,y,Z+dz+z,y===yy?GRASS:STONE);
|
||||
}
|
||||
|
||||
function generateChunk(cx,cz){
|
||||
const chunk={cx,cz,data:new Uint8Array(CHUNK*WORLD_H*CHUNK),mesh:null,dirty:true};
|
||||
for(let lz=0;lz<CHUNK;lz++)for(let lx=0;lx<CHUNK;lx++){const x=cx*CHUNK+lx,z=cz*CHUNK+lz,h=heightAt(x,z);for(let y=0;y<=h;y++)chunk.data[voxelIndex(lx,y,lz)]=terrainBlockAt(x,y,z)}
|
||||
decorateNature(chunk);buildWanderingSpires(chunk);buildTemple(chunk);buildWorldTree(chunk);buildMoonShrine(chunk);buildDragon(chunk);buildFloatingIsland(chunk);
|
||||
const stored=editsByChunk.get(chunkKey(cx,cz));if(stored)for(const [idx,type] of stored)chunk.data[idx]=type;
|
||||
return chunk;
|
||||
}
|
||||
|
||||
const FACES=[
|
||||
{n:[1,0,0], side:'side', shade:.87, v:[[1,0,0],[1,1,0],[1,1,1],[1,0,1]]},
|
||||
{n:[-1,0,0],side:'side', shade:.73, v:[[0,0,1],[0,1,1],[0,1,0],[0,0,0]]},
|
||||
{n:[0,1,0], side:'top', shade:1, v:[[0,1,1],[1,1,1],[1,1,0],[0,1,0]]},
|
||||
{n:[0,-1,0],side:'bottom',shade:.54,v:[[0,0,0],[1,0,0],[1,0,1],[0,0,1]]},
|
||||
{n:[0,0,1], side:'side', shade:.81,v:[[1,0,1],[1,1,1],[0,1,1],[0,0,1]]},
|
||||
{n:[0,0,-1],side:'side', shade:.68,v:[[0,0,0],[0,1,0],[1,1,0],[1,0,0]]}
|
||||
];
|
||||
const meshGroup=new THREE.Group();meshGroup.name='Voxel world';scene.add(meshGroup);
|
||||
function getVoxel(x,y,z){
|
||||
x=Math.floor(x);y=Math.floor(y);z=Math.floor(z);if(y<0||y>=WORLD_H)return AIR;
|
||||
const cx=floorDiv(x,CHUNK),cz=floorDiv(z,CHUNK),chunk=chunks.get(chunkKey(cx,cz));
|
||||
if(chunk)return chunk.data[voxelIndex(mod(x,CHUNK),y,mod(z,CHUNK))];
|
||||
return terrainBlockAt(x,y,z);
|
||||
}
|
||||
function buildChunkMesh(chunk){
|
||||
const pos=[],normal=[],uv=[],color=[],indices=[];let vi=0;
|
||||
for(let y=0;y<WORLD_H;y++)for(let lz=0;lz<CHUNK;lz++)for(let lx=0;lx<CHUNK;lx++){
|
||||
const type=chunk.data[voxelIndex(lx,y,lz)];if(type===AIR)continue;const def=BLOCKS[type],wx=chunk.cx*CHUNK+lx,wz=chunk.cz*CHUNK+lz;
|
||||
for(const face of FACES){if(getVoxel(wx+face.n[0],y+face.n[1],wz+face.n[2])!==AIR)continue;
|
||||
const tile=def[face.side],tx=tile%4,ty=Math.floor(tile/4),pad=.0018,u0=tx/4+pad,u1=(tx+1)/4-pad,v0=1-(ty+1)/4+pad,v1=1-ty/4-pad;
|
||||
const uvs=[[u0,v0],[u0,v1],[u1,v1],[u1,v0]],tint=def.tint;
|
||||
for(let k=0;k<4;k++){const p=face.v[k];pos.push(lx+p[0],y+p[1],lz+p[2]);normal.push(...face.n);uv.push(...uvs[k]);const sparkle=type===CRYSTAL?1.12:1;color.push(clamp(tint[0]*face.shade*sparkle,0,1),clamp(tint[1]*face.shade*sparkle,0,1),clamp(tint[2]*face.shade*sparkle,0,1))}
|
||||
indices.push(vi,vi+1,vi+2,vi,vi+2,vi+3);vi+=4;
|
||||
}
|
||||
}
|
||||
const geo=new THREE.BufferGeometry();geo.setAttribute('position',new THREE.Float32BufferAttribute(pos,3));geo.setAttribute('normal',new THREE.Float32BufferAttribute(normal,3));geo.setAttribute('uv',new THREE.Float32BufferAttribute(uv,2));geo.setAttribute('color',new THREE.Float32BufferAttribute(color,3));geo.setIndex(indices);geo.computeBoundingSphere();
|
||||
const mesh=new THREE.Mesh(geo,worldMaterial);mesh.position.set(chunk.cx*CHUNK,0,chunk.cz*CHUNK);mesh.userData.chunk=chunk;mesh.matrixAutoUpdate=false;mesh.updateMatrix();
|
||||
if(chunk.mesh){meshGroup.remove(chunk.mesh);chunk.mesh.geometry.dispose()}chunk.mesh=mesh;meshGroup.add(mesh);chunk.dirty=false;
|
||||
}
|
||||
function rebuildChunk(cx,cz){const ch=chunks.get(chunkKey(cx,cz));if(ch)buildChunkMesh(ch)}
|
||||
const queued=new Set();
|
||||
function queueChunk(cx,cz,priority=false){const k=chunkKey(cx,cz);if(chunks.has(k)||queued.has(k))return;queued.add(k);priority?loadQueue.unshift([cx,cz]):loadQueue.push([cx,cz])}
|
||||
function loadChunk(cx,cz){const k=chunkKey(cx,cz);if(chunks.has(k))return;const chunk=generateChunk(cx,cz);chunks.set(k,chunk);buildChunkMesh(chunk);queued.delete(k)}
|
||||
function queueNeighborhood(x,z){
|
||||
const cx=floorDiv(x,CHUNK),cz=floorDiv(z,CHUNK),todo=[];
|
||||
for(let dz=-VIEW_RADIUS;dz<=VIEW_RADIUS;dz++)for(let dx=-VIEW_RADIUS;dx<=VIEW_RADIUS;dx++)if(dx*dx+dz*dz<=(VIEW_RADIUS+.35)**2)todo.push([cx+dx,cz+dz,dx*dx+dz*dz]);
|
||||
todo.sort((a,b)=>a[2]-b[2]);for(const q of todo)queueChunk(q[0],q[1]);
|
||||
for(const [k,ch] of chunks){if(Math.max(Math.abs(ch.cx-cx),Math.abs(ch.cz-cz))>VIEW_RADIUS+2){meshGroup.remove(ch.mesh);ch.mesh.geometry.dispose();chunks.delete(k)}}
|
||||
}
|
||||
function processLoadQueue(){
|
||||
const start=performance.now();while(loadQueue.length&&performance.now()-start<9){const q=loadQueue.shift();if(!chunks.has(chunkKey(q[0],q[1])))loadChunk(q[0],q[1]);else queued.delete(chunkKey(q[0],q[1]))}
|
||||
}
|
||||
function setVoxel(x,y,z,type){
|
||||
x=Math.floor(x);y=Math.floor(y);z=Math.floor(z);if(y<=0||y>=WORLD_H)return false;
|
||||
const cx=floorDiv(x,CHUNK),cz=floorDiv(z,CHUNK),k=chunkKey(cx,cz),idx=voxelIndex(mod(x,CHUNK),y,mod(z,CHUNK));let edit=editsByChunk.get(k);if(!edit){edit=new Map();editsByChunk.set(k,edit)}edit.set(idx,type);
|
||||
const chunk=chunks.get(k);if(!chunk)return false;chunk.data[idx]=type;rebuildChunk(cx,cz);
|
||||
if(mod(x,CHUNK)===0)rebuildChunk(cx-1,cz);if(mod(x,CHUNK)===CHUNK-1)rebuildChunk(cx+1,cz);if(mod(z,CHUNK)===0)rebuildChunk(cx,cz-1);if(mod(z,CHUNK)===CHUNK-1)rebuildChunk(cx,cz+1);queueSave();return true;
|
||||
}
|
||||
|
||||
const selection=new THREE.LineSegments(new THREE.EdgesGeometry(new THREE.BoxGeometry(1.012,1.012,1.012)),new THREE.LineBasicMaterial({color:0xffe3a0,transparent:true,opacity:.9,depthTest:false}));selection.visible=false;selection.renderOrder=5;scene.add(selection);
|
||||
const raycaster=new THREE.Raycaster();raycaster.far=7.5;const centerNdc=new THREE.Vector2(0,0);let currentHit=null;
|
||||
function traceTarget(){
|
||||
raycaster.setFromCamera(centerNdc,camera);const hits=raycaster.intersectObjects(meshGroup.children,false);currentHit=null;
|
||||
for(const hit of hits){if(hit.distance>7.5||!hit.face)continue;const n=hit.face.normal,breakPos=hit.point.clone().addScaledVector(n,-.012),placePos=hit.point.clone().addScaledVector(n,.012);currentHit={hit,normal:n.clone(),break:{x:Math.floor(breakPos.x),y:Math.floor(breakPos.y),z:Math.floor(breakPos.z)},place:{x:Math.floor(placePos.x),y:Math.floor(placePos.y),z:Math.floor(placePos.z)}};break}
|
||||
if(currentHit){const p=currentHit.break,type=getVoxel(p.x,p.y,p.z);selection.visible=true;selection.position.set(p.x+.5,p.y+.5,p.z+.5);targetText.textContent=`${BLOCKS[type]?.name||'未知方块'} · 左键采集 / 右键放置`;}
|
||||
else{selection.visible=false;targetText.textContent='凝视方块以采集'}
|
||||
}
|
||||
|
||||
let saveTimer=0;
|
||||
function restoreEdits(){try{const raw=localStorage.getItem('mythos-craft-edits-v1');if(!raw)return;for(const [k,entries] of JSON.parse(raw))editsByChunk.set(k,new Map(entries))}catch(e){console.warn('存档读取失败',e)}}
|
||||
function queueSave(){clearTimeout(saveTimer);saveTimer=setTimeout(()=>{try{localStorage.setItem('mythos-craft-edits-v1',JSON.stringify([...editsByChunk].map(([k,v])=>[k,[...v]])))}catch(e){}},600)}
|
||||
restoreEdits();
|
||||
|
||||
let selectedBlock=RUNE, toastTimer=0;
|
||||
function buildHotbar(){
|
||||
hotbar.innerHTML='';for(let i=1;i<=9;i++){const d=BLOCKS[i],slot=document.createElement('div');slot.className='slot'+(i===selectedBlock?' active':'');slot.dataset.block=i;slot.innerHTML=`<span class="key">${i}</span><i class="cube" style="background:${d.css}"></i><span class="name">${d.short}</span>`;hotbar.appendChild(slot)}
|
||||
}
|
||||
function selectBlock(n,announce=true){selectedBlock=clamp(n,1,9);for(const el of hotbar.children)el.classList.toggle('active',+el.dataset.block===selectedBlock);if(announce)showToast(`已选择 · ${BLOCKS[selectedBlock].name}`)}
|
||||
function showToast(text){toastEl.textContent=text;toastEl.classList.add('show');clearTimeout(toastTimer);toastTimer=setTimeout(()=>toastEl.classList.remove('show'),1500)}
|
||||
buildHotbar();
|
||||
|
||||
const player={pos:new THREE.Vector3(.5,20,8.5),vel:new THREE.Vector3(),radius:.29,eye:1.62,head:.18,onGround:false,stamina:100};
|
||||
const spawn={x:.5,z:8.5,y:20};let yaw=0,pitch=-.04;
|
||||
function highestSolid(x,z){for(let y=WORLD_H-2;y>=1;y--)if(getVoxel(x,y,z)!==AIR)return y;return heightAt(x,z)}
|
||||
function respawn(silent=false){const y=highestSolid(spawn.x,spawn.z)+1+player.eye+.03;player.pos.set(spawn.x,y,spawn.z);player.vel.set(0,0,0);yaw=0;pitch=-.05;if(!silent)showToast('星印将你送回日冕古道')}
|
||||
function playerCollides(pos){
|
||||
const e=.001,minX=Math.floor(pos.x-player.radius+e),maxX=Math.floor(pos.x+player.radius-e),minY=Math.floor(pos.y-player.eye+e),maxY=Math.floor(pos.y+player.head-e),minZ=Math.floor(pos.z-player.radius+e),maxZ=Math.floor(pos.z+player.radius-e);
|
||||
for(let y=minY;y<=maxY;y++)for(let z=minZ;z<=maxZ;z++)for(let x=minX;x<=maxX;x++)if(getVoxel(x,y,z)!==AIR)return true;return false;
|
||||
}
|
||||
function moveAxis(axis,delta){if(!delta)return;const steps=Math.max(1,Math.ceil(Math.abs(delta)/.16)),d=delta/steps;for(let i=0;i<steps;i++){player.pos[axis]+=d;if(playerCollides(player.pos)){player.pos[axis]-=d;player.vel[axis]=0;if(axis==='y'&&d<0)player.onGround=true;break}}}
|
||||
const keys=new Set();let locked=false,started=false;
|
||||
function updatePlayer(dt){
|
||||
if(!locked){camera.position.copy(player.pos);return}
|
||||
const f=(keys.has('KeyW')?1:0)-(keys.has('KeyS')?1:0),s=(keys.has('KeyD')?1:0)-(keys.has('KeyA')?1:0),mag=Math.hypot(f,s)||1;
|
||||
const sprint=keys.has('ShiftLeft')&&f>0&&player.stamina>1,speed=sprint?7.5:4.65,tx=(-Math.sin(yaw)*f+Math.cos(yaw)*s)/mag*speed,tz=(-Math.cos(yaw)*f-Math.sin(yaw)*s)/mag*speed;
|
||||
const response=1-Math.exp(-(player.onGround?13:4.5)*dt);player.vel.x+=(tx-player.vel.x)*response;player.vel.z+=(tz-player.vel.z)*response;
|
||||
if(f===0&&s===0){const drag=Math.exp(-(player.onGround?15:2)*dt);player.vel.x*=drag;player.vel.z*=drag}
|
||||
if(sprint&&(f||s))player.stamina=Math.max(0,player.stamina-dt*22);else player.stamina=Math.min(100,player.stamina+dt*14);
|
||||
player.vel.y-=27*dt;player.onGround=false;moveAxis('x',player.vel.x*dt);moveAxis('z',player.vel.z*dt);moveAxis('y',player.vel.y*dt);
|
||||
if(player.pos.y<-8)respawn();camera.position.copy(player.pos);camera.rotation.x=pitch;camera.rotation.y=yaw;
|
||||
$('#stamina').style.width=player.stamina+'%';$('#staminaText').textContent=Math.round(player.stamina);
|
||||
}
|
||||
function blockIntersectsPlayer(x,y,z){const minX=player.pos.x-player.radius,maxX=player.pos.x+player.radius,minY=player.pos.y-player.eye,maxY=player.pos.y+player.head,minZ=player.pos.z-player.radius,maxZ=player.pos.z+player.radius;return x<maxX&&x+1>minX&&y<maxY&&y+1>minY&&z<maxZ&&z+1>minZ}
|
||||
|
||||
let audioCtx=null,lastAction=0;const particles=[];
|
||||
function initAudio(){if(!audioCtx)audioCtx=new (window.AudioContext||window.webkitAudioContext)();if(audioCtx.state==='suspended')audioCtx.resume()}
|
||||
function tone(freq,dur=.08,type='triangle',vol=.025,slide=0){if(!audioCtx)return;const t=audioCtx.currentTime,o=audioCtx.createOscillator(),g=audioCtx.createGain();o.type=type;o.frequency.setValueAtTime(freq,t);o.frequency.exponentialRampToValueAtTime(Math.max(20,freq+slide),t+dur);g.gain.setValueAtTime(vol,t);g.gain.exponentialRampToValueAtTime(.0001,t+dur);o.connect(g).connect(audioCtx.destination);o.start(t);o.stop(t+dur)}
|
||||
function burst(x,y,z,type){
|
||||
const color=new THREE.Color(BLOCKS[type]?.css||'#aaa');for(let i=0;i<7;i++){const mesh=new THREE.Mesh(new THREE.BoxGeometry(.11,.11,.11),new THREE.MeshBasicMaterial({color}));mesh.position.set(x+.5+(Math.random()-.5)*.4,y+.5+(Math.random()-.5)*.4,z+.5+(Math.random()-.5)*.4);scene.add(mesh);particles.push({mesh,life:.48,vel:new THREE.Vector3((Math.random()-.5)*2.5,1+Math.random()*2,(Math.random()-.5)*2.5)})}
|
||||
}
|
||||
function updateParticles(dt){for(let i=particles.length-1;i>=0;i--){const p=particles[i];p.life-=dt;p.vel.y-=7*dt;p.mesh.position.addScaledVector(p.vel,dt);p.mesh.rotation.x+=dt*5;p.mesh.rotation.y+=dt*4;p.mesh.material.opacity=Math.max(0,p.life*2);p.mesh.material.transparent=true;if(p.life<=0){scene.remove(p.mesh);p.mesh.geometry.dispose();p.mesh.material.dispose();particles.splice(i,1)}}}
|
||||
function useTool(button){
|
||||
if(!locked||performance.now()-lastAction<145)return;lastAction=performance.now();traceTarget();if(!currentHit)return;
|
||||
if(button===0){const p=currentHit.break,type=getVoxel(p.x,p.y,p.z);if(type!==AIR&&p.y>0&&setVoxel(p.x,p.y,p.z,AIR)){burst(p.x,p.y,p.z,type);tone(150,.095,'square',.018,-90);showToast(`采集 · ${BLOCKS[type].name}`)}}
|
||||
if(button===2){const p=currentHit.place;if(p.y>0&&p.y<WORLD_H&&getVoxel(p.x,p.y,p.z)===AIR&&!blockIntersectsPlayer(p.x,p.y,p.z)&&setVoxel(p.x,p.y,p.z,selectedBlock)){tone(105,.07,'triangle',.025,45);showToast(`放置 · ${BLOCKS[selectedBlock].name}`)}}
|
||||
}
|
||||
|
||||
document.addEventListener('keydown',e=>{
|
||||
keys.add(e.code);if(locked&&['Space','ArrowUp','ArrowDown','ArrowLeft','ArrowRight'].includes(e.code))e.preventDefault();
|
||||
if(e.code==='Space'&&locked&&player.onGround){player.vel.y=8.8;player.onGround=false;tone(170,.06,'sine',.014,40)}
|
||||
if(/^Digit[1-9]$/.test(e.code))selectBlock(+e.code.slice(5));if(e.code==='KeyR')respawn();
|
||||
});
|
||||
document.addEventListener('keyup',e=>keys.delete(e.code));
|
||||
document.addEventListener('mousemove',e=>{if(!locked)return;yaw-=e.movementX*.00215;pitch=clamp(pitch-e.movementY*.00215,-Math.PI/2+.015,Math.PI/2-.015)});
|
||||
document.addEventListener('mousedown',e=>useTool(e.button));document.addEventListener('contextmenu',e=>e.preventDefault());
|
||||
document.addEventListener('wheel',e=>{if(!locked)return;selectBlock(mod(selectedBlock-1+(e.deltaY>0?1:-1),9)+1);e.preventDefault()},{passive:false});
|
||||
$('#startBtn').addEventListener('click',()=>{initAudio();renderer.domElement.requestPointerLock()});
|
||||
document.addEventListener('pointerlockchange',()=>{locked=document.pointerLockElement===renderer.domElement;keys.clear();if(locked){started=true;veil.classList.add('hidden');hud.classList.remove('hidden');showToast('向北沿古道寻找日冕圣门')}else if(started){veil.classList.remove('hidden');$('#startBtn').textContent='继 续 探 索'}});
|
||||
|
||||
function makeAtmosphere(){
|
||||
const rnd=mulberry32(SEED^0x7799),motePos=[];for(let i=0;i<180;i++)motePos.push((rnd()-.5)*50,(rnd()-.5)*28,(rnd()-.5)*50);
|
||||
const geo=new THREE.BufferGeometry();geo.setAttribute('position',new THREE.Float32BufferAttribute(motePos,3));const motes=new THREE.Points(geo,new THREE.PointsMaterial({color:0x92f7dc,size:.105,transparent:true,opacity:.62,depthWrite:false,blending:THREE.AdditiveBlending}));scene.add(motes);
|
||||
const clouds=new THREE.Group(),mat=new THREE.MeshBasicMaterial({color:0xcde4dd,transparent:true,opacity:.07,depthWrite:false});
|
||||
for(let i=0;i<18;i++){const m=new THREE.Mesh(new THREE.BoxGeometry(10+rnd()*18,.7+rnd()*1.4,4+rnd()*9),mat);m.position.set((rnd()-.5)*170,38+rnd()*20,(rnd()-.5)*170);m.rotation.y=rnd()*.5;clouds.add(m)}scene.add(clouds);return {motes,clouds};
|
||||
}
|
||||
const atmosphere=makeAtmosphere();let dayPhase=.18;
|
||||
const dawnColor=new THREE.Color(0xa66b58),dayColor=new THREE.Color(0x5e9aaa),nightColor=new THREE.Color(0x07121f),skyColor=new THREE.Color();
|
||||
function updateSky(dt,time){
|
||||
dayPhase=(dayPhase+dt*.00125)%1;const a=dayPhase*Math.PI*2,sunUp=clamp(Math.sin(a)*1.7+.14,0,1),twilight=clamp(1-Math.abs(Math.sin(a))*3.2,0,1);
|
||||
skyColor.copy(nightColor).lerp(dayColor,sunUp).lerp(dawnColor,twilight*.48);scene.background.copy(skyColor);scene.fog.color.copy(skyColor);
|
||||
const sx=Math.cos(a)*115,sy=Math.sin(a)*105,sz=42;sun.position.set(player.pos.x+sx,sy,player.pos.z+sz);sun.target.position.set(player.pos.x,0,player.pos.z);sun.intensity=.12+sunUp*2.25;hemi.intensity=.32+sunUp*1.05;
|
||||
sky.sunDisc.position.set(player.pos.x+sx,sy,player.pos.z+sz);sky.sunDisc.visible=sy>-12;sky.moonDisc.position.set(player.pos.x-sx*.82,-sy*.75+18,player.pos.z-sz*1.8);sky.moonDisc.visible=sy<30;
|
||||
sky.stars.position.set(player.pos.x,0,player.pos.z);sky.stars.material.opacity=.12+(1-sunUp)*.75;sky.stars.rotation.y=time*.000008;
|
||||
atmosphere.motes.position.set(player.pos.x,player.pos.y,player.pos.z);atmosphere.motes.rotation.y=time*.000025;atmosphere.motes.position.y+=Math.sin(time*.0004)*1.5;
|
||||
atmosphere.clouds.position.x=time*.00032;const wrap=180;if(atmosphere.clouds.position.x-player.pos.x>wrap)atmosphere.clouds.position.x-=wrap*2;if(atmosphere.clouds.position.x-player.pos.x<-wrap)atmosphere.clouds.position.x+=wrap*2;
|
||||
daytimeEl.textContent=sy>55?'曜日':sy>8?(Math.cos(a)>0?'晨曦':'暮色'):sy>-18?'星昏':'月夜';
|
||||
}
|
||||
|
||||
function updateWorldReadout(){
|
||||
const x=Math.floor(player.pos.x),y=Math.floor(player.pos.y-player.eye),z=Math.floor(player.pos.z),bio=biomeAt(x,z);coordsEl.textContent=`${x} / ${y} / ${z}`;biomeEl.textContent=bio.name;
|
||||
const dir=mod(Math.round((-yaw)/(Math.PI/4)),8),names=['N','NE','E','SE','S','SW','W','NW'];$('#heading').textContent=names[dir];
|
||||
let nearest=LANDMARKS[0],dist=Infinity;for(const m of LANDMARKS){const d=Math.hypot(player.pos.x-m.x,player.pos.z-m.z);if(d<dist){nearest=m;dist=d}}
|
||||
$('#objName').textContent=nearest.name;$('#objCopy').textContent=nearest.copy;$('#objDistance').textContent=dist<9?'你已抵达此处':`距离 ${Math.round(dist)} 格 · 朝 ${Math.abs(nearest.x-player.pos.x)>Math.abs(nearest.z-player.pos.z)?nearest.x>player.pos.x?'东':'西':nearest.z>player.pos.z?'南':'北'}`;
|
||||
$('#debug').textContent=`区块 ${floorDiv(x,CHUNK)}, ${floorDiv(z,CHUNK)} · 已载入 ${chunks.size} · 地形队列 ${loadQueue.length}`;
|
||||
}
|
||||
function drawMinimap(){
|
||||
const W=mapCanvas.width,c=W/2,scale=2.18,step=6;mapCtx.clearRect(0,0,W,W);mapCtx.save();mapCtx.beginPath();mapCtx.arc(c,c,c-2,0,Math.PI*2);mapCtx.clip();mapCtx.fillStyle='#071216';mapCtx.fillRect(0,0,W,W);
|
||||
for(let py=-c;py<c;py+=step)for(let px=-c;px<c;px+=step){if(px*px+py*py>c*c)continue;const wx=Math.floor(player.pos.x+px/scale),wz=Math.floor(player.pos.z+py/scale),h=heightAt(wx,wz),bio=biomeAt(wx,wz);mapCtx.fillStyle=h>24?'#77817e':bio.color;mapCtx.globalAlpha=.55+Math.min(.35,h/70);mapCtx.fillRect(c+px,c+py,step+1,step+1)}mapCtx.globalAlpha=1;
|
||||
mapCtx.strokeStyle='rgba(244,217,150,.18)';mapCtx.lineWidth=1;for(const r of [46,92,136]){mapCtx.beginPath();mapCtx.arc(c,c,r,0,Math.PI*2);mapCtx.stroke()}mapCtx.beginPath();mapCtx.moveTo(c,3);mapCtx.lineTo(c,W-3);mapCtx.moveTo(3,c);mapCtx.lineTo(W-3,c);mapCtx.stroke();
|
||||
for(const m of LANDMARKS){let dx=(m.x-player.pos.x)*scale,dz=(m.z-player.pos.z)*scale,rr=Math.hypot(dx,dz);if(rr>c-11){dx*= (c-11)/rr;dz*=(c-11)/rr}mapCtx.save();mapCtx.translate(c+dx,c+dz);mapCtx.rotate(Math.PI/4);mapCtx.fillStyle=m.name==='日冕圣门'?'#f1c66c':'#76ead5';mapCtx.shadowColor=mapCtx.fillStyle;mapCtx.shadowBlur=7;mapCtx.fillRect(-4,-4,8,8);mapCtx.restore()}
|
||||
mapCtx.save();mapCtx.translate(c,c);mapCtx.rotate(-yaw);mapCtx.fillStyle='#fff5cf';mapCtx.shadowColor='#fff';mapCtx.shadowBlur=6;mapCtx.beginPath();mapCtx.moveTo(0,-10);mapCtx.lineTo(6,7);mapCtx.lineTo(0,4);mapCtx.lineTo(-6,7);mapCtx.closePath();mapCtx.fill();mapCtx.restore();mapCtx.restore();
|
||||
}
|
||||
|
||||
// The spawn ring is loaded synchronously; the wider world streams in while the title screen is visible.
|
||||
for(let dz=-1;dz<=1;dz++)for(let dx=-1;dx<=1;dx++)loadChunk(dx,dz);
|
||||
queueNeighborhood(spawn.x,spawn.z);respawn(true);camera.position.copy(player.pos);scene.add(sun.target);
|
||||
loading.classList.add('hidden');
|
||||
let last=performance.now(),frame=0,maintenance=0,mapTimer=0;
|
||||
function animate(now){
|
||||
requestAnimationFrame(animate);const dt=Math.min(.045,(now-last)/1000||0);last=now;frame++;processLoadQueue();updatePlayer(dt);updateParticles(dt);updateSky(dt,now);
|
||||
maintenance+=dt;mapTimer+=dt;if(maintenance>.8){maintenance=0;queueNeighborhood(player.pos.x,player.pos.z);updateWorldReadout()}if(mapTimer>.65){mapTimer=0;drawMinimap()}if(locked&&frame%3===0)traceTarget();
|
||||
renderer.render(scene,camera);
|
||||
}
|
||||
updateWorldReadout();drawMinimap();requestAnimationFrame(animate);
|
||||
addEventListener('resize',()=>{camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix();renderer.setPixelRatio(Math.min(devicePixelRatio,1.65));renderer.setSize(innerWidth,innerHeight)});
|
||||
})();
|
||||
</script>
|
||||
</body>
|
||||
</html>
|
||||
@@ -0,0 +1,357 @@
|
||||
<svg xmlns="http://www.w3.org/2000/svg" width="1600" height="900" viewBox="0 0 1600 900" role="img" aria-labelledby="title desc">
|
||||
<title id="title">海边骑自行车的鹈鹕</title>
|
||||
<desc id="desc">一只羽毛细腻的鹈鹕迎着海风骑行在金色海岸小路上,青绿色海浪、暖阳、海鸥和摇曳的沙丘草环绕四周。</desc>
|
||||
|
||||
<defs>
|
||||
<linearGradient id="sky" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#51b7d4"/>
|
||||
<stop offset=".5" stop-color="#9fdfeb"/>
|
||||
<stop offset="1" stop-color="#ffd7aa"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="sun" cx="50%" cy="50%" r="50%">
|
||||
<stop offset="0" stop-color="#fffde4"/>
|
||||
<stop offset=".48" stop-color="#ffe989" stop-opacity=".95"/>
|
||||
<stop offset="1" stop-color="#ffbf61" stop-opacity="0"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="ocean" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#267f98"/>
|
||||
<stop offset=".46" stop-color="#0a8f9c"/>
|
||||
<stop offset="1" stop-color="#35b9ad"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="waterShine" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#fff5c9" stop-opacity=".8"/>
|
||||
<stop offset=".52" stop-color="#e9ffff" stop-opacity=".24"/>
|
||||
<stop offset="1" stop-color="#d7ffff" stop-opacity="0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="headland" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#66856f"/>
|
||||
<stop offset="1" stop-color="#314f4a"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="sand" x1="0" y1="0" x2=".9" y2="1">
|
||||
<stop offset="0" stop-color="#f5ca83"/>
|
||||
<stop offset=".55" stop-color="#d9a45e"/>
|
||||
<stop offset="1" stop-color="#b67945"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="path" x1="0" y1="0" x2="0" y2="1">
|
||||
<stop offset="0" stop-color="#dfc49c"/>
|
||||
<stop offset="1" stop-color="#ac805e"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="frame" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#21aeb0"/>
|
||||
<stop offset=".55" stop-color="#08737b"/>
|
||||
<stop offset="1" stop-color="#034b55"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="metal" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#f8ffff"/>
|
||||
<stop offset=".32" stop-color="#9ebabe"/>
|
||||
<stop offset=".68" stop-color="#526f73"/>
|
||||
<stop offset="1" stop-color="#e2eeee"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="bodyFeathers" x1=".2" y1="0" x2=".85" y2="1">
|
||||
<stop offset="0" stop-color="#fffdf2"/>
|
||||
<stop offset=".44" stop-color="#f6f1dc"/>
|
||||
<stop offset=".78" stop-color="#cbd8d6"/>
|
||||
<stop offset="1" stop-color="#8da6a8"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="wingFeathers" x1=".25" y1="0" x2=".8" y2="1">
|
||||
<stop offset="0" stop-color="#f8f1dc"/>
|
||||
<stop offset=".48" stop-color="#c6d1cc"/>
|
||||
<stop offset="1" stop-color="#667f82"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="neckFeathers" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#fffdf0"/>
|
||||
<stop offset=".58" stop-color="#eee7ce"/>
|
||||
<stop offset="1" stop-color="#aebfc0"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="beak" x1="0" y1="0" x2="1" y2=".2">
|
||||
<stop offset="0" stop-color="#f5c55a"/>
|
||||
<stop offset=".5" stop-color="#ef9544"/>
|
||||
<stop offset=".86" stop-color="#d7603e"/>
|
||||
<stop offset="1" stop-color="#7f3f38"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="pouch" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#ffc267" stop-opacity=".93"/>
|
||||
<stop offset=".62" stop-color="#ec7546" stop-opacity=".9"/>
|
||||
<stop offset="1" stop-color="#b94d3d" stop-opacity=".86"/>
|
||||
</linearGradient>
|
||||
<linearGradient id="leg" x1="0" y1="0" x2="1" y2="0">
|
||||
<stop offset="0" stop-color="#bf573b"/>
|
||||
<stop offset=".48" stop-color="#e88a4c"/>
|
||||
<stop offset="1" stop-color="#9b4337"/>
|
||||
</linearGradient>
|
||||
<radialGradient id="eye" cx="35%" cy="30%" r="65%">
|
||||
<stop offset="0" stop-color="#ffd56b"/>
|
||||
<stop offset=".46" stop-color="#b47227"/>
|
||||
<stop offset=".5" stop-color="#161716"/>
|
||||
<stop offset="1" stop-color="#050707"/>
|
||||
</radialGradient>
|
||||
<linearGradient id="tire" x1="0" y1="0" x2="1" y2="1">
|
||||
<stop offset="0" stop-color="#18282b"/>
|
||||
<stop offset=".5" stop-color="#071114"/>
|
||||
<stop offset="1" stop-color="#34484a"/>
|
||||
</linearGradient>
|
||||
|
||||
<filter id="sunGlow" x="-100%" y="-100%" width="300%" height="300%">
|
||||
<feGaussianBlur stdDeviation="16"/>
|
||||
</filter>
|
||||
<filter id="softShadow" x="-30%" y="-50%" width="160%" height="220%">
|
||||
<feGaussianBlur stdDeviation="16"/>
|
||||
</filter>
|
||||
<filter id="objectShadow" x="-25%" y="-25%" width="160%" height="170%">
|
||||
<feDropShadow dx="0" dy="12" stdDeviation="10" flood-color="#153c40" flood-opacity=".28"/>
|
||||
</filter>
|
||||
<filter id="featherShadow" x="-20%" y="-20%" width="150%" height="155%">
|
||||
<feDropShadow dx="2" dy="7" stdDeviation="5" flood-color="#36585a" flood-opacity=".25"/>
|
||||
</filter>
|
||||
<filter id="mist" x="-20%" y="-100%" width="140%" height="300%">
|
||||
<feGaussianBlur stdDeviation="9"/>
|
||||
</filter>
|
||||
<filter id="grain" x="0" y="0" width="100%" height="100%">
|
||||
<feTurbulence type="fractalNoise" baseFrequency=".65" numOctaves="2" seed="8" result="noise"/>
|
||||
<feColorMatrix in="noise" values="1 0 0 0 0 0 1 0 0 0 0 0 1 0 0 0 0 0 .035 0"/>
|
||||
</filter>
|
||||
|
||||
<clipPath id="seaClip">
|
||||
<path d="M0 322H1600V624C1290 575 910 572 566 602 345 621 147 647 0 670Z"/>
|
||||
</clipPath>
|
||||
|
||||
<g id="wheel">
|
||||
<circle r="155" fill="none" stroke="url(#tire)" stroke-width="18"/>
|
||||
<circle r="145.5" fill="none" stroke="#7e9799" stroke-width="4"/>
|
||||
<circle r="140" fill="none" stroke="#d7e4e3" stroke-width="2.5"/>
|
||||
<g fill="none" stroke="#b8cccd" stroke-width="2" stroke-opacity=".78">
|
||||
<path d="M0-140V140M-140 0H140"/>
|
||||
<path d="M0-140V140M-140 0H140" transform="rotate(30)"/>
|
||||
<path d="M0-140V140M-140 0H140" transform="rotate(60)"/>
|
||||
<path d="M0-140V140M-140 0H140" transform="rotate(15)"/>
|
||||
<path d="M0-140V140M-140 0H140" transform="rotate(45)"/>
|
||||
<path d="M0-140V140M-140 0H140" transform="rotate(75)"/>
|
||||
</g>
|
||||
<circle r="16" fill="url(#metal)" stroke="#3b5b60" stroke-width="5"/>
|
||||
<path d="M-2-147h22l-2 8H1Z" fill="#ffcf49" stroke="#9e6a1d" stroke-width="2"/>
|
||||
<path d="M-4 139h22l3 8H-2Z" fill="#f35f48" stroke="#88372f" stroke-width="2"/>
|
||||
<path d="M-112-94a145 145 0 0 1 84-47" fill="none" stroke="#f2ffff" stroke-width="5" stroke-linecap="round" opacity=".6"/>
|
||||
</g>
|
||||
|
||||
<g id="tuft" fill="none" stroke-linecap="round">
|
||||
<path d="M0 32Q-3 9-20-9M3 32Q12 4 30-18M-1 31Q-20 15-34 7M3 31Q3 6 1-21" stroke="#426b4a" stroke-width="7"/>
|
||||
<path d="M0 31Q8 7 18-7M-2 30Q-12 12-24 4" stroke="#85a65d" stroke-width="3"/>
|
||||
</g>
|
||||
</defs>
|
||||
|
||||
<style>
|
||||
.wheel-spin { transform-origin: 0 0; animation: wheel-turn 2.5s linear infinite; }
|
||||
.wave-drift { animation: wave-slide 5.5s ease-in-out infinite alternate; }
|
||||
.grass-sway { transform-box: fill-box; transform-origin: 50% 100%; animation: sway 2.8s ease-in-out infinite alternate; }
|
||||
.grass-sway.slow { animation-duration: 4.2s; animation-delay: -1.4s; }
|
||||
.pelican-bob { animation: bob 1.25s ease-in-out infinite alternate; }
|
||||
.scarf-tail { transform-box: fill-box; transform-origin: 100% 50%; animation: scarf 1.7s ease-in-out infinite alternate; }
|
||||
@keyframes wheel-turn { to { transform: rotate(360deg); } }
|
||||
@keyframes wave-slide { to { transform: translateX(20px); } }
|
||||
@keyframes sway { to { transform: rotate(4deg); } }
|
||||
@keyframes bob { to { transform: translateY(-3px); } }
|
||||
@keyframes scarf { to { transform: rotate(-3deg) scaleX(1.035); } }
|
||||
@media (prefers-reduced-motion: reduce) {
|
||||
.wheel-spin, .wave-drift, .grass-sway, .pelican-bob, .scarf-tail { animation: none; }
|
||||
}
|
||||
</style>
|
||||
|
||||
<g id="background" aria-hidden="true">
|
||||
<rect width="1600" height="900" fill="url(#sky)"/>
|
||||
<circle cx="230" cy="165" r="150" fill="url(#sun)" filter="url(#sunGlow)" opacity=".9"/>
|
||||
<circle cx="230" cy="165" r="55" fill="#fff3ac" opacity=".88"/>
|
||||
|
||||
<g fill="#fff" opacity=".48" filter="url(#mist)">
|
||||
<path d="M80 244c27-33 78-35 108-7 41-36 112-18 119 35H58c1-12 9-23 22-28Z"/>
|
||||
<path d="M1110 177c21-28 67-32 94-6 36-29 94-12 101 34h-216c1-12 9-23 21-28Z"/>
|
||||
<path d="M1334 276c18-24 54-26 75-5 29-24 77-9 83 29h-176c1-10 7-18 18-24Z"/>
|
||||
</g>
|
||||
|
||||
<path d="M0 322H1600V624C1290 575 910 572 566 602 345 621 147 647 0 670Z" fill="url(#ocean)"/>
|
||||
<path d="M0 335C275 326 511 340 760 330s534-12 840 5" fill="none" stroke="#efffff" stroke-width="5" opacity=".62"/>
|
||||
<path d="M0 345C356 330 664 350 951 340c224-8 423-3 649 3" fill="none" stroke="#174f68" stroke-width="3" opacity=".25"/>
|
||||
|
||||
<path d="M125 340c56 38 119 116 178 244l-197 46-148 11V368c55-27 106-35 167-28Z" fill="url(#headland)"/>
|
||||
<path d="M-10 374c58-41 99-41 133-32 42 13 70 40 94 79-75-37-151-35-227-10Z" fill="#739777"/>
|
||||
<path d="M-5 405c73-26 143-21 214 17-79-14-146-5-214 23Z" fill="#3f6c5c" opacity=".7"/>
|
||||
|
||||
<g clip-path="url(#seaClip)">
|
||||
<path d="M118 350 279 350 529 606 344 625Z" fill="url(#waterShine)" opacity=".75"/>
|
||||
<g class="wave-drift" fill="none" stroke-linecap="round">
|
||||
<path d="M282 405c72-22 131 17 202-3 67-18 112-18 176 1" stroke="#b7eff0" stroke-width="8" opacity=".38"/>
|
||||
<path d="M794 390c95 24 179-17 268 3 78 18 130 17 208-2" stroke="#c8f5f2" stroke-width="7" opacity=".36"/>
|
||||
<path d="M100 476c91-22 169 21 260-3 81-21 145-14 220 5" stroke="#e4ffff" stroke-width="10" opacity=".48"/>
|
||||
<path d="M658 496c109-31 189 27 301-3 86-23 165-12 258 8" stroke="#d8ffff" stroke-width="11" opacity=".5"/>
|
||||
<path d="M1115 444c80-22 152 20 238-2 75-20 140-12 207 5" stroke="#e6ffff" stroke-width="8" opacity=".43"/>
|
||||
<path d="M-42 562c128-42 225 39 347 2 105-31 200-21 307 6" stroke="#f4ffff" stroke-width="15" opacity=".58"/>
|
||||
<path d="M817 560c132-41 244 35 376-4 120-35 228-14 355 17" stroke="#efffff" stroke-width="14" opacity=".56"/>
|
||||
</g>
|
||||
<g fill="#eaffff" opacity=".64">
|
||||
<circle cx="696" cy="487" r="5"/><circle cx="718" cy="475" r="3"/><circle cx="746" cy="488" r="4"/>
|
||||
<circle cx="1219" cy="540" r="6"/><circle cx="1247" cy="527" r="3"/><circle cx="1280" cy="542" r="5"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g stroke="#2c5960" stroke-width="5" fill="none" stroke-linecap="round" opacity=".72">
|
||||
<path d="M436 218q18-18 36 0 18-18 36 0"/>
|
||||
<path d="M1298 148q15-14 30 0 15-14 30 0"/>
|
||||
<path d="M1383 244q11-11 22 0 11-11 22 0"/>
|
||||
</g>
|
||||
|
||||
<g transform="translate(483 371)" opacity=".7">
|
||||
<path d="M0 0h55" stroke="#5d4c3c" stroke-width="3"/>
|
||||
<path d="M25-2v-54l36 43H25Z" fill="#fff7de" stroke="#b68155" stroke-width="2"/>
|
||||
<path d="M25-2v-45L-5-11h30Z" fill="#f27654" stroke="#9c523f" stroke-width="2"/>
|
||||
<path d="M4 2q23 13 50 0" fill="#745347"/>
|
||||
</g>
|
||||
|
||||
<path d="M0 621C286 586 594 567 881 561c264-6 495-27 719-68v407H0Z" fill="url(#sand)"/>
|
||||
<path d="M0 676c369-55 697-58 1000-64 247-5 422-33 600-76v364H0Z" fill="url(#path)"/>
|
||||
<path d="M0 659c415-54 756-50 1057-59 223-7 377-35 543-73" fill="none" stroke="#fff0c5" stroke-width="11" opacity=".64"/>
|
||||
<path d="M0 704c329-48 656-42 949-43 261 0 439-39 651-80" fill="none" stroke="#8e654c" stroke-width="4" stroke-dasharray="25 38" opacity=".3"/>
|
||||
<path d="M80 775c301-40 577-39 866-35 250 4 421-30 633-75" fill="none" stroke="#f5d6a3" stroke-width="7" stroke-dasharray="12 45" opacity=".45"/>
|
||||
|
||||
<g opacity=".58" fill="#7b5a43">
|
||||
<ellipse cx="266" cy="726" rx="10" ry="4" transform="rotate(-14 266 726)"/>
|
||||
<ellipse cx="330" cy="810" rx="7" ry="3" transform="rotate(24 330 810)"/>
|
||||
<ellipse cx="1335" cy="728" rx="11" ry="4" transform="rotate(-6 1335 728)"/>
|
||||
<ellipse cx="1490" cy="641" rx="8" ry="3" transform="rotate(16 1490 641)"/>
|
||||
</g>
|
||||
<rect width="1600" height="900" filter="url(#grain)" opacity=".65"/>
|
||||
</g>
|
||||
|
||||
<g id="ground-details" aria-hidden="true">
|
||||
<g transform="translate(78 650) scale(1.5)" class="grass-sway slow"><use href="#tuft"/></g>
|
||||
<g transform="translate(196 637) scale(1.15)" class="grass-sway"><use href="#tuft"/></g>
|
||||
<g transform="translate(1440 574) scale(.88)" class="grass-sway slow"><use href="#tuft"/></g>
|
||||
<g transform="translate(1519 552) scale(1.06)" class="grass-sway"><use href="#tuft"/></g>
|
||||
<path d="M214 664q21-27 37-1M1474 590q16-24 33-1" fill="none" stroke="#e9c660" stroke-width="6" stroke-linecap="round"/>
|
||||
|
||||
<ellipse cx="842" cy="838" rx="454" ry="42" fill="#31565a" opacity=".22" filter="url(#softShadow)"/>
|
||||
<ellipse cx="560" cy="839" rx="96" ry="13" fill="#2e4c4c" opacity=".25" filter="url(#softShadow)"/>
|
||||
<ellipse cx="1110" cy="839" rx="96" ry="13" fill="#2e4c4c" opacity=".25" filter="url(#softShadow)"/>
|
||||
<path d="M355 832q-86 8-148 1M358 807q-53-4-96-19M1292 816q71-5 130-25" fill="none" stroke="#87634e" stroke-width="7" stroke-linecap="round" opacity=".48"/>
|
||||
</g>
|
||||
|
||||
<g id="bicycle" filter="url(#objectShadow)">
|
||||
<g transform="translate(560 680)">
|
||||
<g class="wheel-spin"><use href="#wheel"/></g>
|
||||
</g>
|
||||
<g transform="translate(1110 680)">
|
||||
<g class="wheel-spin"><use href="#wheel"/></g>
|
||||
</g>
|
||||
|
||||
<g fill="none" stroke-linecap="round" stroke-linejoin="round">
|
||||
<path d="M560 680 790 681 735 522 560 680Z" stroke="url(#frame)" stroke-width="21"/>
|
||||
<path d="M790 681 972 548 735 522Z" stroke="url(#frame)" stroke-width="21"/>
|
||||
<path d="M790 681 1110 680" stroke="url(#frame)" stroke-width="18"/>
|
||||
<path d="M972 548 1110 680" stroke="url(#metal)" stroke-width="17"/>
|
||||
<path d="M958 520 1110 680" stroke="#075d66" stroke-width="9"/>
|
||||
<path d="M735 522 714 476" stroke="#294d51" stroke-width="16"/>
|
||||
<path d="M958 521 983 472 1045 471" stroke="url(#metal)" stroke-width="13"/>
|
||||
<path d="M1016 471h48" stroke="#363c3c" stroke-width="18"/>
|
||||
<path d="M1039 471h29" stroke="#f08b48" stroke-width="12"/>
|
||||
<path d="M1027 463c36 22 47 62 31 101" stroke="#263e42" stroke-width="4"/>
|
||||
</g>
|
||||
|
||||
<path d="M675 473q48-14 105 1c8 2 10 12 3 18-33 15-76 15-111 4-12-4-10-20 3-23Z" fill="#4d3930" stroke="#251d1b" stroke-width="6"/>
|
||||
<path d="M688 480q38-8 77 1" fill="none" stroke="#ac7b5c" stroke-width="4" opacity=".7"/>
|
||||
|
||||
<g transform="translate(790 681)">
|
||||
<circle r="38" fill="#214d52" stroke="url(#metal)" stroke-width="7"/>
|
||||
<circle r="14" fill="#d6e1dd" stroke="#435c5f" stroke-width="5"/>
|
||||
<g class="wheel-spin">
|
||||
<path d="M0 0 63-38M0 0l-63 38" stroke="url(#metal)" stroke-width="9" stroke-linecap="round"/>
|
||||
<path d="M54-44h45" stroke="#273a3c" stroke-width="12" stroke-linecap="round"/>
|
||||
<path d="M-98 44h45" stroke="#273a3c" stroke-width="12" stroke-linecap="round"/>
|
||||
</g>
|
||||
</g>
|
||||
<path d="M790 681Q672 692 560 680" fill="none" stroke="#3b2e28" stroke-width="7" stroke-dasharray="7 5"/>
|
||||
<circle cx="1042" cy="489" r="14" fill="#ffd34f" stroke="#8a6122" stroke-width="5"/>
|
||||
<path d="M1039 487q-15-19-29-2" fill="none" stroke="#8a6122" stroke-width="4"/>
|
||||
</g>
|
||||
|
||||
<g id="pelican" class="pelican-bob">
|
||||
<g id="legs" fill="url(#leg)" stroke="#8a4438" stroke-width="5" stroke-linejoin="round">
|
||||
<path d="M737 582c-5 42-2 85 11 126l25-8c-5-37-5-78 3-111Z"/>
|
||||
<path d="M834 582c2 25 9 48 20 70l24-12c-7-23-9-45-6-67Z"/>
|
||||
<path d="M741 705c-9 8-21 16-35 23 22 10 45 11 68 0l-2-27Z"/>
|
||||
<path d="M850 648c11 6 25 9 43 10-12-17-26-27-42-30Z"/>
|
||||
</g>
|
||||
<g fill="none" stroke="#713b33" stroke-width="3" stroke-linecap="round" opacity=".72">
|
||||
<path d="M716 724q19-7 55-2M735 712l-17 14M750 710l-8 19M864 643l17 12M850 645l28 5"/>
|
||||
</g>
|
||||
|
||||
<g id="body" filter="url(#featherShadow)">
|
||||
<path d="M572 550c11-80 74-139 165-151 95-13 187 28 217 101 25 61-13 125-99 153-82 27-190 11-254-33-28-19-39-43-29-70Z" fill="url(#bodyFeathers)" stroke="#768e90" stroke-width="5"/>
|
||||
<path d="M585 528c-29 5-57 20-86 50 34 3 60 11 79 30 21-13 36-33 43-60Z" fill="#b7c8c5" stroke="#6f8888" stroke-width="5"/>
|
||||
<path d="M522 573c-28-1-50 5-72 17 30 6 49 16 66 31 22-8 41-22 54-40Z" fill="#718c8c"/>
|
||||
<path d="M655 452c58-34 139-23 184 20 33 31 42 74 22 111-22 41-74 62-129 48-64-16-115-68-121-117-3-26 13-47 44-62Z" fill="url(#wingFeathers)" stroke="#718789" stroke-width="5"/>
|
||||
<g fill="none" stroke-linecap="round">
|
||||
<path d="M653 486q89 12 175 89" stroke="#7f9697" stroke-width="7" opacity=".66"/>
|
||||
<path d="M638 511q86 20 166 93" stroke="#799091" stroke-width="7" opacity=".64"/>
|
||||
<path d="M643 540q65 26 123 79" stroke="#6f8788" stroke-width="6" opacity=".62"/>
|
||||
<path d="M683 460q80 33 138 91" stroke="#eff3e6" stroke-width="5" opacity=".65"/>
|
||||
<path d="M706 447q-8 34 12 56M746 451q-4 34 22 62M786 467q1 33 25 54" stroke="#fff9e6" stroke-width="5" opacity=".6"/>
|
||||
</g>
|
||||
<g fill="#f7f1dc" opacity=".9">
|
||||
<path d="M601 555q23-19 48-6-18 5-25 24Z"/>
|
||||
<path d="M616 584q25-20 50-5-18 5-26 23Z"/>
|
||||
<path d="M657 611q25-19 49-4-18 5-25 22Z"/>
|
||||
<path d="M861 581q24-16 45-1-17 4-24 19Z"/>
|
||||
<path d="M823 613q22-15 44-1-16 5-22 18Z"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g id="neck-and-head" filter="url(#featherShadow)">
|
||||
<path d="M840 466c3-56 8-112 42-160 22-31 47-52 84-70 56-26 120-7 145 42 18 35 9 77-22 104-25 22-59 27-88 42-36 18-49 54-45 101-43 4-84-16-116-59Z" fill="url(#neckFeathers)" stroke="#879b9b" stroke-width="5"/>
|
||||
<path d="M890 444c0-68 19-124 67-163" fill="none" stroke="#fffdf0" stroke-width="24" stroke-linecap="round" opacity=".64"/>
|
||||
<path d="M917 281c-13-33 1-68 34-86 49-27 116-13 145 29 19 27 19 62 1 87-21 29-58 40-96 30-38-9-70-31-84-60Z" fill="url(#neckFeathers)" stroke="#829798" stroke-width="5"/>
|
||||
<path d="M957 202c21-27 62-36 96-18 14 8 26 19 33 32-36-9-68-3-99 18Z" fill="#f3d885" opacity=".9"/>
|
||||
<path d="M947 207q27-39 61-45l-8 29q28-28 59-25l-16 30q31-19 55-8l-28 30Z" fill="#fff8d8" stroke="#9b9d8f" stroke-width="4" stroke-linejoin="round"/>
|
||||
|
||||
<path d="M1056 248c101-9 220 2 337 25 21 4 29 22 15 35-19 18-61 25-113 22l-219-15c-28-2-45-17-43-38 1-14 9-24 23-29Z" fill="url(#beak)" stroke="#8b4e3d" stroke-width="5"/>
|
||||
<path d="M1050 282c95 11 204 17 333 19-23 34-76 52-151 44l-138-16c-30-4-49-20-52-40Z" fill="url(#pouch)" stroke="#a95342" stroke-width="5"/>
|
||||
<path d="M1065 279c102 7 207 14 317 21" fill="none" stroke="#fff0b4" stroke-width="5" stroke-linecap="round" opacity=".72"/>
|
||||
<path d="M1393 273c15 2 25 9 27 19-2 17-19 27-51 31 20-11 29-26 24-50Z" fill="#6f3d38"/>
|
||||
<path d="M1079 314q86 18 167 22" fill="none" stroke="#9b443b" stroke-width="4" opacity=".58"/>
|
||||
|
||||
<path d="M1033 238c7-14 25-20 39-12 12 7 15 23 7 34-9 12-27 13-38 3-7-6-10-16-8-25Z" fill="#e9bf57" stroke="#6f6752" stroke-width="4"/>
|
||||
<circle cx="1058" cy="244" r="8" fill="url(#eye)"/>
|
||||
<circle cx="1061" cy="241" r="2.4" fill="#fff"/>
|
||||
<path d="M1040 225q19-11 35 3" fill="none" stroke="#6d6150" stroke-width="5" stroke-linecap="round"/>
|
||||
<path d="M1100 264q18-7 34 2-16 3-31 1Z" fill="#7f4d3d" opacity=".78"/>
|
||||
<g fill="none" stroke="#9cacab" stroke-width="4" stroke-linecap="round" opacity=".67">
|
||||
<path d="M866 369q31 14 56 3M855 395q35 16 65 6M854 425q31 17 64 10"/>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g id="reaching-wing" filter="url(#featherShadow)">
|
||||
<path d="M827 466c45-10 88-1 124 26 27 20 53 32 90 35 18 2 28 16 23 30-6 17-30 24-61 17-42-10-78-25-111-47-31-20-53-38-65-61Z" fill="url(#wingFeathers)" stroke="#6f8587" stroke-width="5"/>
|
||||
<path d="M891 486q60 53 142 61" fill="none" stroke="#ecf0e3" stroke-width="7" stroke-linecap="round" opacity=".68"/>
|
||||
<path d="M1035 530q22-6 40 11-15 2-22 16 17-2 30 11-25 16-54 1Z" fill="#b5c3bd" stroke="#687e80" stroke-width="4"/>
|
||||
<path d="M921 509q8 25 36 46M947 522q11 22 36 39" fill="none" stroke="#728789" stroke-width="5" opacity=".65"/>
|
||||
</g>
|
||||
|
||||
<g id="scarf">
|
||||
<path d="M871 337q55 20 110 4l8 28q-66 25-128-5Z" fill="#e9524b" stroke="#993b3b" stroke-width="5"/>
|
||||
<g class="scarf-tail">
|
||||
<path d="M872 349c-73 7-116-3-157-32 19 30 42 47 71 56-32 4-65-1-98-15 47 50 113 60 190 23Z" fill="#ef5d4f" stroke="#9b3f3a" stroke-width="5"/>
|
||||
<path d="M722 331q56 35 145 31" fill="none" stroke="#ff9a6f" stroke-width="6" opacity=".65"/>
|
||||
</g>
|
||||
</g>
|
||||
</g>
|
||||
|
||||
<g id="foreground" aria-hidden="true">
|
||||
<g transform="translate(25 865) scale(2.25)" class="grass-sway slow"><use href="#tuft"/></g>
|
||||
<g transform="translate(139 882) scale(1.75)" class="grass-sway"><use href="#tuft"/></g>
|
||||
<g transform="translate(1450 872) scale(2.1)" class="grass-sway slow"><use href="#tuft"/></g>
|
||||
<g transform="translate(1555 858) scale(1.6)" class="grass-sway"><use href="#tuft"/></g>
|
||||
<path d="M0 870q69-27 137-5t138-2q78-28 163 11L438 900H0Z" fill="#815e45" opacity=".46"/>
|
||||
<path d="M1268 879q68-28 130-10t101-4q54-24 101 3v32h-332Z" fill="#815e45" opacity=".42"/>
|
||||
<g fill="#f8ffff" opacity=".72">
|
||||
<circle cx="1510" cy="505" r="5"/><circle cx="1535" cy="488" r="3"/><circle cx="1483" cy="521" r="4"/>
|
||||
<circle cx="326" cy="583" r="4"/><circle cx="349" cy="569" r="2.5"/><circle cx="370" cy="582" r="3.5"/>
|
||||
</g>
|
||||
</g>
|
||||
</svg>
|
||||
|
After Width: | Height: | Size: 21 KiB |
File diff suppressed because it is too large.
Load diff
Reference in new issue
Block a user