Bitluni-Supercluster2/web/raymarcherMCU.html
2025-05-16 21:08:27 +02:00

579 lines
12 KiB
HTML

<!DOCTYPE html>
<html>
<head>
<meta charset="UTF-8" />
<title>MCU Raymarcher</title>
<style>canvas {display: block; }</style>
</head>
<body>
<button id="connect">Connect Serial</button>
<button id="send">Render</button>
<canvas id="canvas" width="320" height="180"></canvas>
<script>
///////////////////////////// WebSerial //////////////////////////////
const TARGET_VENDOR_ID = 0x1A86; // Replace with your device's USB Vendor ID (e.g., Arduino = 0x2341)
const TARGET_PRODUCT_ID = 0xFFFF; // Optional: use if you want to filter by USB Product ID
let port;
let writer;
let reader;
document.getElementById("connect").addEventListener("click", async () => {
try {
port = await navigator.serial.requestPort({
filters: [{ usbVendorId: TARGET_VENDOR_ID, usbProductId:TARGET_PRODUCT_ID }]
});
await connectToPort(port);
} catch (err) {
console.error("Manual connection failed:", err);
}
});
document.getElementById("send").addEventListener("click", async () => {
if (!port || !port.writable) {
console.warn("Serial port not connected.");
return;
}
start();
});
async function connectToPort(serialPort) {
await serialPort.open({ baudRate: 115200 });
console.log("Serial port opened.");
// Setup reader
reader = serialPort.readable.getReader();
readLoop();
port = serialPort;
}
let receiveBuffer = new Uint8Array(0); // growable buffer
async function readLoop()
{
try {
while (true) {
const { value, done } = await reader.read();
if (done) break;
if (value) bufferIncomingData(value);
}
} catch (error) {
console.error("Read error:", error);
} finally {
reader.releaseLock();
}
}
function bufferIncomingData(newData) {
// Concatenate existing buffer with new incoming data
const combined = new Uint8Array(receiveBuffer.length + newData.length);
combined.set(receiveBuffer);
combined.set(newData, receiveBuffer.length);
receiveBuffer = combined;
const PACKET_SIZE = 12;
while (receiveBuffer.length >= PACKET_SIZE) {
// Extract a full message
const packet = receiveBuffer.slice(0, PACKET_SIZE);
receiveData(packet);
// Remove processed bytes from buffer
receiveBuffer = receiveBuffer.slice(PACKET_SIZE);
}
}
function receiveData(data) {
const view = new DataView(data.buffer);
//const myByte = view.getUint8(0);
const myDword = view.getUint32(0, true);
const pixel = workers[0];
pixel.color = veci(view.getUint32(0, true), view.getUint32(4, true), view.getUint32(8, true));
workerDone(pixel);
}
let lockCount = 0;
async function sendData(pixel)
{
//12 + 12
const buffer = new ArrayBuffer(25);
const view = new DataView(buffer);
view.setUint8(0, 0x69);
view.setUint32(1, pixel.origin.v[0], true); // little-endian
view.setUint32(5, pixel.origin.v[1], true);
view.setUint32(9, pixel.origin.v[2], true);
view.setUint32(13, pixel.dir.v[0], true);
view.setUint32(17, pixel.dir.v[1], true);
view.setUint32(21, pixel.dir.v[2], true);
setTimeout(() =>
{
lockCount++;
const writer = port.writable.getWriter();
writer.write(buffer);
writer.releaseLock();
lockCount--;
}, 0);
}
window.addEventListener("DOMContentLoaded", async () => {
const ports = await navigator.serial.getPorts();
const matchingPorts = ports.filter(p => {
const info = p.getInfo();
return info.usbVendorId === TARGET_VENDOR_ID &&
(!TARGET_PRODUCT_ID || info.usbProductId === TARGET_PRODUCT_ID);
});
if (matchingPorts.length === 1) {
console.log("Auto-connecting to authorized device...");
await connectToPort(matchingPorts[0]);
} else {
console.log("Manual connection required.");
}
});
//////////////////////////// RENDERER ////////////////////////////////
const canvas = document.getElementById("canvas");
const ctx = canvas.getContext("2d");
const imageData = ctx.createImageData(canvas.width, canvas.height);
const data = imageData.data;
const E = fix(0.01);
const E2 = fix(0.02);
const FAR = fix(100);
function start()
{
render(new Scene());
}
class Scene
{
constructor()
{
this.objects = [];
this.cameraPos = vec(0.3, 1.0, -6.0);
this.lightPos = vec(0, 5, -2);
this.skyMat = new Sky(vec(0.4, 0.4, 0.7), vec(0.8, 0.8, 1.0));
this.fog = fix(0.05);
this.objects.push(new Sphere(vec(0.5, 0.7, 1.0), fix(1.5), new Material(vec(1, 0.5, 0.7), fix(0.2), fix(0.8), fix(0.8), fix(0.2))));
this.objects.push(new Box(vec(2.5, 0, -1.5), vec(1.0, 1.0, 1.0), new Material(vec(0.4, 0.4, 1.0), fix(0.2), fix(0.8), fix(0.2), fix(0.2))));
this.objects.push(new Cylinder(vec(-3.0, 1.0, -1.0), vec(1.0, 2.0, 0), new Material(vec(0.3, 0.7, 0.3), fix(0.2), fix(0.8), fix(0.2), fix(0.2))));
this.objects.push(new PlaneY(vec(0, -1.0, 0), new Checker(vec(0.6, 0.1, 0.1), vec(1, 1, 1), fix(0.2), fix(0.8), fix(0.1), fix(0.2))));
}
}
////renderer stuff
let xres = 0;
let yres = 0;
let renderx = 0;
let rendery = 0;
let rendererScene = null;
function nextPixel()
{
if(rendery == yres) return;
let u = -(renderx - xres / 2) / xres * 1.6 * (xres / yres);
let v = -(rendery - yres / 2) / yres * 1.6;
let ro = rendererScene.cameraPos;
let rd = vec(-u, v, 1).normalize();
renderx++;
if(renderx == xres)
{
renderx = 0;
rendery++;
ctx.putImageData(imageData, 0, 0);
}
const pixel = {
scene: rendererScene,
origin: ro,
dir: rd,
x: renderx,
y: rendery,
depth: 2
}
return pixel;
}
const WORKER_COUNT = 1; //160;
const workers = [];
async function workerDone(pixel)
{
let color = pixel.color;
color = color.scale(fix(255))
color = color.clamp(0, fix(255));
let i = (pixel.y * xres + pixel.x) * 4;
data[i + 0] = color.v[0] >> 16;
data[i + 1] = color.v[1] >> 16;
data[i + 2] = color.v[2] >> 16;
data[i + 3] = 255;
const id = pixel.id;
//do next pixel
pixel = nextPixel();
if(pixel)
{
pixel.id = id;
workers[id] = pixel;
await sendData(pixel);
}
}
function render(scene) {
rendererScene = scene;
xres = canvas.width;
yres = canvas.height;
renderx = 0;
rendery = 0;
for(let i = 0; i < WORKER_COUNT; i++)
{
pixel = nextPixel();
if(pixel)
{
pixel.id = i;
workers.push(pixel);
sendData(pixel);
}
}
}
function __lzcnt(a)
{
let r = 32;
if (a >= 0x00010000) { a >>= 16; r -= 16; }
if (a >= 0x00000100) { a >>= 8; r -= 8; }
if (a >= 0x00000010) { a >>= 4; r -= 4; }
if (a >= 0x00000004) { a >>= 2; r -= 2; }
r -= a - (a & (a >> 1));
return r;
}
function fix2binFloat(a)
{
let i = __lzcnt(a & 0x7fffffff);
let sign = a & 0x80000000;
let exp = (15 - i) + 127;
let mantissa = ((a << i) >> 8) & 0x7fffff;
return sign | (exp << 23) | mantissa;
}
function binFloat2fix(a)
{
if(!a) return 0;
let sign = a & 0x80000000;
let exp = ((a >> 23) & 0xff) - 127;
let mantissa = 0x800000 | (a & 0x7fffff);
return sign | ((mantissa << 7) >> (14 - exp));
}
function b_rsqrt(number)
{
let i;
let x2;
let y;
const threehalfs = fix(1.5);
x2 = number >> 1;
y = fix2binFloat(number);
i = y; // evil floating point bit level hacking
i = 0x5f3759df - (i >> 1); // what the fuck?
y = binFloat2fix(i);
y = muli(y, threehalfs - (muli(x2, muli(y, y)))); // 1st iteration
y = muli(y, threehalfs - (muli(x2, muli(y, y)))); // 2nd iteration, this can be removed
return y;
}
function rsqrti(a)
{
return b_rsqrt(a);
}
function sqrti(a)
{
return muli(a, rsqrti(a));
}
function addi(a, b)
{
return (a + b) & 0xffffffff;
}
function subi(a, b)
{
return addi(a, -b);
}
class Vec3
{
constructor(x, y, z)
{
this.v = new Int32Array([x, y, z]);
}
length(){
let d2 = this.dot(this);
if(d2 > 0x7fffffff) return 0x7fffffff;
return sqrti(d2);
}
normalize(){
let l2 = this.dot(this);
return this.scale(rsqrti(l2));
}
sub(v2){
return new Vec3(this.v[0] - v2.v[0], this.v[1] - v2.v[1], this.v[2] - v2.v[2]);
}
add(v2){
return new Vec3(this.v[0] + v2.v[0], this.v[1] + v2.v[1], this.v[2] + v2.v[2]);
}
scale(s){
return new Vec3(muli(this.v[0], s), muli(this.v[1], s), muli(this.v[2], s));
}
dot(v2){
return muli(this.v[0], v2.v[0]) + muli(this.v[1], v2.v[1]) + muli(this.v[2], v2.v[2]);
}
reflect(n){
return this.sub(n.scale(2 * this.dot(n)));
}
clamp(a, b){
return new Vec3(Math.max(a, Math.min(b, this.v[0])), Math.max(a, Math.min(b, this.v[1])), Math.max(a, Math.min(b, this.v[2])));
}
abs(){
return new Vec3(Math.abs(this.v[0]), Math.abs(this.v[1]), Math.abs(this.v[2]));
}
mix(s, v){
if(s > 0x10000) s = 0x10000;
return v.scale(s).add(this.scale(65536 - s));
}
real()
{
return [this.v[0] / 65536, this.v[1] / 65536, this.v[2] / 65536];
}
toString()
{
let r = this.real();
return r[0].toFixed(4) + " " + r[1].toFixed(4) + " " + r[2].toFixed(4);
}
};
function veci(x, y, z)
{
return new Vec3(x, y, z);
}
function vec(x, y, z)
{
return new Vec3(fix(x), fix(y), fix(z));
}
function fix(f)
{
return Math.round(f * 65536);
}
function unfix(i)
{
return i / 65536;
}
function muli(a, b)
{
// return (a >> 8) * (b >> 8);
let r = Math.floor((a / 256) * (b / 256));
if(r > 0x7fffffff) return 0x7fffffff;
if(r < -0x80000000) return 0x80000000;
return r;
}
function divi(a, b)
{
return (a << 16) / b;
}
function floori(a)
{
return a & 0xffff0000
}
function powi16(a)
{
let a2 = muli(a, a);
let a4 = muli(a2, a2);
return muli(a4, a4);
}
class RenderObject
{
constructor(pos, mat)
{
this.pos = pos;
this.mat = mat;
}
sdf(p)
{
return fix(32767);
}
}
class Collision
{
constructor(d, p, n, obj)
{
this.d = d;
this.p = p;
this.n = n;
this.obj = obj;
}
}
class Sphere extends RenderObject
{
constructor(pos, r, mat)
{
super(pos, mat);
this.r = r;
}
sdf(p)
{
return p.sub(this.pos).length() - this.r;
}
}
class Box extends RenderObject
{
constructor(pos, dim, mat)
{
super(pos, mat);
this.dim = dim;
}
sdf(p)
{
let d = p.sub(this.pos).abs().sub(this.dim);
let dist = Math.max(...d.v);
return dist;
}
}
class Cylinder extends RenderObject
{
constructor(pos, dim, mat)
{
super(pos, mat);
this.dim = dim;
}
sdf(p)
{
let rel = p.sub(this.pos);
let rel2 = veci(rel.v[0], 0, rel.v[2]);
let dxz = rel2.length() - this.dim.v[0];
let dy = Math.max(rel.v[1] - this.dim.v[1], -rel.v[1] - this.dim.v[1]);
return Math.max(dxz, dy);
}
}
class PlaneX extends RenderObject
{
constructor(pos, mat)
{
super(pos, mat);
}
sdf(p)
{
return p.v[0] - this.pos.v[0];
}
}
class PlaneY extends RenderObject
{
constructor(pos, mat)
{
super(pos, mat);
}
sdf(p)
{
return p.v[1] - this.pos.v[1];
}
}
class PlaneZ extends RenderObject
{
constructor(pos, mat)
{
super(pos, mat);
}
sdf(p)
{
return p.v[2] - this.pos.v[2];
}
}
class Material
{
constructor(color, ambient, diffuse, specular, reflection)
{
this.color = color;
this.ambient = ambient;
this.diffuse = diffuse;
this.specular = specular;
this.reflection = reflection;
}
shade(p, n)
{
return this.color;
}
}
class Checker extends Material
{
constructor(color1, color2, ambient, diffuse, specular, reflection)
{
super(color1, ambient, diffuse, specular, reflection);
this.color2 = color2;
}
shade(p, n)
{
let check = ((p.v[0] >> 16) + (p.v[2] >> 16)) & 1;
let color = check ? this.color : this.color2; // white/red
return color;
}
}
class Sky extends Material
{
constructor(color1, color2)
{
super(color1);
this.color2 = color2;
}
shade(p, n)
{
return this.color.add(this.color2.sub(this.color).scale(n.v[1]));
}
}
</script>
</body>
</html>