marks
marks

Reputation: 1391

Push particles away from mouseposition in glsl and three js

I have the following setup for my THREE.Points Object:

        this.particleGeometry = new THREE.BufferGeometry()
        this.particleMaterial = new THREE.ShaderMaterial(
            {
                vertexShader: vshader,
                fragmentShader: fshader,
                blending: THREE.AdditiveBlending,
                depthWrite: false,
                uniforms: {
                    uTime: new THREE.Uniform(0),
                    uMousePosition: this.mousePosition
                }
            }
        )

and then some code to place points in the BufferGeometry on a sphere. That is working fine.

Particlesphere

I also set up a Raycaster to track the mouse position intersecting a hidden plane and then update the uniform uMousePosition accordingly. That also works fine, I get the mouse position sent to my vertex shader.

Now I am trying to make the particles that are in a certain distance d to the mouse push away from it where the closest ones are pushed most of course, and also apply a gravity back to their original position to restore everything after time.

So here is what I have in my vertex shader:

void main() {
float lerp(float a, float b, float amount) {
    return a + (b - a) * amount;
}

void main() {
    vec3 p = position;

    float dist = min(distance(p, mousePosition), 1.);

    float lerpFactor = .2;

    p.x = lerp(p.x, position.x * dist, lerpFactor);
    p.y = lerp(p.y, position.y * dist, lerpFactor);
    p.z = lerp(p.z, position.z * dist, lerpFactor);//Mouse is always in z=0

    vec4 mvPosition = modelViewMatrix * vec4(p, 1.);
    gl_PointSize = 30. * (1. / -mvPosition.z );
    gl_Position = projectionMatrix * mvPosition;
}
}

And here is what it looks like when the mouse is outside the sphere (added a small sphere that moves with the mouseposition to indicate the mouseposition)

enter image description here

And here when the mouse is inside:

enter image description here

Outside already looks kind of correct, but mouse inside only moves the particles closer back to their original position, where it should push them further outside instead. I guess I somehow have to determine the direction of the distance.

Also, the lerp method does not lerp, the particles directly jump to their position.

So I wonder how I get the correct distance to the mouse to always move the particles in a certain area and also how to animate the lerp / gravity effect.

Upvotes: 3

Views: 3572

Answers (2)

Benedict Lang
Benedict Lang

Reputation: 175

Thanks @prisoner849 for the pen!

In case someone wants to know how to handle a rotating object collision where the local coordinate system does not match the world anymore:

https://codepen.io/benedictlang/pen/WNqEKxo

