and a nice evening.
I've the following problem:
A Box2d dynamic body with one fixture all vertices placed with positiv x / y from the origin of the body.
http://i.stack.imgur.com/MXQRr.png
But the rotation is on body origin 0/0 not on mass center..
So I tried these approaches:
1:
Set origin of Body in the Middle of the body(vertices positive and negativ)
Problem: rotation works, Sprite positioning is nearly impossible
2:
Set mass data center =
originOfBody.x + width / 2, originOfBody.y + height / 2
Problem: It's like I'm doing nothing, still rotation around origin of body at 0, 0
In the following Code snippet you can see my instantiation.
BodyDef bodyDef = new BodyDef();
bodyDef.type = BodyDef.BodyType.DynamicBody;
bodyDef.position.set(new Vector2(200, 2));
body = world.createBody(bodyDef);
FixtureDef fixtureDef = new FixtureDef();
fixtureDef.density = 0.6f;
fixtureDef.friction = 0.4f;
fixtureDef.restitution = 0.2f;
bodyLoader.attachFixture(body, "Rocket1n2", fixtureDef, WIDTH);
originOfBody = bodyLoader.getOrigin("Rocket1n2", WIDTH);
MassData data = body.getMassData();
data.center.set(Box2DUtils.getWidth(body) / 2, 0);
data.I = body.getMassData().I; //without this line programm fails with assertion
body.setMassData(data);
Vector2 cpy = data.center.cpy();
I rotate the player with following code:
body.setAngularVelocity(addition); //addition is predefined(atm 1 / -1)
I currently use Body Physics Editor from aurelia(?) with some hacks it worked, I dont have any Problems with other objects, because I dont rotate them. Placement works like a charm..
I wish I do a significant error.. Normally box2d must calculate the rotationcenter (Mass Center) automatically?
I cant help myself this time.. :(
I'd recommend getting your first approach to work.
Set origin of Body in the Middle of the body(vertices positive and
negativ) Problem: rotation works, Sprite positioning is nearly
impossible
The rotation works fine, but the sprite positioning is difficult. I had this same problem when I was making a physics game last year. What helped me was seeing exactly how the positioning worked.
Box2D bodies are center-focused. That means that calling the position of the body will give you the object's middle. Sprites are corner focused, meaning a sprite's position is it's bottom-left corner. Thus, you can't just set each of them as the same thing. I recommend shifting the sprite from the body's position down and to the left by half the body's height and width, respectively.
Related
The past few days I've been trying to figure out a display bug I don't understand. I've been working on a simple 2d platformer with box2d and orthogonal Tiled maps. So far so good, the physics work and using the b2d debug renderer I can assert proper player fixture and camera movement through the level.
Now next step I've tried to load textures to display sprites instead of debug shapes. This is where I stumble. I can load animations for my player body/fixture, but when I use the setCenter() method to center the texture on the fixture it is always out of center.
I've tried approaches via halving texture witdths and heights hoping to center the texture on the player fixture but I get the exact same off position rendering. I've played aorund with world/camera/screen unit coordinates but the misalignement persists.
I'm creating the player in my Player class with the following code.
First I define the player in box2d:
//define player's physical behaviour
public void definePlayer() {
//definitions to later use in a body
BodyDef bdef = new BodyDef();
bdef.position.set(120 / Constants.PPM, 60 / Constants.PPM);
bdef.type = BodyDef.BodyType.DynamicBody;
b2body = world.createBody(bdef);
//Define needed components of the player's main fixture
FixtureDef fdef = new FixtureDef();
PolygonShape shape = new PolygonShape();
shape.setAsBox(8 / Constants.PPM, 16 / Constants.PPM); //size of the player hitbox
//set the player's category bit
fdef.filter.categoryBits = Constants.PLAYER_BIT;
//set which category bits the player should collide with. If not mentioned here, no collision occurrs
fdef.filter.maskBits = Constants.GROUND_BIT |
Constants.GEM_BIT |
Constants.BRICK_BIT |
Constants.OBJECT_BIT |
Constants.ENEMY_BIT |
Constants.TREASURE_CHEST_BIT |
Constants.ENEMY_HEAD_BIT |
Constants.ITEM_BIT;
fdef.shape = shape;
b2body.createFixture(fdef).setUserData(this);
}
Then I call the texture Region to be drawn in the Player class constructor:
//define in box2d
definePlayer();
//set initial values for the player's location, width and height, initial animation.
setBounds(0, 0, 64 / Constants.PPM, 64 / Constants.PPM);
setRegion(playerStand.getKeyFrame(stateTimer, true));
And finally, I update() my player:
public void update(float delta) {
//center position of the sprite on its body
// setPosition(b2body.getPosition().x - getWidth() / 2, b2body.getPosition().y - getHeight() / 2);
setCenter(b2body.getPosition().x, b2body.getPosition().y);
setRegion(getFrame(delta));
//set all the boolean flags during update cycles approprietly. DO NOT manipulate b2bodies
//while the simulation happens! therefore, only set flags there, and call the appropriate
//methods outside the simulation step during update
checkForPitfall();
checkIfAttacking();
}
And my result is
this, facing right
and this, facing left
Update:
I've been trying to just run
setCenter(b2body.getPosition().x, b2body.getPosition().y);
as suggested, and I got the following result:
facing right and facing left.
The sprite texture flip code is as follows:
if((b2body.getLinearVelocity().x < 0 || !runningRight) && !region.isFlipX()) {
region.flip(true, false);
runningRight = false;
} else if ((b2body.getLinearVelocity().x > 0 || runningRight) && region.isFlipX()) {
region.flip(true, false);
runningRight = true;
}
I'm testing if either the boolean flag for facing right is set or the x-axis velocity of my player b2body has a positive/negative value and if my texture region is already flipped or not and then use libGDX's flip() accordingly. I should not be messing with fixture coords anywhere here, hence my confusion.
The coordinates of box2d fixtures are offsets from the position, the position isn't necessarily the center (although it could be depending on your shape definition offsets). So in your case i think the position is actually the lower left point of the box2d polygon shape.
In which case you don't need to adjust for width and height because sprites are also drawn from bottom left position. So all you need is ;
setPosition(b2body.getPosition().x , b2body.getPosition().y );
I'm guessing you flip the box2d body when the player looks left the position of the shape is now bottom right so the sprite offset of width/2 and height/2 is from the bottom right instead. So specifically when you are looking left you need an offset of
setPosition(b2body.getPosition().x - getWidth() , b2body.getPosition().y );
I think looking right will be fixed from this, but i don't know for sure how you handle looking left in terms of what you do to the body, but something is done because the offset changes entirely as shown in your capture. If you aren't doing some flipping you could add how you handle looking right to the question.
EDIT
It seems the answer was that the sprite wasn't centered in the sprite sheet and this additional space around the sprite caused the visual impression of being in the wrong place (see comments).
A Tiled map object hat a position x, y in pixels and a rotation in degrees.
I am loading the coordinates and the rotation from the map and trying to assign them to a box2d Body. There are a couple of differences between the location models, for example Tiled object rotation is in degrees and box2d body angle is in radians.
How do I convert the location to the BodyDef coordinates x, y and angle so that the body will be created at the correct position?
Background:
Using the code:
float angle = -rotation * MathUtils.degreesToRadians;
bodyDef.angle = angle;
bodyDef.position.set(x, y);
Works when the rotation is 0, but the body is not positioned correctly when rotation is different than 0.
I found a couple of hints here:
http://www.tutorialsface.com/2015/12/qu ... dx-solved/
and here:
https://github.com/libgdx/libgdx/issues/2742
That seem to tackle this exact problem, however neither solution worked for me, the body objects are still positioned wrong after applying those transformations. By positioned wrong I mean that the body is positioned in the area of the map where it should be but slightly off depending on its rotation.
I feel that it should be pretty simple but I do not know how to mediate the differences between Tiled and box2d locations.
For reference these are the two solutions I tried from the links above (after transforming the values x, y, width, height from pixels to world units):
float angle = rotation * MathUtils.degreesToRadians;
bodyDef.angle = -angle;
Vector2 correctionPosition = new Vector2(
height * MathUtils.cosDeg(-rotation - 90),
height + height * MathUtils.sinDeg(-rotation - 90));
bodyDef.position.set(x, y).add(correctionPosition);
and
float angle = rotation * MathUtils.degreesToRadians;
bodyDef.angle = -angle;
// Top left corner of object
Vector2 correctedPosition = new Vector2(x, y + height);
// half of diagonal for rectangular object
float radius = (float)Math.sqrt(width * width + height * height) / 2.0f;
// Angle at diagonal of rectangular object
float theta = (float)Math.tanh(height / width) * MathUtils.degreesToRadians;
// Finding new position if rotation was with respect to top-left corner of object.
// X=x+radius*cos(theta-angle)+(h/2)cos(90+angle)
// Y=y+radius*sin(theta-angle)-(h/2)sin(90+angle)
correctedPosition = correctedPosition
.add(
radius * MathUtils.cos(theta - angle),
radius * MathUtils.sin(theta - angle))
.add(
((height / 2) * MathUtils.cos(MathUtils.PI2 + angle)),
(-(height / 2) * MathUtils.sin(MathUtils.PI2 + angle)));
bodyDef.position.set(correctedPosition);
Any hint would be highly welcomed.
Found the correct solution, lost about 1 day of my life :)
The information from above links is incorrect and/or outdated. Currently Tiled saves the object position depending on it's type. For an image is relative to bottom-left position.
Box2d doesn't really have an "origin" point, but you can consider is its center and the shapes of the fixtures attached to the body should be positioned relative to (0,0).
Step 1: Read tiled properties
float rotation = textureMapObject.getRotation();
float x = textureMapObject.getX();
float y = textureMapObject.getY();
float width = textureMapObject.getProperties()
.get("width", Float.class).floatValue();
float height = textureMapObject.getProperties()
.get("height", Float.class).floatValue();
Step 2: Scale these acording to your box2d world size, for example x = x * 1/25; etc.
Step 3: Create a body without any position or angle.
Step 4: Transform body position and angle with:
private void applyTiledLocationToBody(Body body,
float x, float y,
float width, float height,
float rotation) {
// set body position taking into consideration the center position
body.setTransform(x + width / 2, y + height / 2, 0);
// bottom left position in local coordinates
Vector2 localPosition = new Vector2(-width / 2, -height / 2);
// save world position before rotation
Vector2 positionBefore = body.getWorldPoint(localPosition).cpy();
// calculate angle in radians
float angle = -rotation * MathUtils.degreesToRadians;
// set new angle
body.setTransform(body.getPosition(), angle);
// save world position after rotation
Vector2 positionAfter = body.getWorldPoint(localPosition).cpy();
// adjust position with the difference (before - after)
// so that the bottom left position remains unchanged
Vector2 newPosition = body.getPosition()
.add(positionBefore)
.sub(positionAfter);
body.setTransform(newPosition, angle);
}
Hope it helps.
Im having issue with clearRect, i have an image u can move up and down and which follow the angle where the mousse is but in some frames of the animation the clearRect let a small edge of the previous image state ( 'this' reference to the image and 'ctx' is the 2d context, 'this.clear()' is called each frame before redrawing the image at the new coordinates )
this.clear = function(){
game.ctx.save();
game.ctx.translate(this.x+this.width/2, this.y+this.height/2);//i translate to the old image center
game.ctx.rotate(this.angle);//i rotate the context to the good angle
game.ctx.clearRect(this.width/-2, this.height/-2, this.width, this.height);//i clear the old image
game.ctx.restore();
};
if i replace the clearRect line by
game.ctx.clearRect(this.width/-2-1, this.height/-2-1, this.width+2, this.height+2);
it works but its not the logical way
The problem is that you are only clearing at position half the width/height, not position minus half the width/height.
Regarding anti-aliasing: when you do a rotation there will be anti-aliased pixels regardless of the original position being integer values. This is because after the pixels relative positions are run through the transformation matrix their offsets will in most cases be float values.
Try to change this line:
game.ctx.clearRect(this.width/-2, this.height/-2, this.width, this.height);
to this instead including compensation for anti-aliased pixels (I'll split the lines for clearity):
game.ctx.clearRect(this.x - this.width/2 - 1, /// remember x and y
this.y - this.height/2 - 1,
this.width + 2,
this.height + 2);
I need to scale a dynamic text box from it's center point using Tween class AS3.
Basically, I need to scale down to 50% in 300ms or so... and after finish I want to scale up again to 100% and stop animation.
I tried to set the center point to the center of the text box but it always scale from left.
Well I been trying hard to learn the basics of Tween class and I believe it's missing some good properties and methods like greensock!
Thank you.
title_txt.text = "Text";
var textScaleX:Tween;
var textScaleY:Tween;
title_txt.addEventListener(MouseEvent.MOUSE_OVER, scaleObj(1,2, 1));
function scaleObj(startUp:int, endUp:int, duration:int){
textScaleX = new Tween(title_txt, "scaleX", Strong.easeInOut, startUp, endUp, duration, true);
textScaleY = new Tween(title_txt, "scaleY", Strong.easeInOut, startUp, endUp, duration, true);
}
The oringin of a textfield will alwasy be in the top left corner. You could however calculate where the top left corner would be at 50% of the scale and the tween the x and y postion along with the scaleX and scaleY values.
A quick calculation of the 50% x and y would be something like:
50% x = 100% x position + (100% textfield width)/4
50% y = 100% y position + (100% textfield height)/4
Edit: Here is how this calculation in code would look like:
var targetScale:Number = .5; //scale 50% but any other scale would work here as well
var targetX:Number = title_txt.x + (title_txt.width - title_txt.width * targetScale) / 2;
var targetY:Number = title_txt.y + (title_txt.height - title_txt.height * targetScale) / 2;
I use my own tween class so I'm not sure how to implement this with either the Adobe tween class or the TweenLite class, but if you stick these numbers in any tween class the textfield (or any object with it's origin in the top left corner for that matter) will scale around is center point.
Number 1 tip for tweening:
Don't use adobe tweening classes: their functionalities are not good enough.
Use TweenLite: it's easier to use and has more features.
I realise it's not really an answer to your question, but others have already covered that.
Scrolling graph. Currently I show a real-time graph by drawing each piece of data as it is generated. To scroll, I simply move the container object left to compensate and erase all the data left of the bounds.
Simple, fast, surprisingly memory efficient...but is there a concern of the coordinates overflowing as I keep adding contents to the right and scrolling the container left? Is there a limit on the x coordinate of an (empty) object? I'm afraid if I keep scrolling the container left indefinitely and writing to ever increasing x coordinates the program would misbehave eventually. Would this occur? If so, at what max coordinates? Or does AS3 handle this automatically?
I did a test, where I keep drawing an item to a container making it bigger and bigger, and moving it to the left. It stopped getting bigger at 105,000,000 pixels. After that the width reported 0 and the x property reported -107,374,182.4 and wouldn't move beyond that.
If you have bitmap data though, the FP10 limit is 8,191 pixels in width or height, and the total number of pixels cannot exceed 16,777,215 pixels.
Here was my code used to test:
var b:Sprite = new Sprite();
addChild(b);
b.x = stage.stageWidth * .5;
var t:Timer = new Timer(100);
t.addEventListener(TimerEvent.TIMER,tick);
t.start();
var moveAmt:Number = 50;
function tick(e:Event):void {
b.x -= moveAmount;
b.graphics.beginFill(Math.random() * 0xFFFFFF);
b.graphics.drawRect(b.width,0,moveAmount,stage.stageHeight);
b.graphics.endFill();
trace(b.width + " : " + b.x);
}