OpenGL Shader - Rotating a model around its origin (2D World) - java

So I created a vertex shader that takes in an angle and calculates the rotation. There is a problem though that the model rotates around the world center and not its own axis/origin.
Side note: This is 2D rotation.
How do I make the model rotate through its own axis?
Here is my current vertex shader:
#version 150 core
in vec4 in_Position;
in vec4 in_Color;
in vec2 in_TextureCoord;
out vec4 pass_Color;
out vec2 pass_TextureCoord;
void main(void) {
gl_Position = in_Position;
pass_Color = in_Color;
pass_TextureCoord = in_TextureCoord;
}
Rotating CPU side:
Vector3f center = new Vector3f(phyxBody.getPosition().x,phyxBody.getPosition().y,0);
Matrix4f pos = new Matrix4f();
pos.m00 = (phyxBody.getPosition().x)-(getWidth()/30f/2f);
pos.m01 = (phyxBody.getPosition().y)+(getHeight()/30f/2f);
pos.m10 = (phyxBody.getPosition().x)-(getWidth()/30f/2f);
pos.m11 = (phyxBody.getPosition().y)-(getHeight()/30f/2f);
pos.m20 = (phyxBody.getPosition().x)+(getWidth()/30f/2f);
pos.m21 = (phyxBody.getPosition().y)-(getHeight()/30f/2f);
pos.m30 = (phyxBody.getPosition().x)+(getWidth()/30f/2f);
pos.m31 = (phyxBody.getPosition().y)+(getHeight()/30f/2f);
pos.rotate(phyxBody.getAngle(),center);
Result is a weird rotated stretch of the object.. Do you know why? Don't worry about the /30f part.
phyxBody is an instance of the class Body from the JBox2D library.
phyxBody.getAngle() is in raidians.
Matrix4f is a class from the LWJGL library.
EDIT:
Vector3f center = new Vector3f(0,0,0);
Matrix4f pos = new Matrix4f();
pos.m00 = -(getWidth()/30f/2f);
pos.m01 = +(getHeight()/30f/2f);
pos.m10 = -(getWidth()/30f/2f);
pos.m11 = -(getHeight()/30f/2f);
pos.m20 = +(getWidth()/30f/2f);
pos.m21 = -(getHeight()/30f/2f);
pos.m30 = +(getWidth()/30f/2f);
pos.m31 = +(getHeight()/30f/2f);
pos.rotate(phyxBody.getAngle(),center);
pos.m00 += phyxBody.getPosition().x;
pos.m01 += phyxBody.getPosition().y;
pos.m10 += phyxBody.getPosition().x;
pos.m11 += phyxBody.getPosition().y;
pos.m20 += phyxBody.getPosition().x;
pos.m21 += phyxBody.getPosition().y;
pos.m30 += phyxBody.getPosition().x;
pos.m31 += phyxBody.getPosition().y;
This is currently the transformation code, yet the rotation still doesn't work correctly.
My try at the rotate method: (What am I doing wrong?)
if (phyxBody.getAngle() != 0.0) {
pos.m00 *= Math.cos(Math.toDegrees(phyxBody.getAngle()));
pos.m01 *= Math.sin(Math.toDegrees(phyxBody.getAngle()));
pos.m10 *= -Math.sin(Math.toDegrees(phyxBody.getAngle()));
pos.m11 *= Math.cos(Math.toDegrees(phyxBody.getAngle()));
pos.m20 *= Math.cos(Math.toDegrees(phyxBody.getAngle()));
pos.m21 *= Math.sin(Math.toDegrees(phyxBody.getAngle()));
pos.m30 *= -Math.sin(Math.toDegrees(phyxBody.getAngle()));
pos.m31 *= Math.cos(Math.toDegrees(phyxBody.getAngle()));
}

