[SOLVED] Need help with my custom throw system

Render the original bezier curve up to the intersection point. If you move point 3, you could change the shape of the curve.

I think I do that already:

var Throw = pc.createScript('throw');
var throwPoint;

// initialize code called once per entity
Throw.prototype.initialize = function() {
    this.divisions = 30;
    this.pathRoot = this.entity.findByName("ThrowPath");
  
    this.point1 = this.entity.findByName("Point1");
    this.point2 = this.entity.findByName("Point2");
    this.point3 = this.entity.findByName("Point3"); 
};

// update code called every frame
Throw.prototype.update = function(dt) {
    // Point 1 is the throw start point, the same player entity position with an offset
    
    // Point 3 is the throw end point, the mouse hit position until an obstacle blocks the path
    // ThrowPoint is the mouse hit point in another script
    this.point3.setPosition(throwPoint);
    
    // Point 2 is the middle point, between point 1 and point 3
    var middle = this.point3.localPosition.z / 2;
    this.point2.setLocalPosition(new pc.Vec3(this.point2.getLocalPosition().x, this.point2.getLocalPosition().y, middle));

    // Create the division between the three points
    var children = this.pathRoot.children;
        for (var p = 0; i < children.length; p++) {
        var position = children[p].getPosition();
        points.push({x:position.x, y:position.y, z:position.z});
        this.positions.push(position);
    }
    
    if (children.length !== 3) {
        console.error('You need to create three child entities of the Bezier entity that are control points.');
    }
    
    var p1 = children[0].getPosition(); // this.point1
    var p2 = children[1].getPosition(); // this.point2
    var p3 = children[2].getPosition(); // this.point3
    
    this.curve = new Bezier(p1.x, p1.y, p1.z, p2.x, p2.y+1.5, p2.z, p3.x, p3.y, p3.z);
    
    this.from = new pc.Vec3();
    this.to = new pc.Vec3();
    
    var divisions = Math.floor(this.divisions);
    
    // Render the curve itself
    var lut = this.curve.getLUT(divisions);
    for (var i = 0; i < lut.length-1; i++) {
        this.from.x = lut[i].x;
        this.from.y = lut[i].y;
        this.from.z = lut[i].z;
        this.to.x = lut[i+1].x;
        this.to.y = lut[i+1].y;
        this.to.z = lut[i+1].z;
        
        // Render the line
        this.app.renderLine(this.from, this.to, colorBlue);
        
        // Raycast
        var obstacle = this.app.systems.rigidbody.raycastFirst(this.from, this.to);
        if (obstacle) {
            // If there is an obstacle that block the path then set point 3 to the hit point
            this.point3.setPosition(obstacle.point);
            this.app.root.findByName("DebugPoint").setPosition(obstacle.point);
        }
    }
};

So now I create a second bezier. One for the raycast and one for the line. Sometimes it works and sometimes it doesn’t. I feel like the raycast is missing the obstacle. This is also one of the situations where I would like to use sphereCast.

Something is a little funky as the curve shouldn’t change that much between the two frames at 0:05:


It looks like the curve changes when it intersects with something when it shoudn’t.

Just to sanity check, what happens when you remove this line:

this.point3.setPosition(obstacle.point);

If I remove that line there is just a curved line between the player and the mouse (that goes through objects).

That is precisely the intention. So the second image (at 0:06) is the correct result.

That feels wrong to me as when an object is thrown at the same angle and power, it’s going to follow the same path no matter what in the path.

With the line of code removed, the curved line is more stable, it doesn’t change regardless what it intersects with which is what I would expect as a player.

If you want that curve to change if it intersects with something (which it sounds like you do), I would do a second bezier curve just to render the line with a new set of points and not change the value of point3.

So that’s what I want to avoid doing the raycast and update the path (and line) before the player actually throws it.

That is not the intention because I want to help the player see where his thrown object will end.

I have already tried that, but it is not stable either. The first video I sent was done that way.

