WebGL ray tracing: inspect the forward root
HTML for this example
Select the code and use your copy shortcut. Changes in this preview are temporary.
<!doctype html>
<html lang="en-US"><head><meta charset="utf-8"><meta name="viewport" content="width=device-width,initial-scale=1"><title>WebGL ray tracing: inspect the forward root</title><style>:root{--paper:#101b27;--ink:#e8f2ff;--accent:#8ec9f4;--surface:#172638;--border:#3c5064;--wash:#20374b;--muted:#b8cadb;--title:system-ui,sans-serif;--radius:7px;--buttonText:#102032}*{box-sizing:border-box}body{margin:0;background:var(--paper);color:var(--ink);font:17px/1.65 system-ui,sans-serif}main{max-width:980px;margin:auto;padding:24px 26px 48px}header{margin-bottom:20px;border-bottom:2px solid var(--accent);padding-bottom:16px}h1{font:700 clamp(28px,5vw,42px)/1.18 var(--title);margin:0 0 10px;letter-spacing:-.025em}h2{font-size:23px;line-height:1.35;margin:24px 0 12px}h3{font-size:19px}p{margin:0 0 16px}.eyebrow{font-size:16px;color:var(--accent);font-weight:700;margin:0 0 5px}.grid{display:grid;grid-template-columns:repeat(2,minmax(0,1fr));gap:22px}.panel{background:var(--surface);border:1px solid var(--border);border-radius:var(--radius);padding:22px;min-width:0;margin-bottom:20px}label{display:block;font-weight:650;margin-bottom:14px}input,textarea,select{display:block;font:inherit;line-height:1.5;width:100%;min-width:0;color:var(--ink);background:var(--surface);border:1px solid var(--accent);border-radius:5px;padding:9px 11px;margin-top:5px}textarea{resize:vertical}button{font:650 16px/1.5 system-ui;min-height:44px;padding:9px 16px;color:var(--buttonText);background:var(--accent);border:1px solid var(--accent);border-radius:5px;cursor:pointer;margin:4px 6px 4px 0}button[aria-pressed=true]{outline:3px solid var(--accent);outline-offset:2px}button:disabled{opacity:.55;cursor:default}:focus-visible{outline:3px solid #b86d10;outline-offset:3px}.result{border-left:4px solid var(--accent);background:var(--wash);padding:15px;margin:16px 0;white-space:pre-line;overflow-wrap:anywhere;font-weight:600}.result:empty{display:none}pre{font:16px/1.55 ui-monospace,Consolas,monospace;background:var(--wash);padding:16px;white-space:pre-wrap;overflow-wrap:anywhere;border:1px solid var(--border);border-radius:5px}code{overflow-wrap:anywhere}.note{font-size:16px;color:var(--muted)}.row{border-bottom:1px solid var(--border);padding:12px 0}.row strong{display:block}table{width:100%;border-collapse:collapse;font-size:16px;table-layout:fixed}th,td{padding:10px 8px;text-align:left;border-bottom:1px solid var(--border);overflow-wrap:anywhere}th{background:var(--wash)}a{color:var(--accent);overflow-wrap:anywhere}footer{border-top:1px solid var(--border);padding-top:16px;font-size:16px;color:var(--muted)}.tiles{display:grid;grid-template-columns:repeat(3,1fr);gap:8px;max-width:330px}.tile{padding:16px;text-align:center;background:var(--wash);border:1px solid var(--border);font-size:22px}svg,canvas{max-width:100%;height:auto}mark{background:#d5f525;color:#202600}ol,ul{padding-left:24px}li{margin-bottom:9px}[hidden]{display:none!important}@media(max-width:640px){main{padding:18px 16px 36px}.grid{grid-template-columns:1fr;gap:0}.panel{padding:17px}header{padding-bottom:12px;margin-bottom:16px}h1{font-size:29px}th,td{padding:8px 5px}h2{margin-top:18px}}</style></head><body><main><header><p class="eyebrow">Original worked example</p><h1>WebGL ray tracing: inspect the forward root</h1><p>Ray o=(0,0,0), d=(0,0,−1). Sphere F at z=−5,r=1. Keep only t>0, then choose the closest hit.</p></header><section class="panel"><button id="front" type="button">Front sphere</button><button id="near" type="button">Nearer sphere</button><button id="behind" type="button">Behind sphere</button><label for="offset">Ray origin x<input id="offset" type="number" value="0"></label><button id="calculate" type="button">Calculate roots</button><div class="result" id="result" role="status" aria-live="polite"></div><pre id="roots" tabindex="0"></pre><canvas id="gpu" width="240" height="80" aria-label="Optional GPU display swatch"></canvas><p class="note" id="gpuStatus"></p></section><section class="panel"><p>p(t)=o+td. This direction has unit length. Positive t is a forward parameter; choose the smallest accepted root across objects. Exact surface/tangent tolerances are outside this fixture.</p></section><footer>The analytic CPU inspector and optional GPU display are not a photorealistic renderer or verified GPU numeric readback. Changes are temporary. No data is sent or saved.</footer></main><script>
(() => {
'use strict';
const $=selector=>document.querySelector(selector);
const on=(selector,handler)=>$(selector).addEventListener('click',handler);
(function(){let mode='front';const canvas=$('#gpu'),gl=canvas.getContext('webgl2');let program=null;if(gl){try{const shader=(type,source)=>{const s=gl.createShader(type);gl.shaderSource(s,source);gl.compileShader(s);if(!gl.getShaderParameter(s,gl.COMPILE_STATUS))throw Error(gl.getShaderInfoLog(s));return s;};program=gl.createProgram();gl.attachShader(program,shader(gl.VERTEX_SHADER,'#version 300 es\nvoid main(){vec2 p=vec2((gl_VertexID<<1)&2,gl_VertexID&2);gl_Position=vec4(p*2.0-1.0,0,1);}'));gl.attachShader(program,shader(gl.FRAGMENT_SHADER,'#version 300 es\nprecision highp float;uniform float hit;out vec4 color;void main(){color=hit>0.0?vec4(0.2,0.75,0.9,1):vec4(0.18,0.22,0.28,1);}'));gl.linkProgram(program);if(!gl.getProgramParameter(program,gl.LINK_STATUS))throw Error(gl.getProgramInfoLog(program));$('#gpuStatus').textContent='WebGL2 display compiled. Swatch displays CPU hit/miss; numeric GPU intersection readback not implemented.';}catch(e){program=null;$('#gpuStatus').textContent='GPU display unavailable: '+e.message;}}else $('#gpuStatus').textContent='WebGL2 unavailable. Numeric CPU inspector remains usable.';function retireSwatch(){canvas.hidden=true;}function calculate(){const raw=$('#offset').value,x=Number(raw);if(!raw.trim()||!Number.isFinite(x)||Math.abs(x)>1000){$('#result').textContent='Enter a finite origin x between -1000 and 1000.';$('#roots').textContent='';retireSwatch();return;}const spheres=mode==='behind'?[['B',5,1]]:mode==='near'?[['F',-5,1],['N',-3,.5]]:[['F',-5,1]];let winner=null;const lines=[];for(const [id,z,r]of spheres){const b=2*z,c=x*x+z*z-r*r,disc=b*b-4*c;if(disc<0){lines.push(id+': discriminant '+disc+'; no real roots');continue;}const roots=[(-b-Math.sqrt(disc))/2,(-b+Math.sqrt(disc))/2],valid=roots.filter(t=>t>0);lines.push(id+': roots '+roots.join(', '));if(valid.length&&(!winner||Math.min(...valid)<winner.t))winner={id,t:Math.min(...valid)};}$('#roots').textContent=lines.join('\n');$('#result').textContent=winner?'Closest forward hit: '+winner.id+' at t='+winner.t+'.':'Forward miss: no accepted positive root.';canvas.hidden=!(gl&&program);if(gl&&program){gl.useProgram(program);gl.uniform1f(gl.getUniformLocation(program,'hit'),winner?winner.t:-1);gl.drawArrays(gl.TRIANGLES,0,3);}}on('#front',()=>{mode='front';calculate();});on('#near',()=>{mode='near';calculate();});on('#behind',()=>{mode='behind';calculate();});on('#calculate',calculate);$('#offset').addEventListener('input',()=>{$('#result').textContent='Origin changed. Calculate roots again.';$('#roots').textContent='';retireSwatch();});calculate();})();
})();
</script></body></html>