File size: 3,279 Bytes
ab81f90
 
 
 
 
 
 
 
 
 
 
 
 
 
8450fd0
ab81f90
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
 
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
import React, { useState, useMemo, useEffect } from 'react';
import { Edges } from '@react-three/drei';
import * as THREE from 'three';

const FeatureSlice = ({ texture, gridSize, index, sizeY, sizeZ, xOffset, geometry }) => {
    const sliceTexture = useMemo(() => {
        const cloned = texture.clone();
        cloned.magFilter = THREE.NearestFilter;
        cloned.minFilter = THREE.NearestFilter;
        cloned.repeat.set(1 / gridSize, 1 / gridSize);
        const col = index % gridSize;
        const row = Math.floor(index / gridSize);
        cloned.offset.set(col / gridSize, 1 - (row + 1) / gridSize);
        
        if (cloned.image && cloned.image.width > 0) {
            cloned.needsUpdate = true;
        }
        
        return cloned;
    }, [texture, gridSize, index]);

    useEffect(() => {
        return () => {
            sliceTexture.dispose();
        };
    }, [sliceTexture]);

    return (
        <mesh position={[xOffset, 0, 0]} rotation={[0, -Math.PI / 2, 0]} geometry={geometry}>
            <meshBasicMaterial
                map={sliceTexture}
                transparent={true}
                opacity={0.8}
                alphaTest={0.1}
                depthWrite={false}
                blending={THREE.AdditiveBlending}
                side={THREE.DoubleSide}
            />
        </mesh>
    );
};

export const LayerCube = ({ position, size, shape, textureUrl, isSelected }) => {
    const [sizeX, sizeY, sizeZ] = size;
    const channels = shape[2] || 1;
    const numFeatures = Math.min(channels, 24); 
    const gridSize = Math.ceil(Math.sqrt(Math.min(channels, 64)));

    const [imageEl, setImageEl] = useState(null);

    useEffect(() => {
        if (!textureUrl) return;
        const img = new window.Image();
        img.src = textureUrl;
        img.onload = () => setImageEl(img);
    }, [textureUrl]);

    const sliceGeometry = useMemo(() => new THREE.PlaneGeometry(sizeZ, sizeY), [sizeZ, sizeY]);
    const fallbackTexture = useMemo(() => new THREE.Texture(), []);

    const baseTexture = useMemo(() => {
        if (!imageEl) return fallbackTexture;
        const tex = new THREE.Texture(imageEl);
        tex.needsUpdate = true;
        return tex;
    }, [imageEl, fallbackTexture]);

    return (
        <group position={position}>
            <mesh>
                <boxGeometry args={[sizeX, sizeY, sizeZ]} />
                <meshBasicMaterial color="#050a1f" transparent opacity={0.3} side={THREE.DoubleSide} depthWrite={false} />
                <Edges scale={1.01} threshold={15} color={isSelected ? "#00ffcc" : "#4a90e2"} />
            </mesh>

            {Array.from({ length: numFeatures }).map((_, index) => {
                const xOffset = (-sizeX / 2) * 0.95 + (sizeX * 0.95 * index) / Math.max(1, numFeatures - 1);
                return (
                    <FeatureSlice
                        key={index}
                        texture={baseTexture}
                        gridSize={gridSize}
                        index={index}
                        sizeY={sizeY}
                        sizeZ={sizeZ}
                        xOffset={xOffset}
                        geometry={sliceGeometry}
                    />
                );
            })}
        </group>
    );
};