0

I tried to create a simple light reflection model in HTML, JavaScript and CSS. I had just created the UI till now, and encountered an error. When I move the light source and the mirrors on the screen, and later if I create a new mirror, the coordinates of the light source and the mirrors reset to the starting coordinates.

So, there are range inputs for the abscissa, ordinates, and the angle of rotation. There are text inputs with them, the text input's value changes, its corresponding range, the slider's value is changed, and vice versa. I believe that there is an error in the code that values of ranges and text inputs. I sat for hours to figure out the problem but got nothing. If somebody could help, it would be appreciated. The code for the project is provided below:

HTML:

<html>
    <head>
        <title>Reflection</title>
    </head>
    <body>
        <div style="position:relative;" id="attributes">
            Source X: <input type="text" value="50" class="source x-display" style="width:50;"> <input type="range" class="source x" value="50">
            &nbsp;Source Y: <input type="text" value="50" class="source y-display" style="width:50;"> <input type="range" class="source y" value="50">
            &nbsp;Source Angle: <input type="text" value="0" class="source angle-display" style="width:50;"> <input type="range" class="source angle" value="0"> <br>
        </div> <br>
        <button id="new-straight-mirror">New Straight Mirror</button>
        <script src="script.js"></script>
    </body>
</html>

JavaScript:

const canvas = document.createElement("canvas");
const ctx = canvas.getContext("2d");
const source = vector(50, 50);
const mirrors = [mirror(vector(300, 300), 0, 100, 1)];
const newStraightMirror = document.getElementById("new-straight-mirror");

canvas.width = window.innerWidth-50;
canvas.height = window.innerHeight-50;

document.body.insertBefore(canvas, null);

newStraightMirror.addEventListener("click", e => {
    mirrors.push(mirror(vector(300, 300), 0, 100, mirrors.length+1));
});

function mirror(position, angle, length, index, inverted) {
    return new class Mirror {
        constructor() {
            this.position = position;
            this.angle = angle;
            this.length = length;
            this.inverted = !!inverted;

            let attributes = document.getElementById("attributes");
            attributes.innerHTML += `
                Mirror${index} X: <input type="text" value="50" class="mirror ${index} x-display" style="width:50;"> <input type="range" class="mirror ${index} x" value="50">
                &nbsp;Mirror${index} Y: <input type="text" value="50" class="mirror ${index} y-display" style="width:50;"> <input type="range" class="mirror ${index} y" value="50">
                &nbsp;Mirror${index} Angle: <input type="text" value="0" class="mirror ${index} angle-display" style="width:50;"> <input type="range" class="mirror ${index} angle" value="0"> <br>`;
        }
    }
}

function vector(x, y) {
    return new class Vector {
        constructor() {
            this.moveTo(x, y);
        }

        moveTo(x, y) {
            this.x = x;
            this.y = y;
        }
    }
}

function loop(cb) {
    requestAnimationFrame(() => {
        setInterval(() => {
            cb();
            loop(cb);
        }, 1000/30);
    });
}

function draw() {
    drawSource();
    drawMirrors();
}

function drawSource() {
    ctx.beginPath();
    ctx.fillStyle = "black";
    ctx.arc(source.x, source.y, 3, 0, 2*Math.PI);
    ctx.fill();
    ctx.closePath();
}

function drawMirrors() {
    for(let i = 0; i < mirrors.length; i++) {
        let mirror = mirrors[i];
        ctx.beginPath();
        ctx.strokeWidth = 5;
        ctx.moveTo(mirror.position.x, mirror.position.y);
        ctx.lineTo(parseInt(mirror.position.x)+mirror.length*Math.cos(mirror.angle*Math.PI/180), parseInt(mirror.position.y)-mirror.length*Math.sin(mirror.angle*Math.PI/180));
        ctx.stroke();
        ctx.closePath();
    }
}

function update() {
    let elementX = [...document.getElementsByTagName("input")].filter(v => v.classList.contains("x"));
    let elementY = [...document.getElementsByTagName("input")].filter(v => v.classList.contains("y"));
    let elementAngle = [...document.getElementsByTagName("input")].filter(v => v.classList.contains("angle"));
    elementX.forEach(v => {v.min = 0; v.max = canvas.width; v.style.width = 200});
    elementY.forEach(v => {v.min = 0; v.max = canvas.height; v.style.width = 200});
    elementAngle.forEach(v => {v.min = 0; v.max = 360});
    updateSource();
    updateMirrors();
}

