Rotating a Box2D light/body based on mouse input?

Difference between an impulse and a force?

http://www.iforce2d.net/b2dtut/forces

Applying an impulse is basically “apply as much (or as little) force as it takes to accelerate the body to the specified velocity in the current timestep.”

Using impulses can also be easier to control, as now you could interpolate velocity to get the tweening.

Great tutorial! I think I get it a little better now.

But as I move back to changing the direction of the light, I am lost once again.

I attempted to use applyTorque whenever the mouse was dragged, but I had no clue to keep this “synced” with the direction the body should be pointing.

If the light is attached to the body, and I can get the body to turn towards where the mouse is being dragged, the light will follow. I will keep trying to get this, but I’m not sure I understand.

I implemented the previous method, using your most recent code and using the interpolation, etc. But it does not update as the drag goes along, only once the drag is done. This looks okay for short drags, but glitches badly on long drags.


	@Override
	public boolean touchDragged(int screenX, int screenY, int pointer)
	{
		 Vector3 newTouch = camera.unproject(new Vector3(screenX, screenY, 0));
	      
		   body.applyForceToCenter(
		      new Vector2(newTouch.x, newTouch.y).sub(body.getPosition()).nor().scl(10), true
		   );
		   body.setLinearDamping(5f);
		   body.setAngularDamping(3);
		   
		   Vector3 m3 = camera.unproject(new Vector3(screenX, screenY, 0));
		   targetPosition = new Vector2(m3.x, m3.y);
		   startAngle = body.getAngle();
		   targetAngle = targetPosition.cpy().sub(body.getPosition()).angleRad();	
		   
		   if (Math.abs(targetAngle - startAngle) > MathUtils.PI) {
			      if (startAngle < targetAngle) {
			         startAngle += MathUtils.PI2;
			      } else {
			         targetAngle += MathUtils.PI2;
			      }
		   }
		   
		   time = 0;

		   lastTouch =  new Vector2(newTouch.x, newTouch.y);
		   return false;
	}


Looks like there is a tut for that: http://www.iforce2d.net/b2dtut/rotate-to-angle

What happens if you change it to applyLinearImpulse instead of force? (you’ll probably want to reduce the scl(10))

I followed the tutorial and was able to implement the needed functionality. Though, it never stopped turning! It would spin around depending on the amount of drag. I attempted to fix this myself and was successful.

In the render() method:


	if (moving == true)
	{
		float totalRotation = targetAngle - startAngle;
		float change = (float) Math.toDegrees(1);
		newAngle = body.getAngle() + Math.min( change, Math.max(-change, totalRotation));
		body.setTransform(body.getPosition(), newAngle);
		
		if (newAngle == targetAngle) moving = false;
	}

So many bugs with moving the ray! But I’ll fix those later. I’m trying to get some sort of technical demo going and I think I’ll start a WIP page.

Bugs galore! http://i.imgur.com/8PiyjNd.gifv

Shorter Gif: http://i.imgur.com/E6nUTz2.gif

In your code there, note that box2D expects everything in radians.
If it works though, then it works.

The light moving code is functional, but fairly buggy. Sometimes it will “spin” in a circle as if someone was twirling around with their flash light.

And I do not really understand the math, so I was kind of guessing at whatever I was doing at that code. I am going to leave it as it is until I can open source it, and then hope someone will do a pull request or something.

This line was being interpreted from the C++ line in the tutorial.

C++:

float change = 1 * DEGTORAD;

My possible Java interpretations

float change = (float) Math.toDegrees(1);

Or…

float change = (float) Math.toRadians(1);

toDegrees: Moves it to the correct spot quickly and correctly, sometimes spinning if moved too fast

toRadians: Moves it to the correct spot extremely slowly, as if it was being interpolated between the two angles with a slow duration.

I don’t know what they do math wise, I just tried to figure out which one seemed to work.

Well DEGTORAD is degreesToRadians, and actually has a counterpart in MathUtils

The spinning is because you aren’t doing the [icode]while ( totalRotation < -180 * DEGTORAD ) totalRotation += 360 * DEGTORAD;
while ( totalRotation > 180 * DEGTORAD ) totalRotation -= 360 * DEGTORAD;[/icode] part.
(might also be because of trying to use exact equality comparison with floats, a big no-no)

The whole thing could be:

final float spin_speed = 50; // degrees per frame

if (moving)
{
   float totalRotation = targetAngle - startAngle;
   while (totalRotation < -MathUtils.PI) totalRotation += MathUtils.PI2;
   while (totalRotation >  MathUtils.PI) totalRotation -= MathUtils.PI2;
   float change = spin_speed * MathUtils.degRad;
   newAngle = body.getAngle() + MathUtils.clamp(totalRotation, -change, change);
   body.setTransform(body.getPosition(), newAngle);
      
   moving = !MathUtils.isEqual(newAngle, targetAngle);
}