The order is scaling * rotation * translation - see this question. I'm guessing you've already translated your coordinates outside of your shader. You'll have to rotate first, then translate. It's good to know the linear algebra behind what you're doing so you know why things work or don't work.
The typical way to do this is to pass a pre-computed ModelView matrix that has already taken care of scaling/rotation/translation. If you've already translated your vertices, you can't fix the problem in your shader without needlessly undoing it and then redoing it after. Send in your vertices untranslated and accompany them with data, like your angle, to translate them. Or you can translate and rotate both beforehand. It depends on what you want to do.
Bottom line: You must rotate before you translate.
Here is the typical way you do vertex transformations:
OpenGL side:
Calculate ModelView matrix: Scale * Rotation * Translation
Pass to shader as a uniform matrix
GLSL side:
Multiply vertices by ModelView matrix in vertex shader
Send to gl_Position
Response to Edit:
I'm inclined to think your implementation needs to be completely redone. You have points that belong to a model. These points are all oriented around the origin. For example, if you had a car, the points would form a mesh of triangles.
If you simply do not translate these points and then rotate them, the car will rotate around its center. If you translate afterwards, the car will translate in its rotated fashion to the place you've specified. The key here is that the origin of your model lines up with the origin of rotation so you end up rotating the model "around itself."
If you instead translate to the new position and then rotate, your model will rotate as if it were orbiting the origin. This is probably not what you want.
If you're modifying the actual vertex positions directly instead of using transformation matrices, you're doing it wrong. Even if you just have a square, leave the coordinates at (-1,-1) (-1,1) (1,1) (1,-1) (notice how the center is at (0,0)) and translate them to where you want to be.
You don't have to re-implement math functionality and probably shouldn't (unless your goal is explicitly to do so). GLM is a popular math library that does everything you want and it's tailored specifically for OpenGL.
Final Edit
Here is a beautiful work of art I drew for you demonstrating what you need to do.
Notice how in the bottom right the model has been swept out around the world origin about 45 degrees. If we went another 45, it would have its bottom edge parallel to the X-axis and intersecting the positive Y-axis with the blue vertex in the bottom left and purple vertex in the bottom right.
You should probably review how to work with vertices, matrices, and shaders. Vertices should be specified once, matrices should be updated every time you chance the scale, rotation, or position of the object, and shaders should multiply the each vertex in the model by a uniform (constant).

Your sprite lacks sufficient information to be able to do what you're trying to do. In order to compute a rotation about a point, you need to know what that point is. And you don't.
So if you want to rotate about an arbitrary location, you will need to pass that location to your shader. Once there, you subtract it from your positions, rotate the position, and add it back in. However, that would require a lot of work, which is why you should just compute a matrix on the CPU to do all of that. Your shader would be given this matrix and perform the transform itself.
Of course, that itself requires something else, because you keep updating the position of these objects by offsetting the vertices on the CPU. This is not good; you should be keeping these objects relative to their origin in the buffer. You should then transform them to their world-position as part of their matrix.
So your shader should be taking object-relative coordinates, and it should be passed a matrix that does a rotation followed by a translation to their world-space position. Actually, scratch that; the matrix should transform to their final camera-space position (world-space is always a bad idea).

Related

Java LWJGL Rendering pistol model at right side of screen

Hi everyone I'm working on weapon rendering and I got stuck in part when I have to calculate gun.y and gun.rot.x. Rotation on y axis and calculating x,z of gun works good. But now is question how I can get gun.rot.x and gun.y. My calculation of gun x and y looks like:
float offsetX = (float) Math.sin(Math.toRadians(camera.getRotation().y + 25));
float offsetZ = (float) Math.cos(Math.toRadians(camera.getRotation().y + 25));
gun.x = camera.x + offsetX;
gun.z = camera.z - offsetZ;
Y rotation of gun is really simple:
gun.getRotation().y = 360 - camera.getRotation().y;
I tried to calculate gun.y with code like this:
float offsetY = (float) Math.sin(Math.toRadians(camera.getTransform().getRotation().x + 25));
gun.y = camera.y - offsetY
But it seems to not work correctly.
What you are trying to do is rendering a viewmodel (the gaming term). this i generally done by having another camera parented by the player's camera. In other words, you would use a separate camera to render the model so that it looks close to the face (and also to avoid the gun going through walls for example) and then depending on your implementation add your viemodel camera's transformation to the main camera's transformation.
If you are using the fixed function pipeline (glMatrixMode(), glTranslatef() and so on) all you have to do is apply the transformation (call glTranslatef() and glRotatef()) without reseting the identity matrix (glLoadIdentity()). For example:
{ /* your rendering code */
glLoadIdentity();
camera.applyTransformation();
render_scene();
glPushMatrix(); // ensures that the transformation isent
// directly applied to the matrix should
// you want to render more thing with the
// main camera after the view model
viewmodelCamera.applyTransformation();
render_viewmodel();
glPopMatrix(); // complementary call for glPushMatrix()
// marks the end of this matrix operation
// on the matrix stack
possibly_render_more_things() // if you wish
} /* end of rendering code */
If you are using matrices for your camera (which you should if you intend to properly use modern OpenGL), all you have to do is add the two MVP (Model View Projection) matrices, your base camera's and your modelview camra's, and pass the result of these to your shader for the gun rendering.
Hope this helped!
EDIT: Just thought I'd mention, the second camera is basically like working in model space for your gun model, you would use the units that you are currently storing in gun.x (for ex) (also don't use public variables), and make those the camera's transformation.

