[SOLVED] Trying to build a robot arm grabber using Ammo.js

So I am working on a project where the user can pick up objects using a grabber of sorts.

I want the grabber to be able to pinch an object, such as a cube, and carry it around.

Here is my project: https://playcanvas.com/editor/scene/757743
You can hold ‘A’ to grip and ‘Q’ to release. The cube can be moved with forward and backward arrow keys.

I have two problems I need to solve;

  1. I want the gripper to be able to move around and not just be stationary, it does not seem to
    respond to forces or translations. I feel like this is most likely something to do with me using
    Ammo.js for the hinge joints.

  2. I want to also be able to use a slider joint in order to grip the object as a vice would, I cannot seem
    to find an example of an Ammo.js slider joint that has been implemented in Playcanvas.

If anyone can help with either of these, it would be much appreciated. It would be awesome to be able to create more up to date examples on how to use Ammo.js for joints and constraints in Playcanvas, I think this could be a good first step!

Here’s a quick script I whipped up to implement a point-to-point constraint in PlayCanvas:

var PointToPointConstraint = pc.createScript('pointToPointConstraint');

PointToPointConstraint.attributes.add('pivotA', {
    description: 'Position of the constraint in the local space of this entity',
    type: 'vec3',
    default: [0, 0, 0]
});
PointToPointConstraint.attributes.add('entityB', {
    description: 'Optional second entity',
    type: 'entity'
});
PointToPointConstraint.attributes.add('pivotB', {
    description: 'Position of the constraint in the local space of entity B (if specified)',
    type: 'vec3',
    default: [0, 0, 0]
});
PointToPointConstraint.attributes.add('debugRender', {
    description: 'Enable to render a representation of the constraint',
    type: 'boolean',
    default: false
});
PointToPointConstraint.attributes.add('debugColor', {
    description: 'The color of the debug rendering of the constraint',
    type: 'rgb',
    default: [1, 0, 0]
});

// initialize code called once per entity
PointToPointConstraint.prototype.initialize = function() {
    this.createConstraint();

    this.on('attr', function(name, value, prev) {
        // If any constraint properties change, recreate the constraint
        if (name === 'pivotA' || name === 'entityB' || name === 'pivotB') {
            this.createConstraint();
        }
    });
    this.on('enable', function () {
        this.createConstraint();
    });
    this.on('disable', function () {
        this.destroyConstraint();
    });
    this.on('destroy', function () {
        this.destroyConstraint();
    });
};

PointToPointConstraint.prototype.createConstraint = function() {
    if (this.constraint) {
        this.destroyConstraint();
    }

    var bodyA = this.entity.rigidbody.body;
    var pivotA = new Ammo.btVector3(this.pivotA.x, this.pivotA.y, this.pivotA.z);
    if (this.entityB && this.entityB.rigidbody) {
        var bodyB = this.entityB.rigidbody.body;
        var pivotB = new Ammo.btVector3(this.pivotB.x, this.pivotB.y, this.pivotB.z);
        this.constraint = new Ammo.btPoint2PointConstraint(bodyA, bodyB, pivotA, pivotB);
    } else {
        this.constraint = new Ammo.btPoint2PointConstraint(bodyA, pivotA);
    }

    var dynamicsWorld = this.app.systems.rigidbody.dynamicsWorld;
    dynamicsWorld.addConstraint(this.constraint);
    
    this.entity.rigidbody.activate();
    if (this.entityB) {
        this.entityB.rigidbody.activate();
    }
};

PointToPointConstraint.prototype.destroyConstraint = function() {
    var dynamicsWorld = this.app.systems.rigidbody.dynamicsWorld;
    dynamicsWorld.removeConstraint(this.constraint);
    Ammo.destroy(this.constraint);
};

// update code called every frame
PointToPointConstraint.prototype.update = function(dt) {
    if (this.debugRender) {
        var tempVecA = new pc.Vec3();
        this.entity.getWorldTransform().transformPoint(this.pivotA, tempVecA);
        this.app.renderLine(this.entity.getPosition(), tempVecA, this.debugColor);
        if (this.entityB) {
            this.app.renderLine(this.entityB.getPosition(), tempVecA, this.debugColor);
        }
    }
};

Other constraint types can be created in a similar fashion.

Project: https://playcanvas.com/project/618829

What do you think?

2 Likes

Added a hinge constraint script too.

constraints

The debug rendering is helpful.

1 Like

Will, wow that is an awesome response. I’m struggling to understand exactly how the code works but I think I get the general idea. Is there any documentation/tutorials for learning ammo.js? As I understand it, it’s a direct port from bullet.py, right?

