Create collision with particles of a 3d model Three.js

I want to know, if there is any way to get a particle collision in three.js using a 3D model, I have tried it but it has not kept as I want. I would also like to create an elastic collision so that each particle goes to the opposite angle of the pointer

https://codepen.io/AlainBarrios/pen/YoNooV?editors=0010

const s = {
  vs: `
      uniform float scale;
      uniform vec3 mouse;

      varying vec2 vUv;

      void main(){
        vUv = uv;
        vec3 newpos = position;
        float d = length(newpos - mouse);
        float maxRadius = 5.;

        if(d < maxRadius){
            float koef = d / maxRadius;
            newpos *= vec3(2., 2., 1.);
            newpos = mix(position, newpos, koef);
        }

        vec4 mvPosition = modelViewMatrix * vec4(newpos, 1.0);
        gl_PointSize = 100. * (1. / - mvPosition.z);
        gl_Position = projectionMatrix * mvPosition;
      }
`,
  fs: `
      varying vec2 vUv;

      void main(){

        vec2 uv = vUv;
        gl_FragColor = vec4(1., 1., 1., 1.);
      }
`
};

// Carga modelos 3D externos o texturas dependiendo de la opcion
const loaderFiles = {
  gltf: new THREE.GLTFLoader(),
  texture: new THREE.TextureLoader()
};

class WebGL {
  constructor() {
    this.renderer = new THREE.WebGLRenderer({ antialias: true });
    this.camera = new THREE.PerspectiveCamera(
      45,
      innerWidth / innerHeight,
      0.1,
      1000
    );
    this.scene = new THREE.Scene();
    this.group = new THREE.Group();
    this.clock = new THREE.Clock();
    this.raycaster = new THREE.Raycaster();
    this.mouse = new THREE.Vector2();

    this.helper = new Helper(this.scene);
    this.loader = loaderFiles.gltf;

    this.update = this.update.bind(this);
    this.onResize = this.onResize.bind(this);
  }

  // Coloca el objeto renderer dentro del DOM
  // Instaciamos la clase OrbitControls para mover la camara
  // Agrega la camara y el objeto group dentro de la escena
  init() {
    const _contentCanvas = document.querySelector("#content-canvas");

    this.renderer.setSize(innerWidth, innerHeight);
    this.renderer.shadowMap.enabled = true;
    this.renderer.shadowMap.type = THREE.PCFSoftShadowMap;

    this.scene.add(this.camera);
    this.scene.add(this.group);

    this.scene.background = new THREE.Color(0x140000);

    this.controls = new THREE.OrbitControls(this.camera);

    this.camera.position.set(0, 10, 60);
    this.camera.lookAt(this.scene.position);

    this.helper.axes(100);

    _contentCanvas.appendChild(this.renderer.domElement);

    this.loadModel();

    window.addEventListener("resize", this.onResize);
    window.addEventListener("mousemove", ev => this.onMousemove(ev));
  }

  // Inicia todos los metodos que serviran para crear nuestro espacio y objetos
  loadModel() {
    this.loader.load(
      "https://rawcdn.githack.com/AlainBarrios/3d-models/694ea0751aab59c235390f61e81ee1d37f328d5a/knight_girl.glb?raw=true",
      gltf => {
        this.mat = new THREE.ShaderMaterial({
          uniforms: {
            time: {
              type: "f",
              value: 0
            },
            resolution: {
              type: "v2",
              value: new THREE.Vector2(innerWidth, innerHeight)
            },
            mouse: {
              type: "v3",
              value: new THREE.Vector2(0, 0, 0)
            },
            scale: {
              type: "f",
              value: innerHeight / 2
            }
          },
          vertexShader: s.vs,
          fragmentShader: s.fs
        });

        //const mat = new THREE.PointsMaterial({ color: 0xFFFFFF, size: 0.25 })
        const positions = this.combineBuffer(gltf, "position");

        const geo = new THREE.BufferGeometry();
        geo.addAttribute("position", positions);
        geo.addAttribute("initialPosition", positions);

        geo.computeBoundingBox();
        geo.attributes.position.setDynamic(true);

        this._modelMesh = new THREE.Points(geo, this.mat);
        this._modelMesh.position.set(0, -10, 0);
        this.scene.add(this._modelMesh);

        const planeGeo = new THREE.PlaneBufferGeometry(15, 50, 1, 1);

        this.planeMesh = new THREE.Mesh(
          planeGeo,
          new THREE.MeshBasicMaterial({ color: 0x000, visible: false })
        );

        this.planeMesh.position.set(0, 15, 0)

        this.scene.add(this.planeMesh);

        this._modelMesh.rotation.x = -Math.PI / 2;
        this._modelMesh.rotation.z = -Math.PI;

        this.update();
      }
    );
  }

