Finding Distances
UPBGE, being based on Blender Game Engine, has a rather simple way to find a distance between two objects.
distance = objectA.getDistanceTo(objectB)
Even if I was using some other game engine, your typical
math
module in python or similar languages should include a distance function of some kind. Just you will need to use the position of the object instead of the object itself. In UPBGE I can do this as well:
import math
distance = math.dist(objectA.position, objectB.position)
This is fine. But sometimes this is not enough.
Take for example this problem. I want to draw a lot of trees and a lot of other destructible objects all around the city in the game. Actually as of now, the game has 994 trees, 619 light-posts, 675 metal gates and 576 wooden gates. It is 2864 objects. Yet I want everything to run on a descent speed, so that the game is playable.
What I could do is, say hide objects that are farther than a specific draw distance. And spawn them back in when you get close to the point of where they are. Not too complex.
drawDistance = 250
for object in scene.objects:
if object.getDistanceTo(scene.active_camera) < drawDistance:
Spawn(object)
else:
Despawn(object)
But this will make 2864 distance calculations on every frame. On my beefy CPU calculating 2864
math.dist
calls takes about 18 milliseconds. Which is not a lot. But for 60 FPS you want to have the whole thing ( all calculations of all logic and all rendering ) happen in under 16 milliseconds. So just calculating distances to all those objects, to either hide them or un-hide them takes way too much time. And I'm not even talking about the
penalty of constantly hiding and un-hiding them. So there must be a better way.
Maybe if I reduce the amount of objects? Maybe if I make say every 10 trees on the map be one object in a technical sense, which just so happens to depict 10 trees. I've thought about all of those possible solutions. I considered making all those trees be one chunk, so to speak. But then I will have to hold in memory many different configurations of trees for every part of the map. And that could become very resource intensive really quick too.
Instead I decided to math my way out of it.
Python has this thing called Dictionaries. Which is like a list, but from where you can get things very quickly.
objects = [
["Tree1", objectOfTree()],
["Tree2", objectOfTree()]
]
Tree2 = None
for object in objects:
if object[0] == "Tree2":
Tree2 = object[1]
break
Now compare that to doing this instead.
objects = {
"Tree1":objectOfTree(),
"Tree2":objectOfTree()
}
Tree2 = objcets.get("Tree2", None)
This will find the object you need much faster, like in orders of magnitudes faster, when you are talking about hundreds of objects, and is even cleaner as a piece of code. But anyway. Say I would group every 10 trees into a dictionary like this. How would I do that? The dictionary has a key, which is a name of sorts, and I need to know what that key is, to get the list of the trees. How can I make that key represent the distance to the camera?
So here is what I came up with. I can separate everything into neighborhoods. Square patches of the map. And then load the trees that are from the same square as the camera.
I use a very basic, very quick math calculation to find which square any point in 3D space is in.
def Address(location, precision):
ret = ""
for axis in location[:2]:
ret = ret + str(round(axis/precision))+":"
return ret
Which, as you can see in the code, basically just divides the position coordinated X and Y by a certain precision value ( the size of the square ) and then stitches those numbers together into a string like this
1:1:
or
20:-30:
or
-15:0:
, or something.
Those codes I can then use as keys for the dictionary that is referring to all of the objects in a given square cell. So when I call
chunks.get("1:1:",
I get all of the objects who's address is
1:1:
. Of course the objects needs to be placed in said dictionary beforehand. Luckily it could be done only once. And therefor it is done only once, on the first frame of the game.
Now this is brilliant and all. But it does have an issue. What if the player is at an edge of one square looking toward a direction of a different square. If only the objects in the square where the player is will load, he will see lack of detail ahead of him. And that is not good.
I guess, if I make it read not only the address of the square the camera is in, but also roughly where the camera is looking at, I could give the player a more full picture. For that sampling a few more points: few in front of the camera, and a few diagonally, from the camera, should not make it too slow. Especially if I make it load only one chunk at a time.
The problem now is, how do I get those points?
Doing geometry and stuff
I know I could have parented something to the camera and sample the locations of those somethings. But
if you remember the issue with depsgraph, I'm trying to stay away from having too many objects. So whenever possible I should use math directly to find specific points.
Luckily blender comes with a very good python module ( that I want python to have by default ) called
mathutils. This module is designed to help calculate geometrical things. Vectors, rotations, color stuff and other things that are vector-like.
So to calculate a point which supposed to be, say 250 meters in front of the camera I can do something like this.
import mathutils
cam = scene.active_camera
point = mathutils.Vector((0,0,-250))
point.rotate(cam.orientation.to_euler())
point += cam.position
Yup. Unlike lists of coordinates,
mathutils.Vector()
object can be rotated by a rotation object ( which if you are using euler is just a list of XYZ orientation changes in radians ). And also you can add any of them on the fly. So I can make a point which sits at 0 and 0 on X and Y and -250 on Z ( which is 250 in front of the camera if the camera was at the center of the 3D world and rotated to 0,0,0 ), I can then rotate that point to the rotation of the camera. Which will move the point in space to where this point would be if such rotation would be taken place. And then I can move it to where the camera is. And guess what. I will suddenly be in-front of the camera, 250 meters away.
ret = []
points = [
mathutils.Vector((0,0,-1)),
mathutils.Vector((0,0,0)),
mathutils.Vector((0.5,0,-0.5)),
mathutils.Vector((-0.5,0,-0.5)),
mathutils.Vector((0,0,-2))
]
for point in points:
point *= precision
point.rotate(camera)
point += location
ret.append(Address(point, precision))
So now using a few lines of code that define a few points. I can get the list of all addresses I need to look at when trying to decide which Trees to show and which to hide. But more than that. I can use the math in many different places too.
For example: To map things on the mini-map in the corner of the screen. Yeah those are little objects that move to positions relative to where the mini-map is in the 3D world ( which is parented to the camera ).
Or to test whether the car is on the ground or not. Where a ray is shined onto a relative point towards with bottom the car. Or to know whether Dani is grabbing an edge of something to climb it. Which is done with two rays, one from the stomach and one from the head. Each requiring a relative point.
One thing leads to another. One discovery to the next. And that is how Gamedev makes you better at math.
Happy Hacking!!!