You need to enable JavaScript to run this app.
最新活动
大模型
产品
解决方案
定价
生态与合作
支持与服务
开发者
了解我们

Three.js大量可拖拽对象优化:如何实现粒子系统单个粒子拖拽?

解决大量粒子的单个拖拽问题

Hey there! I get your frustration—200k+ objects with CircleGeometry is a total performance nightmare, but switching to Points gives you speed at the cost of losing individual drag functionality. Let's fix that because you don't have to choose between performance and interactivity.

Why Drag Controls Won't Work Here

First, let's break it down: DragControls is built to handle individual Object3D instances (like your plane1 mesh). A Points object is a single geometry packed with thousands of vertices—Drag Controls can't tell the difference between one vertex and another; it just sees the entire Points system as one draggable thing. So we need a custom solution using raycasting instead.

Step-by-Step Solution

We'll use THREE.Raycaster to detect which particle the user clicks, then manually handle the drag logic with mouse events. Here's how to modify your code:

1. Add Raycaster and Mouse State Variables

First, declare these at the top of your script to track mouse state and raycasting tools:

let raycaster = new THREE.Raycaster();
let mouse = new THREE.Vector2();
let selectedParticleIndex = null;
let dragOffset = new THREE.Vector3();
// Use your plane's orientation as the drag reference plane
let dragPlane = new THREE.Plane(new THREE.Vector3(0, 0, 1), 0);

2. Keep Your Particle System Setup (With a Small Adjustment)

Keep your existing Points setup, but note that we'll need to flag vertex updates later:

// Your existing particle setup code here...
particleSystem = new THREE.Points(geometry, material);
scene.add(particleSystem);
// We'll toggle this to true when we modify vertices
particleSystem.geometry.verticesNeedUpdate = false;

3. Replace DragControls with Custom Mouse Event Handlers

Remove your DragControls code and add these event listeners to manage clicks and drags:

// Hook up mouse events to your container element
container.addEventListener('mousedown', onMouseDown);
container.addEventListener('mousemove', onMouseMove);
container.addEventListener('mouseup', onMouseUp);
container.addEventListener('mouseleave', onMouseUp);

function onMouseDown(event) {
    // Convert mouse position to Three.js normalized device coordinates
    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

    // Update raycaster with camera and mouse position
    raycaster.setFromCamera(mouse, camera);
    // Adjust detection range to match your particle size
    raycaster.params.Points.threshold = material.size * 2;

    // Check for intersections with the particle system
    const intersects = raycaster.intersectObject(particleSystem);
    if (intersects.length > 0) {
        // Store the index of the clicked particle
        selectedParticleIndex = intersects[0].index;
        const selectedVertex = particleSystem.geometry.vertices[selectedParticleIndex];
        
        // Calculate the offset between the mouse hit and the particle's actual position
        const hitPoint = intersects[0].point;
        dragOffset.subVectors(selectedVertex, hitPoint);
    }
}

function onMouseMove(event) {
    if (selectedParticleIndex === null) return;

    mouse.x = (event.clientX / window.innerWidth) * 2 - 1;
    mouse.y = -(event.clientY / window.innerHeight) * 2 + 1;

    raycaster.setFromCamera(mouse, camera);
    // Find where the mouse intersects our drag plane
    let intersectionPoint = new THREE.Vector3();
    raycaster.ray.intersectPlane(dragPlane, intersectionPoint);

    // Update the selected particle's position with the saved offset
    const selectedVertex = particleSystem.geometry.vertices[selectedParticleIndex];
    selectedVertex.copy(intersectionPoint.add(dragOffset));
    
    // Tell Three.js to update the vertices on the next render
    particleSystem.geometry.verticesNeedUpdate = true;
}

function onMouseUp() {
    // Release the selected particle
    selectedParticleIndex = null;
}

4. Optional Performance Tweaks for 200k Particles

With that many particles, small optimizations go a long way:

  • Switch to BufferGeometry instead of Geometry (it's far more efficient for large datasets—you'll just need to access vertices via attributes.position.array instead of the vertices array)
  • If precise detection isn't critical, reduce the raycast threshold or add a debounce to mousemove events
  • Consider using a shader-based approach for even better performance, but the above solution works great for most use cases

Why This Works

  • Raycaster lets us pinpoint exactly which particle is under the mouse, even in a massive Points system
  • We manually update the vertex position during drag and flag the geometry for re-rendering
  • This approach keeps the performance benefits of Points (single draw call, minimal overhead) while adding the individual particle interactivity you need

内容的提问来源于stack exchange,提问作者HS_hendrix

火山引擎 最新活动