The method you just posted actually makes it spin no matter what. It does not stop once it reaches the drag position, which is what I am trying to get mine to do, and which is does, (most of the time, not always).

I don’t know what is causing this in either, but:

Gif of your code: (see it moving without touching it)

Gif of my code:

I can’t take it anymore :point:

[icode]Rot2D.java[/icode]


public class Rot2D {
	public static Rot2D fromDegrees(double angle) {
		return fromRadians(Math.toRadians(angle));
	}

	public static Rot2D fromRadians(double angle) {
		return new Rot2D(Math.cos(angle), Math.sin(angle));
	}

	public static Rot2D fromVector(double dx, double dy) {
		float length = (float) Math.sqrt(dx * dx + dy * dy);
		return new Rot2D(dx / length, dy / length);
	}



	public double cos, sin;

	private Rot2D(double cos, double sin) {
		this.cos = cos;
		this.sin = sin;
	}

	public Rot2D load(Rot2D that) {
		this.cos = that.cos;
		this.sin = that.sin;

		return this;
	}

	public Rot2D copy() {
		return new Rot2D(cos, sin);
	}

	public Rot2D rotate(Rot2D that) {
		double cos = (this.cos * that.cos) - (this.sin * that.sin);
		double sin = (this.cos * that.sin) + (this.sin * that.cos);

		this.cos = cos;
		this.sin = sin;

		return this;
	}

	public static double cross(Rot2D a, Rot2D b) {
		return (a.cos * b.sin) - (a.sin * b.cos);
	}
}

[icode]TurnUI.java[/icode]



import java.awt.*;
import java.awt.event.*;
import javax.swing.*;

public class TurnUI {
	public static void main(String[] args) {

		final Point item = new Point(400, 300);
		final Point target = new Point(350, 400);
		final Rot2D currAngle = Rot2D.fromVector(target.x - item.x, target.y - item.y);
		final Rot2D turnSpeedCW = Rot2D.fromDegrees(1.0);
		final Rot2D turnSpeedCCW = Rot2D.fromDegrees(-1.0);

		JPanel panel = new JPanel() {

			@Override
			protected void paintComponent(final Graphics g) {
				super.paintComponent(g);

				final Rot2D wantedAngle = Rot2D.fromVector(target.x - item.x, target.y - item.y);

				double cross1 = Rot2D.cross(currAngle, wantedAngle);
				if (cross1 > 0.0)
					currAngle.rotate(turnSpeedCW);
				else
					currAngle.rotate(turnSpeedCCW);
				double cross2 = Rot2D.cross(currAngle, wantedAngle);

				if (Math.signum(cross1) != Math.signum(cross2))
					currAngle.load(wantedAngle); // overshot!
				
				

				g.setColor(Color.BLACK);
				g.fillRect(item.x - 3, item.y - 3, 6, 6);
				g.drawLine(item.x, item.y, //
						(int) (item.x + currAngle.cos * 64), //
						(int) (item.y + currAngle.sin * 64));

				g.setColor(Color.RED);
				g.fillRect(target.x - 1, target.y - 1, 2, 2);



				// lousy game-loop:
				try {
					Thread.sleep(1000 / 50); // ~20fps
				} catch (InterruptedException exc) {
					// ok
				}
				this.repaint();
			}
		};

		panel.addMouseListener(new MouseAdapter() {
			@Override
			public void mousePressed(MouseEvent e) {
				if (e.getButton() == MouseEvent.BUTTON1) {
					target.x = e.getX();
					target.y = e.getY();
				}
			}
		});

		panel.setPreferredSize(new Dimension(800, 600));

		JFrame frame = new JFrame("Rot2D");
		frame.getContentPane().setLayout(new BorderLayout());
		frame.getContentPane().add(panel);
		frame.setResizable(true);
		frame.pack();
		frame.setLocationRelativeTo(null);
		frame.setVisible(true);
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
	}
}

Thanks for the code!

But I’m not really sure how I would implement it seeing as I am using LibGDX and not Swing, and am probably not good enough at math to transfer the concepts between frameworks. But I will definitely revisit this code again later in the project when I come back to refine the light movement.

Because there are a few things that need to change, but that I will revisit later so I can get on with the gaming process now

Needed changes:

  • If the mouse is being held down, the player should move towards it no matter what (It currently only moves forward if the angle changed, but someone should be able to hold their mouse down and move)
  • If someone clicks on the opposite side, it kind of just moves there instead of animating there,

But I managed to fix the spinning issues once and for all by adding “moving = false;” to the touchUp() method, so it automatically stops all movement when the person lets go, preventing any kind of “after the fact” spinning. But that method is a bit too trashy for me, so as I said, I will revisit it later.

The core of it comes down to events that happen intermittently and things that must happen each frame:

  • Mouse input updates a waypoint (which is stored persistently) intermittently
  • Each frame the player rotates and moves towards the waypoint

