I am currently trying to get the example from https://threejs.org/examples/?q=buffer#webgl_buffergeometry_indexed working within a React-Three-Fiber environment.
There are several issues that I'm currently seeing within my code.
First, specifically in Chrome, I am repeatedly getting the following warning:
[.WebGL-0x26d30384f700] GL_INVALID_ENUM: Enum is not currently supported.
In addition, I cannot currently see any color being applied to the mesh.
Here is the code that I have working so far:
import React, { useMemo } from "react";
import { Canvas } from "#react-three/fiber";
import { DoubleSide } from "three";
const App = () => {
const size = 20;
const segments = 10;
const [colors, normals, positions] = useMemo(() => {
const colorsArr = [];
const normalsArr = [];
const positionsArr = [];
const halfSize = size / 2;
const segmentSize = size / segments;
// generate vertices, normals and color data for a simple grid geometry
for (let i = 0; i <= segments; i++) {
const y = i * segmentSize - halfSize;
for (let j = 0; j <= segments; j++) {
const x = j * segmentSize - halfSize;
positionsArr.push(x, -y, 0);
normalsArr.push(0, 0, 1);
const r = x / size + 0.5;
const g = y / size + 0.5;
colorsArr.push(r, g, 1);
}
}
return [colorsArr, normalsArr, positionsArr];
}, []);
const indices = useMemo(() => {
const indicesArr = [];
// generate indices (data for element array buffer)
for (let i = 0; i < segments; i++) {
for (let j = 0; j < segments; j++) {
const a = i * (segments + 1) + (j + 1);
const b = i * (segments + 1) + j;
const c = (i + 1) * (segments + 1) + j;
const d = (i + 1) * (segments + 1) + (j + 1);
// generate two faces (triangles) per iteration
indicesArr.push(a, b, d); // face one
indicesArr.push(b, c, d); // face two
}
}
return indicesArr;
}, []);
return (
<Canvas
camera={{
fov: 27,
near: 1,
far: 3500
}}
position-z={64}
>
<color attach="background" args={["#050505"]} />
<mesh>
<bufferGeometry attach="geometry">
<bufferAttribute
array={indices}
attach="index"
count={indices.length}
itemSize={1}
/>
<bufferAttribute
attachObject={["attributes", "position"]}
count={positions.length / 3}
array={positions}
itemSize={3}
/>
<bufferAttribute
attachObject={["attributes", "color"]}
count={colors.length / 3}
array={colors}
itemSize={3}
/>
<bufferAttribute
attachObject={["attributes", "normal"]}
count={normals.length / 3}
array={normals}
itemSize={3}
/>
</bufferGeometry>
<meshPhongMaterial attach="material" side={DoubleSide} vertexColors />
</mesh>
<hemisphereLight />
</Canvas>
);
};
export default App;
Sample Code from Three.js: https://github.com/mrdoob/three.js/blob/master/examples/webgl_buffergeometry_indexed.html
Got this issue fixed finally. The main issue was that I needed to convert the position, color, and normals array to a Float32Array() and the index to a Uint32Array().
So for example, in the case of the index array, the following worked for me:
const indices = useMemo(() => {
const indicesArr = [];
// generate indices (data for element array buffer)
for (let i = 0; i < segments; i++) {
for (let j = 0; j < segments; j++) {
const a = i * (segments + 1) + (j + 1);
const b = i * (segments + 1) + j;
const c = (i + 1) * (segments + 1) + j;
const d = (i + 1) * (segments + 1) + (j + 1);
// generate two faces (triangles) per iteration
indicesArr.push(a, b, d); // face one
indicesArr.push(b, c, d); // face two
}
}
return new Uint32Array(indicesArr);
}, []);
Related
I am attempting to make a 5x5 grid using arrays with the following limitations
Should not exceed more than 4 check marks per grid
Should not have 2 consecutive check marks
This is what I have come up with so far, I would appreciate if someone could help me figure out how would I achieve the latter condition
let emoji = {
0: '✅',
1: '❓',
}
let grid = []
let checkmarks = 0
for (let i = 0; i < 5; i++) {
let row = []
for (let j = 0; j < 5; j++) {
let random = crypto.randomInt(0, 1000) % 2
if (random == 0) {
if(checkmarks < 4) {
row.push(emoji[0])
checkmarks++
}
else {
row.push(emoji[1])
}
} else {
row.push(emoji[1])
}
}
grid.push(row)
}
I am attempting to make it as random as possible.
I'm posting this answer because the accepted answer doesn't seem to produce a consistent result. I agree with most of the approach, but result just wasn't always returning 4 checkmarks (because it seems to reset after each iteration, which can increase the maximum number of loops needed).
But ultimately, the idea is to fill the 5x5 array with the ❓ character first, randomly select a location, verify the surrounding blocks are not ✅, and then place a ✅ if these conditions are met. If not, I instead just select a new position but keep the existing results until the needed number of ✅ have been set.
let grid = [],
rows = 5,
cols = 5,
maxChecks = 4,
totalChecks = 0,
emoji = {
0: '✅',
1: '❓',
};
const _RandomChecks = () => {
grid = [];
totalChecks = 0;
for(let i = 0; i < rows; i++) {
grid[i] = [];
for(let j = 0; j < cols; j++) {
grid[i] = [...grid[i], emoji[1]];
}
}
while(totalChecks < maxChecks) {
let rndRow = parseInt(crypto.randomUUID().replace(/[^0-9]/g, "").substr(-8)) % rows,
rndCol = parseInt(crypto.randomUUID().replace(/[^0-9]/g, "").substr(-8)) % cols,
valid = (grid[rndRow][rndCol] == emoji[1]) ? true : false;
if(grid[rndRow-1]?.[rndCol] && valid) valid = (grid[rndRow-1]?.[rndCol] == emoji[1]) ? true : false;
if(grid[rndRow+1]?.[rndCol] && valid) valid = (grid[rndRow+1]?.[rndCol] == emoji[1]) ? true : false;
if(grid[rndRow][rndCol-1] && valid) valid = (grid[rndRow][rndCol-1] == emoji[1]) ? true : false;
if(grid[rndRow][rndCol+1] && valid) valid = (grid[rndRow][rndCol+1] == emoji[1]) ? true : false;
if(valid) {
grid[rndRow][rndCol] = emoji[0];
totalChecks++;
}
}
console.log(grid.map(row => row.join('')).join('\n'));
}
_RandomChecks();
Instead of randomly determining if a cell should be a checkmark I would rather randomly find cells that should be a checkmark.
Your current solution decreases the chance of getting a checkmark with each cell.
Created some example code for you:
const emojis = ['✅', '❓']
const size = 5
const checkmarks = []
for (let i = 0; i < 4; i += 1) {
while (true) {
// get random x and y
const x = Math.random() * size | 0
const y = Math.random() * size | 0
// check if x and y are far enough from existing checkmarks
const areNeighbours = checkmarks.some(c => {
if (c.x === x) {
return Math.abs(c.y - y) <= 1
}
if (c.y === y) {
return Math.abs(c.x - x) <= 1
}
return false
})
if (!areNeighbours) {
checkmarks.push({
x,
y
})
break
}
}
}
const grid = []
for (let y = 0; y < size; y += 1) {
grid.push([])
for (let x = 0; x < size; x += 1) {
const checkmark = checkmarks.find(c => c.x === x && c.y === y)
grid[y][x] = checkmark ? emojis[0] : emojis[1]
}
}
console.log(grid.map(row => row.join('')).join('\n'))
Imagine a 5x5 board initially filled by ❓.
Next you toss 4 coins at once, each coin will landed in one cell, head or tail.
If head, place a ✅ in the cell.
Now check if non-consecutive ✅ condition is met. If not start over.
Solution:
const emojis = ['✅', '❓'];
function randomInt(min, max) {
return min + Math.floor(Math.random() * (max - min));
}
function tossCoins(checkmarkLimit, size) {
const positions = Array.from({ length: checkmarkLimit }, () => {
const pos = randomInt(0, size * size);
const tail = Math.random() > 0.5;
if (tail) return null;
const x = pos % 5;
const y = (pos - x) / 5;
return [x, y];
})
return positions.filter(Boolean);
}
function checkNonConsecutive(positions) {
for (let i = 0; i < positions.length; i++) {
const p = positions[i];
for (let j = 0; j < positions.length; j++) {
if (i == j) continue;
const o = positions[j];
const distance = Math.abs(p[0] - o[0]) + Math.abs(p[1] - o[1])
if (distance <= 1) {
return false;
}
}
}
return true;
}
function main() {
const checkmarkLimit = 4;
const size = 5;
const grid = Array.from({ length: size }, () => Array.from({ length: size }, () => emojis[1]));
let positions = tossCoins(checkmarkLimit, size);
while (!checkNonConsecutive(positions)) {
positions = tossCoins(checkmarkLimit, size);
}
positions.forEach(([x, y]) => {
grid[y][x] = emojis[0];
});
return grid;
}
for (let n=0; n < 10; n++) {
console.log('round: ' + n);
console.log(main().map(row => row.join('')).join('\n'));
}
Im trying to make a Hexagon Grid and each of the Hexes to have data props with coordinates x y z and with value. I've managed to make the logic to create the grid and coordinates, but I can't figure out how to connect them. When I try to assign x,y,z props the TypeError pops out, saying I cannot change props in Element. The code:
import { StyledHexagonGrid } from './StyledHexagonGrid.jsx';
import Hexagon from '../Hexagon/Hexagon.jsx';
export const coordinatesCalc = (radius) => {
let coordinateRadius = radius - 1;
let coordinates = [];
for (let x = -coordinateRadius; x < coordinateRadius + 1; x++) {
for (let z = -coordinateRadius; z < coordinateRadius + 1; z++) {
for (let y = -coordinateRadius; y < coordinateRadius + 1; y++) {
let sum = x + y + z;
let coordinate = { x, y, z };
if (sum === 0) {
coordinates.push(coordinate);
}
}
}
}
return coordinates;
};
function HexagonGrid({ dataparentToChild }) {
let passedRadius = Object.values({ dataparentToChild });
let radius = passedRadius[0];
let columns = 2 * radius - 1;
let height = 600 / columns;
let width = height * 1.1547;
let dom_content = [];
for (let i = 0, j = radius - 1; i < columns; i++, j--) {
for (let k = 0; k < columns - Math.abs(j); k++) {
let left = width * 0.75 * i;
let top = k * height + Math.abs(j * (height / 2));
dom_content.push(
<StyledHexagon
style={{
top: `${top}px`,
left: `${left}px`,
width: `${width}px`,
height: `${height}px`,
}}
data-x=''
data-y=''
data-z=''
value=''
/>
);
}
}
console.log(dom_content);
let coordinates = coordinatesCalc(radius);
let hexElements = [];
for(let i=0;i<coordinates.length;i++){
let el = dom_content[i];
el.props.x = coordinates[i].x;
el.props.y = coordinates[i].y;
el.props.z = coordinates[i].z;
hexElements.push(el);
}
console.log(hexElements);
return (
<StyledHexagonGrid>
<Hexagon
dataparentToChild={passedRadius[0]}
/>
</StyledHexagonGrid>
);
}
export default HexagonGrid;
So I have this simple function that should show multiple bricks, but it only shows one. Anyone knows why? I doesn't show any errors.
function drawbricks() {
for (var r = 0; r < 12; r++) {
for (var c = 0; c < 6; c++) {
var posix = r + 20;
var posiy = c + 20;
ctx.fillRect(posix, posiy, 50, 50);
}
}
}
I've added some constants so you can configure your bricks.
const c = document.getElementsByTagName('canvas')[0];
const ctx = c.getContext('2d');
ctx.fillStyle = 'orange';
drawbricks(ctx);
function drawbricks(ctx) {
const brickCountRows = 10;
const brickCountColumns = 15;
const brickWidth = 20;
const brickHeight = 10;
const brickSpacingX = 2;
const brickSpacingY = 2;
for (var r = 0; r < brickCountRows; r++) {
for (var c = -1; c < brickCountColumns; c++) { // start at -1 to fill gap caused by offset (see below)
var posix = c * (brickWidth + brickSpacingX); // switched r by c (column is x component)
var posiy = r * (brickHeight + brickSpacingY); // switched c by r (row is y component)
// offset every other row by half brickWidth + brickSpacing
if (r%2 == 1) {
posix += Math.floor((brickWidth + brickSpacingX)/2)
}
ctx.fillRect(posix, posiy, brickWidth, brickHeight);
}
}
}
<canvas/>
I believe you ment to multiply the coordinates instead of adding.
var posix = r * 20;
var posiy = c * 20;
But still, the "bricks" will overlay each other.
// variables
// arr has elements to be sorted
var arr = []
// temp is to store the intermediate results after merging
var temp = []
// seen is for marking visited i.e. sorted half as green
var seen = []
// length of array
var len = 100
// canvas initialisations
var canvas = document.getElementById("myCanvas")
canvas.width = canvas.height = 1000
var canvaswidth = canvas.width
var canvasheight = canvas.height
var ctx = canvas.getContext("2d")
// random array
for (let i = 0; i < len; i++) {
arr.push(parseInt(Math.random() * 500))
temp.push(parseInt(0))
seen.push(parseInt(0))
}
// initial contents of array to be sorted
// console.log(arr)
// draw the bars
draw = (s, e) => {
ctx.clearRect(0, 0, 1000, 1000)
// this loop will make unvisited bars in the upper half as black
// and visited bars in the upper half as green
for (let i = 0; i < len; i++) {
ctx.fillStyle = "#000000"
ctx.fillRect(15 * i, 500 - arr[i], 10, arr[i])
if (seen[i]) {
ctx.fillStyle = "#00ff00"
ctx.fillRect(15 * i, 500 - arr[i], 10, arr[i])
}
}
// the part that was merged is made blue in the lower half
// also its equivalent in the uper half is made white
for (let i = s; i <= e; i++) {
ctx.fillStyle = "#ffffff"
ctx.fillRect(15 * i, 500 - arr[i], 10, arr[i])
ctx.fillStyle = "#0000ff"
ctx.fillRect(15 * i, 500, 10, arr[i])
seen[i] = 1
}
}
// merge
merge = (s, e) => {
let m = parseInt((s + e) / 2)
let p1 = s
let p2 = m + 1
let n1 = m
let n2 = e
let idx = s
while (p1 <= n1 && p2 <= n2) {
if (arr[p1] <= arr[p2]) {
temp[idx++] = arr[p1++]
}
else {
temp[idx++] = arr[p2++]
}
}
while (p1 <= n1) {
temp[idx++] = arr[p1++]
}
while (p2 <= n2) {
temp[idx++] = arr[p2++]
}
idx = s
while (idx <= e) {
arr[idx] = temp[idx++]
}
}
// delay
function mytimeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// mergesort
const mergesort = async (s, e) => {
if (s < e) {
let m = parseInt((s + e) / 2)
await mergesort(s, m)
await mergesort(m + 1, e)
await merge(s, e)
// await console.log(`merged ${s} to ${e} now draw...`)
await draw(s, e)
await mytimeout(500)
}
}
// calls merge sort and at last
// makes all bars become green in upper half
const performer = async () => {
await mergesort(0, len - 1)
// await console.log(arr)
await draw()
}
performer()
<!DOCTYPE html>
<html lang="en">
<head>
<meta charset="UTF-8">
<meta name="viewport" content="width=device-width, initial-scale=1.0">
<title>Document</title>
</head>
<body>
<canvas id="myCanvas">
Your browser does not support the canvas element.
</canvas>
<script src="testmerge.js"></script>
</body>
</html>
I am trying to make a visualisation of merge sort using plain javascript with HTML canvas.
If i change the len variable to 50 or below my code works
currently i have kept the len variable at 100
the entire array after getting sorted, in the final picture, the bars in the histogram decrease in height automatically
i am not sure if there is a better/cleaner way to approach this
There is a visual glitch because the width of your canvas element is fixed to 1000 pixels, and the bars always have a width of 10 pixels with 5 pixel gaps between them. This means that the bar chart gets clipped as soon as the number of bars gets too large to fit in those 1000 pixels.
One way to fix this is to make the width of the bars (and the inter-gap) dynamically smaller as the number of bars increases.
Here is a fix, which introduces the variables bardistance, barwidth and bargap, although the latter is only there to pinpoint what the gap-value is: it is not used in the rest of the code.
// variables
// arr has elements to be sorted
var arr = [];
// temp is to store the intermediate results after merging
var temp = [];
// seen is for marking visited i.e. sorted half as green
var seen = [];
// length of array
var len = 100;
// canvas initialisations
var canvas = document.getElementById("myCanvas");
canvas.width = canvas.height = 1000;
var canvaswidth = canvas.width;
var canvasheight = canvas.height;
var ctx = canvas.getContext("2d");
var bardistance = Math.floor(canvaswidth / len);
if (!bardistance) bardistance = 1;
var barwidth = Math.floor(bardistance * 0.7);
if (!barwidth) barwidth = 1;
var bargap = bardistance - barwidth;
// random array
for (let i = 0; i < len; i++) {
arr.push(parseInt(Math.random() * 500));
temp.push(parseInt(0));
seen.push(parseInt(0));
}
// initial contents of array to be sorted
// console.log(arr)
// draw the bars
var draw = (s, e) => {
ctx.clearRect(0, 0, 1000, 1000);
// this loop will make unvisited bars in the upper half as black
// and visited bars in the upper half as green
for (let i = 0; i < len; i++) {
ctx.fillStyle = "#000000";
ctx.fillRect(bardistance * i, 500 - arr[i], barwidth, arr[i]);
if (seen[i]) {
ctx.fillStyle = "#00ff00";
ctx.fillRect(bardistance * i, 500 - arr[i], barwidth, arr[i]);
}
}
// the part that was merged is made blue in the lower half
// also its equivalent in the uper half is made white
for (let i = s; i <= e; i++) {
ctx.fillStyle = "#ffffff";
ctx.fillRect(bardistance * i, 500 - arr[i], barwidth, arr[i]);
ctx.fillStyle = "#0000ff";
ctx.fillRect(bardistance * i, 500, barwidth, arr[i]);
seen[i] = 1;
}
}
// merge
merge = (s, e) => {
let m = parseInt((s + e) / 2);
let p1 = s;
let p2 = m + 1;
let n1 = m;
let n2 = e;
let idx = s;
while (p1 <= n1 && p2 <= n2) {
if (arr[p1] <= arr[p2]) {
temp[idx++] = arr[p1++];
}
else {
temp[idx++] = arr[p2++];
}
}
while (p1 <= n1) {
temp[idx++] = arr[p1++];
}
while (p2 <= n2) {
temp[idx++] = arr[p2++];
}
idx = s;
while (idx <= e) {
arr[idx] = temp[idx++];
}
}
// delay
function mytimeout(ms) {
return new Promise(resolve => setTimeout(resolve, ms));
}
// mergesort
const mergesort = async (s, e) => {
if (s < e) {
let m = parseInt((s + e) / 2);
await mergesort(s, m);
await mergesort(m + 1, e);
await merge(s, e);
// await console.log(`merged ${s} to ${e} now draw...`)
await draw(s, e);
await mytimeout(500);
}
}
// calls merge sort and at last
// makes all bars become green in upper half
const performer = async () => {
await mergesort(0, len - 1);
// await console.log(arr)
await draw();
}
performer();
<canvas id="myCanvas"></canvas>
So I have this class which is used for shape morphing:
class ShapeOverlays {
constructor(elm) {
this.elm = elm;
this.path = elm.querySelectorAll('path');
this.numPoints = 18;
this.duration = 600;
this.delayPointsArray = [];
this.delayPointsMax = 300;
this.delayPerPath = 100;
this.timeStart = Date.now();
this.isOpened = false;
this.isAnimating = false;
}
toggle() {
this.isAnimating = true;
const range = 4 * Math.random() + 6;
for (var i = 0; i < this.numPoints; i++) {
const radian = i / (this.numPoints - 1) * Math.PI;
this.delayPointsArray[i] = (Math.sin(-radian) + Math.sin(-radian * range) + 2) / 4 * this.delayPointsMax;
}
if (this.isOpened === false) {
this.open();
} else {
this.close();
}
}
open() {
this.isOpened = true;
this.elm.classList.add('is-opened');
this.timeStart = Date.now();
this.renderLoop();
}
close() {
this.isOpened = false;
this.elm.classList.remove('is-opened');
this.timeStart = Date.now();
this.renderLoop();
}
updatePath(time) {
const points = [];
for (var i = 0; i < this.numPoints + 1; i++) {
points[i] = ease.cubicInOut(Math.min(Math.max(time - this.delayPointsArray[i], 0) / this.duration, 1)) * 100
}
let str = '';
str += (this.isOpened) ? `M 0 0 V ${points[0]} ` : `M 0 ${points[0]} `;
for (var i = 0; i < this.numPoints - 1; i++) {
const p = (i + 1) / (this.numPoints - 1) * 100;
const cp = p - (1 / (this.numPoints - 1) * 100) / 2;
str += `C ${cp} ${points[i]} ${cp} ${points[i + 1]} ${p} ${points[i + 1]} `;
}
str += (this.isOpened) ? `V 0 H 0` : `V 100 H 0`;
return str;
}
render() {
if (this.isOpened) {
for (var i = 0; i < this.path.length; i++) {
this.path[i].setAttribute('d', this.updatePath(Date.now() - (this.timeStart + this.delayPerPath * i)));
}
} else {
for (var i = 0; i < this.path.length; i++) {
this.path[i].setAttribute('d', this.updatePath(Date.now() - (this.timeStart + this.delayPerPath * (this.path.length - i - 1))));
}
}
}
renderLoop() {
this.render();
if (Date.now() - this.timeStart < this.duration + this.delayPerPath * (this.path.length - 1) + this.delayPointsMax) {
requestAnimationFrame(() => {
this.renderLoop();
});
}
else {
this.isAnimating = false;
}
}
}
(function() {
const elmHamburger = document.querySelector('.hamburger');
const gNavItems = document.querySelectorAll('.global-menu__item');
const elmOverlay = document.querySelector('.shape-overlays');
const overlay = new ShapeOverlays(elmOverlay);
elmHamburger.addEventListener('click', () => {
if (overlay.isAnimating) {
return false;
}
overlay.toggle();
if (overlay.isOpened === true) {
elmHamburger.classList.add('is-opened-navi');
for (var i = 0; i < gNavItems.length; i++) {
gNavItems[i].classList.add('is-opened');
}
} else {
elmHamburger.classList.remove('is-opened-navi');
for (var i = 0; i < gNavItems.length; i++) {
gNavItems[i].classList.remove('is-opened');
}
}
});
}());
Can some one please explain this code? I don't really get how the paths are created using time,how the points are placed and how could I modify it.What is range used for? Why are trigonometral functions used for the delayPointsArray?
Basically it's this part that I don't get:
updatePath(time) {
const points = [];
for (var i = 0; i < this.numPoints + 1; i++) {
points[i] = ease.cubicInOut(Math.min(Math.max(time - this.delayPointsArray[i], 0) / this.duration, 1)) * 100
}
let str = '';
str += (this.isOpened) ? `M 0 0 V ${points[0]} ` : `M 0 ${points[0]} `;
for (var i = 0; i < this.numPoints - 1; i++) {
const p = (i + 1) / (this.numPoints - 1) * 100;
const cp = p - (1 / (this.numPoints - 1) * 100) / 2;
str += `C ${cp} ${points[i]} ${cp} ${points[i + 1]} ${p} ${points[i + 1]} `;
}
str += (this.isOpened) ? `V 0 H 0` : `V 100 H 0`;
return str;
}
render() {
if (this.isOpened) {
for (var i = 0; i < this.path.length; i++) {
this.path[i].setAttribute('d', this.updatePath(Date.now() - (this.timeStart + this.delayPerPath * i)));
}
} else {
for (var i = 0; i < this.path.length; i++) {
this.path[i].setAttribute('d', this.updatePath(Date.now() - (this.timeStart + this.delayPerPath * (this.path.length - i - 1))));
}
}
}
Why is time being used? What is the purpose of this:
points[i] = ease.cubicInOut(Math.min(Math.max(time - this.delayPointsArray[i], 0) / this.duration, 1)) * 100
If you look at how updatePath() is being called, it's like this:
this.updatePath(Date.now() - (this.timeStart + this.delayPerPath * i))
So the time value passed in is the difference between the current time, and the start time of the path we are working with.
So what then is the line of code you are interested in, doing?
points[i] = ease.cubicInOut(Math.min(Math.max(time - this.delayPointsArray[i], 0) / this.duration, 1)) * 100
I'm going to ignore delayPointsArray. It is modifying the start time slightly based on angle. Without seeing the full demo, I'm not sure of the reason for that.
The purpose of this line of code is to calculate how far through the current path's animation we are. The result is in the form of a coordinate value from 0 to 100.
It's doing a lot in that one line of code. So let's break down the individual steps.
Firstly, we are clamping the elapsed time to minimum of 0.
Math.max(time, 0)
In other words, anything before the animation start time becomes zero.
Then we divide by the animation's duration.
Math.max(time, 0) / duration
This will result in a value from 0, representing the start of the animation, to 1, representing the end of the animation. However, the value might also be greater than 1 if the elapsed time is after the end of the animation. Hence the next step.
Now clamp this value to a maximum of 1.
Math.min( Math.max(time, 0) / duration, 1)
We now have a value >= 0 and <= 1 whichdescribes where in the course of the animation, the path is supposed to be. 0 if we should be at the animations start position. 1 if we should be at the animations end position. And somewhere in between if the animation is in progress.
However this value is strictly linear, corresponding with the progression of time. And usually linear movement is not what you want. It is unnatural. Objects accelarate when the start moving and decelerate when the come to a stop. That will be what the easeInOut() function will be doing. If you are not familiar with easing curves, take a look at the diagram below.
Source: Google: The Basics of Easing
So we pass in a linear time value from 0..1 (horizontal axis). It will return a modified value that takes into account acceleration and deceleration.
The final step is to multiply by 100, to convert to a final coordinate value (0..100).
Hope this helps.