Parse

File Parse mirror.js

This tree is parsed live from the source file.

Classes

  • {{ item.name }}

    • {{ key }}

Not Classes

{{ getTree() }}

Comments

{{ getTreeComments() }}

Source

            /*
title: Mirror
---

Reflect points with a `Mirror`.

    const mirror = new Mirror(points)
    mirror.step()
*/


function reflectPoint(origin, line) {
    /* Given a point `origin`, and the two points representing a `line`,
    return an object plotting the `origin` reflected through line.

    The result can be directly cast as a point to draw:

        let p = reflectPoint(
            {x: 10, y: 20}
            , [{x: 30, y: 50} , {x: 100, y: 100}]
        )

        let point = new Point(p)
    */
    const [p1, p2] = line;
    const x1 = p1.x, y1 = p1.y;
    const x2 = p2.x, y2 = p2.y;
    const x0 = origin.x, y0 = origin.y;

    // Calculate slope (m) and y-intercept (c) of the mirror line
    const m = (y2 - y1) / (x2 - x1);
    const c = y1 - m * x1;

    // Perpendicular slope (negative reciprocal)
    const m_p = -1 / m;

    // Equation of the perpendicular line through the origin point
    const c_p = y0 - m_p * x0;

    // Solve the intersection of the mirror line and the perpendicular line
    const x_intersect = (c_p - c) / (m - m_p);
    const y_intersect = m * x_intersect + c;

    // Reflect the origin point
    const x_prime = 2 * x_intersect - x0;
    const y_prime = 2 * y_intersect - y0;

    // Calculate the reflected angle
    const reflectedRadians = Math.atan2(y2 - y1, x2 - x1) * 2 - origin.radians;

    return { x: x_prime
            , y: y_prime
            , radians: reflectedRadians
            , radius: origin.radius
        };
}


function intersectionPoint(origin, line) {
    const [p1, p2] = line;
    const x1 = p1.x, y1 = p1.y;
    const x2 = p2.x, y2 = p2.y;
    const x0 = origin.x, y0 = origin.y;

    // Calculate slope (m) and y-intercept (c) of the mirror line
    const m = (y2 - y1) / (x2 - x1);
    const c = y1 - m * x1;

    // Perpendicular slope (negative reciprocal)
    const m_p = -1 / m;

    // Equation of the perpendicular line through the origin point
    const c_p = y0 - m_p * x0;

    // Solve the intersection of the mirror line and the perpendicular line
    const x_intersect = (c_p - c) / (m - m_p);
    const y_intersect = m * x_intersect + c;

    // Reflect the origin point but keep it on the same side
    const x_prime = x0 + (x_intersect - x0);
    const y_prime = y0 + (y_intersect - y0);

    // Calculate the reflected angle, ensuring it stays on the same side
    const incidentAngle = Math.atan2(y0 - y_intersect, x0 - x_intersect);
    const mirrorAngle = Math.atan2(y2 - y1, x2 - x1);
    const reflectedRadians = 2 * mirrorAngle - incidentAngle;

    return {
        x: x_prime,
        y: y_prime,
        radians: reflectedRadians,
        radius: origin.radius
    };
}


function reflectPointOnSameSide(origin, line) {
    const [p1, p2] = line;
    const x1 = p1.x, y1 = p1.y;
    const x2 = p2.x, y2 = p2.y;
    const x0 = origin.x, y0 = origin.y;

    // Step 1: Calculate the direction of the mirror line
    const dx = x2 - x1;
    const dy = y2 - y1;

    // Step 2: Normalize the direction vector
    const lineLength = Math.sqrt(dx * dx + dy * dy);
    const ux = dx / lineLength;
    const uy = dy / lineLength;

    // Step 3: Find the perpendicular projection of the point onto the mirror line
    const projection = ((x0 - x1) * ux + (y0 - y1) * uy);
    const x_proj = x1 + projection * ux;
    const y_proj = y1 + projection * uy;

    // Step 4: Calculate the reflected point
     x_reflect = x_proj + (x_proj - x0);
     y_reflect = y_proj + (y_proj - y0);

    // Step 5: Adjust the reflection to ensure it stays on the same side
    const originalToProjection = (x_proj - x0) * dx + (y_proj - y0) * dy;
    const reflectedToProjection = (x_reflect - x_proj) * dx + (y_reflect - y_proj) * dy;

    // if (originalToProjection * reflectedToProjection < 0) {
        // If the reflected point is on the opposite side, correct it
        x_reflect = x_proj - (x_proj - x0);
        y_reflect = y_proj - (y_proj - y0);
    // }

    // Step 6: Calculate the angle of incidence and reflection
    const incidentAngle = Math.atan2(y0 - y_proj, x0 - x_proj);
    const mirrorAngle = Math.atan2(dy, dx);
    const reflectedRadians = 2 * mirrorAngle - incidentAngle;

    return {
        x: x_reflect,
        y: y_reflect,
        radians: reflectedRadians,
        radius: origin.radius
    };
}


