mirror of
https://github.com/bitluni/Supercluster2.git
synced 2026-03-28 07:20:16 +00:00
780 lines
17 KiB
HTML
780 lines
17 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="reset">Reset</button>
|
|
<button id="send">Render</button>
|
|
<canvas id="canvas" width="320" height="180" style="zoom: 2"></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 = null;
|
|
let reader = null;
|
|
let writer = null;
|
|
let readBuffer = [];
|
|
let writeQueue = [];
|
|
|
|
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();
|
|
});
|
|
|
|
document.getElementById("reset").addEventListener("click", async () => {
|
|
sendHostInstruction(BUS_HOST_RESET);
|
|
});
|
|
|
|
async function connectToPort(serialPort)
|
|
{
|
|
await serialPort.open({ baudRate: 115200 });
|
|
console.log("Serial port opened.");
|
|
|
|
// Setup reader
|
|
reader = serialPort.readable.getReader();
|
|
writer = serialPort.writable.getWriter();
|
|
readLoop();
|
|
|
|
port = serialPort;
|
|
}
|
|
|
|
const MAX_MCUS = 64;
|
|
const BUS_RAYMARCHER_INIT = 0x10;
|
|
const BUS_RAYMARCHER_RENDER_PIXEL = 0x11;
|
|
const BUS_RAYMARCHER_RENDER_PIXEL_RESULT = 0x12;
|
|
const BUS_RAYMARCHER_CAM_POS = 0x13;
|
|
|
|
const BUS_LED = 0xd0;
|
|
const BUS_PING = 0xd1;
|
|
|
|
const BUS_CLIENT_RESET = 0xe0; //reset client
|
|
const BUS_CLIENT_SET_INDEX = 0xe4;
|
|
const BUS_CLIENT_ERROR = 0xe5; //error in client
|
|
|
|
const BUS_HOST_RESET = 0xf0;
|
|
const BUS_HOST_FORWARD = 0xf1; //forward packet to client
|
|
const BUS_HOST_BROADCAST = 0xf2; //broadcast packet to flagged clients
|
|
const BUS_HOST_FETCH = 0xf3; //fetch data from client
|
|
const BUS_HOST_LINES_STATE = 0xf4;
|
|
const BUS_HOST_ERROR = 0xf5;
|
|
const BUS_HOST_SUCCESS = 0xf6;
|
|
|
|
/*
|
|
const mcuGrid = document.getElementById("mcuGrid");
|
|
const mcuBoxes = [];
|
|
for (let i = 0; i < MAX_MCUS; i++)
|
|
{
|
|
const div = document.createElement("div");
|
|
div.className = "mcu-box";
|
|
mcuGrid.appendChild(div);
|
|
mcuBoxes.push(div);
|
|
}*/
|
|
/*
|
|
async function connectSerial()
|
|
{
|
|
try {
|
|
port = await navigator.serial.requestPort();
|
|
await port.open({ baudRate: 115200 });
|
|
|
|
writer = port.writable.getWriter();
|
|
reader = port.readable.getReader();
|
|
|
|
document.getElementById("status").textContent = "Connected";
|
|
document.getElementById("status").style.color = "lightgreen";
|
|
|
|
readLoop();
|
|
} catch (err) {
|
|
console.error("Connection error:", err);
|
|
document.getElementById("status").textContent = "Disconnected";
|
|
document.getElementById("status").style.color = "red";
|
|
}
|
|
}
|
|
*/
|
|
async function readLoop()
|
|
{
|
|
let bytesSent = 0;
|
|
let avgBpsUp = 0;
|
|
let bytesReceived = 0;
|
|
let avgBpsDown = 0;
|
|
setInterval(() => {
|
|
avgBpsUp = avgBpsUp * 0.5 + (bytesSent * 8) * 0.5;
|
|
bytesSent = 0;
|
|
avgBpsDown = avgBpsDown * 0.5 + (bytesReceived * 8) * 0.5;
|
|
bytesReceived = 0;
|
|
console.log("bps tx: " + avgBpsUp.toFixed(0) + " rx: " + avgBpsDown.toFixed(0));
|
|
}, 1000);
|
|
try
|
|
{
|
|
while (true)
|
|
{
|
|
if(writeQueue.length == 0)
|
|
{
|
|
while(writeQueue.length == 0)
|
|
{
|
|
await new Promise(r => setTimeout(r, 1));
|
|
fetchData();
|
|
}
|
|
}
|
|
const packet = writeQueue.shift();
|
|
await writer.write(packet.payload);
|
|
bytesSent += packet.payload.length;
|
|
log("TX: " + [...packet.payload].map(b => b.toString(16).padStart(2, '0')).join(' '));
|
|
while(true)
|
|
{
|
|
const {value, done} = await reader.read();
|
|
if (done) break;
|
|
if (value)
|
|
{
|
|
bytesReceived += value.length;
|
|
readBuffer.push(...value);
|
|
if(processBufferedData(packet.callback)) break;
|
|
}
|
|
}
|
|
}
|
|
} catch (err) {
|
|
console.error('Read error:', err);
|
|
}
|
|
}
|
|
|
|
function processBufferedData(callback)
|
|
{
|
|
const length = readBuffer[0];
|
|
if(length == 0)
|
|
{
|
|
readBuffer.shift();
|
|
return true;
|
|
}
|
|
|
|
if (readBuffer.length >= length + 1)
|
|
{
|
|
const packet = readBuffer.slice(0, length + 1);
|
|
readBuffer = readBuffer.slice(length + 1);
|
|
handleHostResponse(new Uint8Array(packet), callback);
|
|
return true;
|
|
}
|
|
return false;
|
|
}
|
|
|
|
function log(bla)
|
|
{
|
|
//console.log(bla);
|
|
}
|
|
|
|
function handleHostResponse(packet, callback)
|
|
{
|
|
const hex = [...packet].map(b => b.toString(16).padStart(2, '0')).join(' ');
|
|
log("RX: " + hex);
|
|
|
|
const instruction = packet[1];
|
|
let error = false;
|
|
switch (instruction)
|
|
{
|
|
case BUS_HOST_FETCH:
|
|
{
|
|
const mcu = packet[2];
|
|
if(packet.length > 3)
|
|
handleClientResponse(mcu, packet.slice(3));
|
|
break;
|
|
}
|
|
case BUS_HOST_ERROR:
|
|
if(callback)
|
|
callback(false, packet);
|
|
return;
|
|
default:
|
|
/*console.log("Unknown host packet:");
|
|
console.log(...packet);*/
|
|
break;
|
|
}
|
|
if(callback) callback(true, packet);
|
|
}
|
|
|
|
function handleClientResponse(mcu, packet)
|
|
{
|
|
const instruction = packet[0];
|
|
switch (instruction)
|
|
{
|
|
case BUS_PING:
|
|
{
|
|
mcuBoxes[mcu].classList.remove("pending");
|
|
mcuBoxes[mcu].classList.add("active");
|
|
break;
|
|
}
|
|
case BUS_RAYMARCHER_RENDER_PIXEL_RESULT:
|
|
if(packet.length < 4) break;
|
|
receiveData(mcu, packet);
|
|
break;
|
|
default:
|
|
console.log("Unknown client packet:");
|
|
console.log(...packet);
|
|
break;
|
|
}
|
|
}
|
|
|
|
|
|
async function sendHostInstruction(instruction, data = [], callback = null)
|
|
{
|
|
if (!writer) return;
|
|
const length = 1 + data.length;
|
|
const payload = new Uint8Array([length, instruction, ...data]);
|
|
//await writer.write(payload);
|
|
writeQueue.push({payload: payload, callback: callback});
|
|
}
|
|
|
|
async function sendBroadcastInstruction(lines, instruction, data = [], callback = null)
|
|
{
|
|
const payload = new Uint8Array([lines & 0xff, (lines >> 8) & 0xff, instruction, ...data]);
|
|
sendHostInstruction(BUS_HOST_BROADCAST, payload, callback);
|
|
}
|
|
|
|
async function sendClientInstruction(mcu, instruction, data = [], callback = null)
|
|
{
|
|
const payload = new Uint8Array([mcu, instruction, ...data]);
|
|
sendHostInstruction(BUS_HOST_FORWARD, payload, callback);
|
|
}
|
|
|
|
function receiveData(mcu, data)
|
|
{
|
|
const view = new DataView(data.buffer);
|
|
const pixel = workers[mcu];
|
|
if(!pixel)
|
|
console.log("wrong mcu? " + mcu);
|
|
else
|
|
pixel.color = vec(view.getUint8(1), view.getUint8(2), view.getUint8(3));
|
|
workerDone(pixel);
|
|
}
|
|
|
|
async function sendData(pixel)
|
|
{
|
|
//12 + 12
|
|
pixel.time = Date.now();
|
|
pixel.lastFetch = Date.now();
|
|
const data = new ArrayBuffer(6);
|
|
const view = new DataView(data);
|
|
/*
|
|
view.setUint32(0, pixel.origin.v[0], true); // little-endian
|
|
view.setUint32(4, pixel.origin.v[1], true);
|
|
view.setUint32(8, pixel.origin.v[2], true);*/
|
|
view.setUint16(0, (pixel.dir.v[0] >> 1) & 0xffff, true);
|
|
view.setUint16(2, (pixel.dir.v[1] >> 1) & 0xffff, true);
|
|
view.setUint16(4, (pixel.dir.v[2] >> 1) & 0xffff, true);
|
|
sendClientInstruction(pixel.id, BUS_RAYMARCHER_RENDER_PIXEL, new Uint8Array(data));
|
|
}
|
|
|
|
async function fetchData()
|
|
{
|
|
for(let i = 0; i < MAX_MCUS; i++)
|
|
if(workers[i] && workers[i].retries < 2)
|
|
{
|
|
const t = Date.now();
|
|
if(t - workers[i].time > 2000)
|
|
{
|
|
workers[i].retries++;
|
|
//sendClientInstruction(i, BUS_CLIENT_RESET);
|
|
sendData(workers[i]);
|
|
}
|
|
else
|
|
if(t - workers[i].lastFetch > 10)
|
|
{
|
|
workers[i].lastFetch = t;
|
|
sendHostInstruction(BUS_HOST_FETCH, [i]);
|
|
}
|
|
}
|
|
}
|
|
|
|
|
|
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)
|
|
{
|
|
ctx.putImageData(imageData, 0, 0);
|
|
return null;
|
|
}
|
|
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().clamp(-65536, 65535);
|
|
|
|
const pixel = {
|
|
scene: rendererScene,
|
|
origin: ro,
|
|
dir: rd,
|
|
x: renderx,
|
|
y: rendery,
|
|
depth: 2,
|
|
retries: 0
|
|
}
|
|
|
|
renderx++;
|
|
if(renderx == xres)
|
|
{
|
|
renderx = 0;
|
|
rendery++;
|
|
ctx.putImageData(imageData, 0, 0);
|
|
}
|
|
|
|
return pixel;
|
|
}
|
|
|
|
let 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;
|
|
workers[id] = null;
|
|
//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;
|
|
workers = new Array(MAX_MCUS);
|
|
/*pos = new Uint8Array(6);
|
|
pos[0] = scene.cameraPos.v[0] >> 8;
|
|
pos[1] = scene.cameraPos.v[0] >> 16;
|
|
pos[2] = scene.cameraPos.v[1] >> 8;
|
|
pos[3] = scene.cameraPos.v[1] >> 16;
|
|
pos[4] = scene.cameraPos.v[2] >> 8;
|
|
pos[5] = scene.cameraPos.v[2] >> 16;
|
|
|
|
for(let i = 0; i < MAX_MCUS; i++)
|
|
sendClientInstruction(i, BUS_RAYMARCHER_CAM_POS, pos);*/
|
|
for(let i = 0; i < MAX_MCUS; i++)
|
|
{
|
|
pixel = nextPixel();
|
|
if(pixel)
|
|
{
|
|
pixel.id = i;
|
|
workers[i] = 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>
|