Rendering a laser beam - how to make it face camera?

(I am using a LibGDX framework which is basically just LWJGL(Java) with OpenGL for rendering)
Hi, I'm trying to render a laser beam, so far I've got this effect,
It's just a rectangle and then the whole effect is done in fragment Shader.
However, as it is a laser beam, I want the rectangle to face a camera, so the player always sees this red transparent "line" everytime. And this is driving me crazy. I tried to do some billboarding stuff, however what I want isn't really billboarding. I just want to rotate it on Z axis so that the player always sees the whole line, that's all. No X and Y rotations.
As you can see, that's what I want. And it's not billboarding at all.
If it was billboarding, it would look like this: .
I also tried to draw cylinder and the effect based on gl_FragCoord, which was working fine, but the coords were varying(sometimes the UVs were 0 and 1, sometimes 0 and 0.7) and it was not sampling whole texture, so the effect was broken.
Thus I don't even know what to do now.
I would really appreciate any help. Thanks in advance.
Here's vertexShader code:
attribute vec3 a_position;
attribute vec2 a_texCoord0;
uniform mat4 u_worldTrans; //model matrix
uniform mat4 u_view; //view matrix
uniform mat4 u_proj; // projection matrix
varying vec2 v_texCoord0;
void main() {
v_texCoord0 = a_texCoord0;
vec4 worldTrans = u_worldTrans * vec4(a_position, 1.0);
gl_Position = u_proj * u_view * worldTrans;
}
and here's fragmentShader codE:
#ifdef GL_ES
precision mediump float;
#endif
varying vec2 v_texCoord0;
uniform sampler2D tex; //texture I apply the red color onto. It's how I get the smooth(transparent) edges.
void main() {
vec4 texelColor = texture2D( tex, v_texCoord0 ); //sampling the texture
vec4 color = vec4(10.0,0.0,0.0,1.0); //the red color
float r = 0.15; //here I want to make the whole texture be red, so when there's less transparency, I want it to be more red, and on the edges(more transparency) less red.
if (texelColor.a > 0.5) r = 0.1;
gl_FragColor = vec4(mix(color.rgb,texelColor.rgb,texelColor.a * r),texelColor.a); //and here I just mix the two colors into one, depengind on the alpha value of texColor and the r float.
}
The texture is just a white line opaque in the middle, but transparent at the edges of the texuture. (smooth transition)
If you use DecalBatch to draw your laser, you can do it this way. It's called axial billboarding or cylindrical billboarding, as opposed to the spherical billboarding you described.
The basic idea is that you calculate the direction the sprite would be oriented for spherical billboarding, and then you do a couple of cross products to get the component of that direction that is perpendicular to the axis.
Let's assume your laser sprite is aligned to point up and down. You would do this series of calculations on every frame that the camera or laser moves.
//reusable calculation vectors
final Vector3 axis = new Vector3();
final Vector3 look = new Vector3();
final Vector3 tmp = new Vector3();
void orientLaserDecal (Decal decal, float beamWidth, Vector3 endA, Vector3 endB, Camera camera) {
axis.set(endB).sub(endA); //the axis direction
decal.setDimensions(beamWidth, axis.len());
axis.scl(0.5f);
tmp.set(endA).add(axis); //the center point of the laser
decal.setPosition(tmp);
look.set(camera.position).sub(tmp); //Laser center to camera. This is
//the look vector you'd use if doing spherical billboarding, so it needs
//to be adjusted.
tmp.set(axis).crs(look); //Axis cross look gives you the
//right vector, the direction the right edge of the sprite should be
//pointing. This is the same for spherical or cylindrical billboarding.
look.set(tmp).crs(axis); //Right cross axis gives you an adjusted
//look vector that is perpendicular to the axis, i.e. cylindrical billboarding.
decal.setRotation(look.nor(), axis); //Note that setRotation method requires
//direction vector to be normalized beforehand.
}
I didn't check to make sure the direction doesn't get flipped, because I draw it with back face culling turned off. So if you have culling on and don't see the sprite, that last cross product step might need to have its order reversed so the look vector points in the opposite direction.