body{
  position: relative;
  overflow: hidden;
  margin: 0;
  height: 100%;
  width: 100%;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/[email protected]";
import { OrbitControls } from "https://cdn.skypack.dev/[email protected]/examples/jsm/controls/OrbitControls.js";
import * as BufferGeometryUtils from "https://cdn.skypack.dev/[email protected]/examples/jsm/utils/BufferGeometryUtils.js";

// Create scene, camera, and renderer
let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(0, 0, 10);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

let controls = new OrbitControls(camera, renderer.domElement);

// Create a marker cylinder
let marker = new THREE.Mesh(
  new THREE.CylinderGeometry(0.5, 0.5, 2, 16),
  new THREE.MeshBasicMaterial({ color: "red", wireframe: true })
);
scene.add(marker);

// Create IcosahedronGeometry and merge vertices
let g = new THREE.IcosahedronGeometry(4, 20);
g = BufferGeometryUtils.mergeVertices(g);

// Define uniforms
let uniforms = {
  mousePos: { value: new THREE.Vector3() }
};

// Define vertex and fragment shaders
let vertexShader = `
  uniform vec3 mousePos;
  varying float vDist;

  void main() {
    vec3 seg = position - mousePos;
    float dist = length(seg);
    float force = clamp(1.0 / (dist * dist), 0.0, 1.0);
    vec3 newPosition = position + normalize(seg) * force;
    vDist = dist;
    gl_Position = projectionMatrix * modelViewMatrix * vec4(newPosition, 1.0);
    gl_PointSize = 1.0;
  }
`;

let fragmentShader = `
  varying float vDist;

  void main() {
    vec3 color;
    float alpha;
    
    if (vDist < 2.0) {
      color = vec3(1.0, 0.0, 1.0); // Magenta color for particles influenced by the marker
      alpha = 1.0;
    } else {
      color = vec3(0.0, 1.0, 1.0); // Cyan color for particles not influenced by the marker
      alpha = 1.0;
    }
    
    gl_FragColor = vec4(color, alpha);
  }
`;

// Create ShaderMaterial
let material = new THREE.ShaderMaterial({
  uniforms: uniforms,
  vertexShader: vertexShader,
  fragmentShader: fragmentShader,
  transparent: true,
  depthTest: false // Disable depth testing for particles
});

// Create Points object
let p = new THREE.Points(g, material);
scene.add(p);


let clock = new THREE.Clock();

renderer.setAnimationLoop(() => {
  let t = clock.getElapsedTime();
  
  // Rotate the IcosahedronGeometry continuously
  p.rotation.x += 0.01;
  p.rotation.y += 0.01;
  
  // Compute the inverse of the object's world matrix
  p.updateMatrixWorld(); // Ensure the matrix world is up-to-date
  
  // Create a new matrix and copy the world matrix of the geometry
  let inverseMatrix = new THREE.Matrix4().copy(p.matrixWorld).invert();
  
  // Calculate the marker position in local coordinates
  let markerLocalPosition = marker.position.clone().applyMatrix4(inverseMatrix);
  
  // Update the mouse position uniform with the local position
  uniforms.mousePos.value.copy(markerLocalPosition);
  
  // Move the marker
  marker.position.x = Math.sin(t * 0.5) * 5;
  marker.position.y = Math.cos(t * 0.3) * 5;
  
  // Render the scene
  renderer.render(scene, camera);
});

</script>

Upvotes: 0

prisoner849
prisoner849

Reputation: 17596

That's how you could do it as a first approximation:

body{
  overflow: hidden;
  margin: 0;
}
<script type="module">
import * as THREE from "https://cdn.skypack.dev/[email protected]";
import {OrbitControls} from "https://cdn.skypack.dev/[email protected]/examples/jsm/controls/OrbitControls.js";
import * as BufferGeometryUtils from "https://cdn.skypack.dev/[email protected]/examples/jsm/utils/BufferGeometryUtils.js";

let scene = new THREE.Scene();
let camera = new THREE.PerspectiveCamera(60, innerWidth / innerHeight, 1, 100);
camera.position.set(0, 0, 10);
let renderer = new THREE.WebGLRenderer();
renderer.setSize(innerWidth, innerHeight);
document.body.appendChild(renderer.domElement);

let controls = new OrbitControls(camera, renderer.domElement);

let marker = new THREE.Mesh(new THREE.SphereGeometry(0.5, 16, 8), new THREE.MeshBasicMaterial({color: "red", wireframe: true}));
scene.add(marker);

let g = new THREE.IcosahedronGeometry(4, 20);
g = BufferGeometryUtils.mergeVertices(g);
let uniforms = {
  mousePos: {value: new THREE.Vector3()}
}
let m = new THREE.PointsMaterial({
  size: 0.1,
  onBeforeCompile: shader => {
    shader.uniforms.mousePos = uniforms.mousePos;
    shader.vertexShader = `
      uniform vec3 mousePos;
      ${shader.vertexShader}
    `.replace(
      `#include <begin_vertex>`,
      `#include <begin_vertex>
        
        vec3 seg = position - mousePos;
        vec3 dir = normalize(seg);
        float dist = length(seg);
        if (dist < 2.){
          float force = clamp(1. / (dist * dist), 0., 1.);
          transformed += dir * force;
        }
      
      `
    );
    console.log(shader.vertexShader);
  }
});
let p = new THREE.Points(g, m);
scene.add(p);

let clock = new THREE.Clock();

renderer.setAnimationLoop( _ => {
  let t = clock.getElapsedTime();
  marker.position.x = Math.sin(t * 0.5) * 5;
  marker.position.y = Math.cos(t * 0.3) * 5;
  uniforms.mousePos.value.copy(marker.position);
  renderer.render(scene, camera);
})
</script>

Upvotes: 9

Related Questions