So the physics will always be driven by the game loop (that’s the whole point of the loop, plus rendering), but is supplied information by user input.

Now this can be implemented directly (literally just a waypoint), or it can be expanded to a queue of waypoints representing a path to follow: the player looks at the current waypoint and consumes it upon reaching it and then continuing on the next point if there is one, while the mouse input processor produces points to add to the queue. It’s a standard producer-consumer problem. In fact the single-waypoint implementation is just a special case of the general queue, it is a single-element queue.
I think the path-following might look better and is only slightly more work. Depends on what behavior you want.

Once you have the architecture, all you need is the moveTowards(point) and rotateTowards(point), which are already supplied by Box2D in various ways that you can experiment with.

This is the same kind of architecture as pretty much every video game ever, and can be applied throughout, not just controlling a character based on mouse input.

There isn’t anything that ties this to Swing, except the demo code that produces a UI :-X

It’s about line 20-30 in TurnUI.java, everything else can be ignored… was meant to help you along, as opposed to scaring you off. There is no ‘transfering between frameworks’ at all.

Ah. I went through it and attempted to bring it over to my project, but I am having some trouble at the very end when I am trying to transform the player to the new angle.


		if (Gdx.input.isTouched())
		{
			// Set current touch
			touch_cur = new Vector2(Gdx.input.getX(), Gdx.input.getY());
			
			// Set default values
			if (touch_prev == null) 
			{
				touch_prev = new Vector2(Gdx.input.getX(), Gdx.input.getY());
				
				touch_angleCur = Rot2D.fromVector(
					touch_cur.x - entity_player.getBody().getPosition().x, 
					touch_cur.y - entity_player.getBody().getPosition().y
				);
			}
			
			// If they are holding down the mouse in one spot, move forward
			if (touch_prev == touch_cur)
			{
				// apply some kind of force
			}
			else
			{
				touch_angleWant = Rot2D.fromVector(
					touch_cur.x - entity_player.getBody().getPosition().x, 
					touch_cur.y - entity_player.getBody().getPosition().y
				);
				
				double crossOne = Rot2D.cross(touch_angleCur, touch_angleWant);
				
				if (crossOne > 0.f) touch_angleCur.rotate(Rot2D.fromDegrees(1.0));
				else touch_angleCur.rotate(Rot2D.fromDegrees(-1.0));
				
				double crossTwo = Rot2D.cross(touch_angleCur, touch_angleWant);
				
				if (Math.signum(crossOne) != Math.signum(crossTwo))
					touch_angleCur.load(touch_angleWant);
				
				entity_player.getBody().setTransform(entity_player.getBody().getPosition(), /* How */);
			}
			
		}

I’m not familiar with Box2D, but if it demands an angle as one of the parameters to define a Transform, you can do:


public class Rot2D {
   ...

+   public double getAngle() { // radians
+      return Math.atan2(sin, cos); // atan2(y, x)
+   }

   ...
}


entity_player.getBody().setTransform(
   entity_player.getBody().getPosition(),
   touch_angleCur.getAngle()
);

Getting some really weird behavior back. Going to try to figure it out.

Why would you only rotate on touch events?

if (Gdx.input.isTouched()) {
   ...
}

From the gif animation it’s (obviously :)) not clear when you’re pressing and releasing mouse buttons. You can print out the values in your game-loop, to see what state it is in; mainly whether it reaches line 37: which detects whether we actually reached (and overshot) our target angle.

I feel stupid for having to say this, but it’s still not working…

Edit: Whenever it moves smoothly I am dragging, whenever it teleports I am clicking.


		if (Gdx.input.isTouched())
		{
			touch_target = new Vector2(Gdx.input.getX(), Gdx.input.getY());
			
			touch_angleCur = Rot2D.fromVector(
				touch_target.x - entity_player.getBody().getPosition().x,
				touch_target.y - entity_player.getBody().getPosition().y
			);
		}
		
		if (touch_target != null)
		{
			touch_angleWant = Rot2D.fromVector(
				touch_target.x - entity_player.getBody().getPosition().x,
				touch_target.y - entity_player.getBody().getPosition().y
			);
			
			double cross1 = Rot2D.cross(touch_angleCur, touch_angleWant);
			
			if (cross1 > 0.0)
				touch_angleCur.rotate(Rot2D.fromDegrees(1.0));
			else
				touch_angleCur.rotate(Rot2D.fromDegrees(-1.0));
			
			double cross2 = Rot2D.cross(touch_angleCur, touch_angleWant);
			
			if (Math.signum(cross1) != Math.signum(cross2))
				touch_angleCur.load(touch_angleWant);
			
			entity_player.getBody().setTransform(entity_player.getBody().getPosition(), (float) touch_angleCur.getAngle());
		}