Raytracer warps cube based on camera angle?

Essentially, what is happening is there is some strange warping of the 3D cube being rendered by my raytracer, which continues to worsen as the camera moves up, even if the cube is in the same location on the screen.
The code is at http://pastebin.com/HucgjRtx
Here is a picture of the output:
http://postimg.org/image/5rnfrlkej/
EDIT: Problem resolved as being that I was just calculating the angles for vectors wrong. The best method I have found is creating a vector based on your FOV (Z) current pixel X, and current pixel Y, then normalizing that vector.
It looks like you're calculating rays to cast based on Euler angles instead of the usual projection.
Typically a "3D" camera is modeled such that the camera is at a point with rays projecting through a grid spaced some distance from it... which is, incidentally, exactly like looking at a monitor placed some distance from your face and projecting a ray through each pixel of the monitor.
The calculations are conceptually simple in fixed cases.. e.g.
double pixelSpacing = 0.005;
double screenDistance = 0.7;
for (int yIndex= -100; yIndex<= 100; yIndex++)
for (int xIndex= -100; xIndex<= 100; xIndex++) {
Vector3 ray = new Vector3(
xIndex * pixelSpacing,
yIndex * pixelSpacing,
screenDistance
);
ray = vec.normalize();
// And 'ray' is now a vector with our ray direction
}
You can use one of the usual techniques (e.g. 4x4 matrix multiplication) if you want to rotate this field of view.

Box rotation around multiple axises using Matrix4f