class Mirror {
    /* Accept two points as the primary line,
    _add_ many points to 'reflect'
    _collect_ the reflected points
    _ and a free draw function _
    */

   renderLine = true
   renderPoints = true
   renderReflections = true

   constructor(points=undefined) {
        this.points = points == undefined? new PointList: points;
        this.reflectPoints = new PointList
        this.mirrorPointsMap = new Map;
   }

   step() {
        /* iterate all the reflectPoints, and update the mirror point positions.
        We avoid rewriting the points, so they're more persistent. */
        let lines = this.points.siblings();

        for (var i = 0; i < lines.length; i++) {
            let line = lines[i]
            for (var j = 0; j < this.reflectPoints.length; j++) {
                let p = this.reflectPoints[j];
                let res = this.reflectSegment(p, line)
                let mirrorP = this.getMirrorIndex(i+j)
                mirrorP.update(res)
                // new Point(reflectPointOnSameSide(p, line))
            }
        }
    }

    reflect(point, line) {
        if(line == undefined || line.siblings != undefined) {
            return this.reflectSiblings(point)
        }

        return this.reflectSegment(point)
    }

    reflectSiblings(point, line=this.points) {
        let res = new PointList
        let lines = line.siblings();
        for (var i = 0; i < lines.length; i++) {
            let line = lines[i]
            let rp = this.reflectSegment(point, line)
            res.push(rp)
        }
        return res
    }

    reflectSegment(point, line=this.points) {
        return new Point(reflectPoint(point, line))
    }

    clean() {
        /* Remove any old references from the mirror map;
        1: Any undefined positions
        2: any point greater than the reflectpointsIndex
        */
        let m = this.mirrorPointsMap
        let l = this.reflectPoints.length;
        for(let k of m.keys()) {
            if(k > l) {
                // scrub
                m.delete(k)
                continue
            }

            if(m.get(k) == undefined) {
                m.delete(k)
            }
        }
    }

    getMirrorIndex(index) {
        let mirrorPoint = this.mirrorPointsMap.get(index);
        if(mirrorPoint == undefined) {
            mirrorPoint = new Point;
            this.mirrorPointsMap.set(index, mirrorPoint);
        }

        return mirrorPoint;
    }

    draw(ctx) {
        this.renderLine && this.drawLine(ctx)
        this.renderPoints && this.drawPoints(ctx)
        this.renderReflections && this.drawReflections(ctx)
    }

    drawLine(ctx) {
        this.points.pen.line(ctx, )
    }

    drawPoints(ctx) {
        return this.drawPointsNew(ctx);

        ctx.setLineDash([3, 3])
        this.points.pen.stroke(ctx)
        // this.stroke.wrap(()=>this.points.pen.stroke(ctx))
        ctx.setLineDash([])
    }

    pointStroke() {
        if(this._pointStroke) {
            return this._pointStroke
        }
        this._pointStroke = new Stroke({
            dash: [4,5]
            , color: 'red'
            , width: 2
            , march: .1
        })

        return this._pointStroke
    }

    drawPointsNew(ctx) {

        let s = this.pointStroke()
        s.step()
        s.set(ctx)
        this.points.pen.stroke(ctx)
        s.unset(ctx)
    }

    drawReflections(ctx) {
        this.mirrorPointsMap.forEach((p)=>{
            p.pen.indicator(ctx)
        })
    }

    add() {
        /* Synonmous to 'push', but this isn't an Array - so we avoid it

            Mirror.add(point, point)
        */
       this.reflectPoints.push.apply(this.reflectPoints, arguments)
    }
}



Polypoint.head.install(Mirror)
copy