In the prior post, we talked about the concepts of different frames of reference. Specifically, we talked about the local or model space, in which the "geometry" of the body is defined in, and the world space defines the "position" of the body. There is another frame of reference that is useful to adopt - the screen space.
In practical terms, the "world" of the physics simulation (or the game world) may be very large. Imagine you are playing a game set in space - the world would be very large. Imagine that what you actually see on the screen maybe just part of that world. So far, in all our demos we have effectively assumed that the world = screen space, ie what you see on your computer screen is the entire world. Hopefully you can understand the flexibility offered by adopting a "screen space" as another frame of reference. In addition to the ability to "render" different parts of the world, there is another "trick" quite often adopting in creating the screen space. In the world of physics or mathematics, the y-axis usually points upwards. However, the HTML canvas has the y-axis pointing downwards. In physics simulations we deal with a lot of maths - and as such it is sometimes easier to think of the world in terms of the x-axis pointing to the right and the y-axis pointing upwards, and quite often the origin not at the top-left hand corner but somewhere in the "middle".
Let's assume that the "HTML canvas" (= screen) is 800 x 600. The origin is the top-left hand corner and the y-axis points downwards. The aspect ratio is 800 / 600 = 1.3333.
And let's assume that you want a world where the x-axis runs from -20 to 20 and the y-axis (which points upward) runs from -5 to 25.
This means that 1 unit in the world space is equivalent to 20 pixels in screen space. If you imagine looking through a viewfinder of a camera, you can imagine this as "zooming out" by x20.
Also Note that the origin of the world is not in the "center" but is shifted slightly towards the "lower". If you imagine a camera initially pointed at the center of the world, and you then "pan" the camera "up" by +10 units, you will be "looking" at the higher parts of the world.
How do we implement this type of screen space? Well, the textbook answer would be to use transformation matrices to map between the world space and the screen space. However, there is an easier "hack"; and that is use the scaleCanvas and translateCanvas methods (ie get the methods to do the transforms in the background).
I have implemented this in a Renderer class as follows.
class Renderer {
constructor(world, left = -width/2, right = width/2, bottom = -height/2, top = height/2) {
this.world = world;
this.graphics = this.world.scene.add.graphics();
this.scaleX = width / (right - left);
this.scaleY = height / (bottom - top);
this.panX = right/(right - left) * width;
this.panY = top/( top- bottom) * height;
}
render() {
const graphics = this.graphics;
graphics.clear();
graphics.translateCanvas(this.panX, this.panY);
graphics.scaleCanvas(this.scaleX, this.scaleY);
this.world.bodies.forEach(b=> {
this.renderBody(b);
})
}
renderBody(b) {
const graphics = this.graphics;
graphics.lineStyle(1/this.scaleX, '0xffffff')
graphics.save() ;
graphics.translateCanvas(b.position.x, b.position.y)
graphics.rotateCanvas(b.angle);
switch (b.shape.type) {
case "Circle":
graphics.strokeCircleShape(b.shape);
break;
case "Box":
case "Polygon":
graphics.strokePoints(b.shape.points, true);
break;
}
graphics.restore();
}
}
You need to create the renderer object in the create function. I have coded it so that the world is created first and passed into the renderer object.
this.world = new World(this);
this.renderer = new Renderer(this.world, -20, 20, -5, 25);
Then you create bodies with relevant world coordinates, add shapes and add them to the world.
this.body0 = new Body(0, 0);
this.body0.addShape(new Box(38, 2));
this.world.addBody(this.body0);
this.body1 = new Body(3,10);
this.body1.addShape(new Circle( 1));
this.world.addBody(this.body1);
this.body2 = new Body(-10,20, Math.PI/8);
this.world.addBody(this.body2);
this.body2.addShape(new Box(2,2));
Now call the renderer object created to display everything on the screen.
this.renderer.render();
Now we have separated (i) body vs the geometry, and (ii) world space vs screen space, hopefully making maintenance of the code as well as thinking about the "maths" much easier.
Comments