The question change a bit, I figured out how to rotate around a single axis
I want to rotate a box around the Y axis using an angle.
The box has a size, and a Vector3f to signal the rotation.
To rotate the box correctly what I do is rotate the origin position then rotate the origin position plus the size, and use those two references to render the box.
However this rotation does not work correctly and causes rendering artifacts.
This is my code to rotate the positions:
Matrix4f matrix = new Matrix4f();
// Rotate the origin position
Vector3f pos = new Vector3f(new Vector3f(blockX, blockY, blockZ));
matrix.m03 = pos.x;
matrix.m13 = pos.y;
matrix.m23 = pos.z;
Vector3f rot = new Vector3f(new Vector3f(0, 1f, 0f));
Matrix4f.rotate((float) Math.toRadians(45f), rot, matrix, matrix);
Vector3f locationMin = new Vector3f(matrix.m03, matrix.m13, matrix.m23);
// Rotate the position with the size
// Top left back is the position of the block
Vector3f sizeRot = new Vector3f(new Vector3f(blockX + size, blockY + size, blockZ + size));
matrix = new Matrix4f();
matrix.m03 = sizeRot.x;
matrix.m13 = sizeRot.y;
matrix.m23 = sizeRot.z;
rot = new Vector3f(new Vector3f(0, 1f, 0f));
Matrix4f.rotate((float) Math.toRadians(45f), rot, matrix, matrix);
Vector3f locationMax = new Vector3f(matrix.m03, matrix.m13, matrix.m23);
// Then here I use the locationMax and the locationMin to render the cube
What could be wrong with this code? Is the logic I am using to rotate the box correct? as in rotate the origin position then rotate the origin position plus the size..
EDIT: I released that rotating after translating is stupid so instead I just rotated the locationMax which is not translated (it is only the size) then I translated and I still get the same result (Graphical Artifacts).
New Code:
float rx = blockX, ry = blockY, rz = blockZ;
Matrix4f matrix = new Matrix4f();
Vector3f rot = new Vector3f(0, 1f, 0f);
matrix = new Matrix4f();
matrix.m03 = size;
matrix.m13 = size;
matrix.m23 = size;
Matrix4f.rotate((float) Math.toRadians(45f), rot, matrix, matrix);
matrix.translate(new Vector3f(rx, ry, rz), matrix);
float mx = matrix.m03;
float my = matrix.m13;
float mz = matrix.m23;
// Here is use rx, ry, rz and mx, my, mz to render the box
============ * I figured it out (See below)* =============
EDIT:
This is what I ended up doing:
// Origin point
Vector4f a = new Vector4f(blockX, blockY, blockZ, 1);
// Rotate a matrix 45 degrees
Matrix4f mat = new Matrix4f();
mat.rotate((float) Math.toRandians(45f), new Vector3f(
0, 1f, 0), mat);
/* Transform the matrix to each point */
Vector4f c = new Vector4f(size.x, 0, size.z, 1);
Matrix4f.transform(mat, c, c);
Vector4f.add(c, a, c);
Vector4f b = new Vector4f(size.x, 0, 0, 1);
Matrix4f.transform(mat, b, b);
Vector4f.add(b, a, b);
Vector4f d = new Vector4f(0, 0, size.z, 1);
Matrix4f.transform(mat, d, d);
Vector4f.add(d, a, d);
// Here is use a, b, c, and d to render the box.
The problem with this is that I want to rotate around all axises and not only around the Y axis. This makes the code very long and unreadable and There are a lot of bugs when I try to rotate around all axises.
Update Question:
How do I take the above code and make it so I can rotate around all 3 axises. I want to do this so I can have a billboard that will always face the camera.
This is how I calculate the angle between the camera and the object:
Vector3f angle = new Vector3f();
// Calculate the distance between camera and object
Vector3f.sub(game.getCamera().getLocation(),
new Vector3f(blockX, blockY, blockZ), angle);
// Calculate the angle around the Y axis.
float vectorAngle = (float) ((float) Math.atan2(angle.z, angle.x) * -1 + (Math.PI / 2.0f));
Billboards are a very common application of computer graphics (as I'm sure you've noticed, since you're asking the question!)
Ultimately I think you are over complicating the problem, based on:
as in rotate the origin position then rotate the origin position plus the size..
For computer graphics, the most common transformations are Scaling, Translating, and Rotating, and you do these in an order to achieve a desired effect (traditionally you scale, then rotate about the origin, then translate the vertex's position).
Additionally, you will have three main matrices to render a model in 3d: World Matrix, View Matrix, and Projection Matrix. I believe you are having misunderstandings of transforming from Model Space to World Space.
Graphics TRS and Matrix info. If you are having conceptual problems, or this answer is insufficient, I highly recommend looking at this link. I have yet to find a better resource explaining the fundamentals of computer graphics.
So right at the moment, you have your three angles (in degrees, in a Vector3) corresponding to the angle difference in the X,Y, and Z coordinate spaces from your billboard and your camera. With this information, we generate the View matrix by first gathering all of our matrix transformations in one place.
I'm going to assume that you already have your Translation and Scaling matrices, and that they both work. This means that we only need to generate our Rotation matrix, and then transform that matrix with the scaling matrix, and then transforming that matrix by our translation matrix.
X Rotation Matrix
Y Rotation Matrix
Z Rotation Matrix
(Images taken from CodingLabs link above)
So you will generate these three matrices, using the X,Y, and Z angles you calculated earlier, and then transform them to consolidate them into a single matrix, transform that matrix by the scaling matrix, and then transform that matrix by the translation matrix. Now you have your awesome matrix that, when you multiply a a vertex by it, will transform that vertex into the desired size, rotation, and position.
So you transform every single vertex point by this generated matrix.
And then after that, you should be done! Using these techniques will hopefully simplify your code greatly, and set you on the right path :)
So now how about some code?
//I do not guarantee that this code compiles! I did not write it in an IDE nor did I compile it
float angleToRotX = 180f;
float angleToRotY = 90f;
float angleToRotZ = 0f;
// example vertex
Vector4f vertex = new Vector4f(0, 1, 0, 1);
// Rotate vertex's X coordinates by the desired degrees
Matrix4f rotationXMatrix = new Matrix4f();
rotationXMatrix.rotX(angleToRotX);
Matrix4f rotationYMatrix = new Matrix4f();
rotationYMatrix.rotY(angleToRotY);
Matrix4f rotationZMatrix = new Matrix4f();
rotationZMatrix.rotZ(angleToRotZ);
//now let's translate it by 1.5, 1, 1.5 in the X,Y,Z directions
Matrix4f translationMatrix = new Matrix4f();
translationMatrix.setTranslate(new Vector3f(1.5, 1, 1.5));
/*
Now we have our three rotational matrices. So we multiply them (transform them) to get a single matrix to transform all of the points in this model to the desired world coordinates
*/
Matrix4f rotationMatrix = new Matrix4f();
rotationMatrix.mul(rotationXMatrix);
rotationMatrix.mul(rotationYMatrix);
rotationMatrix.mul(rotationZMatrix);
Matrix4f worldMatrix = translationMatrix;
worldMatrix.mul(rotationMatrix);
//now worldMatrix, when applied to a vertex, will rotate it by X,Y,Z degrees about the origin of it's model space, and then translate it by the amount given in translationMatrix
worldMatrix.transform(vertex);
//now vertex should be (1.5, 0, 1.5, 1) with (x,y,z,1)
Now this code could really be simplified, and it is excessively verbose. Try it out! I don't have java downloaded on my machine, but I grabbed the methods from the java documentation Here
Here is an image of what is happening (again, taking from coding labs):
(Advanced Info: Quaternions. These are really cool way of orienting a model in 3d space, however I don't quite understand them to the degree I need to in order to explain it to someone else, and I also believe that your problem is more fundamental)
You could generate the matrix without much hassle. The OpenGL matrix looks like the following:
|lx,ux,vx,px| - lx,ly,lz = the left vector
|ly,uy,vy,py| - ux,uy,uz = the up vector
|lz,uz,vz,pz| - vx,vy,vz = the view vector
|0 ,0 ,0 ,1 | - px,py,pz = the translation
All you need to do, is set px,py,pz to the position of your box in the world,
your view vector to the normalized(camera position - box position), your up comes straight from your camera, and the left is calculated via normalized cross product. It's also good practice to reconstruct the up vector, after left one is derived (by another cross product). That's all there's to it.
My solution aims to save you some time coding, rather than explain everything in detail. Hope that is useful to someone.

How to have a "Camera" only show a portion of a loaded area

I'm having a little problem with figuring something out (Obviously).
I'm creating a 2D Top-down mmorpg, and in this game I wish the player to move around a tiled map similar to the way the game Pokemon worked, if anyone has ever played it.
If you have not, picture this: I need to load various areas, constructing them from tiles which contain an image and a location (x, y) and objects (players, items) but the player can only see a portion of it at a time, namely a 20 by 15 tile-wide area, which can be 100s of tiles tall/wide. I want the "camera" to follow the player, keeping him in the center, unless the player reaches the edge of the loaded area.
I don't need code necessarily, just a design plan. I have no idea how to go about this kind of thing.
I was thinking of possibly splitting up the entire loaded area into 10x10 tile pieces, called "Blocks" and loading them, but I'm still not sure how to load pieces off screen and only show them when the player is in range.
The picture should describe it:
Any ideas?
My solution:
The way I solved this problem was through the wonderful world of JScrollPanes and JPanels.
I added a 3x3 block of JPanels inside of a JScrollPane, added a couple scrolling and "goto" methods for centering/moving the JScrollPane around, and voila, I had my camera.
While the answer I chose was a little more generic to people wanting to do 2d camera stuff, the way I did it actually helped me visualize what I was doing a little better since I actually had a physical "Camera" (JScrollPane) to move around my "World" (3x3 Grid of JPanels)
Just thought I would post this here in case anyone was googling for an answer and this came up. :)
For a 2D game, it's quite easy to figure out which tiles fall within a view rectangle, if the tiles are rectangular. Basically, picture a "viewport" rectangle inside the larger world rectangle. By dividing the view offsets by the tile sizes you can easily determine the starting tile, and then just render the tiles in that fit inside the view.
First off, you're working in three coordinate systems: view, world, and map. The view coordinates are essentially mouse offsets from the upper left corner of the view. World coordinates are pixels distances from the upper left corner of tile 0, 0. I'm assuming your world starts in the upper left corner. And map cooridnates are x, y indices into the map array.
You'll need to convert between these in order to do "fancy" things like scrolling, figuring out which tile is under the mouse, and drawing world objects at the correct coordinates in the view. So, you'll need some functions to convert between these systems:
// I haven't touched Java in years, but JavaScript should be easy enough to convey the point
var TileWidth = 40,
TileHeight = 40;
function View() {
this.viewOrigin = [0, 0]; // scroll offset
this.viewSize = [600, 400];
this.map = null;
this.worldSize = [0, 0];
}
View.prototype.viewToWorld = function(v, w) {
w[0] = v[0] + this.viewOrigin[0];
w[1] = v[1] + this.viewOrigin[1];
};
View.prototype.worldToMap = function(w, m) {
m[0] = Math.floor(w[0] / TileWidth);
m[1] = Math.floor(w[1] / TileHeight);
}
View.prototype.mapToWorld = function(m, w) {
w[0] = m[0] * TileWidth;
w[1] = m[1] * TileHeight;
};
View.prototype.worldToView = function(w, v) {
v[0] = w[0] - this.viewOrigin[0];
v[1] = w[1] - this.viewOrigin[1];
}
Armed with these functions we can now render the visible portion of the map...
View.prototype.draw = function() {
var mapStartPos = [0, 0],
worldStartPos = [0, 0],
viewStartPos = [0, 0];
mx, my, // map coordinates of current tile
vx, vy; // view coordinates of current tile
this.worldToMap(this.viewOrigin, mapStartPos); // which tile is closest to the view origin?
this.mapToWorld(mapStartPos, worldStartPos); // round world position to tile corner...
this.worldToView(worldStartPos, viewStartPos); // ... and then convert to view coordinates. this allows per-pixel scrolling
mx = mapStartPos[0];
my = mapStartPos[y];
for (vy = viewStartPos[1]; vy < this.viewSize[1]; vy += TileHeight) {
for (vx = viewStartPos[0]; vx < this.viewSize[0]; vy += TileWidth) {
var tile = this.map.get(mx++, my);
this.drawTile(tile, vx, vy);
}
mx = mapStartPos[0];
my++;
vy += TileHeight;
}
};
That should work. I didn't have time to put together a working demo webpage, but I hope you get the idea.
By changing viewOrigin you can scroll around. To get the world, and map coordinates under the mouse, use the viewToWorld and worldToMap functions.
If you're planning on an isometric view i.e. Diablo, then things get considerably trickier.
Good luck!
The way I would do such a thing is to keep a variable called cameraPosition or something. Then, in the draw method of all objects, use cameraPosition to offset the locations of everything.
For example: A rock is at [100,50], while the camera is at [75,75]. This means the rock should be drawn at [25,-25] (the result of [100,50] - [75,75]).
You might have to tweak this a bit to make it work (for example maybe you have to compensate for window size). Note that you should also do a bit of culling - if something wants to be drawn at [2460,-830], you probably don't want to bother drawing it.
One approach is along the lines of double buffering ( Java Double Buffering ) and blitting ( http://download.oracle.com/javase/tutorial/extra/fullscreen/doublebuf.html ). There is even a design pattern associated with it ( http://www.javalobby.org/forums/thread.jspa?threadID=16867&tstart=0 ).

Categories