Design and create a very creative, elaborate, and detailed voxel art scene of a pagoda in a beautiful garden with trees, including some cherry blossoms. Make the scene impressive and varied and use colorful voxels. Use whatever libraries to get this done but make sure I can paste it all into a single HTML file and open it in Chrome.
Created
July 29, 2026 23:54
-
-
Save 1337hero/0ac2a647a8a4ab19c4a79367784575ef to your computer and use it in GitHub Desktop.
Qwen3.6-27B-MTP-Q8-Voxel-Test
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
| <!DOCTYPE html> | |
| <html lang="en"> | |
| <head> | |
| <meta charset="UTF-8"> | |
| <meta name="viewport" content="width=device-width, initial-scale=1.0"> | |
| <title>桜の庭 — Cherry Blossom Pagoda</title> | |
| <style> | |
| *{margin:0;padding:0;box-sizing:border-box} | |
| body{overflow:hidden;background:#000;font-family:'Segoe UI',system-ui,sans-serif} | |
| canvas{display:block} | |
| #ui-overlay{position:fixed;top:0;left:0;width:100%;height:100%;pointer-events:none;z-index:10} | |
| #title-card{position:absolute;top:20px;left:20px;background:rgba(0,0,0,0.55);backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,0.08);border-radius:14px;padding:18px 24px;color:#fff;min-width:240px;pointer-events:auto} | |
| #title-card h1{font-size:22px;font-weight:300;letter-spacing:2px;margin-bottom:2px} | |
| #title-card h1 span.jp{font-size:26px;font-weight:400;color:#FFB7C5} | |
| #title-card .subtitle{font-size:11px;color:rgba(255,255,255,0.45);letter-spacing:3px;text-transform:uppercase;margin-top:6px} | |
| #title-card .voxel-count{font-size:11px;color:rgba(255,255,255,0.35);margin-top:10px;font-variant-numeric:tabular-nums} | |
| #controls-panel{position:absolute;bottom:20px;left:20px;background:rgba(0,0,0,0.55);backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,0.08);border-radius:14px;padding:16px 20px;color:#fff;pointer-events:auto} | |
| #controls-panel h3{font-size:10px;text-transform:uppercase;letter-spacing:2px;color:rgba(255,255,255,0.35);margin-bottom:10px} | |
| .time-btns{display:flex;gap:6px;flex-wrap:wrap} | |
| .time-btn{background:rgba(255,255,255,0.08);border:1px solid rgba(255,255,255,0.12);color:#fff;padding:6px 14px;border-radius:8px;cursor:pointer;font-size:12px;transition:all .2s;font-family:inherit} | |
| .time-btn:hover{background:rgba(255,255,255,0.18)} | |
| .time-btn.active{background:rgba(255,183,197,0.25);border-color:rgba(255,183,197,0.4);color:#FFB7C5} | |
| .toggle-row{display:flex;gap:8px;margin-top:10px} | |
| .toggle-btn{background:rgba(255,255,255,0.06);border:1px solid rgba(255,255,255,0.1);color:rgba(255,255,255,0.6);padding:5px 12px;border-radius:7px;cursor:pointer;font-size:11px;transition:all .2s;font-family:inherit} | |
| .toggle-btn:hover{background:rgba(255,255,255,0.14)} | |
| .toggle-btn.on{background:rgba(255,183,197,0.2);color:#FFB7C5;border-color:rgba(255,183,197,0.3)} | |
| #hint-line{position:absolute;bottom:20px;right:20px;background:rgba(0,0,0,0.5);backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,0.06);border-radius:10px;padding:12px 16px;color:rgba(255,255,255,0.35);font-size:11px;line-height:1.8;pointer-events:auto} | |
| #hint-line kbd{background:rgba(255,255,255,0.1);padding:1px 6px;border-radius:4px;font-size:10px;color:rgba(255,255,255,0.5)} | |
| #time-label{position:absolute;top:20px;right:20px;background:rgba(0,0,0,0.5);backdrop-filter:blur(12px);border:1px solid rgba(255,255,255,0.06);border-radius:10px;padding:10px 16px;color:rgba(255,255,255,0.6);font-size:13px;font-weight:300;letter-spacing:1px;pointer-events:auto} | |
| #loading{position:fixed;top:0;left:0;width:100%;height:100%;display:flex;flex-direction:column;align-items:center;justify-content:center;background:#0a0a0f;z-index:100;transition:opacity .5s} | |
| #loading.done{opacity:0;pointer-events:none} | |
| #loading .main{color:#FFB7C5;font-size:48px;font-weight:300} | |
| #loading .sub{font-size:13px;color:rgba(255,255,255,0.3);margin-top:12px;letter-spacing:2px} | |
| #loading .err{color:#ff4444;font-size:14px;max-width:500px;text-align:center;margin-top:12px} | |
| </style> | |
| </head> | |
| <body> | |
| <div id="ui-overlay"> | |
| <div id="title-card"> | |
| <h1><span class="jp">桜の庭</span> Cherry Blossom Pagoda</h1> | |
| <div class="subtitle">Voxel Garden Diorama</div> | |
| <div class="voxel-count" id="voxel-count">voxels: —</div> | |
| </div> | |
| <div id="controls-panel"> | |
| <h3>Time of Day</h3> | |
| <div class="time-btns"> | |
| <button class="time-btn" data-time="dawn">🌅 Dawn</button> | |
| <button class="time-btn active" data-time="day">☀️ Day</button> | |
| <button class="time-btn" data-time="dusk">🌇 Dusk</button> | |
| <button class="time-btn" data-time="night">🌙 Night</button> | |
| <button class="time-btn" data-time="auto">🔄 Auto</button> | |
| </div> | |
| <div class="toggle-row"> | |
| <button class="toggle-btn on" id="btn-petals">🌸 Petals</button> | |
| <button class="toggle-btn" id="btn-autoorbit">◎ Auto Orbit</button> | |
| <button class="toggle-btn" id="btn-fly">🚁 Cinematic Fly</button> | |
| </div> | |
| </div> | |
| <div id="hint-line"> | |
| <kbd>Left Drag</kbd> Orbit <kbd>Scroll</kbd> Zoom <kbd>Right Drag</kbd> Pan<br> | |
| <kbd>1</kbd> Dawn <kbd>2</kbd> Day <kbd>3</kbd> Dusk <kbd>4</kbd> Night | |
| </div> | |
| <div id="time-label">Day — 12:00</div> | |
| </div> | |
| <div id="loading"><div class="main">桜</div><div class="sub" id="load-status">building voxel garden…</div></div> | |
| <script type="importmap"> | |
| {"imports":{"three":"https://cdn.jsdelivr.net/npm/three@0.160.0/build/three.module.js","three/addons/":"https://cdn.jsdelivr.net/npm/three@0.160.0/examples/jsm/"}} | |
| </script> | |
| <script type="module"> | |
| import*as THREE from'three'; | |
| import{OrbitControls}from'three/addons/controls/OrbitControls.js'; | |
| const LS=document.getElementById('load-status'); | |
| function setStatus(s){LS.textContent=s;} | |
| // ─── RNG ─── | |
| class RNG{constructor(s=42){this.s=s}next(){this.s=(this.s*16807)%2147483647;return this.s/2147483647}range(a,b){return a+this.next()*(b-a)}int(a,b){return Math.floor(this.range(a,b+1))}pick(a){return a[this.int(0,a.length-1)]}chance(p){return this.next()<p}} | |
| const R=new RNG(42); | |
| // ─── VOXEL MAP ─── | |
| const VM=new Map(); | |
| const vk=(x,y,z)=>`${x},${y},${z}`; | |
| const av=(x,y,z,c)=>{const k=vk(x,y,z);if(!VM.has(k))VM.set(k,{x,y,z,c})}; | |
| const hv=(x,y,z)=>VM.has(vk(x,y,z)); | |
| // ─── PALETTES ─── | |
| const P={ | |
| grass:[0x4CAF50,0x388E3C,0x66BB6A,0x81C784,0x558B2F], | |
| darkGrass:[0x2E7D32,0x33691E,0x1B5E20], | |
| earth:[0x8B6914,0x6B4423,0x96703A,0x7B5B3A], | |
| stone:[0x808080,0x757575,0x9E9E9E,0x616161,0xA0A0A0], | |
| darkStone:[0x555555,0x424242,0x616161], | |
| pagRed:[0xC23B22,0xB71C1C,0xD32F2F,0xA8201A], | |
| pagDark:[0x8C1A10,0x7A1509,0x6D1008], | |
| roof:[0x1C2D3A,0x2C3E50,0x172530,0x34495E,0x15202B], | |
| roofDk:[0x0F1A22,0x0A1419,0x1A2730], | |
| gold:[0xD4A017,0xFFD700,0xC5931A,0xF0C700], | |
| win:[0xFFD54F,0xFFCA28,0xFFC107,0xFFB300], | |
| p1:[0xFFB7C5,0xFF91A4,0xFFC0CB], | |
| p2:[0xFF69B4,0xF4A0B5,0xE8899E], | |
| p3:[0xFF85A2,0xFF7096,0xFF5C8A], | |
| p4:[0xFFA0B4,0xFFD1DC,0xFFE0E8], | |
| trunk:[0x5C3317,0x4A2810,0x6B3A20,0x8B4513], | |
| g1:[0x228B22,0x2E8B57], | |
| g2:[0x3CB371,0x2E8B57], | |
| g3:[0x006400,0x228B22,0x008000], | |
| torii:[0xC23B22,0xB71C1C,0xD32F2F], | |
| ltSt:[0xBDBDBD,0x9E9E9E,0xCFCFCF,0xE0E0E0], | |
| ltLt:[0xFFD54F,0xFFCA28,0xFFC107], | |
| pathSt:[0xA8A8A8,0x9E9E9E,0xBDBDBD,0x8C8C8C], | |
| fuji:[0x455A64,0x37474F,0x546E7A,0x4A5D6A], | |
| snow:[0xFFFFFF,0xF5F5F5,0xECEFF1,0xE8EAF6], | |
| baseRk:[0x6D5D4B,0x5C4A3A,0x7B6B57,0x8B7B67], | |
| petal:[0xFFB7C5,0xFF91A4,0xFFC0CB,0xFF85A2,0xFFD1DC], | |
| bird:[0x2C2C2C,0x3E3E3E,0x1A1A1A], | |
| }; | |
| const pc=a=>R.pick(a); | |
| const pcanopy=[...P.p1,...P.p2,...P.p3,...P.p4]; | |
| // ─── TERRAIN ─── | |
| function genTerrain(){ | |
| setStatus('sculpting terrain…'); | |
| const rad=36; | |
| for(let x=-rad;x<=rad;x++){ | |
| for(let z=-rad;z<=rad;z++){ | |
| const d=Math.sqrt(x*x+z*z);if(d>rad)continue; | |
| const nd=d/rad; | |
| const nh=(R.next()*0.3+R.next()*0.2)*(1-nd*0.5); | |
| const topY=Math.floor(nh-nd*3); | |
| const hw=Math.sqrt(Math.max(0,rad*rad-d*d)); | |
| const botY=topY-Math.floor(hw*0.55+R.next()); | |
| for(let y=botY;y<=topY;y++){ | |
| if(y===topY)av(x,y,z,pc(nd>0.7?P.darkGrass:P.grass)); | |
| else if(y>=topY-2)av(x,y,z,pc(P.earth)); | |
| else if(y>=botY+2)av(x,y,z,pc(R.chance(0.5)?P.earth:P.baseRk)); | |
| else av(x,y,z,pc(P.baseRk)); | |
| } | |
| } | |
| } | |
| } | |
| // ─── PAGODA ─── | |
| function genPagoda(){ | |
| setStatus('building pagoda…'); | |
| const by=3; | |
| // Multi-layered stone foundation | |
| for(let x=-8;x<=8;x++)for(let z=-8;z<=8;z++){ | |
| if(Math.abs(x)+Math.abs(z)>15)continue; | |
| av(x,by-1,z,pc(P.stone)); | |
| av(x,by,z,pc(P.stone)); | |
| } | |
| for(let x=-7;x<=7;x++)for(let z=-7;z<=7;z++){ | |
| if(x*x+z*z>56)continue; | |
| for(let y=by-2;y<=by+1;y++)av(x,y,z,pc(P.stone)); | |
| } | |
| // Stone steps leading to pagoda entrance (south side) | |
| for(let s=0;s<4;s++){ | |
| for(let x=-2;x<=2;x++)av(x,by+s,8+s,pc(P.stone)); | |
| } | |
| const tiers=[ | |
| {s:5,wh:5,oh:6,rh:2},{s:4,wh:4,oh:5,rh:2}, | |
| {s:3,wh:4,oh:4,rh:2},{s:2,wh:3,oh:4,rh:2},{s:2,wh:3,oh:3,rh:2}, | |
| ]; | |
| let cy=by+2; | |
| for(const tier of tiers){ | |
| const s=tier.s; | |
| for(let x=-s;x<=s;x++)for(let z=-s;z<=s;z++)av(x,cy,z,pc(P.pagDark)); | |
| for(let y=cy+1;y<=cy+tier.wh;y++){ | |
| for(let x=-s;x<=s;x++)for(let z=-s;z<=s;z++){ | |
| const ax=Math.abs(x),az=Math.abs(z); | |
| if(ax<s&&az<s)continue; | |
| if(ax!==s&&az!==s)continue; | |
| if(ax===s&&az===s){av(x,y,z,pc(P.pagRed));continue;} | |
| if(y===cy+1||y===cy+tier.wh){av(x,y,z,pc(P.pagRed));continue;} | |
| if(R.chance(0.65))av(x,y,z,pc(P.win)); | |
| else av(x,y,z,pc(P.pagRed)); | |
| } | |
| } | |
| const bs=s+1,bly=cy+tier.wh; | |
| for(let x=-bs;x<=bs;x++)for(let z=-bs;z<=bs;z++){ | |
| if(Math.abs(x)<bs-1&&Math.abs(z)<bs-1)continue; | |
| if(Math.abs(x)>bs||Math.abs(z)>bs)continue; | |
| av(x,bly,z,pc(P.pagDark)); | |
| if(Math.abs(x)===bs&&Math.abs(z)===bs)av(x,bly+1,z,pc(P.pagRed)); | |
| } | |
| cy=bly+1; | |
| const robY=cy,rh=tier.rh,oh=tier.oh; | |
| for(let ry=0;ry<rh+1;ry++){ | |
| const rs=Math.floor(oh-ry*0.3); | |
| if(rs<1)break; | |
| for(let x=-rs;x<=rs;x++)for(let z=-rs;z<=rs;z++){ | |
| const cd=Math.abs(x)+Math.abs(z); | |
| const ed=Math.max(Math.abs(x),Math.abs(z)); | |
| if(cd>rs*1.8)continue; | |
| // Upturned eaves — corners curve up prominently | |
| const cornerFactor=(ed/rs); | |
| const eaveRise=cornerFactor>0.7?(cornerFactor-0.7)*4:0; | |
| const upY=ry+Math.floor(eaveRise); | |
| if(!hv(x,robY+ry,z))av(x,robY+ry,z,pc(ry===0&&ed>=rs-1?P.roofDk:P.roof)); | |
| if(upY>ry&&!hv(x,robY+upY,z))av(x,robY+upY,z,pc(P.roofDk)); | |
| // Gold trim on edges | |
| if(ry===0&&ed>=rs-1&&!hv(x,robY+rh,z)&&!hv(x,robY+rh+1,z)) | |
| av(x,robY+rh,z,pc(P.gold)); | |
| // Corner ornaments (鬼瓦 oni-gawara) | |
| if(ry===0&&ed===rs&&!hv(x,robY+rh+1,z)) | |
| av(x,robY+rh+1,z,pc(P.gold)); | |
| } | |
| } | |
| const rgY=robY+rh+1,ls=Math.floor(oh-(rh-1)*0.5); | |
| for(let i=-ls;i<=ls;i++){ | |
| if(!hv(i,rgY,-ls))av(i,rgY,-ls,pc(P.roofDk)); | |
| if(!hv(i,rgY,ls))av(i,rgY,ls,pc(P.roofDk)); | |
| if(!hv(-ls,rgY,i))av(-ls,rgY,i,pc(P.roofDk)); | |
| if(!hv(ls,rgY,i))av(ls,rgY,i,pc(P.roofDk)); | |
| } | |
| cy=rgY+1; | |
| } | |
| const sbY=cy,sh=8; | |
| for(let y=0;y<sh;y++){ | |
| const rr=y<3?1:0; | |
| if(y%2===0)for(let x=-rr-1;x<=rr+1;x++)for(let z=-rr-1;z<=rr+1;z++){ | |
| if(x===0&&z===0)continue; | |
| if(Math.abs(x)+Math.abs(z)<=rr+1)av(x,sbY+y,z,pc(P.gold)); | |
| } | |
| av(0,sbY+y,0,pc(P.gold)); | |
| } | |
| av(0,sbY+sh,0,pc(P.gold)); | |
| av(0,sbY+sh+1,0,pc(P.gold)); | |
| } | |
| // ─── CHERRY TREES ─── | |
| function genCherryTree(cx,cz,h,cr){ | |
| const by=2;let tx=cx,tz=cz; | |
| // Trunk with natural lean | |
| const leanX=(R.next()-0.5)*2,leanZ=(R.next()-0.5)*2; | |
| for(let y=by;y<by+h;y++){ | |
| av(tx,y,tz,pc(P.trunk)); | |
| if(y<by+3)for(const[dx,dz]of[[-1,0],[1,0],[0,-1],[0,1]]) | |
| if(R.chance(0.5))av(tx+dx,y,tz+dz,pc(P.trunk)); | |
| if(y<by+2&&R.chance(0.3)){ | |
| av(tx+1,y,tz,pc(P.trunk));av(tx-1,y,tz,pc(P.trunk)); | |
| av(tx,y,tz+1,pc(P.trunk));av(tx,y,tz-1,pc(P.trunk)); | |
| } | |
| // More branches | |
| if(y>by+h*0.4&&R.chance(0.12)){ | |
| const dir=R.int(0,3),len=R.int(1,4); | |
| for(let b=1;b<=len;b++){ | |
| let bx=tx,bz=tz,by2=y+b*0.3; | |
| if(dir===0)bx+=b;else if(dir===1)bx-=b;else if(dir===2)bz+=b;else bz-=b; | |
| av(bx,Math.floor(by2),bz,pc(P.trunk)); | |
| } | |
| } | |
| if(R.chance(0.12)){tx+=R.pick([-1,0,0,0,1]);tz+=R.pick([-1,0,0,0,1]);} | |
| } | |
| // Canopy — fluffy, irregular shape using multiple overlapping spheres | |
| const ty=by+h; | |
| // Main canopy sphere | |
| const canopyCenters=[ | |
| {x:cx,z:cz,y:ty+cr*0.3,r:cr}, | |
| {x:cx+cr*0.4,z:cz,y:ty+cr*0.2,r:cr*0.7}, | |
| {x:cx-cr*0.3,z:cz+cr*0.3,y:ty+cr*0.2,r:cr*0.65}, | |
| {x:cx,z:cz-cr*0.4,y:ty+cr*0.15,r:cr*0.6}, | |
| {x:cx+leanX*0.3,z:cz+leanZ*0.3,y:ty+cr*0.5,r:cr*0.5}, | |
| ]; | |
| for(const cc of canopyCenters){ | |
| for(let x=cc.x-cc.r-1;x<=cc.x+cc.r+1;x++) | |
| for(let z=cc.z-cc.r-1;z<=cc.z+cc.r+1;z++) | |
| for(let y=cc.y-cc.r;y<=cc.y+cc.r;y++){ | |
| const dx=x-cc.x,dz=z-cc.z,dy=y-cc.y; | |
| const dist=Math.sqrt(dx*dx+dz*dz+dy*dy); | |
| if(dist<cc.r+R.next()*0.8&&!hv(x,y,z)){ | |
| if(dist<cc.r*0.7||R.chance(0.8))av(x,y,z,R.pick(pcanopy)); | |
| } | |
| } | |
| } | |
| } | |
| function genCherryGarden(){ | |
| setStatus('planting cherry trees…'); | |
| const trees=[ | |
| {x:-14,z:-8,h:9,r:5},{x:-12,z:-12,h:7,r:4},{x:-16,z:-5,h:10,r:5},{x:-10,z:-6,h:8,r:4}, | |
| {x:14,z:-10,h:8,r:5},{x:16,z:-6,h:10,r:5},{x:12,z:-13,h:7,r:4}, | |
| {x:-6,z:-18,h:9,r:4},{x:6,z:-17,h:8,r:5},{x:0,z:-20,h:10,r:5}, | |
| {x:10,z:12,h:7,r:4},{x:13,z:15,h:9,r:5},{x:-10,z:10,h:8,r:4},{x:-14,z:14,h:7,r:5}, | |
| {x:-20,z:0,h:8,r:4},{x:20,z:-2,h:9,r:5},{x:-8,z:20,h:7,r:4}, | |
| {x:8,z:22,h:8,r:4},{x:-18,z:-15,h:6,r:3},{x:18,z:-14,h:7,r:3}, | |
| ]; | |
| for(const t of trees)genCherryTree(t.x,t.z,t.h,t.r); | |
| } | |
| // ─── GREEN TREES ─── | |
| function genGreenTree(cx,cz,h,r){ | |
| const by=2; | |
| for(let y=by;y<by+h;y++){av(cx,y,cz,pc(P.trunk));if(y<by+2&&R.chance(0.5))av(cx+1,y,cz,pc(P.trunk));} | |
| const ty=by+h; | |
| for(let x=cx-r;x<=cx+r;x++)for(let z=cz-r;z<=cz+r;z++){ | |
| const hz=Math.sqrt((x-cx)**2+(z-cz)**2);if(hz>r)continue; | |
| const ch=Math.floor(Math.sqrt(Math.max(0,r*r-hz*hz))*1.2); | |
| for(let y=ty;y<ty+ch;y++)if(R.chance(0.9)) | |
| av(x,y,z,R.pick([...P.g1,...P.g2,...P.g3])); | |
| } | |
| } | |
| function genShrub(cx,cz,sz){ | |
| const by=2; | |
| for(let x=-sz;x<=sz;x++)for(let z=-sz;z<=sz;z++)for(let y=0;y<=sz;y++){ | |
| if(x*x+z*z+y*y>sz*sz+R.next())continue; | |
| if(!hv(cx+x,by+y,cz+z))av(cx+x,by+y,cz+z,R.pick([...P.g2,...P.g3])); | |
| } | |
| } | |
| function genGreenery(){ | |
| setStatus('planting shrubbery…'); | |
| for(const t of[{x:-22,z:-14,h:7,r:3},{x:22,z:-15,h:8,r:3},{x:-18,z:12,h:6,r:3},{x:18,z:10,h:7,r:3},{x:-24,z:-4,h:6,r:3},{x:24,z:4,h:7,r:3}]) | |
| genGreenTree(t.x,t.z,t.h,t.r); | |
| for(let i=0;i<20;i++){ | |
| const a=R.next()*Math.PI*2,d=R.range(10,30); | |
| genShrub(Math.floor(Math.cos(a)*d),Math.floor(Math.sin(a)*d),R.int(1,2)); | |
| } | |
| } | |
| // ─── STONE PATH ─── | |
| function genPath(){ | |
| const pts=[];let x=0,z=28; | |
| for(let i=0;i<28;i++){pts.push({x,z});x+=R.pick([-1,0,0,1,0]);z-=R.pick([1,1,1,2]);if(R.chance(0.25))x+=R.pick([-1,0,1]);} | |
| for(let i=0;i<pts.length;i++){ | |
| const p=pts[i],pw=i<5?3:2; | |
| for(let dx=-pw;dx<=pw;dx++)for(let dz=-1;dz<=1;dz++){ | |
| if(dx*dx+dz*dz>pw*pw)continue; | |
| if(R.chance(0.88)){av(p.x+dx,2,p.z+dz,pc(P.pathSt));if(R.chance(0.35))av(p.x+dx,3,p.z+dz,pc(P.pathSt));} | |
| } | |
| } | |
| // Stone markers at intervals | |
| for(let i=5;i<pts.length-3;i+=4){ | |
| const p=pts[i],pw=i<5?3:2; | |
| for(let s of[-1,1]){ | |
| if(!hv(p.x+pw+1,2,p.z+s)){av(p.x+pw+1,2,p.z+s,pc(P.pathSt));av(p.x+pw+1,3,p.z+s,pc(P.pathSt));} | |
| if(!hv(p.x-pw-1,2,p.z+s)){av(p.x-pw-1,2,p.z+s,pc(P.pathSt));av(p.x-pw-1,3,p.z+s,pc(P.pathSt));} | |
| } | |
| } | |
| } | |
| // ─── DECO PLANTS ─── | |
| function genDecoPlants(){ | |
| // Small flowering bushes around pagoda base | |
| const spots=[ | |
| {x:-10,z:3},{x:10,z:3},{x:-9,z:-3},{x:9,z:-3}, | |
| {x:-7,z:8},{x:7,z:8},{x:-11,z:-8},{x:11,z:-8}, | |
| ]; | |
| for(const sp of spots){ | |
| for(let dx=-1;dx<=1;dx++)for(let dz=-1;dz<=1;dz++){ | |
| if(dx*dx+dz*dz>2)continue; | |
| if(R.chance(0.7)){ | |
| av(sp.x+dx,2,sp.z+dz,pc(P.g3)); | |
| av(sp.x+dx,3,sp.z+dz,R.chance(0.4)?R.pick(pcanopy):pc(P.g2)); | |
| } | |
| } | |
| } | |
| // Fallen petal patches on ground | |
| for(let i=0;i<20;i++){ | |
| const a=R.next()*Math.PI*2,d=R.range(5,28); | |
| const px=Math.floor(Math.cos(a)*d),pz=Math.floor(Math.sin(a)*d); | |
| if(!hv(px,3,pz))av(px,3,pz,R.chance(0.5)?R.pick(pcanopy):pc(P.pathSt)); | |
| } | |
| // Small reflecting pool east of pagoda | |
| const px=15,pz=5; | |
| for(let x=px-3;x<=px+3;x++)for(let z=pz-2;z<=pz+2;z++){ | |
| if((x-px)*(x-px)+(z-pz)*(z-pz)>10)continue; | |
| av(x,2,z,0x1565C0); // Water blue | |
| } | |
| // Pool edge stones | |
| for(let x=px-4;x<=px+4;x++)for(let z=pz-3;z<=pz+3;z++){ | |
| if((x-px)*(x-px)+(z-pz)*(z-pz)>10&&(x-px)*(x-px)+(z-pz)*(z-pz)<16){ | |
| if(R.chance(0.6))av(x,2,z,pc(P.stone)); | |
| } | |
| } | |
| } | |
| // ─── TORII GATE ─── | |
| function genTorii(){ | |
| const tx=0,tz=28,by=2; | |
| for(let y=by;y<by+9;y++){ | |
| av(tx-5,y,tz,pc(P.torii));av(tx+5,y,tz,pc(P.torii)); | |
| if(y<by+2){av(tx-5,y,tz+1,pc(P.torii));av(tx+5,y,tz+1,pc(P.torii));} | |
| } | |
| for(let x=-6;x<=6;x++)av(tx+x,by+7,tz,pc(P.torii)); | |
| for(let x=-7;x<=7;x++){ | |
| av(tx+x,by+9,tz,pc(P.torii)); | |
| if(Math.abs(x)>=6)av(tx+x,by+10,tz,pc(P.torii)); | |
| } | |
| av(tx,by+8,tz,pc(P.pagDark)); | |
| } | |
| // ─── STONE LANTERNS ─── | |
| function genLantern(x,z){ | |
| const by=2; | |
| av(x,by,z,pc(P.ltSt));av(x-1,by,z,pc(P.ltSt));av(x+1,by,z,pc(P.ltSt)); | |
| av(x,by,z-1,pc(P.ltSt));av(x,by,z+1,pc(P.ltSt)); | |
| for(let y=by+1;y<=by+3;y++)av(x,y,z,pc(P.ltSt)); | |
| av(x,by+4,z,pc(P.ltLt));av(x,by+4,z-1,pc(P.ltSt));av(x,by+4,z+1,pc(P.ltSt)); | |
| av(x-1,by+4,z,pc(P.ltSt));av(x+1,by+4,z,pc(P.ltSt)); | |
| av(x-1,by+4,z-1,pc(P.ltSt));av(x+1,by+4,z+1,pc(P.ltSt)); | |
| av(x-1,by+4,z+1,pc(P.ltSt));av(x+1,by+4,z-1,pc(P.ltSt)); | |
| for(let dx=-2;dx<=2;dx++)for(let dz=-2;dz<=2;dz++) | |
| if(Math.abs(dx)+Math.abs(dz)<=3)av(x+dx,by+5,z+dz,pc(P.ltSt)); | |
| av(x,by+6,z,pc(P.ltSt)); | |
| } | |
| function placeLanterns(){ | |
| for(const lp of[{x:-3,z:22},{x:3,z:22},{x:-5,z:14},{x:5,z:14},{x:-8,z:6},{x:8,z:6},{x:12,z:0},{x:-12,z:0}]) | |
| genLantern(lp.x,lp.z); | |
| } | |
| // ─── ROCKS ─── | |
| function genRock(cx,cz,sz){ | |
| const by=2; | |
| for(let x=-sz;x<=sz;x++)for(let z=-sz;z<=sz;z++){ | |
| const h=Math.floor(Math.sqrt(Math.max(0,sz*sz-x*x-z*z))*0.7); | |
| for(let y=0;y<=h;y++)if(R.chance(0.85)) | |
| av(cx+x,by+y,cz+z,pc(R.chance(0.3)?P.darkStone:P.stone)); | |
| } | |
| } | |
| function placeRocks(){ | |
| for(let i=0;i<12;i++){ | |
| const a=R.next()*Math.PI*2,d=R.range(8,32); | |
| genRock(Math.floor(Math.cos(a)*d),Math.floor(Math.sin(a)*d),R.int(1,3)); | |
| } | |
| } | |
| // ─── MOUNT FUJI ─── | |
| function genFuji(){ | |
| const fx=-60,fz=-70,by=-1,h=25,br=18; | |
| for(let y=0;y<h;y++){ | |
| const r=Math.floor(br*(1-y/h)); | |
| for(let x=-r;x<=r;x++)for(let z=-r;z<=r;z++){ | |
| if(x*x+z*z>r*r)continue; | |
| if(y>h*0.6){if(R.chance(0.85))av(fx+x,by+y,fz+z,pc(P.snow));} | |
| else if(R.chance(0.7))av(fx+x,by+y,fz+z,pc(P.fuji)); | |
| } | |
| } | |
| } | |
| // ─── BUILD VOXEL MESH ─── | |
| let windowMatList=[]; | |
| let totalVoxels=0; | |
| function buildVoxels(scene){ | |
| setStatus('rendering voxels…'); | |
| const groups=new Map(); | |
| for(const v of VM.values()){ | |
| const isWin=P.win.includes(v.c); | |
| const key=isWin?'__window__':v.c; | |
| if(!groups.has(key))groups.set(key,[]); | |
| groups.get(key).push(v); | |
| } | |
| const geo=new THREE.BoxGeometry(1,1,1); | |
| for(const[key,voxels]of groups){ | |
| const isWin=key==='__window__'; | |
| const mat=new THREE.MeshStandardMaterial({ | |
| color:isWin?0xFFC107:new THREE.Color(key), | |
| roughness:0.8,metalness:0.05,flatShading:true, | |
| }); | |
| if(isWin){ | |
| mat.emissive=new THREE.Color(0x000000); | |
| mat.emissiveIntensity=0; | |
| windowMatList.push(mat); | |
| } | |
| const mesh=new THREE.InstancedMesh(geo,mat,voxels.length); | |
| const dummy=new THREE.Object3D(); | |
| for(let i=0;i<voxels.length;i++){ | |
| const v=voxels[i];dummy.position.set(v.x,v.y,v.z);dummy.updateMatrix(); | |
| mesh.setMatrixAt(i,dummy.matrix); | |
| } | |
| mesh.instanceMatrix.needsUpdate=true; | |
| mesh.castShadow=true;mesh.receiveShadow=true; | |
| scene.add(mesh); | |
| totalVoxels+=voxels.length; | |
| } | |
| VM.clear(); | |
| document.getElementById('voxel-count').textContent=`voxels: ${totalVoxels.toLocaleString()}`; | |
| } | |
| // ─── WATER ─── | |
| let waterMesh; | |
| function genWater(scene){ | |
| const geo=new THREE.PlaneGeometry(200,200,80,80); | |
| const mat=new THREE.MeshStandardMaterial({ | |
| color:0x1565C0,roughness:0.05,metalness:0.4,transparent:true,opacity:0.75, | |
| side:THREE.DoubleSide, | |
| }); | |
| waterMesh=new THREE.Mesh(geo,mat); | |
| waterMesh.rotation.x=-Math.PI/2;waterMesh.position.y=-2.5; | |
| waterMesh.receiveShadow=true;scene.add(waterMesh); | |
| } | |
| function animWater(t){ | |
| if(!waterMesh)return; | |
| const pos=waterMesh.geometry.attributes.position; | |
| for(let i=0;i<pos.count;i++){ | |
| const x=pos.getX(i),z=pos.getY(i); | |
| pos.setZ(i,Math.sin(x*0.08+t*0.8)*0.3+Math.sin(z*0.1+t*0.6)*0.2+Math.sin((x+z)*0.05+t*1.1)*0.15); | |
| } | |
| pos.needsUpdate=true;waterMesh.geometry.computeVertexNormals(); | |
| } | |
| // ─── BIRDS ─── | |
| let birds=[]; | |
| function genBirds(scene){ | |
| for(let i=0;i<5;i++){ | |
| const g=new THREE.Group(); | |
| const body=new THREE.Mesh(new THREE.BoxGeometry(0.5,0.2,0.2),new THREE.MeshStandardMaterial({color:P.bird[0],roughness:0.8})); | |
| const wL=new THREE.Mesh(new THREE.BoxGeometry(0.6,0.1,0.2),new THREE.MeshStandardMaterial({color:P.bird[1],roughness:0.8})); | |
| wL.position.set(-0.4,0.05,0); | |
| const wR=wL.clone();wR.position.set(0.4,0.05,0); | |
| g.add(body,wL,wR); | |
| const px=R.range(-30,30),pz=R.range(-30,30); | |
| g.position.set(px,R.range(25,40),pz); | |
| scene.add(g); | |
| birds.push({g,wL,wR,or:Math.sqrt(px*px+pz*pz),oa:Math.atan2(pz,px),os:R.range(0.05,0.15),oy:g.position.y,ot:R.range(0.2,0.5)}); | |
| } | |
| } | |
| function animBirds(t){ | |
| for(const b of birds){ | |
| b.oa+=b.os*0.016; | |
| b.g.position.x=Math.cos(b.oa)*b.or;b.g.position.z=Math.sin(b.oa)*b.or; | |
| b.g.position.y=b.oy+Math.sin(t*2+b.oa)*1.5; | |
| b.g.rotation.y=-b.oa+Math.PI/2;b.g.rotation.z=Math.sin(t*3)*b.ot*0.2; | |
| const fl=Math.sin(t*8)*0.4;b.wL.rotation.z=fl;b.wR.rotation.z=-fl; | |
| } | |
| } | |
| // ─── PETALS ─── | |
| let petalSystem=null,petalVisible=true,mistSystem=null,starsSystem=null; | |
| function genPetals(scene){ | |
| const N=1200; | |
| const geo=new THREE.BufferGeometry(); | |
| const pos=new Float32Array(N*3),vel=new Float32Array(N*3),col=new Float32Array(N*3),sz=new Float32Array(N); | |
| for(let i=0;i<N;i++){ | |
| pos[i*3]=R.range(-40,40);pos[i*3+1]=R.range(3,35);pos[i*3+2]=R.range(-40,40); | |
| vel[i*3]=R.range(-0.3,0.3);vel[i*3+1]=R.range(-0.4,-0.08);vel[i*3+2]=R.range(-0.2,0.2); | |
| const c=new THREE.Color(pc(P.petal));col[i*3]=c.r;col[i*3+1]=c.g;col[i*3+2]=c.b; | |
| sz[i]=R.range(0.2,0.6); | |
| } | |
| geo.setAttribute('position',new THREE.BufferAttribute(pos,3)); | |
| geo.setAttribute('color',new THREE.BufferAttribute(col,3)); | |
| geo.setAttribute('size',new THREE.BufferAttribute(sz,1)); | |
| petalSystem=new THREE.Points(geo,new THREE.PointsMaterial({size:0.35,vertexColors:true,transparent:true,opacity:0.8,sizeAttenuation:true,depthWrite:false})); | |
| petalSystem.userData.vel=vel;petalSystem.userData.sizes=sz;scene.add(petalSystem); | |
| } | |
| function animPetals(){ | |
| if(!petalSystem||!petalVisible)return; | |
| const pos=petalSystem.geometry.attributes.position,vel=petalSystem.userData.vel; | |
| for(let i=0;i<pos.count;i++){ | |
| let x=pos.getX(i)+vel[i*3]*0.016,y=pos.getY(i)+vel[i*3+1]*0.016,z=pos.getZ(i)+vel[i*3+2]*0.016; | |
| vel[i*3]+=(Math.random()-0.5)*0.02;vel[i*3+2]+=(Math.random()-0.5)*0.02; | |
| vel[i*3]=Math.max(-1,Math.min(1,vel[i*3]));vel[i*3+2]=Math.max(-1,Math.min(1,vel[i*3+2])); | |
| if(y<-3){y=R.range(20,35);x=R.range(-35,35);z=R.range(-35,35);} | |
| pos.setXYZ(i,x,y,z); | |
| } | |
| pos.needsUpdate=true; | |
| } | |
| // ─── MIST ─── | |
| function genMist(scene){ | |
| const N=300; | |
| const geo=new THREE.BufferGeometry(); | |
| const pos=new Float32Array(N*3),vel=new Float32Array(N*3); | |
| for(let i=0;i<N;i++){ | |
| const a=Math.random()*Math.PI*2,d=R.range(5,35); | |
| pos[i*3]=Math.cos(a)*d;pos[i*3+1]=R.range(-1,3);pos[i*3+2]=Math.sin(a)*d; | |
| vel[i*3]=R.range(-0.1,0.1);vel[i*3+1]=R.range(0.01,0.05);vel[i*3+2]=R.range(-0.1,0.1); | |
| } | |
| geo.setAttribute('position',new THREE.BufferAttribute(pos,3)); | |
| mistSystem=new THREE.Points(geo,new THREE.PointsMaterial({size:0.8,color:0xffffff,transparent:true,opacity:0.12,sizeAttenuation:true,depthWrite:false})); | |
| mistSystem.userData.vel=vel;scene.add(mistSystem); | |
| } | |
| function animMist(t){ | |
| if(!mistSystem)return; | |
| const pos=mistSystem.geometry.attributes.position,vel=mistSystem.userData.vel; | |
| for(let i=0;i<pos.count;i++){ | |
| let x=pos.getX(i)+vel[i*3]*0.016,y=pos.getY(i)+vel[i*3+1]*0.016,z=pos.getZ(i)+vel[i*3+2]*0.016; | |
| vel[i*3]+=(Math.random()-0.5)*0.005;vel[i*3+2]+=(Math.random()-0.5)*0.005; | |
| if(y>5){y=R.range(-1,1);} | |
| pos.setXYZ(i,x,y,z); | |
| } | |
| pos.needsUpdate=true; | |
| if(curTime==='dawn'||curTime==='night')mistSystem.material.opacity=0.18; | |
| else mistSystem.material.opacity=0.08; | |
| } | |
| // ─── STARS (visible at night) ─── | |
| function genStars(scene){ | |
| const N=400; | |
| const geo=new THREE.BufferGeometry(); | |
| const pos=new Float32Array(N*3); | |
| for(let i=0;i<N;i++){ | |
| const theta=R.next()*Math.PI*2; | |
| const phi=R.range(0.1,Math.PI*0.45); | |
| const r=120; | |
| pos[i*3]=r*Math.sin(phi)*Math.cos(theta); | |
| pos[i*3+1]=r*Math.cos(phi)+10; | |
| pos[i*3+2]=r*Math.sin(phi)*Math.sin(theta); | |
| } | |
| geo.setAttribute('position',new THREE.BufferAttribute(pos,3)); | |
| starsSystem=new THREE.Points(geo,new THREE.PointsMaterial({size:0.4,color:0xffffff,transparent:true,opacity:0,sizeAttenuation:true,depthWrite:false})); | |
| scene.add(starsSystem); | |
| } | |
| function animStars(){ | |
| if(!starsSystem)return; | |
| const target=curTime==='night'?0.7:curTime==='dusk'?0.15:0; | |
| starsSystem.material.opacity+=(target-starsSystem.material.opacity)*0.05; | |
| } | |
| // ─── TIME OF DAY ─── | |
| const TP={ | |
| dawn:{label:'Dawn — 06:00',sky:0xFF8C69,fog:0xFFB088,fd:0.008,ac:0xFFD0A0,ai:0.4,sc:0xFF9966,si:1.2,sp:new THREE.Vector3(40,10,30),wg:0.3}, | |
| day:{label:'Day — 12:00',sky:0x87CEEB,fog:0xC0D8F0,fd:0.005,ac:0xffffff,ai:0.6,sc:0xFFF5E0,si:2.0,sp:new THREE.Vector3(20,50,20),wg:0}, | |
| dusk:{label:'Dusk — 18:30',sky:0xCC7744,fog:0xDD9966,fd:0.01,ac:0xFFCC99,ai:0.35,sc:0xFF6633,si:1.0,sp:new THREE.Vector3(-40,8,30),wg:0.5}, | |
| night:{label:'Night — 00:00',sky:0x0A0A1A,fog:0x15152A,fd:0.015,ac:0x2233AA,ai:0.15,sc:0x4466AA,si:0.3,sp:new THREE.Vector3(-10,5,-30),wg:1.0}, | |
| }; | |
| let curTime='day',autoCycle=false,cycleTimer=0; | |
| const timeLabelEl=document.getElementById('time-label'); | |
| let dirLight,ambientLight,sky,pagodaLight; | |
| function setTime(p){ | |
| const t=TP[p];if(!t)return;curTime=p; | |
| timeLabelEl.textContent=t.label; | |
| document.querySelectorAll('.time-btn').forEach(b=>b.classList.toggle('active',b.dataset.time===p)); | |
| scene.background=new THREE.Color(t.sky);scene.fog.color.set(t.fog);scene.fog.density=t.fd; | |
| ambientLight.color.set(t.ac);ambientLight.intensity=t.ai; | |
| dirLight.color.set(t.sc);dirLight.intensity=t.si;dirLight.position.copy(t.sp); | |
| for(const m of windowMatList){ | |
| m.emissive.set(t.wg>0.1?0xFF8C00:0x000000); | |
| m.emissiveIntensity=t.wg*2; | |
| } | |
| pagodaLight.intensity=t.wg*3; | |
| } | |
| // ─── CAMERA ─── | |
| let cinFly=false,cinTime=0,autoOrbit=false; | |
| function startFly(){if(cinFly)return;cinFly=true;cinTime=0;} | |
| function updateFly(d){ | |
| if(!cinFly)return;cinTime+=d; | |
| const t=Math.min(cinTime/15,1); | |
| if(t>=1){cinFly=false;document.getElementById('btn-fly').classList.remove('on');return;} | |
| // Phase 1: rise up from ground (0-0.3), Phase 2: circle high (0.3-0.7), Phase 3: descend (0.7-1.0) | |
| let dist,h,a; | |
| if(t<0.3){ | |
| const p=t/0.3; | |
| dist=80-p*30;h=5+p*25;a=p*Math.PI*0.5; | |
| }else if(t<0.7){ | |
| const p=(t-0.3)/0.4; | |
| dist=50;h=30+Math.sin(p*Math.PI)*5;a=Math.PI*0.5+p*Math.PI*1.5; | |
| }else{ | |
| const p=(t-0.7)/0.3; | |
| dist=50+p*20;h=30-p*20;a=Math.PI*2+p*Math.PI*0.25; | |
| } | |
| camera.position.set(Math.cos(a)*dist,h,Math.sin(a)*dist); | |
| controls.target.lerp(new THREE.Vector3(0,12,0),d*0.5); | |
| } | |
| // ─── SCENE SETUP ─── | |
| const scene=new THREE.Scene(); | |
| scene.background=new THREE.Color(TP.day.sky); | |
| scene.fog=new THREE.FogExp2(TP.day.fog,TP.day.fd); | |
| const camera=new THREE.PerspectiveCamera(50,innerWidth/innerHeight,0.5,500); | |
| camera.position.set(55,30,55); | |
| const renderer=new THREE.WebGLRenderer({antialias:true,powerPreference:'high-performance'}); | |
| renderer.setSize(innerWidth,innerHeight); | |
| renderer.setPixelRatio(Math.min(devicePixelRatio,2)); | |
| renderer.shadowMap.enabled=true;renderer.shadowMap.type=THREE.PCFSoftShadowMap; | |
| renderer.toneMapping=THREE.ACESFilmicToneMapping;renderer.toneMappingExposure=1.0; | |
| document.body.appendChild(renderer.domElement); | |
| const controls=new OrbitControls(camera,renderer.domElement); | |
| controls.enableDamping=true;controls.dampingFactor=0.08;controls.target.set(0,10,0); | |
| controls.minDistance=15;controls.maxDistance=150;controls.maxPolarAngle=Math.PI*0.85;controls.update(); | |
| ambientLight=new THREE.AmbientLight(TP.day.ac,TP.day.ai);scene.add(ambientLight); | |
| dirLight=new THREE.DirectionalLight(TP.day.sc,TP.day.si); | |
| dirLight.position.copy(TP.day.sp);dirLight.castShadow=true; | |
| dirLight.shadow.mapSize.set(2048,2048); | |
| dirLight.shadow.camera.left=-60;dirLight.shadow.camera.right=60; | |
| dirLight.shadow.camera.top=60;dirLight.shadow.camera.bottom=-60; | |
| dirLight.shadow.camera.near=1;dirLight.shadow.camera.far=200;dirLight.shadow.bias=-0.001; | |
| scene.add(dirLight); | |
| // Fill light for per-face contrast | |
| const fillLight=new THREE.DirectionalLight(0x8899BB,0.4); | |
| fillLight.position.set(-30,20,-20);scene.add(fillLight); | |
| scene.add(new THREE.HemisphereLight(0x87CEEB,0x3CB371,0.25)); | |
| // Warm point light inside pagoda | |
| pagodaLight=new THREE.PointLight(0xFF8C00,0,20,2); | |
| pagodaLight.position.set(0,8,0);scene.add(pagodaLight); | |
| // ─── GENERATE ─── | |
| setStatus('sculpting terrain…'); | |
| genTerrain();genPagoda();genCherryGarden();genGreenery();genPath(); | |
| genTorii();placeLanterns();placeRocks();genDecoPlants();genFuji(); | |
| buildVoxels(scene); | |
| genWater(scene);genBirds(scene);genPetals(scene);genMist(scene);genStars(scene); | |
| setTime('day'); | |
| // Fade out loading | |
| setTimeout(()=>{document.getElementById('loading').classList.add('done');},500); | |
| setTimeout(()=>{document.getElementById('loading').remove();},1500); | |
| // ─── EVENTS ─── | |
| window.addEventListener('resize',()=>{ | |
| camera.aspect=innerWidth/innerHeight;camera.updateProjectionMatrix(); | |
| renderer.setSize(innerWidth,innerHeight); | |
| }); | |
| document.querySelectorAll('.time-btn').forEach(b=>b.addEventListener('click',()=>{ | |
| const t=b.dataset.time;if(t==='auto'){autoCycle=!autoCycle;cycleTimer=0;b.classList.toggle('on',autoCycle);} | |
| else{autoCycle=false;setTime(t);} | |
| })); | |
| document.getElementById('btn-petals').addEventListener('click',e=>{ | |
| petalVisible=!petalVisible;e.target.classList.toggle('on',petalVisible); | |
| }); | |
| document.getElementById('btn-autoorbit').addEventListener('click',e=>{ | |
| autoOrbit=!autoOrbit;e.target.classList.toggle('on',autoOrbit); | |
| }); | |
| document.getElementById('btn-fly').addEventListener('click',e=>{ | |
| startFly();e.target.classList.add('on'); | |
| }); | |
| window.addEventListener('keydown',e=>{ | |
| const km={'1':'dawn','2':'day','3':'dusk','4':'night'}; | |
| if(km[e.key]){autoCycle=false;setTime(km[e.key]);} | |
| }); | |
| // ─── ANIMATE ─── | |
| const clock=new THREE.Clock(); | |
| function animate(){ | |
| requestAnimationFrame(animate); | |
| const d=clock.getDelta(),t=clock.getElapsedTime(); | |
| controls.update(); | |
| if(autoOrbit){ | |
| const r=camera.position.distanceTo(controls.target); | |
| const phi=Math.acos(Math.max(-1,Math.min(1,(camera.position.y-controls.target.y)/r))); | |
| controls.autoRotate=true;controls.autoRotateSpeed=1.5; | |
| }else{controls.autoRotate=false;} | |
| controls.update(); | |
| updateFly(d); | |
| if(autoCycle){ | |
| cycleTimer+=d; | |
| if(cycleTimer>=15){cycleTimer=0;const ph=['dawn','day','dusk','night'];setTime(ph[(ph.indexOf(curTime)+1)%4]);} | |
| } | |
| animWater(t);animBirds(t);animPetals();animMist(t);animStars(); | |
| renderer.render(scene,camera); | |
| } | |
| animate(); | |
| </script> | |
| </body> | |
| </html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment