I am trying to implement smooth 3D camera in my game. So, I have a few reserved positions and directions and I want to change view of my camera by applying them to my camera. Now I am doing it like below:
//Reserved positions and directions
//View #1
Vector3 pos1 = new Vector3(0, 5, -10);
Vector3 dir1 = new Vector3(0, 0, 10);
//View #2
Vector3 pos2 = new Vector3(5, 2, 5);
Vector3 dir2 = new Vector3(-2, 2, 7);
//Applying view #2
camera.position.set(pos2);
camera.lookAt(dir2);
It works, but I need to do it smoothly. I've seen a similar question for 2D camera, but it doesn't appropriate for me, because orthographic camera doesn't need direction and Z-axis.
UPDATE:
I tried to use method which is described for 2D camera. So, positioning works as needed, but I can't say the same about direction. Behaviour of direction is wrong. It rotates camera on Z-axis. Seems to me I have to update Up vector too.
float lerp = 0.01f;
Vector3 position = camera.position;
position.x += (pos2.x - position.x) * lerp;
position.y += (pos2.y - position.y) * lerp;
position.z += (pos2.z - position.z) * lerp;
Vector3 direction = camera.direction;
direction.x += (dir2.x - direction.x) * lerp;
direction.y += (dir2.y - direction.y) * lerp;
direction.z += (dir2.z - direction.z) * lerp;
//CODE FROM LIBGDX'S CAMERA
//LookAt function
/*
tmpVec.set(x, y, z).sub(position).nor();
if (!tmpVec.isZero()) {
float dot = tmpVec.dot(up); // up and direction must ALWAYS be orthonormal vectors
if (Math.abs(dot - 1) < 0.000000001f) {
// Collinear
up.set(direction).scl(-1);
} else if (Math.abs(dot + 1) < 0.000000001f) {
// Collinear opposite
up.set(direction);
}
direction.set(tmpVec);
normalizeUp();
}
*/
What is the correct way to set direction?
I would use the Univesal Tween Engine, which is a library for interpolating pretty much anything.
You'd have to write a Vector3Accessor class (which would be very simple), but then you'd have all sorts of controls and options for smooth movement. It has a very rich API and is used a lot for exactly this sort of thing.
I'd strongly recommend you put a little time into investigating it as an option -It'll give you a better result, and once you know it you'll find all sorts of other cool uses for it in your app.
Related
I have tried to create NPC character that can "see" the player by using cones of vision.
The NPC will rotate back and forth at all times.
My problem is that the arc has a generic and unchanging position, but when its drawn to the screen it looks correct.
[Screenshots of the collisions in action][1]
[GitHub link for java files][2]
I'm using Arc2D to draw the shape like this in my NPC class
// Update the shapes used in the npc
rect.setRect(x, y, w, h);
ellipse.setFrame(rect);
visionArc.setArcByCenter(cx, cy, visionDistance, visionAngle, visionAngle * 2, Arc2D.PIE);
/ CenterX, CenterY (of the npc),
/ the distance from the arc to the npc
/ a constant value around 45 degrees and a constant value around 90 degress (to make a pie shape)
I've tried multiplying the position and the angles by the sin and cosine of the NPC's current angle
something like these
visionArc.setArcByCenter(cx * (Math.cos(Math.toRadians(angle))), cy (Math.sin(Math.toRadians(angle)), visionDistance, visionAngle, visionAngle * 2, Arc2D.PIE);
or
visionArc.setArcByCenter(cx, cy, visionDistance, visionAngle - angle, (visionAngle + angle) * 2, Arc2D.PIE);
or
visionArc.setArcByCenter(cx, cy, visionDistance, visionAngle * (Math.cos(Math.toRadians(angle))), visionAngle * 2, Arc2D.PIE);
I've tried a lot but can't seem to find what works. Making the vision angles not constant makes an arc that expands and contracts, and multiplying the position by the sin or cosine of the angle will make the arc fly around the screen, which doesn't really work either.
This is the function that draws the given NPC
public void drawNPC(NPC npc, Graphics2D g2, AffineTransform old) {
// translate to the position of the npc and rotate
AffineTransform npcTransform = AffineTransform.getRotateInstance(Math.toRadians(npc.angle), npc.x, npc.y);
// Translate back a few units to keep the npc rotating about its own center
// point
npcTransform.translate(-npc.halfWidth, -npc.halfHeight);
g2.setTransform(npcTransform);
// g2.draw(npc.rect); //<-- show bounding box if you want
g2.setColor(npc.outlineColor);
g2.draw(npc.visionArc);
g2.setColor(Color.BLACK);
g2.draw(npc.ellipse);
g2.setTransform(old);
}
This is my collision detection algorithim - NPC is a superclass to ninja (Shorter range, higher peripheral)
public void checkNinjas(Level level) {
for (int i = 0; i < level.ninjas.size(); i++) {
Ninja ninja = level.ninjas.get(i);
playerRect = level.player.rect;
// Check collision
if (playerRect.getBounds2D().intersects(ninja.visionArc.getBounds2D())) {
// Create an area of the object for greater precision
Area area = new Area(playerRect);
area.intersect(new Area(ninja.visionArc));
// After checking if the area intersects a second time make the NPC "See" the player
if (!area.isEmpty()) {
ninja.seesPlayer = true;
}
else {
ninja.seesPlayer = false;
}
}
}
}
Can you help me correct the actual positions of the arcs for my collision detection? I have tried creating new shapes so I can have one to do math on and one to draw to the screen but I scrapped that and am starting again from here.
[1]: https://i.stack.imgur.com/rUvTM.png
[2]: https://github.com/ShadowDraco/ArcCollisionDetection
After a few days of coding and learning and testing new ideas I came back to this program and implemented the collision detection using my original idea (ray casting) and have created the equivalent with rays!
Screenshot of the new product
Github link to the project that taught me the solution
Here's the new math
public void setRays() {
for (int i = 0; i < rays.length; i++) {
double rayStartAngleX = Math.sin(Math.toRadians((startAngle - angle) + i));
double rayStartAngleY = Math.cos(Math.toRadians((startAngle - angle) + i));
rays[i].setLine(cx, cy, cx + visionDistance * rayStartAngleX, cy + visionDistance * rayStartAngleY);
}
}
Here is a link the the program I started after I asked this question and moved on to learn more, and an image to what the new product looks like
(The original github page has been updated with a new branch :) I'm learning git hub right now too
I do not believe that using Arc2D in the way I intended is possible, however there is .setArcByTangent method, it may be possible to use that but I wasn't going to get into that. Rays are cooler.
I am trying to shoot an object(a spell) depending on the rotation of the players arm. The spell is supposed to come out of the hand and shoot towards where the mouse cicked(the arm rotates and points to where the mouse is). This is how the arm rotates in game.
public boolean mouseMoved(int screenX, int screenY) {
tmp.x = screenX;
tmp.y = screenY;
tmp.z = 0;
cam.unproject(tmp);
rot = MathUtils.radiansToDegrees * MathUtils.atan2((float)tmp.y - (float)player.getArmSprite().getY() - player.getArmSprite().getHeight(),
tmp.x -player.getArmSprite().getX() - player.getArmSprite().getWidth());
if (rot < 0) rot += 360;
//Last right or left means if hes looking left or right
if(player.lastRight)
player.setObjectRotation(rot + 80);
if(player.lastLeft)
player.setObjectRotation(-rot - 80);
And this is how the spell is supposed to shoot based off rotation
//destination is a vector of where on screen the mouse was clicked
if(position.y < destination.y){
position.y += vel.y * Gdx.graphics.getDeltaTime();
}
if(position.x < destination.x){
position.x += vel.x * Gdx.graphics.getDeltaTime();
}
However this is very wonky and never really reacts the way it supposed to with a few exceptions. It fires from the hand and then if the y axis is equal it completely evens out and goes till it reaches the x position, I want it to fire from the hand to the position clicks perfectly straight from point a to point b, this is clearly a rotation problem that I just can't seem to figure out how to tackle.
Here is an image of what is happening
Example image
The red indicates where I clicked, as you can see it reached the x pos first and now is traveling to the y when it should have reached the x and y pos of where I clicked first
Any help with this problem is extremely appreciated!
I'm pretty bad at radians and tangents but luckily we have vectors.
Since you have the rot ation in degrees of the arm. I advice to use Vectors to use for any Vector related math now.
//A vector pointing up
Vector2 direction = new Vector2(0, 1);
//Let's rotate that by the rotation of the arm
direction.rotate(rot);
Now direction is the direction the arm is pointing. If your rotation is calculated where up = 0. So you might need to rotate it 180, 90 or -90 degrees. Or in the case you did something silly any degrees.
Your spell should have a Vector too for it's position. Set that to the hand or wherever you want to start from. Now all you need to do is scale that direction since it's currently has a length of 1. If you want to move 5 units each frame you can do direction.scl(5) now it is of length 5. But technically speaking it's no direction anymore now everybody calls it velocity so let's do.
//when you need to fire
float speed = 5;
Vector2 velocity = direction.cpy().scl(speed);
//update
position.add(velocity);
draw(fireballImage, position.x, position.y);
I copied direction first, otherwise it would also be scaled. Then I just added the velocity to the position and draw using that Vector.
And to show Vectors are awesome you should see this awesome badlogic vs mouse program I created. https://github.com/madmenyo/FollowMouse there are just a view lines of my own code. It just takes a little bit of vector knowledge and it's very readable.
I have got some trees, which are greatly lagging the game, so I would like to check if the trees are in front of the camera or not.
I have had some help from the Mathematics forum, and also had a look at This link to help me convert pitch/yaw to the directional vector needed.
But for some reason, whenever I move the camera to the left, the trees become visible, wheras whenever I move it to the right, they become unvisible (So if camera is pointing at +1 on the Z axis, it seems to be rendering the trees, but -1 on the Z axis and it seems to not render them).
(See http://i.gyazo.com/cdd05dc3f5dbdc07577c6e41fab3a549 for a less-jumpy .mp4)
I am using the following code to check if an object is in front of the camera or not:
Ship you = shipsID.get(UID);
int dis = 300;
Vector3f X = new Vector3f(camera.x(), camera.y(), camera.z());
float x = (float) (Math.cos(Math.toRadians(camera.yaw()))*Math.cos(Math.toRadians(camera.pitch())));
float y = (float) (Math.sin(Math.toRadians(camera.yaw()))*Math.cos(Math.toRadians(camera.pitch())));
float z = (float) Math.sin(Math.toRadians(camera.pitch()));
Vector3f V = new Vector3f(x, y, z);
for (Tree tree : trees){
Vector3f Y = new Vector3f(tree.location.x, tree.location.y, tree.location.z);
Vector3f YMinusX = Y.negate(X);//new Vector3f(Y.x - X.x, Y.y - X.y, Y.z - X.z);
float dot = Vector3f.dot(YMinusX, V);
if (dot > 0){
tree.render();
}
}
Is anyone able to tell me what I have done wrong here? I can't work out if it's the math.. Or the code.. Or what?
Camera translation code:
public void applyTranslations() {
glPushAttrib(GL_TRANSFORM_BIT);
glMatrixMode(GL_MODELVIEW);
glRotatef(pitch, 1, 0, 0);
glRotatef(yaw, 0, 1, 0);
lastYaw = yaw;
glRotatef(roll, 0, 0, 1);
glTranslatef(-x, -y, -z);
glPopAttrib();
}
UPDATE:
It appears to be where the camera is looking. For example, if I look to -Z, nothing happens, but if I look to +Z, they all render.
The if (dot > 0) code appears to somehow being +Z rather than +TheCameraRotation.
Your camera rotations yaw around Y, implying Y is your up vector. However, float z = (float) Math.sin(Math.toRadians(camera.pitch())); gives Z for your up vector. There is an inconsistency. I'd start by swapping y and z here, then print everything out every frame so you can see what happens as you rotate the camera. Also render just one tree and print dot. E.g. you might quickly notice the numbers approach 1.0 only when you look at 90 degrees left of the tree which narrows down the problem. As #DWilches notes, swapping cos/sin will change the phase of the rotation, which would produce such an effect.
You might consider limiting the dot product to the camera's field of view. There are still problems in that trees are not just points. A better way would be to test tree bounding boxes against the camera frustum, as #glampert suggests.
Still, the tree geometry doesn't look that complex. Optimization wise, I'd start trying to draw them faster. Are you using VBOs? Perhaps look at methods to reduce draw calls such as instancing. Perhaps even use a few models for LOD or billboards. Going even further, billboards with multiple trees on them. Occlusion culling methods could be used to ignore trees behind mountains.
[EDIT]
Since your trees are all roughly on a plane, you could limit the problem to the camera's yaw:
float angleToTree = Math.atan2(tree.location.z - camera.z(), tree.location.x - camera.x());
float angleDiff = angleToTree - camera.yaw();
if (angleDiff > Math.PI)
angleDiff -= 2.0f * Math.PI;
if (angleDiff < -Math.PI)
angleDiff += 2.0f * Math.PI;
if (abs(angleDiff) < cameraFOV + 0.1f) //bias as trees are not points
tree.render();
Could you write it like this
Ship you = shipsID.get(UID);
int dis = 300;
Vector3f X = new Vector3f(camera.x(), camera.y(), camera.z());
float x = (float) (Math.cos(Math.toRadians(camera.yaw()))*Math.cos(Math.toRadians(camera.pitch())));
float y = (float) (Math.sin(Math.toRadians(camera.yaw()))*Math.cos(Math.toRadians(camera.pitch())));
float z = (float) Math.sin(Math.toRadians(camera.pitch()));
Vector3f V = new Vector3f(x, y, z);
for (Tree tree : trees){
Vector3f Y = new Vector3f(tree.location.x, tree.location.y, tree.location.z);
Vector3f YMinusX = Y.negate(X);//new Vector3f(Y.x - X.x, Y.y - X.y, Y.z - X.z);
float dot = Vector3f.dot(YMinusX, V);
if (dot > 0){
tree.render();
}
}
As you can see there is far less calculation being performed for each tree.
For what I see here the correct formulas are:
x = Math.sin(pitch) * Math.cos(yaw);
y = Math.sin(pitch) * Math.sin(yaw);
z = Math.cos(pitch);
Could you try them ?
Hey all I'm trying to implement 3D picking into my program, and it works perfectly if I don't move from the origin. It is perfectly accurate. But if I move the model matrix away from the origin (the viewmatrix eye is still at 0,0,0) the picking vectors are still drawn from the original location. It should still be drawing from the view matrix eye (0,0,0) but it isn't. Here's some of my code to see if you can find out why..
Vector3d near = unProject(x, y, 0, mMVPMatrix, this.width, this.height);
Vector3d far = unProject(x, y, 1, mMVPMatrix, this.width, this.height);
Vector3d pickingRay = far.subtract(near);
//pickingRay.z *= -1;
Vector3d normal = new Vector3d(0,0,1);
if (normal.dot(pickingRay) != 0 && pickingRay.z < 0)
{
float t = (-5f-normal.dot(mCamera.eye))/(normal.dot(pickingRay));
pickingRay = mCamera.eye.add(pickingRay.scale(t));
addObject(pickingRay.x, pickingRay.y, pickingRay.z+.5f, Shape.BOX);
//a line for the picking vector for debugging
PrimProperties a = new PrimProperties(); //new prim properties for size and center
Prim result = null;
result = new Line(a, mCamera.eye, far);//new line object for seeing look at vector
result.createVertices();
objects.add(result);
}
public static Vector3d unProject(
float winx, float winy, float winz,
float[] resultantMatrix,
float width, float height)
{
winy = height-winy;
float[] m = new float[16],
in = new float[4],
out = new float[4];
Matrix.invertM(m, 0, resultantMatrix, 0);
in[0] = (winx / width) * 2 - 1;
in[1] = (winy / height) * 2 - 1;
in[2] = 2 * winz - 1;
in[3] = 1;
Matrix.multiplyMV(out, 0, m, 0, in, 0);
if (out[3]==0)
return null;
out[3] = 1/out[3];
return new Vector3d(out[0] * out[3], out[1] * out[3], out[2] * out[3]);
}
Matrix.translateM(mModelMatrix, 0, this.diffX, this.diffY, 0); //i use this to move the model matrix based on pinch zooming stuff.
Any help would be greatly appreciated! Thanks.
I wonder which algorithm you have implemented. Is it a ray casting approach to the problem?
I didn't focus much on the code itself but this looks a way too simple implementation to be a fully operational ray casting solution.
In my humble experience, i would like to suggest you, depending on the complexity of your final project (which I don't know), to adopt a color picking solution.
This solution is usually the most flexible and the easiest to be implemented.
It consist in the rendering of the objects in your scene with unique flat colors (usually you disable lighting as well in your shaders) to a backbuffer...a texture, then you acquire the coordinates of the click (touch) and you read the color of the pixel in that specific coordinates.
Having the color of the pixel and the tables of the colors of the different objects you rendered, makes possible for you to understand what the user clicked from a logical perspective.
There are other approaches to the object picking problem, this is probably universally recognized as the fastest one.
Cheers
Maurizio
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 ).