So to make it short I am trying to make visualization of sorting algorithms using html , CSS , JS (additional libraries- [three.js] for rendering the objects and [tween.js] for animating those objects).
What I am trying to do is add controls to the animation so that user can play or pause the animation, which I did but it seems that whenever I pause the animation while a tween animation is ongoing it simply just skips to the last position. Another major issue is that suppose some other function in the code is running (say like the elements are getting compared) and if user pauses at that time then the visualization of that comparison (change in color of the objects basically) the function keeps running. I also want to add narrative speech which should synchronize with both the code and animation.
So to counter the tween skipping to the final position I just couldn't find any solution and as for pausing the function I was thinking of adding a decoy object and adding a tween animation for it in every function so that every time user hits pause it will pause the tween and the function at the same time but again its inefficient and turns back to the same issue of tween skipping , either way it isn't optimal when I add speech because it won't synchronize at all. Do I need to switch to a different library for animation?
Here is the code , let me know if I need to include anything else -
async function bubbleSortAnimation(customArray,highlightGradientTexture, highlightfGradientTexture, gradientTexture) {
addButton();
console.log('bubble sort running')
console.log('init');
customArray.forEach((item,index) => console.log(index,item));
const n = customArray.length;
console.log('array length',n);
let swapped;
for (let i = 0; i < n - 1; i++) {
let j;
swapped = false;
console.log('i = ',i);
for ( j = 0; j < n - i - 1; j++) {
await highlightComparison(j, j + 1, highlightGradientTexture);
const bar1Position = groups[j].children.find(child => child instanceof THREE.Mesh).position.clone();
const bar2Position = groups[j + 1].children.find(child => child instanceof THREE.Mesh).position.clone();
if (parseFloat(customArray[j]) > parseFloat(customArray[j + 1])) {
await animateSwap(j, j + 1,customArray,bar1Position, bar2Position);
swapped = true;
let tempSwap1 = parseFloat(customArray[j]);
let tempSwap2 = parseFloat(customArray[j + 1]);
if (tempSwap1 > tempSwap2) {
await swapGroupChildren(j , j + 1);
[customArray[j], customArray[j + 1]] = [tempSwap2, tempSwap1];
await resetColors(j, j + 1, gradientTexture);
} else {
console.log('No need to swap elements at indices', j, 'and', j + 1);
}
}
else if (parseFloat(customArray[j]) < parseFloat(customArray[j + 1])) {
await resetColors(j, j + 1, gradientTexture);
}
}
console.log('iteration finished');
await finalBarHighlight( j ,highlightfGradientTexture);
if (!swapped )
break;
}
}
async function swapGroupChildren(index1, index2) {
let group1 = groups[index1];
let group2 = groups[index2];
if (group1 && group2) {
const originalPositionGroup1 = group1.position.clone();
const originalPositionGroup2 = group2.position.clone();
const childrenGroup1 = group1.children.slice();
const childrenGroup2 = group2.children.slice();
group1.remove(...group1.children);
group2.remove(...group2.children);
group2.add(...childrenGroup1);
group1.position.copy(originalPositionGroup2);
group1.add(...childrenGroup2);
group2.position.copy(originalPositionGroup1);
}
}
async function highlightComparison(index1, index2, originalTexture) {
console.log('Highlighting comparison for index:', index1, index2);
const group1 = groups[index1];
const group2 = groups[index2];
console.log('group info', group1 , group2);
if (group1 && group2){
const bar1 = group1.children.find(child => child instanceof THREE.Mesh);
const bar2 = group2.children.find(child => child instanceof THREE.Mesh);
console.log('bars' , bar1 , bar2);
if(bar1 && bar2){
const highlightedTexture1 = originalTexture.clone();
const highlightedTexture2 = originalTexture.clone();
const canvas1 = createHighlightedCanvas(originalTexture.image.width, originalTexture.image.height);
const canvas2 = createHighlightedCanvas(originalTexture.image.width, originalTexture.image.height);
const ctx1 = canvas1.getContext('2d');
const ctx2 = canvas2.getContext('2d');
const gradient1 = ctx1.createLinearGradient(0, 0, 0, originalTexture.image.height);
const gradient2 = ctx2.createLinearGradient(0, 0, 0, originalTexture.image.height);
gradient1.addColorStop(0, '#2b2b2b');
gradient1.addColorStop(1, '#ff0000');
gradient2.addColorStop(0, '#2b2b2b');
gradient2.addColorStop(1, '#ff0000');
ctx1.fillStyle = gradient1;
ctx1.fillRect(0, 0, canvas1.width, canvas1.height);
ctx2.fillStyle = gradient2;
ctx2.fillRect(0, 0, canvas2.width, canvas2.height);
highlightedTexture1.image = canvas1;
highlightedTexture1.needsUpdate = true;
highlightedTexture2.image = canvas2;
highlightedTexture2.needsUpdate = true;
bar1.material = new THREE.MeshStandardMaterial({ map: highlightedTexture1.clone() });
bar2.material = new THREE.MeshStandardMaterial({ map: highlightedTexture2.clone() });
await new Promise(resolve => setTimeout(resolve, 500));
}
}
}
async function resetColors(index1, index2, originalTexture) {
const group1 = groups[index1];
const group2 = groups[index2];
console.log('group info', group1 , group2);
if (group1 && group2){
const bar1 = group1.children.find(child => child instanceof THREE.Mesh);
const bar2 = group2.children.find(child => child instanceof THREE.Mesh);
console.log('bars' , bar1 , bar2);
if(bar1 && bar2){
const highlightedTexture1 = originalTexture.clone();
const highlightedTexture2 = originalTexture.clone();
const canvas1 = createHighlightedCanvas(originalTexture.image.width, originalTexture.image.height);
const canvas2 = createHighlightedCanvas(originalTexture.image.width, originalTexture.image.height);
const ctx1 = canvas1.getContext('2d');
const ctx2 = canvas2.getContext('2d');
const gradient1 = ctx1.createLinearGradient(0, 0, 0, originalTexture.image.height);
const gradient2 = ctx2.createLinearGradient(0, 0, 0, originalTexture.image.height);
gradient1.addColorStop(0, '#2b2b2b');
gradient1.addColorStop(1, '#00d5ff');
gradient2.addColorStop(0, '#2b2b2b');
gradient2.addColorStop(1, '#00d5ff');
ctx1.fillStyle = gradient1;
ctx1.fillRect(0, 0, canvas1.width, canvas1.height);
ctx2.fillStyle = gradient2;
ctx2.fillRect(0, 0, canvas2.width, canvas2.height);
highlightedTexture1.image = canvas1;
highlightedTexture1.needsUpdate = true;
highlightedTexture2.image = canvas2;
highlightedTexture2.needsUpdate = true;
bar1.material = new THREE.MeshStandardMaterial({ map: highlightedTexture1.clone() });
bar2.material = new THREE.MeshStandardMaterial({ map: highlightedTexture2.clone() });
await new Promise(resolve => setTimeout(resolve, 500));
}
}
}
function SortFinishHighlight(index1, originalTexture) {
for( let i = 0 ; i < index1 ; i++){
const group = groups[i];
if (group){
const bar = group.children.find(child => child instanceof THREE.Mesh);
if(bar){
const highlightedTexture1 = originalTexture.clone();
const canvas1 = createHighlightedCanvas(originalTexture.image.width, originalTexture.image.height);
const ctx1 = canvas1.getContext('2d');
const gradient1 = ctx1.createLinearGradient(0, 0, 0, originalTexture.image.height);
gradient1.addColorStop(0, '#2b2b2b');
gradient1.addColorStop(1, '#00ff11');
ctx1.fillStyle = gradient1;
ctx1.fillRect(0, 0, canvas1.width, canvas1.height);
highlightedTexture1.image = canvas1;
highlightedTexture1.needsUpdate = true;
bar.material = new THREE.MeshStandardMaterial({ map: highlightedTexture1.clone() });
}
}
}
}
async function finalBarHighlight(index1, originalTexture) {
console.log('bar fhglght');
const group1 = groups[index1];
if (group1){
const bar1 = group1.children.find(child => child instanceof THREE.Mesh);
if(bar1){
const highlightedTexture1 = originalTexture.clone();
const canvas1 = createHighlightedCanvas(originalTexture.image.width, originalTexture.image.height);
const ctx1 = canvas1.getContext('2d');
const gradient1 = ctx1.createLinearGradient(0, 0, 0, originalTexture.image.height);
gradient1.addColorStop(0, '#2b2b2b');
gradient1.addColorStop(1, '#ff6600');
ctx1.fillStyle = gradient1;
ctx1.fillRect(0, 0, canvas1.width, canvas1.height);
highlightedTexture1.image = canvas1;
highlightedTexture1.needsUpdate = true;
bar1.material = new THREE.MeshStandardMaterial({ map: highlightedTexture1.clone() });
await new Promise(resolve => setTimeout(resolve, 500));
}
}
}
function createHighlightedCanvas(width, height) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
return canvas;
}
function createCanvasTexture(width, height, color) {
const canvas = document.createElement('canvas');
canvas.width = width;
canvas.height = height;
const ctx = canvas.getContext('2d');
ctx.fillStyle = color;
ctx.fillRect(0, 0, canvas.width, canvas.height);
return new THREE.CanvasTexture(canvas);
}
async function animateSwap(index1, index2, customArray, bar1Position, bar2Position) {
console.log('Animating swap for indices:', index1, index2);
console.log('Elements:', customArray[index1], customArray[index2]);
console.log('grp pos',groups[index1].position,groups[index2].position);
if (groups[index1] && groups[index2]) {
console.log('Initial positions:');
console.log('Bar 1:', bar1Position);
console.log('Bar 2:', bar2Position);
const sidePath = 120;
console.log('Side path:', sidePath);
const tween1 = new TWEEN.Tween(groups[index1].position)
.to({ z: groups[index1].position.z - sidePath }, 500) // Move group1
.easing(TWEEN.Easing.Quadratic.Out)
.start();
const tween2 = new TWEEN.Tween(groups[index2].position)
.to({ z: groups[index2].position.z + sidePath }, 500) // Move group2
.easing(TWEEN.Easing.Quadratic.Out)
.start();
await new Promise(resolve => {
tween1.chain(tween2);
tween2.onComplete(resolve);
});
console.log('Halfway positions:');
console.log('Bar 1:', bar1Position);
console.log('Bar 2:', bar2Position);
const tween3 = new TWEEN.Tween(groups[index1].position)
.to({ x: groups[index1].position.x + sidePath }, 500) // Move group1 along x-axis
.easing(TWEEN.Easing.Quadratic.Out)
.start();
const tween4 = new TWEEN.Tween(groups[index2].position)
.to({ x: groups[index2].position.x - sidePath }, 500) // Move group2 along x-axis
.easing(TWEEN.Easing.Quadratic.Out)
.start();
await new Promise(resolve => {
tween3.chain(tween4);
tween4.onComplete(resolve);
});
console.log('Final positions:');
console.log('Bar 1:', bar1Position);
console.log('Bar 2:', bar2Position);
}
}
document.getElementById('scrollToPage3').addEventListener('click', async function () {
const userValues = JSON.parse(localStorage.getItem('userValues'));
console.log('Retrieved userValues:', userValues);
if (userValues) {
await createBars(userValues);
renderer.render(scene, camera);
} else {
console.error('No user input values found in local storage.');
}
});
async function animateBars() {
const numGroups = groups.length;
const delayBetweenGroups = 1000; // milliseconds
const animations = [];
for (let i = 0; i < numGroups; i++) {
const group = groups[i];
const tween = new TWEEN.Tween(group.position)
.to({ y: 1000 }, 2000)
.delay(i * delayBetweenGroups)
.easing(TWEEN.Easing.Quadratic.InOut)
.start();
console.log('Animating bars:', i);
console.log('group pos', groups[i].position);
animations.push(new Promise(resolve => tween.onComplete(resolve)));
}
await Promise.all(animations);
console.log('Bars animation completed');
}
function addButton() {
const button = document.getElementById('PlayPause_button');
button.style.display = 'block';
}
function removeButton() {
const button = document.getElementById('PlayPause_button');
button.style.display = 'none';
}
let animationPaused = false;
function animate() {
if (!animationPaused){
requestAnimationFrame(animate);
TWEEN.update();
controls.update();
renderer.render(scene, camera);
}
}
function toggleAnimation() {
animationPaused = !animationPaused;
const button = document.getElementById('PlayPause_button');
if (animationPaused) {
button.style.backgroundImage = "url('https://i.imgur.com/6fD4Lse.jpeg')";
TWEEN.stop();
} else {
button.style.backgroundImage = "url('https://i.imgur.com/0pmy3io.jpeg')";
animate();
TWEEN.start();
}
}
animate();
removeButton();
document.getElementById('PlayPause_button').addEventListener('click', toggleAnimation);