But otherwise I will do it so that the line is only updated when the player clicks. And if the player double-clicks then actually throw.

Not working. I think the raycast fails and don’t always detect the collider.

My raycast goes through all divisions of the curve. How do I prevent from going to the next division if something has already been hit? Maybe that’s the problem.

Edit: Yes, that was the problem. It’s working now.

@Albertos actually, that is not the only problem. I have re-created the setup you use. At least in a sense, where you use the Bezier curves for ray hits.

The problem occures at the joints of the curve segments. Say, you have a curve with only 2 segments: AB and BC. The problem you describe, where the curve goes through the obstacle in your case is when point B is exactly at the surface of the obstacle object.

When that happens, the ray you cast from A to B does not detect the hit. And also the ray from B to C will not detect it, resulting in a pass through.

image

I remember you have raised this issue in the past and I think one of the proposals that time was to increase the number of segments in your cuve. However, given the root cause of the problem, you are actaully making it worse by adding more joints, that may end up on the surface. Also, increasing the precision or the number of segments on the curve, increases the number of ray cast iterations you would have to doeach frame to detect a hit. This might be expensive.

It is a valid problem though, so after some thought, I came up with a solution I believe could help you. Let’s get back to example with only to 2 segments: AB and BC. So, the problem happens when B is right at the surface of the obstacle. In order to mitigate it, I create another segment - from the middle of AB to the middle of BC. Now, when we check the hit, we first check the AB segment, then BC. If both don’t give a result - we check the middle segment between them.

image

In this case, if B is at the surface and rays fail to detect a hit, the middle section will hit definitely, since B will be in the middle of it. This way you will not end up with free joints at the surface - one of the segments will guarantee to hit. I have made an example project to demonstrate it:

https://playcanvas.com/editor/scene/934492

1 Like

Hay @LeXXik, a quick response as I have to go to work.

Thank you very much for your input!

I now use two beziers. One for the raycast, which now consists of 3 segments because more segments indeed make it less stable. The raycast hit then determines the position of point 3 of the second bezier, which consists of more segments for a smooth line.

You may be able to optimize these scripts.

https://playcanvas.com/editor/scene/908116

Yes, your option will work, because you have only 3 segments. This means each segment is long enough, so most probably the last segment will hit the surface in the middle of it, and you would adjust the surface end point.

However, with this approach an issue might come in the precision of your throws - for example, try to move the cursor on top of one of your boxes. You can see how the target point is jumping around unpredictably. This is due to the low precision of your curve.

In my approach the curve has 20 segments and very stable in detection, without jitter. You should consider it.

I hate glitching things! Only when I took a quick look at your project, I see that the line didn’t stop at the hit point of the obstacle. Can this be adjusted?

Yes, it is possible. You can do it by making a projection from the point of intersection with the surface to the curve. Once you did the projection you know the normalized position of the cut. Using this position you can split the original curve into a new one.

I have adjusted the example to demonstrate it.

Hay @LeXXik! Thanks again for your detailed explanation and example project. I try to translate your example into my game but unfortunately I will not get there. You use a different way of coding than I am used to. Also, I see that the obstacles you use in the example are pre-processed in the script, and I don’t think I can do that for all obstacles in my game.

Yes, the coding styles are different, but the technique actually matches the way you calculate the hit. Perhaps, it would be easier, if you could give me write access to that small project and reset it to the state before you tried to implement my solution.

I can adjust a few lines to show how it can be done.

Just wanted to update on the topic. After implementing the ability to cast convex shapes, I have changed the .raycastFirst() method to .sphereCast(). This eliminated the need in checking secondary segments. Also, switched to native pc.Curve() to do the same thing without a need in third party bezier library.

Project link

1 Like

I want to use this, change it to make a raycast for a gun system, where when you click it starts the raycast, and it broadcasts the hit, and if it’s the other player, it will do dmg

@Gavin_Durbin Can you start a new topic as this topic has been marked as solved please?

1 Like