optimaize with webgl
All checks were successful
ci/woodpecker/push/woodpecker Pipeline was successful

This commit is contained in:
NekoVari 2025-01-05 07:05:36 +07:00
parent e4704c62f3
commit 6b62ebe64f
2 changed files with 395 additions and 71 deletions

View file

@ -1,79 +1,403 @@
import React, { useEffect, useState } from 'react';
import JSZip from 'jszip';
//version 1
// import React, { useEffect, useState } from 'react';
// import JSZip from 'jszip';
const Animation = ({src="src/assets/mainCharacters/M_Porsche_cross_arm.zip" ,animationWidth="500px"}) => {
const [images, setImages] = useState([]);
const [currentFrame, setCurrentFrame] = useState(0);
const [loading, setLoading] = useState(true);
const frameRate = 1000/24;
// const Animation = ({src="src/assets/mainCharacters/M_Porsche_cross_arm.zip" ,animationWidth="500px"}) => {
// const [images, setImages] = useState([]);
// const [currentFrame, setCurrentFrame] = useState(0);
// const [loading, setLoading] = useState(true);
// const frameRate = 1000/24;
useEffect(() => {
const loadImages = async () => {
const zip = new JSZip();
try {
const response = await fetch(src);
if (!response.ok) {
throw new Error('Network response was not ok');
}
const data = await response.arrayBuffer();
const zipContent = await zip.loadAsync(data);
// useEffect(() => {
// const loadImages = async () => {
// const zip = new JSZip();
// try {
// const response = await fetch(src);
// if (!response.ok) {
// throw new Error('Network response was not ok');
// }
// const data = await response.arrayBuffer();
// const zipContent = await zip.loadAsync(data);
const imgPromises = [];
zipContent.forEach((relativePath, file) => {
if (file.name.endsWith('.webp')) {
imgPromises.push(
file.async('base64').then(base64 => {
return `data:image/webp;base64,${base64}`;
})
);
// const imgPromises = [];
// zipContent.forEach((relativePath, file) => {
// if (file.name.endsWith('.webp')) {
// imgPromises.push(
// file.async('base64').then(base64 => {
// return `data:image/webp;base64,${base64}`;
// })
// );
// }
// });
// const imgUrls = await Promise.all(imgPromises);
// if (imgUrls.length === 0) {
// console.error('No images found in the ZIP file.');
// }
// setImages(imgUrls);
// } catch (error) {
// console.error('Error loading images:', error);
// } finally {
// setLoading(false);
// }
// };
// loadImages();
// }, []);
// useEffect(() => {
// if (images.length > 0) {
// const interval = setInterval(() => {
// setCurrentFrame((prevFrame) => (prevFrame + 1) % images.length);
// }, frameRate);
// return () => clearInterval(interval);
// }
// }, [images]);
// if (loading) {
// return <div>Loading...</div>;
// } else {
// return (
// <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
// {images.length > 0 ? (
// <img
// src={images[currentFrame]}
// alt={`Animation frame ${currentFrame + 1}`}
// style={{ maxWidth: animationWidth, height: 'auto' }}
// />
// ) : (
// <div>No images to display.</div>
// )}
// </div>
// );
// }
// };
// export default Animation;
//version 2
// import React, { useEffect, useState, useRef } from 'react';
// import JSZip from 'jszip';
// const Animation = ({ src = "src/assets/mainCharacters/M_Porsche_cross_arm.zip", animationWidth = "500px" }) => {
// const [images, setImages] = useState([]);
// const [loading, setLoading] = useState(true);
// const frameRate = 1000 / 24; // Original frame rate (24 FPS)
// const canvasRef = useRef(null);
// const animationRef = useRef(null);
// const currentFrameRef = useRef(0);
// const imageElementsRef = useRef([]);
// useEffect(() => {
// const loadImages = async () => {
// const zip = new JSZip();
// try {
// const response = await fetch(src);
// if (!response.ok) {
// throw new Error('Network response was not ok');
// }
// const data = await response.arrayBuffer();
// const zipContent = await zip.loadAsync(data);
// const imgPromises = [];
// zipContent.forEach((relativePath, file) => {
// if (file.name.endsWith('.webp')) {
// imgPromises.push(
// file.async('base64').then(base64 => {
// const img = new Image();
// img.src = `data:image/webp;base64,${base64}`;
// return img; // Return the Image object
// })
// );
// }
// });
// const imgElements = await Promise.all(imgPromises);
// if (imgElements.length === 0) {
// console.error('No images found in the ZIP file.');
// }
// imageElementsRef.current = imgElements; // Store preloaded images
// setImages(imgElements); // Set images state
// } catch (error) {
// console.error('Error loading images:', error);
// } finally {
// setLoading(false);
// }
// };
// loadImages();
// }, [src]);
// useEffect(() => {
// if (images.length > 0) {
// const canvas = canvasRef.current;
// const ctx = canvas.getContext('2d');
// let lastFrameTime = 0;
// const animate = (timestamp) => {
// if (lastFrameTime === 0) lastFrameTime = timestamp;
// const elapsed = timestamp - lastFrameTime;
// if (elapsed > frameRate) {
// currentFrameRef.current = (currentFrameRef.current + 1) % images.length;
// lastFrameTime = timestamp;
// }
// // Clear the canvas
// ctx.clearRect(0, 0, canvas.width, canvas.height);
// // Draw the current frame
// ctx.drawImage(imageElementsRef.current[currentFrameRef.current], 0, 0, canvas.width, canvas.height);
// animationRef.current = requestAnimationFrame(animate);
// };
// animationRef.current = requestAnimationFrame(animate);
// return () => {
// if (animationRef.current) {
// cancelAnimationFrame(animationRef.current);
// }
// };
// }
// }, [images]);
// if (loading) {
// return <div>Loading...</div>;
// } else {
// return (
// <div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
// <canvas
// ref={canvasRef}
// width={'500px'}
// height={'700'} // Maintain aspect ratio (example: 16:9)
// style={{ maxWidth: animationWidth, height: 'auto' }}
// />
// </div>
// );
// }
// };
// export default Animation;
import React, { useEffect, useState, useRef } from 'react';
import JSZip from 'jszip';
const Animation = ({ src = "src/assets/mainCharacters_pow2/M_porsche_cross_arm_power2.zip", animationWidth = "500px" }) => {
const [images, setImages] = useState([]);
const [loading, setLoading] = useState(true);
const frameRate = 1000 / 24; // Original frame rate (24 FPS)
const canvasRef = useRef(null);
const animationRef = useRef(null);
const currentFrameRef = useRef(0);
const imageElementsRef = useRef([]);
// Load images from the ZIP file
useEffect(() => {
const loadImages = async () => {
const zip = new JSZip();
try {
const response = await fetch(src);
if (!response.ok) {
throw new Error('Network response was not ok');
}
});
const imgUrls = await Promise.all(imgPromises);
if (imgUrls.length === 0) {
console.error('No images found in the ZIP file.');
const data = await response.arrayBuffer();
const zipContent = await zip.loadAsync(data);
const imgPromises = [];
zipContent.forEach((relativePath, file) => {
if (file.name.endsWith('.webp')) {
imgPromises.push(
file.async('base64').then(base64 => {
const img = new Image();
img.src = `data:image/webp;base64,${base64}`;
return new Promise((resolve, reject) => {
img.onload = () => resolve(img);
img.onerror = reject;
});
})
);
}
});
const imgElements = await Promise.all(imgPromises);
if (imgElements.length === 0) {
console.error('No images found in the ZIP file.');
}
imageElementsRef.current = imgElements; // Store preloaded images
setImages(imgElements); // Set images state
} catch (error) {
console.error('Error loading images:', error);
} finally {
setLoading(false);
}
};
loadImages();
}, [src]);
// WebGL setup and animation loop
useEffect(() => {
if (images.length > 0) {
const canvas = canvasRef.current;
const gl = canvas.getContext('webgl');
if (!gl) {
console.error("WebGL is not supported.");
return;
}
// Set up WebGL context (basic)
gl.clearColor(0.0, 0.0, 0.0, 0.0);
gl.clear(gl.COLOR_BUFFER_BIT);
gl.viewport(0, 0, canvas.width, canvas.height);
setImages(imgUrls);
} catch (error) {
console.error('Error loading images:', error);
} finally {
setLoading(false);
// Enable blending
gl.enable(gl.BLEND);
gl.blendFunc(gl.SRC_ALPHA, gl.ONE_MINUS_SRC_ALPHA);
// gl.blendFunc(gl.SRC_COLOR, gl.DST_COLOR);
// Shader program
const vertexShaderSource = `
attribute vec2 a_position;
attribute vec2 a_texCoord;
varying vec2 v_texCoord;
void main() {
gl_Position = vec4(a_position, 0.0, 1.0);
v_texCoord = a_texCoord;
}
`;
const fragmentShaderSource = `
precision mediump float;
uniform sampler2D u_texture;
varying vec2 v_texCoord;
void main() {
gl_FragColor = texture2D(u_texture, v_texCoord);
}
`;
const createShader = (source, type) => {
const shader = gl.createShader(type);
gl.shaderSource(shader, source);
gl.compileShader(shader);
if (!gl.getShaderParameter(shader, gl.COMPILE_STATUS)) {
console.error('ERROR compiling shader', gl.getShaderInfoLog(shader));
}
return shader;
};
const vertexShader = createShader(vertexShaderSource, gl.VERTEX_SHADER);
const fragmentShader = createShader(fragmentShaderSource, gl.FRAGMENT_SHADER);
const shaderProgram = gl.createProgram();
gl.attachShader(shaderProgram, vertexShader);
gl.attachShader(shaderProgram, fragmentShader);
gl.linkProgram(shaderProgram);
if (!gl.getProgramParameter(shaderProgram, gl.LINK_STATUS)) {
console.error('ERROR linking program', gl.getProgramInfoLog(shaderProgram));
}
gl.useProgram(shaderProgram);
// Create buffer and set up attributes
const positionBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, positionBuffer);
const vertices = new Float32Array([
-1.0, -1.0, // Bottom-left
1.0, -1.0, // Bottom-right
-1.0, 1.0, // Top-left
1.0, 1.0 // Top-right
]);
gl.bufferData(gl.ARRAY_BUFFER, vertices, gl.STATIC_DRAW);
const positionLocation = gl.getAttribLocation(shaderProgram, "a_position");
gl.vertexAttribPointer(positionLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(positionLocation);
// Create texture and load image
const texture = gl.createTexture();
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.LINEAR);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.LINEAR);
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MAG_FILTER, gl.NEAREST);
// gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_MIN_FILTER, gl.NEAREST);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_S, gl.CLAMP_TO_EDGE);
gl.texParameteri(gl.TEXTURE_2D, gl.TEXTURE_WRAP_T, gl.CLAMP_TO_EDGE);
const loadTexture = (image) => {
gl.bindTexture(gl.TEXTURE_2D, texture);
gl.texImage2D(gl.TEXTURE_2D, 0, gl.RGBA, gl.RGBA, gl.UNSIGNED_BYTE, image);
gl.generateMipmap(gl.TEXTURE_2D);
};
loadTexture(imageElementsRef.current[0]); // Initially load the first image
// Texture coordinates
const texCoordBuffer = gl.createBuffer();
gl.bindBuffer(gl.ARRAY_BUFFER, texCoordBuffer);
const texCoords = new Float32Array([
0.0, 1.0, // Bottom-left (now corresponds to top-left of the image)
1.0, 1.0, // Bottom-right (now corresponds to top-right of the image)
0.0, 0.0, // Top-left (now corresponds to bottom-left of the image)
1.0, 0.0 // Top-right (now corresponds to bottom-right of the image)
]);
gl.bufferData(gl.ARRAY_BUFFER, texCoords, gl.STATIC_DRAW);
const texCoordLocation = gl.getAttribLocation(shaderProgram, "a_texCoord");
gl.vertexAttribPointer(texCoordLocation, 2, gl.FLOAT, false, 0, 0);
gl.enableVertexAttribArray(texCoordLocation);
const uTextureLocation = gl.getUniformLocation(shaderProgram, "u_texture");
let lastFrameTime = 0;
const animate = (timestamp) => {
if (lastFrameTime === 0) lastFrameTime = timestamp;
const elapsed = timestamp - lastFrameTime;
if (elapsed > frameRate) {
currentFrameRef.current = (currentFrameRef.current + 1) % images.length;
loadTexture(imageElementsRef.current[currentFrameRef.current]);
lastFrameTime = timestamp;
}
gl.clear(gl.COLOR_BUFFER_BIT);
gl.drawArrays(gl.TRIANGLE_STRIP, 0, 4);
animationRef.current = requestAnimationFrame(animate);
};
animationRef.current = requestAnimationFrame(animate);
return () => {
if (animationRef.current) {
cancelAnimationFrame(animationRef.current);
}
};
}
};
loadImages();
}, []);
useEffect(() => {
if (images.length > 0) {
const interval = setInterval(() => {
setCurrentFrame((prevFrame) => (prevFrame + 1) % images.length);
}, frameRate);
return () => clearInterval(interval);
}
}, [images]);
if (loading) {
return <div>Loading...</div>;
} else {
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
{images.length > 0 ? (
<img
src={images[currentFrame]}
alt={`Animation frame ${currentFrame + 1}`}
style={{ maxWidth: animationWidth, height: 'auto' }}
}, [images]);
if (loading) {
return <div>Loading...</div>;
} else {
return (
<div style={{ display: 'flex', justifyContent: 'center', alignItems: 'center' }}>
<canvas
ref={canvasRef}
width={'1920'}
height={'1080'} // Maintain aspect ratio (example: 16:9)
style={{ maxWidth: animationWidth, height: 'auto' }}
/>
) : (
<div>No images to display.</div>
)}
</div>
);
}
};
export default Animation;
</div>
);
}
};
export default Animation;