  combineBuffer(model, bufferName) {
    let count = 0;
    model.scene.traverse(child => {
      if (child.isMesh) {
        const buffer = child.geometry.attributes[bufferName];
        count += buffer.array.length;
      }
    });

    const combined = new Float32Array(count);

    let offset = 0;

    model.scene.traverse(child => {
      if (child.isMesh) {
        const buffer = child.geometry.attributes[bufferName];
        combined.set(buffer.array, offset);
        offset += buffer.array.length;
      }
    });

    return new THREE.BufferAttribute(combined, 3);
  }

  onMousemove(ev) {
    this.mouse.x = 1 - ev.clientX / innerWidth * 2;
    this.mouse.y = (ev.clientY / innerHeight) * 2 - 1;

    console.log(this.mouse)
  }

  // Actualiza cualquier cambio, para luego representarlo en el canvas
  update() {
    //this._modelMesh.rotation.z += .01;

    this.detectIntersection();
    this.render();
    requestAnimationFrame(this.update);
  }

  detectIntersection() {
    this.raycaster.setFromCamera(this.mouse, this.camera);

    // calculate objects intersecting the picking ray
    this.intersects = this.raycaster.intersectObjects([this.planeMesh]);

    if (this.intersects.length > 0) {
      this.mat.uniforms.mouse.value = this.intersects[0].point; 
      console.log(this.intersects[0].uv)
    }
  }

  // Rescala el canvas y escenario
  onResize() {
    const _w = innerWidth;
    const _h = innerHeight;

    this.renderer.setSize(_w, _h);
    this.camera.aspect = _w / _h;

    this.mat.uniforms.scale.value = domElement.height / 2;
    /*if (_w / _h > 1) {
      this.mesh.scale.x = this.mesh.scale.y = this.mesh.scale.z = 1.05 * w / h;
    }*/

    this.camera.updateProjectionMatrix();
  }

  // Renderiza nuestro escenario
  render() {
    this.renderer.render(this.scene, this.camera);
  }
}

const webgl = new WebGL();
webgl.init();

/cc

Just an example:
https://jsfiddle.net/prisoner849/cLt80kse/
Use THREE.Plane() to find a point of intersection of this plane with the ray of a raycaster.

4 Likes

@prisoner849’s example works great for static camera.

If your camera is not static, you might need to update your Plane normal whenever your view change.

controls.addEventListener("end", event => {
  plane.normal.copy( camera.position ).normalize();
});

This way, your interaction plane will always be facing the camera, centered at the origin.

If you want to make sure it always stays aligned vertically, you can simply enforce y-coordinate to be zero, before normalizing.

plane.normal.copy( camera.position );
plane.normal.y = 0;
plane.normal.normalize();

JSFiddle

3 Likes

You guys make so many instructive basic and advanced examples that I can hardly keep up with adding them to the collection. Collection of examples from discourse.threejs.org

:running_man: My list has six entries again.

But nevertheless: keep up the great work :+1::slightly_smiling_face:

3 Likes