Hello so i'm doing an webpage i want to load very high quality images so for te moment the webpage serve the images in like 10s i did a charging srceen for it but it's to long so i made 3 dir for low mid and high but my charging screen stops just when the high quality is loaded so it didn't changed enything. I also have a node.js serveur but i think i need to do it by my frontend.
import './contact.css'
import './pics.css'
import { MyHeader } from './Welcome'
import { useGLTF, useProgress } from '@react-three/drei'
import React, { useRef, useState, useEffect, Suspense } from 'react'
import { Canvas, useFrame } from '@react-three/fiber'
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader'
function MyModel() {
const gltf = useGLTF('/3dModels/pictures_shower.glb')
return <primitive object={gltf.scene} position={[0, -1.5, 0]} scale={2.5} />
}
export default function Pics() {
const [model, setModel] = useState('none')
return (
<React.StrictMode>
<MyHeader setModel={setModel} />
<VitrineCanvas />
</React.StrictMode>
)
}
function VitrineCanvas() {
const qualityLevels = ['low', 'mid', 'high']
const imageThemes = ['noir-blanc', 'nerd', 'bg']
const imagesNum = [8, 18, 31] // nombre d’images dans chaque dossier respectif
const [image, setImage] = useState(0)
const [isLoading, setIsLoading] = useState(true)
const [loadedCount, setLoadedCount] = useState(0)
const imagesDirs = ['noir-blanc', 'nerd', 'bg']
const preloadDone = useRef(false)
const totalCount = imagesNum.reduce((a, b) => a + b, 0)
const [showCanvas, setShowCanvas] = useState(false)
const { active, loaded, total, progress } = useProgress()
const [canvasReady, setCanvasReady] = useState(false)
const [gltfLoaded, setGLTFLoaded] = useState(false)
const [imagesLoaded, setImagesLoaded] = useState(false)
const [rotationOffset, setRotationOffset] = useState(0)
const [imagesOppen, setImagesOppen] = useState(false)
useEffect(() => {
const container = document.querySelector('.carousel-3d')
if (!container) return
const onWheel = (e) => {
e.preventDefault()
const delta = e.deltaY > 0 ? 1 : -1
setRotationOffset((prev) => prev + delta * (360 / imagesNum[image])) // 20° par scroll
}
container.addEventListener('wheel', onWheel)
return () => container.removeEventListener('wheel', onWheel)
}, [image])
useEffect(() => {
if (imagesLoaded && gltfLoaded) {
setIsLoading(false)
setTimeout(() => {
setShowCanvas(true)
}, 150)
}
}, [imagesLoaded, gltfLoaded])
useEffect(() => {
const loader = new GLTFLoader()
loader.load(
'/3dModels/pictures_shower.glb',
(gltf) => {
console.log('✅ GLTF préchargé')
setGLTFLoaded(true)
},
undefined,
(error) => {
console.error('❌ Erreur lors du chargement GLTF', error)
}
)
}, [])
useEffect(() => {
if (!isLoading && showCanvas && canvasReady) {
const themeCount = imageThemes.length
const swapThemeImages = (themeIndex = 0) => {
if (themeIndex >= themeCount) return
let i = 1
const swapNext = () => {
if (i > imagesNum[themeIndex]) {
setTimeout(() => swapThemeImages(themeIndex + 1), 200) // passe au dossier suivant
return
}
const imgClass = `.VitrineImg-${themeIndex}-${i}`
const imgElement = document.querySelector(imgClass)
if (imgElement) {
const midSrc = `/pictures/mid/${imageThemes[themeIndex]}/${i}.jpg`
const newImg = new Image()
newImg.onload = () => {
imgElement.src = midSrc
i++
setTimeout(swapNext, 50)
}
newImg.onerror = () => {
i++
setTimeout(swapNext, 50)
}
newImg.src = midSrc
} else {
i++
setTimeout(swapNext, 50)
}
}
swapNext()
}
setTimeout(() => {
swapThemeImages()
}, 1000) // laisse tout se stabiliser d’abord
}
}, [isLoading, showCanvas, canvasReady])
useEffect(() => {
if (!active && loaded === total && progress === 100) {
setCanvasReady(true)
}
}, [active, loaded, total, progress])
useEffect(() => {
if (preloadDone.current) return
let total = 0
const preload = (dir, count, callback) => {
let i = 1
const loadNext = () => {
if (i > count) {
callback()
return
}
const img = new Image()
const onLoadOrError = () => {
total++
setLoadedCount(total)
i++
setTimeout(loadNext, 150) // délai entre les images
}
img.onload = onLoadOrError
img.onerror = onLoadOrError
img.src = `/pictures/${dir}/${i}.jpg`
}
loadNext()
}
let done = 0
const onPartDone = () => {
done++
if (done === imagesDirs.length) {
setImagesLoaded(true)
if (gltfLoaded) {
setIsLoading(false)
setTimeout(() => {
setShowCanvas(true)
}, 150)
}
}
}
for (let d = 0; d < imagesDirs.length; d++) {
preload(imagesDirs[d], imagesNum[d], onPartDone)
}
}, [])
useEffect(() => {
const allImages = document.querySelectorAll('.carousel-3d img')
const visibleImages = Array.from(allImages).filter((img) => {
const rect = img.getBoundingClientRect()
return (
rect.width > 0 &&
rect.height > 0 &&
window.getComputedStyle(img).display !== 'none' &&
img.offsetParent !== null
)
})
const total = visibleImages.length
const anglePerImage = 360 / total
const centerAngle = 0 // l’angle de la caméra en face
const radius = 200
let closestImage = null
let closestDelta = Infinity
// 1. Déterminer quelle image est la plus proche du centre
visibleImages.forEach((img, i) => {
const angle = (anglePerImage * i + rotationOffset) % 360
const delta = Math.abs(((angle - centerAngle + 540) % 360) - 180)
if (delta < closestDelta) {
closestDelta = delta
closestImage = img
}
})
// 2. Appliquer les styles à chaque image
visibleImages.forEach((img, i) => {
const angle = (anglePerImage * i + rotationOffset) % 360
const isCentered = img === closestImage
const scale = isCentered ? 2 : 1.2
const z = isCentered ? radius + 50 : radius
const zIndex = isCentered ? 100 : 0
img.style.transform = `
rotateY(${angle}deg)
translateZ(${z}px)
scale(${scale})
`
img.style.zIndex = String(zIndex)
})
}, [image, rotationOffset])
const allImages = []
for (let themeIndex = 0; themeIndex < imageThemes.length; themeIndex++) {
for (let i = 1; i <= imagesNum[themeIndex]; i++) {
allImages.push(
<img
key={`img-${themeIndex}-${i}`}
src={`/pictures/low/${imageThemes[themeIndex]}/${i}.jpg`}
className={`VitrineImg-${themeIndex}-${i} VistrineImgs`}
style={{ display: image === themeIndex ? 'block' : 'none' }}
/>
)
}
}
console.log(imagesOppen)
return (
<>
<div
className="loader"
style={
isLoading
? { zIndex: '9999', animationName: 'LoaderArrive', opacity: 1 }
: { zIndex: '-9999', animationName: 'loaderDisapear', opacity: 0 }
}
>
Chargement des images… {loadedCount} / {totalCount}
</div>
{
<Canvas
style={{
width: '100vw',
height: 'calc(100vh - 100px)',
marginTop: '0',
}}
onClick={() => {
const imagesRot = [0, 6.28311 / 3 + 0.2, 6.28311 / 1.5]
rotation = imagesRot[image]
setImagesOppen(!imagesOppen)
console.log('hello world')
}}
camera={{ position: [0, 5, 0], fov: 50 }}
>
<ambientLight intensity={0.25} />
<pointLight position={[0, 5, 5]} intensity={0.25} />
<pointLight position={[5, 5, 10]} intensity={0.25} />
<pointLight position={[5, 5, 5]} intensity={0.25} />
<pointLight position={[-5, 5, -5]} intensity={0.25} />
<Vitrine
position={[-1, 0, 0]}
image={image}
setImage={setImage}
imagesOppen={imagesOppen}
/>
</Canvas>
}
<div
className="VitrineImages"
style={imagesOppen ? { opacity: '1' } : { opacity: '0' }}
>
<div className="carousel-3d">{allImages}</div>
</div>
</>
)
}
var rotation = 0
var tBefor = 0
function Vitrine({ image, setImage, imagesOppen }) {
const cubeRef = useRef()
useFrame(({ clock }) => {
cubeRef.current.position.y = 1
cubeRef.current.position.z = 0.5
const t = clock.getElapsedTime()
var tour = Math.floor((cubeRef.current.rotation.y + 1) / 6.283118)
var beforImage = image
const newImage = Math.floor(
(cubeRef.current.rotation.y + 1 - tour * 6.283118) / 6.283119 / 0.33
)
if (newImage !== image) {
setImage(newImage)
}
cubeRef.current.scale.set(0.45, 0.45, 0.45)
cubeRef.current.rotation.x = (3.14 / 2) * 3
var arriveOnImage = true
if (!arriveOnImage) {
arriveOnImage = boforImage != image ? true : false
}
if (
tour * 6.283118 + (image - 1) * (3.14 / 1.5) + 2.5 <=
cubeRef.current.rotation.y + 1
) {
arriveOnImage = false
}
if (cubeRef.current) {
if (!imagesOppen) {
if (arriveOnImage) {
rotation += 1 * (t - tBefor)
} else {
rotation += 0.75 * (t - tBefor)
}
}
cubeRef.current.rotation.y = rotation
// légère rotation Y
}
tBefor = t
})
return (
<mesh ref={cubeRef}>
<MyModel />
</mesh>
)
}
Modify VitrineCanvas to use IntersectionObserver:
For each image in your allImages array, initially set its src to the low-quality version.
Attach an IntersectionObserver to each image element.
When an image enters the viewport (or a certain threshold near it), update its src to the mid-quality version.
You could even have another step to load the high-quality version when it's more centrally focused in the carousel.
Example IntersectionObserver logic (Conceptual):
JavaScript
// Inside VitrineCanvas component
useEffect(() => {
const imageElements = document.querySelectorAll('.VitrineImages img');
const observerOptions = {
root: null, // viewport
rootMargin: '0px',
threshold: 0.1 // Trigger when 10% of the image is visible
};
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const img = entry.target;
const fullSrc = img.dataset.fullSrc; // Store mid/high quality URL in a data attribute
if (img.src !== fullSrc) { // Prevent re-loading if already loaded
const tempImg = new Image();
tempImg.src = fullSrc;
tempImg.onload = () => {
img.src = fullSrc;
img.classList.add('loaded'); // Add a class for styling if needed
};
observer.unobserve(img); // Stop observing once loaded
}
}
});
}, observerOptions);
imageElements.forEach(img => {
// Set a data attribute with the mid-quality source
const themeIndex = parseInt(img.dataset.themeIndex);
const imgNumber = parseInt(img.dataset.imgNumber);
img.dataset.fullSrc = `/pictures/mid/<span class="math-inline">\{imageThemes\[themeIndex\]\}/</span>{imgNumber}.jpg`; // Or high
observer.observe(img);
});
return () => {
imageElements.forEach(img => imageObserver.unobserve(img));
};
}, [imageThemes, imagesNum]); // Dependencies for useEffect
You'd need to adjust your allImages.push() to include data-full-src and other data attributes like data-theme-index and data-img-number for easier reference.
While you stated you think you need to do it by your frontend, your Node.js server is actually a powerful tool for solving this problem.
Image Resizing and Compression on the Fly (or Pre-generated):
Serve different image sizes based on client needs.
Compress images without losing too much perceived quality.
Content Delivery Network (CDN): For production, serving images from a CDN drastically reduces load times and improves global accessibility.
Server-Side Image Processing Library:
Use libraries like Sharp or Jimp in Node.js to resize and compress images.
You can set up an API endpoint like /images/:quality/:theme/:id.jpg.
When a request comes in, your Node.js server can:
Check if the requested quality (e.g., low, mid, high) exists.
If not, process the original high-quality image to the requested size/quality, save it (to avoid re-processing), and then send it.
This is called on-demand image optimization.
Example Server-Side Logic (Conceptual with Express and Sharp):
JavaScript
// In your Node.js server file
const express = require('express');
const sharp = require('sharp');
const path = require('path');
const fs = require('fs');
const app = express();
const imagesBaseDir = path.join(__dirname, 'public', 'pictures'); // Adjust path
app.get('/pictures/:quality/:theme/:filename', async (req, res) => {
const { quality, theme, filename } = req.params;
const originalImagePath = path.join(imagesBaseDir, 'original', theme, filename); // Assume 'original' folder for highest quality
if (!fs.existsSync(originalImagePath)) {
return res.status(404).send('Image not found');
}
try {
let imageStream = sharp(originalImagePath);
switch (quality) {
case 'low':
imageStream = imageStream.resize({ width: 300 }).jpeg({ quality: 50 });
break;
case 'mid':
imageStream = imageStream.resize({ width: 800 }).jpeg({ quality: 75 });
break;
case 'high':
imageStream = imageStream.resize({ width: 1600 }).jpeg({ quality: 90 });
break;
default:
imageStream = imageStream.jpeg({ quality: 80 }); // Default to a good quality
}
res.setHeader('Content-Type', 'image/jpeg'); // Or image/png, etc.
imageStream.pipe(res);
} catch (error) {
console.error('Error processing image:', error);
res.status(500).send('Error processing image');
}
});
// ... other server setup
You would then update your frontend src attributes to point to these new server endpoints.
While you're preloading the GLTF model, ensure it's also optimized.
Compress GLTF/GLB Files: Use tools like gltf-pipeline to compress your 3D models. This reduces file size significantly.
Draco Compression: If your model contains complex meshes, consider applying Draco compression. useGLTF from @react-three/drei supports Draco loader automatically if configured.
Bash
# Install gltf-pipeline
npm install -g gltf-pipeline
# Compress your model with Draco
gltf-pipeline -i pictures_shower.glb -o pictures_shower_draco.glb --draco
Then, update your useGLTF path to the compressed version.
Your current loading screen reports loadedCount / totalCount based on low-quality image preloading. You want this to reflect the actual readiness for interaction.
Focus on Initial Render Readiness: Your loading screen should disappear when the essential elements (GLTF model, low-quality images) are ready for the user to start interacting, not necessarily when every single high-quality image is loaded.
Provide User Feedback for Progressive Loads: Once the main loader is gone, if mid/high quality images are still loading, you could show small loading indicators on individual images, or a subtle spinner on the carousel itself.
Your useEffect that sets isLoading(false) and setShowCanvas(true) when imagesLoaded && gltfLoaded is conceptually correct for initial readiness.
The "mid-quality swap" logic that runs after isLoading is false should be integrated with the IntersectionObserver approach, rather than a blanket swap.
You're already doing display: none for non-selected themes, which is good. Ensure that images for inactive themes or those far off-screen in the carousel are not fetched until needed. The IntersectionObserver helps with this for images within the active carousel.
VitrineCanvas Structure (High-Level)JavaScript
import './contact.css';
import './pics.css';
import { MyHeader } from './Welcome';
import { useGLTF, useProgress } from '@react-three/drei';
import React, { useRef, useState, useEffect, Suspense, useCallback } from 'react';
import { Canvas, useFrame } from '@react-three/fiber';
import { GLTFLoader } from 'three/examples/jsm/loaders/GLTFLoader';
// ... MyModel component (unchanged)
export default function Pics() {
const [model, setModel] = useState('none');
return (
<React.StrictMode>
<MyHeader setModel={setModel} />
<VitrineCanvas />
</React.StrictMode>
);
}
function VitrineCanvas() {
const qualityLevels = ['low', 'mid', 'high']; // Use these for server requests
const imageThemes = ['noir-blanc', 'nerd', 'bg'];
const imagesNum = [8, 18, 31]; // number of images in each respective folder
const [image, setImage] = useState(0); // Currently active theme/image set
const [isLoadingInitial, setIsLoadingInitial] = useState(true); // For initial low-res + GLTF
const [loadedLowResCount, setLoadedLowResCount] = useState(0);
const totalLowResImages = imagesNum.reduce((a, b) => a + b, 0);
const [gltfLoaded, setGLTFLoaded] = useState(false);
const [lowResImagesPreloaded, setLowResImagesPreloaded] = useState(false);
const [showCanvas, setShowCanvas] = useState(false); // To control canvas visibility
const [rotationOffset, setRotationOffset] = useState(0);
const [imagesOpen, setImagesOpen] = useState(false); // Misspelled in original, corrected to 'Open'
// --- GLTF Model Preload ---
useEffect(() => {
const loader = new GLTFLoader();
loader.load(
'/3dModels/pictures_shower_draco.glb', // Use compressed GLB if available
(gltf) => {
console.log('✅ GLTF preloaded');
setGLTFLoaded(true);
},
undefined,
(error) => {
console.error('❌ Error loading GLTF', error);
}
);
}, []);
// --- Low-Resolution Image Preload (for initial loader) ---
useEffect(() => {
let loadedCount = 0;
let imagesToLoad = totalLowResImages;
if (imagesToLoad === 0) { // Handle case with no images
setLowResImagesPreloaded(true);
return;
}
const loadNextImage = (themeIndex = 0, imgNumber = 1) => {
if (themeIndex >= imageThemes.length) {
setLowResImagesPreloaded(true);
return;
}
if (imgNumber > imagesNum[themeIndex]) {
loadNextImage(themeIndex + 1, 1); // Move to next theme
return;
}
const img = new Image();
const src = `/pictures/low/${imageThemes[themeIndex]}/${imgNumber}.jpg`; // Assuming server handles this path
img.onload = img.onerror = () => {
loadedCount++;
setLoadedLowResCount(loadedCount);
// Throttle loading slightly to prevent freezing UI
setTimeout(() => loadNextImage(themeIndex, imgNumber + 1), 10); // Small delay
};
img.src = src;
};
loadNextImage(); // Start loading
}, []);
// --- Determine when initial loading is complete ---
useEffect(() => {
if (gltfLoaded && lowResImagesPreloaded) {
setIsLoadingInitial(false);
setTimeout(() => {
setShowCanvas(true);
}, 150); // Small delay before showing canvas
}
}, [gltfLoaded, lowResImagesPreloaded]);
// --- Carousel Wheel Event ---
useEffect(() => {
const container = document.querySelector('.carousel-3d');
if (!container) return;
const onWheel = (e) => {
e.preventDefault();
const delta = e.deltaY > 0 ? 1 : -1;
// Adjust rotation calculation based on your carousel's angle per image
setRotationOffset((prev) => prev + delta * (360 / imagesNum[image]));
};
container.addEventListener('wheel', onWheel);
return () => container.removeEventListener('wheel', onWheel);
}, [image, imagesNum]); // Added imagesNum to dependencies
// --- Intersection Observer for Progressive Image Loading ---
useEffect(() => {
if (!showCanvas) return; // Only set up observer once canvas is shown
const observerOptions = {
root: null, // viewport
rootMargin: '150px', // Load images when they are 150px away from viewport
threshold: 0.01 // Small threshold to trigger early
};
const imageObserver = new IntersectionObserver((entries, observer) => {
entries.forEach(entry => {
if (entry.isIntersecting) {
const imgElement = entry.target;
const midSrc = imgElement.dataset.midSrc;
const highSrc = imgElement.dataset.highSrc;
// Load mid-quality if not already loaded, then high-quality
if (imgElement.src !== highSrc) { // If high-quality not loaded
const tempImg = new Image();
tempImg.onload = () => {
imgElement.src = tempImg.src; // Update src
// Optional: Load high quality after mid if needed
if (imgElement.dataset.highLoaded !== 'true' && highSrc) {
const highTempImg = new Image();
highTempImg.onload = () => {
imgElement.src = highSrc;
imgElement.dataset.highLoaded = 'true';
};
highTempImg.src = highSrc;
}
};
tempImg.onerror = () => {
console.warn('Failed to load image:', midSrc);
};
tempImg.src = midSrc;
imgElement.dataset.midLoaded = 'true'; // Mark mid as loaded
}
// Don't unobserve immediately if you want to swap between mid/high
// If you only want to load once and keep, then unobserve:
// observer.unobserve(imgElement);
}
});
}, observerOptions);
// Observe all images
const allCarouselImages = document.querySelectorAll('.carousel-3d img');
allCarouselImages.forEach(img => {
observer.observe(img);
});
return () => {
allCarouselImages.forEach(img => observer.unobserve(img));
};
}, [showCanvas, imageThemes, imagesNum]); // Re-run if these dependencies change
// --- Carousel 3D Positioning Logic (unchanged from your original) ---
useEffect(() => {
const allImages = document.querySelectorAll('.carousel-3d img');
const visibleImages = Array.from(allImages).filter((img) => {
const rect = img.getBoundingClientRect();
return (
rect.width > 0 &&
rect.height > 0 &&
window.getComputedStyle(img).display !== 'none' &&
img.offsetParent !== null
);
});
const total = visibleImages.length;
if (total === 0) return; // Prevent division by zero
const anglePerImage = 360 / total;
const centerAngle = 0;
const radius = 200;
let closestImage = null;
let closestDelta = Infinity;
visibleImages.forEach((img, i) => {
const angle = (anglePerImage * i + rotationOffset) % 360;
const delta = Math.abs(((angle - centerAngle + 540) % 360) - 180);
if (delta < closestDelta) {
closestDelta = delta;
closestImage = img;
}
});
visibleImages.forEach((img, i) => {
const angle = (anglePerImage * i + rotationOffset) % 360;
const isCentered = img === closestImage;
const scale = isCentered ? 2 : 1.2;
const z = isCentered ? radius + 50 : radius;
const zIndex = isCentered ? 100 : 0;
img.style.transform = `
rotateY(${angle}deg)
translateZ(${z}px)
scale(${scale})
`;
img.style.zIndex = String(zIndex);
});
}, [image, rotationOffset]); // Added image to dependencies as it affects which set is visible
// --- Prepare all image elements for render ---
const allImageElements = [];
for (let themeIndex = 0; themeIndex < imageThemes.length; themeIndex++) {
for (let i = 1; i <= imagesNum[themeIndex]; i++) {
const lowSrc = `/pictures/low/${imageThemes[themeIndex]}/${i}.jpg`;
const midSrc = `/pictures/mid/${imageThemes[themeIndex]}/${i}.jpg`; // Server-side optimized mid-res
const highSrc = `/pictures/high/${imageThemes[themeIndex]}/${i}.jpg`; // Server-side optimized high-res
allImageElements.push(
<img
key={`img-${themeIndex}-${i}`}
src={lowSrc} // Start with low quality
className={`VitrineImg-${themeIndex}-${i} VistrineImgs`}
style={{ display: image === themeIndex ? 'block' : 'none' }}
// Custom data attributes for Intersection Observer
data-theme-index={themeIndex}
data-img-number={i}
data-mid-src={midSrc}
data-high-src={highSrc}
data-mid-loaded="false"
data-high-loaded="false"
/>
);
}
}
return (
<>
<div
className="loader"
style={
isLoadingInitial
? { zIndex: '9999', animationName: 'LoaderArrive', opacity: 1 }
: { zIndex: '-9999', animationName: 'loaderDisapear', opacity: 0 }
}
>
Chargement des images… {loadedLowResCount} / {totalLowResImages}
</div>
{/* Conditionally render Canvas to prevent unnecessary mounts/unmounts */}
{showCanvas && (
<Canvas
style={{
width: '100vw',
height: 'calc(100vh - 100px)',
marginTop: '0',
}}
onClick={() => {
// Your existing logic for imagesRot and rotation
const imagesRot = [0, 6.28311 / 3 + 0.2, 6.28311 / 1.5];
rotation = imagesRot[image]; // `rotation` is a global var, consider making it state or ref
setImagesOpen(!imagesOpen);
console.log('hello world');
}}
camera={{ position: [0, 5, 0], fov: 50 }}
>
<ambientLight intensity={0.25} />
<pointLight position={[0, 5, 5]} intensity={0.25} />
<pointLight position={[5, 5, 10]} intensity={0.25} />
<pointLight position={[5, 5, 5]} intensity={0.25} />
<pointLight position={[-5, 5, -5]} intensity={0.25} />
{/* Ensure Vitrine component is memoized or optimized if it causes re-renders */}
<Vitrine
position={[-1, 0, 0]}
image={image}
setImage={setImage}
imagesOpen={imagesOpen} // Corrected prop name
/>
</Canvas>
)}
<div
className="VitrineImages"
style={imagesOpen ? { opacity: '1' } : { opacity: '0' }}
>
<div className="carousel-3d">{allImageElements}</div>
</div>
</>
);
}
// Global variable, consider making it a ref within VitrineCanvas or Vitrine
// if it causes issues with strict mode or concurrent rendering
var rotation = 0;
var tBefor = 0;
function Vitrine({ image, setImage, imagesOpen }) { // Corrected prop name
const cubeRef = useRef();
useFrame(({ clock }) => {
cubeRef.current.position.y = 1;
cubeRef.current.position.z = 0.5;
const t = clock.getElapsedTime();
var tour = Math.floor((cubeRef.current.rotation.y + 1) / 6.283118);
var beforImage = image; // Misspelled 'beforeImage'
// This calculation of newImage based on rotation seems tied to your specific 3D setup
const newImage = Math.floor(
(cubeRef.current.rotation.y + 1 - tour * 6.283118) / 6.283119 / 0.33
);
if (newImage !== image) {
setImage(newImage);
}
cubeRef.current.scale.set(0.45, 0.45, 0.45);
cubeRef.current.rotation.x = (3.14 / 2) * 3;
var arriveOnImage = true;
if (!arriveOnImage) { // This logic branch seems to always be false
arriveOnImage = beforImage !== image; // Corrected comparison
}
if (
tour * 6.283118 + (image - 1) * (3.14 / 1.5) + 2.5 <=
cubeRef.current.rotation.y + 1
) {
arriveOnImage = false;
}
if (cubeRef.current) {
if (!imagesOpen) { // Corrected prop name
if (arriveOnImage) {
rotation += 1 * (t - tBefor);
} else {
rotation += 0.75 * (t - tBefor);
}
}
cubeRef.current.rotation.y = rotation;
}
tBefor = t;
});
return (
<mesh ref={cubeRef}>
<MyModel />
</mesh>
);
}