start .
me .
HTML Document File : reflective-raytracer.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8">
<meta name="viewport" content="width=device-width, initial-scale=1">
<title>ray-ai</title>
<style>
canvas {
border: 1px solid #333;
background: #000;
}
.controls {
margin-top: 10px;
}
</style>
</head>
<body>
<canvas id="rayCanvas" width="400" height="400"></canvas>
<div class="controls">
<button onclick="toggleAnimation()">Toggle Animation</button>
</div>
<script>
const canvas = document.getElementById('rayCanvas');
const ctx = canvas.getContext('2d');
const width = canvas.width;
const height = canvas.height;
const imageData = ctx.createImageData(width, height);
let animationId = null;
let time = 0;
class Vec3 {
constructor(x, y, z) {
this.x = x;
this.y = y;
this.z = z;
}
add(v) {
return new Vec3(this.x + v.x, this.y + v.y, this.z + v.z);
}
sub(v) {
return new Vec3(this.x - v.x, this.y - v.y, this.z - v.z);
}
mul(s) {
return new Vec3(this.x * s, this.y * s, this.z * s);
}
dot(v) {
return this.x * v.x + this.y * v.y + this.z * v.z;
}
normalize() {
const len = Math.sqrt(this.dot(this));
return this.mul(1 / len);
}
reflect(normal) {
return this.sub(normal.mul(2 * this.dot(normal)));
}
}
class Ray {
constructor(origin, direction) {
this.origin = origin;
this.direction = direction;
}
}
class Sphere {
constructor(center, radius, reflectivity) {
this.center = center;
this.radius = radius;
this.reflectivity = reflectivity;
}
intersect(ray) {
const oc = ray.origin.sub(this.center);
const a = ray.direction.dot(ray.direction);
const b = 2 * oc.dot(ray.direction);
const c = oc.dot(oc) - this.radius * this.radius;
const discriminant = b * b - 4 * a * c;
if (discriminant < 0) return null;
const t = (-b - Math.sqrt(discriminant)) / (2 * a);
if (t < 0.001) return null;
const point = ray.origin.add(ray.direction.mul(t));
const normal = point.sub(this.center).normalize();
return { point, normal, t };
}
}
class Cylinder {
constructor(baseCenter, radius, height, reflectivity) {
this.baseCenter = baseCenter; // Bottom center of cylinder
this.radius = radius;
this.height = height;
this.reflectivity = reflectivity;
}
intersect(ray) {
// Vector from base center to ray origin
const oc = ray.origin.sub(this.baseCenter);
// Quadratic equation coefficients for infinite cylinder
const a = ray.direction.x * ray.direction.x + ray.direction.z * ray.direction.z;
const b = 2 * (oc.x * ray.direction.x + oc.z * ray.direction.z);
const c = oc.x * oc.x + oc.z * oc.z - this.radius * this.radius;
const discriminant = b * b - 4 * a * c;
if (discriminant < 0) return null;
// Find intersection points with infinite cylinder
let t1 = (-b - Math.sqrt(discriminant)) / (2 * a);
let t2 = (-b + Math.sqrt(discriminant)) / (2 * a);
// Sort intersections
if (t1 > t2) [t1, t2] = [t2, t1];
// Check height constraints
let t = t1;
let point = ray.origin.add(ray.direction.mul(t));
let y = point.y - this.baseCenter.y;
if (y < 0 || y > this.height) {
t = t2;
point = ray.origin.add(ray.direction.mul(t));
y = point.y - this.baseCenter.y;
if (y < 0 || y > this.height) return null;
}
if (t < 0.001) return null;
// Calculate normal at intersection point
const normal = new Vec3(
point.x - this.baseCenter.x,
0,
point.z - this.baseCenter.z
).normalize();
return { point, normal, t };
}
}
class Floor {
constructor(y, reflectivity) {
this.y = y;
this.reflectivity = reflectivity;
}
intersect(ray) {
if (Math.abs(ray.direction.y) < 0.001) return null;
const t = (this.y - ray.origin.y) / ray.direction.y;
if (t < 0.001) return null;
const point = ray.origin.add(ray.direction.mul(t));
const normal = new Vec3(0, 1, 0);
return { point, normal, t };
}
getColor(point) {
const x = Math.floor(point.x * 2);
const z = Math.floor(point.z * 2);
return (x + z) % 2 === 0 ? 0.9 : 0.5;
}
}
const camera = {
position: new Vec3(0, 2, -5),
target: new Vec3(0, 0, 0),
up: new Vec3(0, 1, 0),
fov: 60
};
const scene = {
sphere: new Sphere(new Vec3(0, 1, 0), 1, 0.8),
cylinder: new Cylinder(new Vec3(-2, -0.5, 0), 0.5, 2, 0.3), // Add this line
floor: new Floor(-0.5, 0.5),
light: new Vec3(5, 5, -5)
};
function trace(ray, depth = 0) {
if (depth > 4) return new Vec3(0, 0, 0);
let hit = null;
let object = null;
// Check sphere intersection
const sphereHit = scene.sphere.intersect(ray);
if (sphereHit) {
hit = sphereHit;
object = scene.sphere;
}
// Check cylinder intersection
const cylinderHit = scene.cylinder.intersect(ray);
if (cylinderHit && (!hit || cylinderHit.t < hit.t)) {
hit = cylinderHit;
object = scene.cylinder;
}
// Check floor intersection
const floorHit = scene.floor.intersect(ray);
if (floorHit && (!hit || floorHit.t < hit.t)) {
hit = floorHit;
object = scene.floor;
}
if (!hit) return new Vec3(0.1, 0.1, 0.2); // Sky color
// Calculate lighting
const lightDir = scene.light.sub(hit.point).normalize();
const shadowRay = new Ray(hit.point, lightDir);
const inShadow = scene.sphere.intersect(shadowRay) !== null ||
scene.cylinder.intersect(shadowRay) !== null;
let color;
if (object === scene.floor) {
const checkerColor = object.getColor(hit.point);
color = new Vec3(checkerColor, checkerColor, checkerColor);
} else if (object === scene.cylinder) {
color = new Vec3(0.7, 0.3, 0.3); // Reddish color for cylinder
} else {
color = new Vec3(0.8, 0.8, 0.8);
}
// Calculate reflection
const reflectedRay = new Ray(
hit.point,
ray.direction.reflect(hit.normal)
);
const reflectedColor = trace(reflectedRay, depth + 1);
const diffuse = Math.max(0, hit.normal.dot(lightDir));
const ambient = 0.1;
const lighting = inShadow ? ambient : ambient + diffuse * 0.9;
return color.mul(lighting).add(reflectedColor.mul(object.reflectivity));
}
function render() {
// Animate sphere position
scene.sphere.center.x = Math.sin(time * 0.5) * 1.5;
scene.sphere.center.z = Math.cos(time * 0.5) * 1.5;
const aspect = width / height;
const fovRad = (camera.fov * Math.PI) / 180;
const halfWidth = Math.tan(fovRad / 2);
for (let y = 0; y < height; y++) {
for (let x = 0; x < width; x++) {
const px = (2 * (x + 0.5) / width - 1) * halfWidth * aspect;
const py = (1 - 2 * (y + 0.5) / height) * halfWidth;
const rayDir = new Vec3(px, py, 1).normalize();
const ray = new Ray(camera.position, rayDir);
const color = trace(ray);
const idx = (y * width + x) * 4;
imageData.data[idx] = Math.min(255, color.x * 255);
imageData.data[idx + 1] = Math.min(255, color.y * 255);
imageData.data[idx + 2] = Math.min(255, color.z * 255);
imageData.data[idx + 3] = 255;
}
}
ctx.putImageData(imageData, 0, 0);
time += 0.03;
}
function animate() {
render();
animationId = requestAnimationFrame(animate);
}
function toggleAnimation() {
if (animationId) {
cancelAnimationFrame(animationId);
animationId = null;
} else {
animate();
}
}
// Start animation
animate();
</script>
<hr>
<a href=".">go to current directory contents</a>
<hr>
<script src="https://arkenidar.com/web/show-source.js"></script>
</body>
</html>