I have a drone following a path for movement. That is, it doesn't use a rigidbody so I don't have access to velocity or magnitude and such. It follows the path just fine, but I would like to add banking to it when it turns left or right. I use a dummy object in front of the drone, thinking I could calculate the bank/tilt amount using the transform vectors from the two objects.
I've been working on this for days as I don't have a lot of math skills. Basically I've been copying pieces of code trying to get things to work. Nothing I do works to make the drone bank. The following code manages to spin (not bank).
// Update is called once per frame
void Update () {
Quaternion rotation = Quaternion.identity;
Vector3 dir = (dummyObject.transform.position - this.transform.position).normalized;
float angle = Vector3.Angle( dir, transform.up );
float rollAngle = CalculateRollAngle(angle);
rotation.SetLookRotation(dir, transform.right);// + rollIntensity * smoothRoll * right);
rotation *= Quaternion.Euler(new Vector3(0, 0, rollAngle));
transform.rotation = rotation;
}
/// <summary>
/// Calculates Roll and smoothes it (to compensates for non C2 continuous control points algorithm) /// </summary>
/// <returns>The roll angle.</returns>
/// <param name="rollFactor">Roll factor.</param>
float CalculateRollAngle(float rollFactor)
{
smoothRoll = Mathf.Lerp(smoothRoll, rollFactor, rollSmoothing * Time.deltaTime);
float angle = Mathf.Atan2(1, smoothRoll * rollIntensity);
angle *= Mathf.Rad2Deg;
angle -= 90;
TurnRollAngle = angle;
angle += RollOffset;
return angle;
}
Assuming you have waypoints the drone is following, you should figure out the angle between the last two (i.e. your "now-facing" and "will be facing" directions). The easy way is to use Vector2.Angle.
I would use this angle to determine the amount I'll tilt the drone's body: the sharper the turn, the harder the banking. I would use a ratio value (public initially so I can manipulate it from the editor).
Next, instead of doing any math I would rely on the engine to do the rotation for me - so I would go for Transform.Rotate function.In case banking can go too high and look silly, I would set a maximum for that and Clamp my calculated banking angle between zero and max.
Without knowing exactly what you do and how, it's not easy to give perfect code, but for a better understand of the above, here's some (untested, i.e. pseudo) code for the solution I visualize:
public float turnSpeed = 7.0f; //the drone will "rotate toward the new waypoint" by this speed
//bankSpeed+turnBankRatio must be two times "faster" (and/or smaller degree) than turning, see details in 'EDIT' as of why:
public float bankSpeed = 14.0f; //banking speed
public float turnBankRatio = .5f; //90 degree turn == 45 degree banking
private float turnAngle = 0.0f; //this is the 'x' degree turning angle we'll "Lerp"
private float turnAngleABS = 0.0f; //same as turnAngle but it's an absolute value. Storing to avoid Mathf.Abs() in Update()!
private float bankAngle = 0.0f; //banking degree
private bool isTurning = false; //are we turning right now?
//when the action is fired for the drone it should go for the next waypoint, call this guy
private void TurningTrigger() {
//remove this line after testing, it's some extra safety
if (isTurning) { Debug.LogError("oups! must not be possible!"); return; }
Vector2 droneOLD2DAngle = GetGO2DPos(transform.position);
//do the code you do for the turning/rotation of drone here!
//or use the next waypoint's .position as the new angle if you are OK
//with the snippet doing the turning for you along with banking. then:
Vector2 droneNEW2DAngle = GetGO2DPos(transform.position);
turnAngle = Vector2.Angle(droneOLD2DAngle, droneNEW2DAngle); //turn degree
turnAngleABS = Mathf.Abs(turnAngle); //avoiding Mathf.Abs() in Update()
bankAngle = turnAngle * turnBankRatio; //bank angle
//you can remove this after testing. This is to make sure banking can
//do a full run before the drone hits the next waypoint!
if ((turnAngle * turnSpeed) < (bankAngle * bankSpeed)) {
Debug.LogError("Banking degree too high, or banking speed too low to complete maneuver!");
}
//you can clamp or set turnAngle based on a min/max here
isTurning = true; //all values were set, turning and banking can start!
}
//get 2D position of a GO (simplified)
private Vector2 GetGO2DPos(Vector3 worldPos) {
return new Vector2(worldPos.x, worldPos.z);
}
private void Update() {
if (isTurning) {
//assuming the drone is banking to the "side" and "side" only
transform.Rotate(0, 0, bankAngle * time.deltaTime * bankSpeed, Space.Self); //banking
//if the drone is facing the next waypoint already, set
//isTurning to false
} else if (turnAngleABS > 0.0f) {
//reset back to original position (with same speed as above)
//at least "normal speed" is a must, otherwise drone might hit the
//next waypoint before the banking reset can finish!
float bankAngle_delta = bankAngle * time.deltaTime * bankSpeed;
transform.Rotate(0, 0, -1 * bankAngle_delta, Space.Self);
turnAngleABS -= (bankAngle_delta > 0.0f) ? bankAngle_delta : -1 * bankAngle_delta;
}
//the banking was probably not set back to exactly 0, as time.deltaTime
//is not a fixed value. if this happened and looks ugly, reset
//drone's "z" to Quaternion.identity.z. if it also looks ugly,
//you need to test if you don't """over bank""" in the above code
//by comparing bankAngle_delta + 'calculated banking angle' against
//the identity.z value, and reset bankAngle_delta if it's too high/low.
//when you are done, your turning animation is over, so:
}
Again, this code might not perfectly fit your needs (or compile :P), so focus on the idea and the approach, not the code itself. Sorry for not being able right now to put something together and test myself - but I hope I helped. Cheers!
EDIT: Instead of a wall of text I tried to answer your question in code (still not perfect, but goal is not doing the job, but to help with some snippets and ideas :)
So. Basically, what you have is a distance and "angle" between two waypoints. This distance and your drone's flight/walk/whatever speed (which I don't know) is the maximum amount of time available for:
1. Turning, so the drone will face in the new direction
2. Banking to the side, and back to zero/"normal"
As there's two times more action on banking side, it either has to be done faster (bankSpeed), or in a smaller angle (turnBankRatio), or both, depending on what looks nice and feels real, what your preference is, etc. So it's 100% subjective. It's also your call if the drone turns+banks quickly and approaches toward the next waypoint, or does things in slow pace and turns just a little if has a lot of time/distance and does things fast only if it has to.
As of isTurning:
You set it to true when the drone reached a waypoint and heads out to the next one AND the variables to (turn and) bank were set properly. When you set it to false? It's up to you, but the goal is to do so when the maneuver is finished (this was buggy in the snippet the first time as this "optimal status" was not possible to ever be reached) so he drone can "reset banking".For further details on what's going on, see code comments.Again, this is just a snippet to support you with a possible solution for your problem. Give it some time and understand what's going on. It really is easy, you just need some time to cope ;)Hope this helps! Enjoy and cheers! :)
Related
I am playing around, trying to make a little JavaFX application to visualize data received via the serial port from an arduino-based board and some sensors.
After adding some live-updating LineGraphs, I am currently trying to display the roll, pitch and yaw values received from the micro-controller, by rotating a simple box-element.
I have one thread calling a function every x ms, which stores the incoming data into an ObservableList with an changeListener and calls the controller based function to update/rotate the orientation of the box.
Since the calculation of the angles is allready done on the micro-controller, I would like to rotate the box to the received absolute orientation.
From what I've understand so far, I can't simply rotate from any previous orientation to a new absolute one, but only change the orientation relatively to the previous one.
I came up with the following idea to just subtract the last roll/pitch/yaw values from the penultimate one of the observableList.
Data dataTmp = observableList.get(observableList.size()-2);
Data dataTmp2 = observableList.get(observableList.size()-1);
newRoll = dataTmp2.getRoll() - dataTmp.getRoll();
newPitch = dataTmp2.getPitch() - dataTmp.getPitch();
newYaw = dataTmp2.getYaw() - dataTmp.getYaw();
Platform.runLater(new Runnable() {
#Override
public void run() {
controller.setToPosition(newRoll, newPitch, newYaw);
}
});
//...
This only works out to a certain extent. I still want to rotate to the absolute position received from the micro-controller.
So my question is this: Is there a way to reset the orientation of the box to e.g. 0, 0, 0 from where I could rotate to my new absolute orientation? Simply removing the box and adding a new one did not work out at all.
group.getChildren().remove(box);
box = new Box(300,50,300);
group.getChildren().add(box);
Thank you in advance for any ideas or even solutions. If you need more information or code snippets let me know.
Referring to this example, an onMouseMoved handler rotates the red Box around the x and y axes as the mouse moves. The following onKeyPressed handler restores the red Box to its original position when the Z key is pressed. Each handler uses the setAngle() method of the Rotate class.
scene.setOnKeyPressed(e -> {
if (e.getCode() == KeyCode.Z) {
content.rx.setAngle(0);
content.ry.setAngle(0);
content.rz.setAngle(0);
}
});
Similarly, your setToPosition() implementation can invoke setAngle() to establish the new roll, pitch and yaw values.
Before:
After:
More subtly, verify that you synchronize access to any data shared between your data acquisition thread and the JavaFX application thread. This example illustrates a Task<Canvas>, while your application might instead implement a Task<Point3D>, where a Point3D holds the roll, pitch and yaw values.
I have a player and a few NPCs.
The NPCs have random movement, and I control my players movement. They both have RigidBody2D to deal with physics and BoxCollider2D to deal with Collisions.
However, when I walk into a NPC my player pushes it. Same thing if a NPC moves into my player while the player stands still.
I can't set the mass of either object to some extreme number since that will interfere with how they behave with other objects in my game.
What I want:
When an NPC collides with the player, the NPC stops (I get this effect if I set player mass to ex. 1000, but then the player can push the NPC, which I dont want), and the NPC acts as a "wall", i.e it doesnt move, but nor can the player push it around. How can I do this?
EDIT: So I created my own method for it:
void OnCollisionEnter2D(Collision2D other){
if (other.gameObject.name == "Player") {
collidedWithPlayer = true; //we only move if !collidedWithPlayer
isMoving = false; //stop moving
myRigidBody.mass = 1000; //turn NPC into "wall"
}
}
void OnCollisionExit2D(Collision2D other){
if (other.gameObject.name == "Player") {
collidedWithPlayer = false;
waitCounter = waitTime; //stop NPC from starting to move right after we exit
myRigidBody.mass = 1;
}
}
I mean this works, but is there no native method to do this?
What you are trying to do is essentially use a "realistic" physics engine to create rather unrealistic physics. That's why it's not supported by Unity's built-in functions. Furthermore, you are correct in assuming that messing with the object masses is not a good idea.
Here's one suggestion that avoids playing with mass. It's a bit kludgey, but give it a try and see if it works for you. (I assume your player rigidbody is not Kinematic?)
Step 1: Create 2 new layers; call them NPCWall and PlayerWall. Setup 2D physics so that player collides with NPCWall and NPC collides with PlayerWall, but player does not collide with NPCs. (If your NPCs and player are on the same layer, then of course put them on 2 separate layers.)
Step 2: Create an NPCWall prefab that uses the same kind of collider as the NPCs. I assume you only have one size of NPC. Likewise, create a PlayerWall prefab that uses the same kind of collider as the player. Set the NPCWall prefab to NPCWall layer, and PlayerWall prefab to PlayerWall layer.
Step 3: We can't parent the NPCWall to the NPC, because it would end up as part of the rigidbody. Therefore add a simple script to the NPCWall and PlayerWall:
public class TrackingWall
{
//This invisible wall follows an NPC around to block the player.
//It also follows the player around to block NPCs.
Transform followTransform;
public void Init(Transform targetTrans)
{
followTransform = targetTrans;
transform.position = followTransform.position;
transform.rotation = followTransform.rotation;
}
void Update()
{
if (followTransform == null)
Destroy(gameObject);
transform.position = followTransform.position;
transform.rotation = followTransform.rotation;
}
}
Step 4: In the NPC and player scripts:
TrackingWall myWallPrefab;
void Start()
{
[whatever else you are doing in Start()]
TrackingWall myWall = Instantiate<TrackingWall>(myWallPrefab);
myWall.Init(transform);
}
Obviously, for NPCs, myWallPrefab should be set to the NPCWall prefab, and for players, myWallPrefab should be set to the PlayerWall prefab.
In theory this should give each character an impenetrable, immovable wall that only moves when they do, prevents other characters from pushing them, and cleans itself up when they are destroyed. I can't guarantee it will work though!
I am using a cosine curve to apply a force on an object between the range [0, pi]. By my calculations, that should give me a sine curve for the velocity which, at t=pi/2 should have a velocity of 1.0f
However, for the simplest of examples, I get a top speed of 0.753.
Now if this is a floating point issue, that is fine, but that is a very significant error so I am having trouble accepting that it is (and if it is, why is there such a huge error computing these values).
Some code:
// the function that gives the force to apply (totalTime = pi, maxForce = 1.0 in this example)
return ((Mathf.Cos(time * (Mathf.PI / totalTime)) * maxForce));
// the engine stores this value and in the next fixed update applies it to the rigidbody
// the mass is 1 so isn't affecting the result
engine.ApplyAccelerateForce(applyingForce * ship.rigidbody2D.mass);
Update
There is no gravity being applied to the object, no other objects in the world for it to interact with and no drag. I'm also using a RigidBody2D so the object is only moving on the plane.
Update 2
Ok have tried a super simple example and I get the result I am expecting so there must be something in my code. Will update once I have isolated what is different.
For the record, super simple code:
float forceThisFrame;
float startTime;
// Use this for initialization
void Start () {
forceThisFrame = 0.0f;
startTime = Time.fixedTime;
}
// Update is called once per frame
void Update () {
float time = Time.fixedTime - startTime;
if(time <= Mathf.PI)
{
forceThisFrame = Mathf.Cos (time);
if(time >= (Mathf.PI /2.0f)- 0.01f && time <= (Mathf.PI /2.0f) + 0.01f)
{
print ("Speed: " + rigidbody2D.velocity);
}
}
else
{
forceThisFrame = 0.0f;
}
}
void FixedUpdate()
{
rigidbody2D.AddForce(forceThisFrame * Vector2.up);
}
Update 3
I have changed my original code to match the above example as near as I can (remaining differences listed below) and I still get the discrepancy.
Here are my results of velocity against time. Neither of them make sense to me, with a constant force of 1N, that should result in a linear velocity function v(t) = t but that isn't quite what is produced by either example.
Remaining differences:
The code that is "calculating" the force (now just returning 1) is being run via a non-unity DLL, though the code itself resides within a Unity DLL (can explain more but can't believe this is relevant!)
The behaviour that is applying the force to the rigid body is a separate behaviour.
One is moving a cube in an empty enviroment, the other is moving a Model3D and there is a plane nearby - tried a cube with same code in broken project, same problem
Other than that, I can't see any difference and I certainly can't see why any of those things would affect it. They both apply a force of 1 on an object every fixed update.
For the cosine case this isn't a floating point issue, per se, it's an integration issue.
[In your 'fixed' acceleration case there are clearly also minor floating point issues].
Obviously acceleration is proportional to force (F = ma) but you can't just simply add the acceleration to get the velocity, especially if the time interval between frames is not constant.
Simplifying things by assuming that the inter-frame acceleration is constant, and therefore following v = u + at (or alternately ∂v = a.∂t) you need to scale the effect of the acceleration in proportion to the time elapsed since the last frame. It follows that the smaller ∂t is, the more accurate your integration.
This was a multi-part problem that started with me not fully understanding Update vs. FixedUpdate in Unity, see this question on GameDev.SE for more info on that part.
My "fix" from that was advancing a timer that went with the fixed update so as to not apply the force wrong. The problem, as demonstrated by Eric Postpischil was because the FixedUpdate, despite its name, is not called every 0.02s but instead at most every 0.02s. The fix for this was, in my update to apply some scaling to the force to apply to accomodate for missed fixed updates. My code ended up looking something like:
Called From Update
float oldTime = time;
time = Time.fixedTime - startTime;
float variableFixedDeltaTime = time - oldTime;
float fixedRatio = variableFixedDeltaTime / Time.fixedDeltaTime;
if(time <= totalTime)
{
applyingForce = forceFunction.GetValue(time) * fixedRatio;
Vector2 currentVelocity = ship.rigidbody2D.velocity;
Vector2 direction = new Vector2(ship.transform.right.x, ship.transform.right.y);
float velocityAlongDir = Vector2.Dot(currentVelocity, direction);
float velocityPrediction = velocityAlongDir + (applyingForce * lDeltaTime);
if(time > 0.0f && // we are not interested if we are just starting
((velocityPrediction < 0.0f && velocityAlongDir > 0.0f ) ||
(velocityPrediction > 0.0f && velocityAlongDir < 0.0f ) ))
{
float ratio = Mathf.Abs((velocityAlongDir / (applyingForce * lDeltaTime)));
applyingForce = applyingForce * ratio;
// We have reversed the direction so we must have arrived
Deactivate();
}
engine.ApplyAccelerateForce(applyingForce);
}
Where ApplyAccelerateForce does:
public void ApplyAccelerateForce(float requestedForce)
{
forceToApply += requestedForce;
}
Called from FixedUpdate
rigidbody2D.AddForce(forceToApply * new Vector2(transform.right.x, transform.right.y));
forceToApply = 0.0f;
I've been trying to apply an angular impulse to a Body object in PlayN, but to no avail. Whichever value (radials) I enter, the angle of the body never changes. I have tried to set the torque as well with no results.
Example code that doesn't work:
BodyDef def = new BodyDef();
def.type = BodyType.DYNAMIC;
Body body = world.createBody(def);
float degToRad = (float) (180 / Math.PI);
float radials = (float) (50 / degToRad);
// None of the following options work.
body.applyAngularImpulse(radials); // Immediate angular change.
body.applyTorque(radials); // Angular change over time.
How can I get a valid body object to change its angle without manually setting its angular velocity (e.g. with setAngularVelocity)?
Thanks in advance!
I did notice that the torque and angular velocity are reset by calling the setWake method, which I never do manually, but it is invoked by the Island class:
public void setAwake(boolean flag) {
...
m_angularVelocity = 0.0f;
m_torque = 0.0f;
...
}
Note: Setting the angular velocity is not an option because I rely on the physics simulation. I've found an article for Box2D angle rotation, but it didn't change the outcome of the applyAngularImpulse method.
I've debugged some, and come to the conclusion the values that I've been using were way too low. When going through the applyAngularImpulse code I noticed a few things:
public void applyAngularImpulse(float impulse) {
if (m_type != BodyType.DYNAMIC) {
return;
}
if (isAwake() == false) {
setAwake(true);
}
m_angularVelocity += m_invI * impulse;
}
m_invI is a really low value, something like 2.44882598E-4 so it makes sense if I use a too low value, that there is just no visible effect. If calculate the impulse from the question's example multiplied by the m_invI field:
m_invI = 0.00044882598 = 2.449E-4
impulse = 50 / 180 / PI = 0.08841941282 = 8.842E-2
m_invI * impulse = 2.449E-4 * 8.842E-2 = 0.00002165237 = 2.165E-5 <--
That means just an angular velocity increment of 0.00002165237 per update cycle... Which is way too low to be noticeable it seems.
Also, while browsing the Box2D forums for similar situations, I found it there's a best practice to include the body's inertia into your angular impulse calculation. Like this:
body.applyAngularImpulse(speedValue * body.getInertia());
The inertia tends to be a high value (+4000) for me.
Hope this can be some help to others that have trouble applying an angular impulse or torque.
In my case I forgot to call
body.setFixedRotation(false);
Even though I was setting mass, an inertia of 0 was reported and all torques and angular impulses were ignored.
Apparently it is fixed by default.
I had the same problem.
In my case I accidentally put my points in CW order (leading to negative area and zero mass). This was my incorrect code:
// incorrect version
triangleVertices.push(new B2Vec2(width / 2, 0.0)); // top
triangleVertices.push(new B2Vec2(0.0, height)); // left
triangleVertices.push(new B2Vec2(width, heightM)); // right
That looks like CCW, right? Nope, not in my coordinate system (+Y is down).
// corrected version
triangleVertices.push(new B2Vec2(width / 2, 0.0)); // top
triangleVertices.push(new B2Vec2(width, heightM)); // right
triangleVertices.push(new B2Vec2(0.0, height)); // left
I created this function to move a unit along way points that are saved in list_. Every Unit has its own list_. move() is initially called with the speed (distance/step) every step. Then depending on the distance to the next way point three possible actions are taken.
Can you suggest any improvements?
void Unit::move(qreal maxDistance)
{
// Construct a line that goes from current position to next waypoint
QLineF line = QLineF(pos(), list_.firstElement().toPointF());
// Calculate the part of this line that can be "walked" during this step.
qreal part = maxDistance / line.length();
// This step's distance is exactly the distance to next waypoint.
if (part == 1) {
moveBy(line.dx(), line.dy());
path_.removeFirst();
}
// This step's distance is bigger than the distance to the next waypoint.
// So we can continue from next waypoint in this step.
else if (part > 1)
{
moveBy(line.dx() , line.dy());
path_.removeFirst();
if (!path_.isEmpty())
{
move(maxDistance - line.length());
}
}
// This step's distance is not enough to reach next waypoint.
// Walk the appropriate part of the length.
else /* part < 1 */
{
moveBy(line.dx() * part, line.dy() * part);
}
}
I'll hate myself for suggesting a deprecated way of doing things, but there's no reference to the replacing method :(
QGraphicsItemAnimation
It has addStep and linear interpolation stuff as a convenience.
It seems Qt devs would like you to use QTimeLine itself as a replacement.
I'd use Qt Animation Framework, more precisely QPropertyAnimation:
// I use QPainterPath to calculate the % of whole route at each waypoint.
QVector<qreal> lengths;
QPainterPath path;
path.moveTo(list_.first());
lengths.append(0);
foreach (const QPointF &waypoint, list_.mid(1)) {
path.lineTo(waypoint);
lengths.append(path.length());
}
// KeyValues is typedef for QVector< QPair<qreal, QVariant> >
KeyValues animationKeyValues;
for (int i(0); i != lenghts.count(); ++i) {
animationKeyValues.append(qMakePair(path.percentAtLength(lenghts.at(i)), list_.at(i)));
}
// I assume unit is a pointer to a QObject deriving Unit instance and that
// Unit has QPointF "position" property
QPropertyAnimation unitAnimation(unit, "position");
unitAnimation.setKeyValues(animationKeyValues);
unitAnimation.setDuration(/* enter desired number here */);
unitAnimation.start();
I haven't tested this solution, but you should get the general idea.