Simon
Simon

Reputation: 13

I'm trying the cut these spheres into the other direction to keep the "bitten part" of the spheres

Title: How to properly quarter-cut spheres in Three.js with clipping planes?

Body:

I'm working on a Three.js project where I'm trying to create a visual of layered spheres, and I want to cut each sphere in a "quarter-cut" style — keeping three quarters of the sphere while removing one quarter, like in these typical cross-section graphics of the Earth's layers.

So far, I’ve been trying to use clipping planes, but I can’t seem to get the right part of the sphere to remain — it either cuts too much or leaves the wrong section. I tried flipping the planes and moving them on different axes, but it didn’t help.

Here’s the current code:

<!DOCTYPE html>
<html lang="en">
<head>
    <meta charset="UTF-8">
    <meta name="viewport" content="width=device-width, initial-scale=1.0">
    <title>Orbital</title>
    <style>
        #slider, #rotationSlider {
            position: absolute;
            top: 10px;
            left: 50%;
            transform: translateX(-50%);
        }
        #rotationSlider {
            top: 40px;
        }
    </style>
</head>
<body>
    <input type="range" id="slider" min="-10" max="10" value="0" step="0.1">
    <input type="range" id="rotationSlider" min="0" max="0.1" value="0.01" step="0.001">
    <script type="module">
        import * as THREE from "https://esm.sh/[email protected]";
        import { OrbitControls } from "https://esm.sh/[email protected]/examples/jsm/controls/OrbitControls.js";

        const scene = new THREE.Scene();
        const camera = new THREE.PerspectiveCamera(75, window.innerWidth / window.innerHeight, 0.1, 1000);
        camera.position.z = 30;

        const renderer = new THREE.WebGLRenderer();
        renderer.localClippingEnabled = true;
        renderer.setSize(window.innerWidth, window.innerHeight);
        document.body.appendChild(renderer.domElement);

        const controls = new OrbitControls(camera, renderer.domElement);
        controls.enableDamping = true; // Enable damping (inertia)
        controls.dampingFactor = 0.25; // Damping factor
        controls.enableZoom = true; // Enable zooming

        const ambientLight = new THREE.AmbientLight(0xffffff, 0.5); // Soft white light
        scene.add(ambientLight);

        const directionalLight = new THREE.DirectionalLight(0xffffff, 1);
        directionalLight.position.set(5, 5, 5).normalize();
        scene.add(directionalLight);

        // Create a clipping plane
        // Angled planes meeting at the "bite" point
        const cut = 3;
        const planeX = new THREE.Plane(new THREE.Vector3(-1, 0, 0), -cut);
        const planeY = new THREE.Plane(new THREE.Vector3(0, -1, 0), -cut);
        const planeHelperX = new THREE.PlaneHelper(planeX, 20, 0xffffff);
        const planeHelperY = new THREE.PlaneHelper(planeY, 20, 0xffffff);
        scene.add(planeHelperX);
        scene.add(planeHelperY);
        
        // Add additional spheres
        const numSpheres = 6;
        const radius = 3;
        const spheres = [];
        for (let i = 0; i < numSpheres; i++) {
            const colors = [0xff0000, 0xff3b3b, 0xff6b6b, 0xff9292, 0xffbdbd, 0xfffafa];
            const angle = (i / numSpheres) * Math.PI * 2;

            const material = new THREE.MeshPhongMaterial({
                color: colors[i],
                side: THREE.DoubleSide,
                wireframe: false,
                transparent: false,
                opacity: 1-(i/numSpheres),
                clippingPlanes: [ planeX, planeY ],
                clipShadows: true
            });
            const geometry = new THREE.SphereGeometry(radius + i, 64, 64);
            const sphere = new THREE.Mesh(geometry, material);
            sphere.position.set(0, 0, 0);
            spheres.push(sphere);
            scene.add(sphere);
        }

        // Create particles as small spheres
        const particleCountX = 150;
        const particleCountY = 150;
        const particleGeometry = new THREE.SphereGeometry(0.05, 8, 8);
        const particleMaterial = new THREE.MeshPhongMaterial({ color: 0xffffff, transparent:true, opacity:0.2 });
        const particlesMatrix = [];
        const particleAngles = [];
        for (let j = 0; j < particleCountX; j++) {
            particlesMatrix[j] = [];
            for (let k = 0; k < particleCountY; k++) {
                const particle = new THREE.Mesh(particleGeometry, particleMaterial);
                particlesMatrix[j][k] = particle;
                particleAngles.push({ angleX: (j / particleCountX) * Math.PI * 2, angleY: (k / particleCountY) * Math.PI * 2 });
                scene.add(particle);
            }
        }

        // Handle slider input
        slider.addEventListener('input', function() {
            const depth = parseFloat(this.value);
            planeX.constant = depth;
            planeY.constant = depth;
        });

        const rotationSlider = document.getElementById('rotationSlider');
        let rotationSpeed = parseFloat(rotationSlider.value);
        rotationSlider.addEventListener('input', function() {
            rotationSpeed = parseFloat(this.value);
        });

        let clock = new THREE.Clock();
        let timeScale = 10; // Adjust this value to slow down or speed up the particle movement

        // Animation loop
        function animate() {
            requestAnimationFrame(animate);

            let deltaTime = clock.getDelta() * timeScale;

            // Update particles to follow spheres
            for (let j = 0; j < particleCountX; j++) {
                for (let k = 0; k < particleCountY; k++) {
                    const particle = particlesMatrix[j][k];
                    const sphere = spheres[(j + k) % spheres.length];
                    const angles = particleAngles[j * particleCountY + k];
                    angles.angleX += rotationSpeed * deltaTime;
                    angles.angleY += rotationSpeed * deltaTime;
                    const x = sphere.position.x + (radius + (j + k) % numSpheres) * Math.cos(angles.angleX) * Math.sin(angles.angleY);
                    const y = sphere.position.y + (radius + (j + k) % numSpheres) * Math.sin(angles.angleX) * Math.sin(angles.angleY);
                    const z = sphere.position.z + (radius + (j + k) % numSpheres) * Math.cos(angles.angleY);
                    particle.position.set(x, y, z);
                }
            }

            // Rotate spheres
            spheres.forEach(sphere => {
                sphere.rotation.y += rotationSpeed * deltaTime;
            });

            controls.update();
            renderer.render(scene, camera);
        }
        animate();
    </script>
</body>
</html>

What I’d like to achieve:

Keep three quarters of the spheres (like a visible cross-section) while cutting along two perpendicular planes.

Adjust the cut depth with a slider.

What’s happening now:

The planes cut the spheres, but I often see the wrong part of the shape remaining.

What am I missing? Is there a better approach to achieve this type of clipping in Three.js? Any guidance would be appreciated!

Upvotes: 1

Views: 24

Answers (0)

Related Questions