I was looking through the ammo.js docs and found that there is also a btSliderConstraint(), is it possible to add a slider constraint to playcanvas?

I was just starting to look at the cone twist constraint (because once I have that, I can easily construct a rag doll). But yes, it’d be easy to add the slider constraint too. I can do that when I get a second.

If you have any questions about how my script works, just ask.

As for docs about ammo.js and Bullet, it’s pretty incredible that the docs are so sparse. To write that script, I’ve literally been visually inspecting the Bullet source code to figure out what’s going on and what certain functions actually do.

I found this project you did a while ago: https://playcanvas.com/editor/scene/468930. It has a cone twist constraint in it.

It’s a pity the docs are so sparse, I know bullet.py was built originally for use as a robot simulator library and this is exactly what I am interested in using Playcanvas for.

Aha, nice detective work! Yes, I had a play with Ammo constraints a long time ago. But I never quite got them working in a predictable way. So this time around, I thought I wouldn’t refer to my original code and try again. So far, so good. This new implementation takes a slightly different approach. But it seems to work as expected. So I’m hopeful the cone twist implementation will be perfect this time around. :slight_smile:

Actually, Bullet.py is a Python port of the original Bullet C++ codebase (which is used in a ton of video games). The JS version (ammo) is also generated from the C++ source by the Empscripten tool.

BTW, if anyone wants to retweet me on this, I’d appreciate it!

Awesome! I’m excited to see what you produce! Do you have any plans to introduce constraints to the official PlayCanvas api?

I think that’d be the next logical step if all this works out well. :slight_smile:

But there’s another part of me that likes the fact you can just grab useful scripts that ‘just work’ and don’t bloat the core engine. We’ll see.

Well if you don’t decide to add them to the API and instead make them “useful scripts”, I think it’s important that those scripts are well commented. It would be awesome to see some PlayCanvas documentation too if you decide not to make them part of the official API.

That’s just my two cents! Knowing how to work with and implement features like constraints are vital for my particular application.

Hey @will ! I tried to implement a slider constraint, got most of the way there. You can check out my project here: https://playcanvas.com/editor/scene/757980.

Press UP/DOWN Arrows to move slider.

I took my inspiration from the forklift example in bullet3.

I don’t know how to anchor the slider so that it doesn’t tip over kinda like how the hinge joints stay upright.

You’ll also see some commented out code that doesn’t seem to run, I checked https://code.playcanvas.com/ammo.dcab07b.js here and was not able to find any reference of the following functions:

    setPoweredLinMotor();
    setMaxLinMotorForce();
    setTargetLinMotorVelocity();

Would love to hear if you made any progress on the stuff you were working on earlier!

Good progress. :slight_smile:

So we’re still using quite an old version of ammo.js. This is because ammo.js changed to an asynchronous loading system which meant we’d have to make a bunch of changes in the engine. However, we should resolve that reasonably soon.

In the meantime, yeah, you might find a handful of discrepancies between our ammo.js exposed API and the latest in the source repo for ammo.

I’ll take a look at slider constraints myself now. I’m gonna give up on cone-twist for the moment. I can’t figure out how to correctly calculate the orientation frames passed to the constraint constructor.

Yeah, looking at the available API in PlayCanvas revision of ammo.js, the motor related functions for the slider constraint aren’t there. But then, they’re not even in the latest ammo.js either:

We’d need to submit a PR to ammo.js and upgrade PlayCanvas’ version for motor powered slider constraints to work. TBH, I do actually think it’s very useful for developers to pick the version of ammo.js that they want to use. Because it is possible to build your own version of ammo.js from the source and expose and much or as little of the API as you want.

The site for the official documentation had gone down earlier in the year (or even last). I’ve been going through PyBullet’s documentation for some things :frowning:

OK, I think I’m done for today. I’ve added an initial draft of a slider constraint:

constraints2

1 Like

Is there a project link for this?
EDIT: Oh, it’s just the previous link.

@Will, do you know what might cause a hinge joint to slip? In my project: https://playcanvas.com/editor/scene/758636.
Up/Down, Left/Right & W/S keys for controls.

I can move the arms of the robot and they are relatively stiff and precise but when I bring the arm close to parallel to the ground, the hinge joints start to slip and gravity pulls them down.

I have reduced this ‘slip’ as much as possible by messing with the mass, half-extents and max motor impulse.

Is there any other reason you are aware of that could cause this slip?

I want to add a robot gripper at the end of the robot arm using slider joints too but am not sure if this is possible without the linarMotor function for the slider constraint.

@will how would u use the hinge points to create a ragdoll on it own asset collision?