MVP checks / mvp (push) Waiting to run
Add shared Rust/WASM physics, worker meshing and diagnostics, 64-chunk full-height streaming, atlas texture support, and baseline world import. Document the current implementation and include the supplied in-game lobby screenshot.
76 lines
4.8 KiB
JavaScript
76 lines
4.8 KiB
JavaScript
/** 16³ sections; uniform air/solid sections retain a single uint32 value.
|
|
* Compatibility iterators visit non-air blocks; hot paths use numeric access.
|
|
*/
|
|
export class SectionVoxelMap {
|
|
#sections = new Map();
|
|
#bytes = 0;
|
|
size = 0;
|
|
static sectionKey(x,y,z) { return `${Math.floor(x/16)},${Math.floor(y/16)},${Math.floor(z/16)}`; }
|
|
static index(x,y,z) { return ((x%16+16)%16)+16*((z%16+16)%16)+256*((y%16+16)%16); }
|
|
getAt(x,y,z) { const cells=this.#sections.get(SectionVoxelMap.sectionKey(x,y,z))?.cells; return cells ? cells[cells.length===1 ? 0 : SectionVoxelMap.index(x,y,z)] : 0; }
|
|
get(key) { const p=key.split(',').map(Number); return this.getAt(...p) || undefined; }
|
|
has(key) { return this.get(key)!==undefined; }
|
|
hasSection(id) { return this.#sections.has(id); }
|
|
set(key,value) {
|
|
const p=key.split(',').map(Number), id=SectionVoxelMap.sectionKey(...p);
|
|
let section=this.#sections.get(id);
|
|
if(!section) {section={cells:new Uint32Array(4096),count:0,ids:new Map()};this.#sections.set(id,section);this.#bytes+=16384;}
|
|
const index=SectionVoxelMap.index(...p), old=section.cells[section.cells.length===1 ? 0 : index];
|
|
if(old===value) return this;
|
|
if(section.cells.length===1) {section.cells=new Uint32Array(4096).fill(old);this.#bytes+=16380;}
|
|
if(old) {this.size--;section.count--;const count=section.ids.get(old)-1;if(count) section.ids.set(old,count);else section.ids.delete(old);}
|
|
section.cells[index]=value;
|
|
if(value) {this.size++;section.count++;section.ids.set(value,(section.ids.get(value)||0)+1);}
|
|
return this;
|
|
}
|
|
setInSection(key,value) {return this.set(key,value);}
|
|
delete(key) {if(!this.has(key)) return false;this.set(key,0);return true;}
|
|
clear() {this.#sections.clear();this.size=0;this.#bytes=0;}
|
|
deleteSection(id) {const previous=this.#sections.get(id);if(!previous)return 0;this.size-=previous.count;this.#bytes-=previous.cells.byteLength;this.#sections.delete(id);return previous.count;}
|
|
setSection(id,cells) {
|
|
if(!(cells instanceof Uint32Array)||![1,4096].includes(cells.length)) throw Error('A section must contain 4096 uint32 cells or one uniform value');
|
|
const ids=new Map();let count=0;
|
|
for(const value of cells) if(value) {const n=cells.length===1 ? 4096 : 1;count+=n;ids.set(value,(ids.get(value)||0)+n);}
|
|
this.size+=count-(this.#sections.get(id)?.count||0);
|
|
this.#bytes+=cells.byteLength-(this.#sections.get(id)?.cells.byteLength||0);
|
|
this.#sections.set(id,{cells,count,ids});
|
|
}
|
|
/** Borrowed dense cells or a single uniform value; copy before transferring. */
|
|
getSectionCells(id) { return this.#sections.get(id)?.cells; }
|
|
get sectionCount() {return this.#sections.size;}
|
|
get byteLength() {return this.#bytes;}
|
|
sectionIsEmpty(id) {return this.#sections.get(id)?.count===0;}
|
|
loadedSectionKeys() {return this.#sections.keys();}
|
|
*sectionEntries() {for(const [id,section] of this.#sections) yield [id,section.cells];}
|
|
*materialIds() {const seen=new Set();for(const section of this.#sections.values())for(const id of section.ids.keys())if(!seen.has(id)){seen.add(id);yield id;}}
|
|
forEachBlock(callback,bounds=null) {
|
|
const selected = function* (sections) {
|
|
if (!bounds) {yield* sections;return;}
|
|
for(let x=Math.floor(bounds.min[0]/16);x<=Math.floor(bounds.max[0]/16);x++)
|
|
for(let y=Math.floor(bounds.min[1]/16);y<=Math.floor(bounds.max[1]/16);y++)
|
|
for(let z=Math.floor(bounds.min[2]/16);z<=Math.floor(bounds.max[2]/16);z++) {
|
|
const id=`${x},${y},${z}`,section=sections.get(id);
|
|
if(section)yield [id,section];
|
|
}
|
|
};
|
|
for(const [id,section] of selected(this.#sections)) {
|
|
if(!section.count)continue;
|
|
const [sx,sy,sz]=id.split(',').map(v=>Number(v)*16), cells=section.cells;
|
|
if(bounds&&(sx>bounds.max[0]||sx+15<bounds.min[0]||sy>bounds.max[1]||sy+15<bounds.min[1]||sz>bounds.max[2]||sz+15<bounds.min[2]))continue;
|
|
for(let i=0;i<4096;i++){const value=cells[cells.length===1?0:i];if(value){
|
|
const x=sx+(i&15),y=sy+(i>>8),z=sz+((i>>4)&15);
|
|
if(!bounds||(x>=bounds.min[0]&&x<=bounds.max[0]&&y>=bounds.min[1]&&y<=bounds.max[1]&&z>=bounds.min[2]&&z<=bounds.max[2]))callback(x,y,z,value);
|
|
}}
|
|
}
|
|
}
|
|
*keysInSection(id) {
|
|
const section=this.#sections.get(id);if(!section?.count)return;
|
|
const [sx,sy,sz]=id.split(',').map(v=>Number(v)*16);
|
|
for(let i=0;i<4096;i++)if(section.cells[section.cells.length===1?0:i])yield `${sx+(i&15)},${sy+(i>>8)},${sz+((i>>4)&15)}`;
|
|
}
|
|
*entries() {for(const id of this.#sections.keys())for(const key of this.keysInSection(id))yield [key,this.get(key)];}
|
|
*keys() {for(const id of this.#sections.keys())yield* this.keysInSection(id);}
|
|
*values() {for(const section of this.#sections.values())if(section.count)for(let i=0;i<4096;i++){const value=section.cells[section.cells.length===1?0:i];if(value)yield value;}}
|
|
[Symbol.iterator]() {return this.entries();}
|
|
}
|