Voxel textures
Debug textures to make sure things are working at all...
And finally working...
Debug textures to make sure things are working at all...
And finally working...
Dug a tunnel into the side of the mountain...
The engine now supports brushes for adding, removing, and painting multiple voxels at once.
Also added a micro heads up display at the bottom so toggling between voxel types and brushes is not fully an exercise in memorization.
There are still a few bugs and, now with larger persisted worlds, the frames-per-second (fps) is quickly dropping out of the ~60 range. Nonetheless, the engine is feeling more capable.
Working a bit on terrain generation. Turns out it can be a lot more fun working on this in native desktop with Rust rather than WebGL given the runtime limitations of the latter.
Still working on voxel rendering in Bevy. Restarted from fresh as I've learned a bit more since last time...
Been working on a lot of different experiments lately. Merging my recent interest in learning Bevy, Rust, and WASM with my long-time interest in voxel rendering, I've been working on an experimental program to convert a Quake 2 BSP38 map file into voxelized representation.
The above is slow and inefficient, but it's an interesting starting point as it's all running in WASM using Bevy.
Added an experiment of rendering with Bevy using WASM. It's nothing too exciting as I'm new to Bevy, WASM, and still relatively inexperienced with Rust!
A few notes on the development:
I wanted to host this experiment (and future ones) within the context of pages within the Docusaurus app. I certainly do not know the "best" way to do this yet, but one definite constraint to pass the canvas id
to the WASM module at startup rather than hard-coding it in the WASM and browser.
As far as I can tell, the WASM init
function does not take arguments, therefore the startup is exposed via a separate wasm_bindgen
exported function called start
.
In the Rust snippet below, you can see we do nothing in main
and explicitly pass in an id
to the WindowPlugin
in a separate start
function.
fn main() {
// The run() entrypoint does the work as it can be called
// from the browser with parameters.
}
#[wasm_bindgen]
pub fn start(canvas_id: &str) {
let id = format!("#{}", canvas_id);
App::new()
.add_plugins(DefaultPlugins.set(WindowPlugin {
primary_window: Some(Window {
canvas: Some(id.into()),
..default()
}),
..default()
}))
.add_systems(Startup, setup)
.add_systems(
Update,
(
move_ball, //
update_transforms,
),
)
.run();
}
The JavaScript code to bootstrap this looks like this:
const go = async () => {
let mod = await import(moduleName);
await mod.default();
await mod.start(canvasID);
};
go();
But admittedly the above is not the actual JavaScript code for hosting the WASM module...
This is workaround code. It "works" but I'm sure there is a correct way to handle this that I was not able to discover!
There's something I don't understand about WASM module loading and, more importantly, reloading/reuse. This is problematic in the context of a Single Page Application (SPA) like Docusaurus where if you navigate to page ABC, then to page XYZ, then back to ABC, any initialization that happened on the first visit to page ABC will happen again on the second visit. In other words, I'm not sure how to make the WASM initalization idempotent.
If there's a correct way to...
...I'd enjoy learning how!
Docusaurus also has logic for renaming, bundling, rewriting, etc. JavaScript code used on the pages. I'm not sure what the exact logic of what it does, but end of the day, I did not want Docusaurus manipulating the JS generated by the WASM build process.
Admittedly this is a bit of laziness on my part for not really understanding what Docusaurus does and how best to circumvent it.
I worked around the reload problems and script mangling with a custom MDX Component in Docusaurus that:
<script>
element so Docusaurus can't modify what it doesexport function CanvasWASM({
id,
module,
width,
height,
style,
}: {
id: string,
module: string,
width: number,
height: number,
style?: React.CSSProperties,
}) {
React.useEffect(() => {
// We create a a DOM element since Docusaurus ends up renaming /
// changing the JS file to load the WASM which breaks the import
// in production. This is pretty hacky but it works (for now).
const script = document.createElement('script');
script.type = 'module';
script.text = `
let key = 'wasm-retry-count-${module}-${id}';
let success = setTimeout(function() {
localStorage.setItem(key, "0");
}, 3500);
const go = async () => {
try {
let mod = await import('${module}');
await mod.default();
await mod.start('${id}');
localStorage.setItem(key, "0");
} catch (e) {
if (e.message == "unreachable") {
clearTimeout(success);
let value = parseInt(localStorage.getItem(key) || "0", 10);
if (value < 10) {
console.log("WASM error, retry attempt: ", value);
setTimeout(function() {
localStorage.setItem(key, (value + 1).toString());
window.location.reload();
}, 20 + 100 * value);
} else {
throw e;
}
}
}
};
go();
`.trim();
document.body.appendChild(script);
}, []);
return <canvas id={id} width={width} height={height} style={style}></canvas>;
}
In case anyone on the internet runs into this and stumbles upon this page...
I also wasted quite a bit of time on this problem:
.gitattributes
file in all my repos to store generated files using git LFS*.wasm
to store those in LFSThis meant my code was working locally but when I was trying to load the WASM files on the published site, the LFS "pointer" text file was being served rather than the binary WASM file itself. It took me a while to figure this out. Ultimately the fix was to remove the .gitattributes
from the GitHub Pages repo so LFS is not used on the published site. (Aside: this might be a good reason to consider hosting this site via another platform, but I'll leave that for another day!)