How to add complex collider (a bowl shape for example)?

Hi everyone. I’m trying to implement a small game here, and I hope to add a collider to my bowl model. I want the collider to be exact same shape with the bowl, so that it can holds other objects. Is it possible to do that? Thank you!

Hello @HelpAR_Inc what physics engine are you using? Is this A-frame? More information would be helpful.

Hi Ian, thanks for getting back. I’m using A-frame. I’m pasting my code here. I’m having trouble applying a correct collider for the ‘bowl’ model (code at bottom part). Any advice will be greatly appreciated!

export const shootCerealComponent = () => ({
  init() {
    const camera = document.getElementById('camera')
    this.modelPlaced = false

    this.el.sceneEl.addEventListener('touchstart', (event) => {
      const position = this.calculatePlacementPosition(camera)

      if (!this.modelPlaced) {
        this.createAndPlaceModel(position)

        this.modelPlaced = true
      }

      if (this.modelPlaced) {
        const cereal = document.createElement('a-entity')
        cereal.setAttribute('position', camera.object3D.position)
        cereal.setAttribute('scale', '0.5 0.5 0.5')
        cereal.setAttribute('gltf-model', '#cerealModel')

        const randomRotation = {x: -90 + Math.random() * 30, y: Math.random() * 360, z: 0}
        cereal.setAttribute('rotation', randomRotation)

        const velocity = new THREE.Vector3(0, 0, -10)
        velocity.applyQuaternion(camera.object3D.quaternion)
        cereal.setAttribute('velocity', velocity)

        cereal.setAttribute('body', {
          type: 'dynamic',
          sphereRadius: 0.35,
          shape: 'sphere',
        })

        cereal.setAttribute('shadow', {
          receive: false,
        })

        this.el.sceneEl.appendChild(cereal)

        let didCollide = false
        cereal.addEventListener('collide', (e) => {
          if (didCollide || e.detail.body.el.id !== 'ground') {
            return
          }
          didCollide = true
        })
      }
    })
  },

  calculatePlacementPosition(camera) {
    const position = camera.getAttribute('position')
    const rotation = camera.getAttribute('rotation')

    const distance = 10
    const x = position.x - distance * Math.sin(rotation.y * Math.PI / 180)
    const z = position.z - distance * Math.cos(rotation.y * Math.PI / 180)
    const y = 0

    return {x, y, z}
  },

  createAndPlaceModel(position) {
    const model = document.createElement('a-entity')
    model.setAttribute('position', position)
    model.setAttribute('gltf-model', '#bowlModel')
    model.setAttribute('shadow', {
      receive: false,
    })
    model.setAttribute('material', {
      color: '#FFC0CB',
    })

    model.setAttribute('body', {
      type: 'static',
      sphereRadius: 2,
      shape: 'sphere',
    })

    this.el.sceneEl.appendChild(model)

    model.addEventListener('model-loaded', () => {
      model.setAttribute('visible', 'true')
      model.setAttribute('animation', {
        property: 'scale',
        to: '2 2 2',
        easing: 'easeOutElastic',
        dur: 800,
      })
    })
  },
})

Hello @HelpAR_Inc it looks like you are using CANNON.js physics engine. This question would probably be better suite for the A-frame slack channel rather than 8th Wall sepcficially.

You can find the Slack channel here: Community – A-Frame

That being said some things to keep it is suggested to use AMMO.js as CANNON.js may be depricated in the future. As stated here:

If you decide to move forward with AMMO.js you can reference our sample project here:

specifically our glb-physics-object.js file which allows you to add a physics collider to .glb models.

Thank you so much for your amazing help Ian! I switched to AMMO.js and successfully apply the collider. However, the velocity doesn’t seem to be applied to the cereal as I tap the screen, it just drops down instead of being tossed forward. Could you please help with any insights? Thank you so much!

const tossComponent = {

init() {
const camera = document.getElementById(‘camera’)
this.tossAnimation = () => {
const cereal = document.createElement(‘a-entity’)
cereal.setAttribute(‘position’, camera.object3D.position)
cereal.setAttribute(‘scale’, ‘0.5 0.5 0.5’)
cereal.setAttribute(‘gltf-model’, ‘#cereal’)

  const randomRotation = {x: -90 + Math.random() * 30, y: Math.random() * 360, z: 0}
  cereal.setAttribute('rotation', randomRotation)

  const velocity = new THREE.Vector3(0, 0, -10)
  velocity.applyQuaternion(camera.object3D.quaternion)
  cereal.setAttribute('velocity', velocity)

  cereal.setAttribute('ammo-body', {
    type: 'dynamic',
    shape: 'hull',
    mass: 1,
  })

  cereal.setAttribute('shadow', {
    receive: false,
  })

  this.el.sceneEl.appendChild(cereal)
}

this.el.sceneEl.addEventListener('click', this.tossAnimation)

},
}

export {tossComponent}

This might be a better component

AFRAME.registerComponent('toss-component', {
  init() {
    const camera = document.getElementById('camera');  // Ensure this ID matches your camera entity
    this.tossAnimation = () => {
      const cereal = document.createElement('a-entity');
      cereal.setAttribute('position', camera.object3D.position);
      cereal.setAttribute('scale', '0.5 0.5 0.5');
      cereal.setAttribute('gltf-model', '#cereal');

      const randomRotation = {x: -90 + Math.random() * 30, y: Math.random() * 360, z: 0};
      cereal.setAttribute('rotation', `${randomRotation.x} ${randomRotation.y} ${randomRotation.z}`);

      cereal.setAttribute('ammo-body', 'type: dynamic; shape: hull; mass: 1');
      cereal.setAttribute('shadow', 'receive: false');

      // Append to scene
      this.el.sceneEl.appendChild(cereal);

      cereal.addEventListener('body-loaded', () => {
        const velocity = new THREE.Vector3(0, 0, -10);
        velocity.applyQuaternion(camera.object3D.quaternion);
        
        const ammoVelocity = new Ammo.btVector3(velocity.x, velocity.y, velocity.z);
        cereal.components['ammo-body'].body.setLinearVelocity(ammoVelocity);
        Ammo.destroy(ammoVelocity); // Prevent memory leaks by cleaning up

        // Optionally, remove the cereal entity after some time
        setTimeout(() => {
          if (cereal.parentNode) {
            cereal.parentNode.removeChild(cereal);
          }
        }, 5000); // Adjust the time as needed
      });
    };

    this.el.sceneEl.addEventListener('click', this.tossAnimation);
  },
});

Key Changes and Notes:

Position and Scale: These are set similarly to your original component, aligning the cereal’s starting position with the camera’s position.
Rotation: Randomized as before, but ensure you’re setting the attribute correctly.
Physics and Velocity: The handling of physics and velocity now waits for the body-loaded event, ensuring the physics body is fully loaded before attempting to set the velocity. The velocity application now properly creates an Ammo.btVector3 object for use with Ammo.js physics.
Cleanup: I’ve included an optional timeout to remove the cereal entity from the scene after a certain time to prevent clutter and potential performance issues.
Make sure to replace ‘camera’ and ‘#cereal’ with the correct IDs or selectors matching your actual camera and cereal model entities in your A-Frame scene.

1 Like