start . me .
Directory path . web , canvas .

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>