function updateSource() {
    let element = document.getElementsByClassName("source");
    source.moveTo(element[1].value, element[3].value);
}

function updateMirrors() {
    for(let i = 0; i < mirrors.length; i++) {
        let mirror = mirrors[i];
        let elements = [...document.getElementsByClassName("mirror")];
        mirror.position.x = parseInt(elements.filter(v => v.classList.contains("x"))[i].value);
        mirror.position.y = parseInt(elements.filter(v => v.classList.contains("y"))[i].value);
        mirror.angle = parseInt(elements.filter(v => v.classList.contains("angle"))[i].value);
    }
}

loop(() => {
    ctx.clearRect(0, 0, canvas.width, canvas.height);
    update();
    draw();
});

function distance(vector1, vector2) {
    return Math.sqrt((vector1.x-vector2.x)**2+(vector1.y-vector2.y)**2);
}

[...document.getElementsByTagName("input")].filter(v => v.type === "text").filter(v => v.classList.contains("x-display") || v.classList.contains("y-display") || v.classList.contains("angle-display")).forEach((v, i) => {
    v.addEventListener("change", e => {
        [...document.getElementsByTagName("input")].filter(v => v.type === "range")[i].value = v.value;
    });
});

[...document.getElementsByTagName("input")].filter(v => v.type === "range").filter(v => v.classList.contains("x") || v.classList.contains("y") || v.classList.contains("angle")).forEach((v, i) => {
    v.addEventListener("change", e => {
        [...document.getElementsByTagName("input")].filter(v => v.type === "text")[i].value = v.value;
    });
});

Github Code

Github Pages

Zayaan
  • 169
  • 1
  • 8
  • Why do you have class definitions inside your factory functions? Why not just define the classes at top-level and call the constructors? – Barmar Jan 03 '23 at 01:54
  • Javascript strings are immutable, `+=` doesn't append to the inner html, it overwrites it. You should be creating nodes with functions like `document.createElement` and appending them with `element.append`. Or just use a framework. – Chris Hamilton Jan 03 '23 at 01:55
  • @Barmar, creating class definitions inside factory functions allows me to move the definition of the functions anywhere in the code, while directly declaring the classes, would force me to declare the classes at the top/start of the code. – Zayaan Jan 03 '23 at 13:49
  • @ChrisHamilton, I have tried changing it to `attributes.innerHTML = attributes.innerHTML + ''`, but still I am not getting a different result. – Zayaan Jan 03 '23 at 14:00
  • @Zayaan that's the same as `+=`. Again strings are immutable, you can't modify / append to them, only overwrite them. See Barmar's answer. – Chris Hamilton Jan 03 '23 at 20:59

2 Answers2

0

Check this line of code:

mirrors.push(mirror(vector(300, 300), 0, 100, mirrors.length+1));

Every time you click the new-straight-mirror button, you are adding a new mirror to the mirrors array, with its initial position being (300, 300). This is causing all the existing mirrors to reset to this position, because their positions are being overwritten.

To fix this issue, maybe you could initialize each new mirror with a different initial position, instead of using the same position for all mirrors?

  • I am just adding a new element to the array, how would this affect the position of other mirrors? I believe, it doesn't change anything, even though, I will once try it. – Zayaan Jan 03 '23 at 13:43
0

Assigning to the .value of an element doesn't change the value= attribute. This attribute still holds the original value, which is used again when the HTML is parsed. This happens when you concatenate to the innerHTML of the container -- all the HTML of the container is re-parsed, so the elements revert to their initial positions.

Instead of concatenating to .innerHTML, you can use .insertAdjacentHTML() to add new HTML without reparsing the old HTML. See Append HTML to container element without innerHTML

Barmar
  • 741,623
  • 53
  • 500
  • 612
  • Thankyou for helping, this indeed was the result i was expecting. Thankyou for introducing me to this new function which i didn't know about. – Zayaan Jan 05 '23 at 17:24