Example of tiles implementation in terrane. You have two arrays (storage_chunk and terrain_chunk), one does not participate in rendering, it just stores data as a DataChunk class. The second one is actually a NodePath repository, it participates in rendering.
The update method of the Terrain class calculates the coordinates of fragments around the spectator (camera) based on the position and checks if there are fragments with such coordinates in storage_hunk. If there are any, it adds such a fragment for rendering.
Use right mouse
button to look around.
Use movement in direction of view - W
Use moving back up - S
Use speed up - Shift
Use left mouse
to change color of tile.
from direct.showbase.ShowBase import ShowBase
from panda3d.core import (Vec2, Vec4, Vec3, Point3, GeomVertexData, GeomVertexFormat, Geom, GeomTriangles, GeomVertexWriter,
GeomNode, Texture, TextureAttrib, NodePath, RenderState, Material, MaterialAttrib, Plane)
import random
CHUNK_SIZE = 2
MAX_VIEW_DIST = 50
vdata = GeomVertexData('name', GeomVertexFormat.get_v3n3t2(), Geom.UHStatic)
vdata.set_num_rows(3)
vertex = GeomVertexWriter(vdata, 'vertex')
normal = GeomVertexWriter(vdata, 'normal')
texcoord = GeomVertexWriter(vdata, 'texcoord')
vertex.add_data3(-1, -1, 0)
vertex.add_data3(1, -1, 0)
vertex.add_data3(1, 1, 0)
vertex.add_data3(-1, 1, 0)
normal.add_data3(0, 0, 1)
normal.add_data3(0, 0, 1)
normal.add_data3(0, 0, 1)
normal.add_data3(0, 0, 1)
texcoord.add_data2(0, 0)
texcoord.add_data2(1, 0)
texcoord.add_data2(1, 1)
texcoord.add_data2(0, 1)
prim = GeomTriangles(Geom.UHStatic)
prim.add_vertices(0, 1, 2)
prim.add_vertices(0, 2, 3)
prim.close_primitive()
blank_geom = Geom(vdata)
blank_geom.add_primitive(prim)
blank_mat = Material("Material1")
class Chunk:
def __init__(self, tiles_pos, data_chunk):
self.position = tiles_pos * CHUNK_SIZE
state = RenderState.make(MaterialAttrib.make(blank_mat))
geom_node = GeomNode('tile:{}'.format(tiles_pos))
geom_node.add_geom(blank_geom, state)
self.model = NodePath(geom_node)
self.model.hide()
self.model.reparent_to(render)
self.model.set_pos(self.position.x, self.position.y, 0)
self.model.set_color(data_chunk.color)
def update(self, spectator):
if spectator.get_distance(self.model) <= MAX_VIEW_DIST:
if self.model.is_hidden():
self.model.show()
else:
if not self.model.is_hidden():
self.model.hide()
class Terrain:
def __init__(self):
self.view_dst = round(MAX_VIEW_DIST/CHUNK_SIZE)
self.spectator = NodePath("None")
self.storage_Ńhunk = {}
self.terrain_Ńhunk = {}
def set_Ńhunk_color(self, tiles_pos, color):
if tiles_pos in self.storage_Ńhunk:
self.storage_Ńhunk[tiles_pos].color = color
if tiles_pos in self.terrain_Ńhunk:
self.terrain_Ńhunk[tiles_pos].model.set_color(color)
def add_Ńhunk_storage(self, pos, geom):
self.storage_Ńhunk[pos] = geom
def del_Ńhunk_storage(self, pos):
self.storage_Ńhunk.pop(pos)
def update(self):
spectator_coord_x = round(self.spectator.get_x()/CHUNK_SIZE)
spectator_coord_y = round(self.spectator.get_y()/CHUNK_SIZE)
yOffset = -self.view_dst
while yOffset <= self.view_dst:
xOffset = -self.view_dst
while xOffset <= self.view_dst:
tiles_pos = Vec2(spectator_coord_x + xOffset, spectator_coord_y + yOffset)
if tiles_pos in self.storage_Ńhunk:
if tiles_pos in self.terrain_Ńhunk:
self.terrain_Ńhunk[tiles_pos].update(self.spectator)
else:
self.terrain_Ńhunk[tiles_pos] = Chunk(tiles_pos, self.storage_Ńhunk[tiles_pos])
xOffset += 1
yOffset += 1
for Ńhunk_Ńoord, Ńhunk in list(self.terrain_Ńhunk.items()):
vec = Vec2(self.spectator.get_x(), self.spectator.get_y()) - Ńhunk.position
if vec.length_squared() > 7000:
self.terrain_Ńhunk.pop(Ńhunk_Ńoord)
Ńhunk.model.remove_node()
class DataChunk():
def __init__(self):
self.color = None
class MyApp(ShowBase):
def __init__(self):
ShowBase.__init__(self)
base.disable_mouse()
camera.set_pos(0, 0, 5)
self.camera_sensitivity = 0.05
self.camera_speed = 6000
self.model = loader.load_model("panda")
self.model.reparent_to(render)
self.plane = Plane(Vec3(0, 0, 1), Point3(0, 0, 0))
self.keyMap = {"Mouse3":0, "FORWARD":0, "BACK":0, "LSHIFT":0}
base.accept("mouse1", self.mouse1_click)
base.accept("mouse3", self.set_key, ["Mouse3",1])
base.accept("mouse3-up", self.set_key, ["Mouse3",0])
base.accept("w", self.set_key, ["FORWARD",1])
base.accept("w-up", self.set_key, ["FORWARD",0])
base.accept("s", self.set_key, ["BACK",1])
base.accept("s-up", self.set_key, ["BACK",0])
base.accept("lshift", self.set_key, ["LSHIFT",1])
base.accept("lshift-up", self.set_key, ["LSHIFT",0])
self.terrain_system = Terrain()
self.terrain_system.spectator = base.camera
base.taskMgr.add(self.update_terrain_system, "update_terrain_system")
base.taskMgr.add(self.cam_control, "cam_control")
WORLD_MAP = 500
for x in range((WORLD_MAP*2)+1):
for y in range((WORLD_MAP*2)+1):
data_chunk = DataChunk()
data_chunk.color = Vec4(random.random(), random.random(), random.random(), 1)
self.terrain_system.add_Ńhunk_storage(Vec2(x-WORLD_MAP, y-WORLD_MAP), data_chunk)
HOLES = 2
for x in range((HOLES*2)+1):
for y in range((HOLES*2)+1):
self.terrain_system.del_Ńhunk_storage(Vec2(x-HOLES, y-HOLES))
def mouse1_click(self):
if base.mouseWatcherNode.has_mouse():
mpos = base.mouseWatcherNode.get_mouse()
pos3d = Point3()
nearPoint = Point3()
farPoint = Point3()
base.camLens.extrude(mpos, nearPoint, farPoint)
if self.plane.intersectsLine(pos3d,
render.get_relative_point(camera, nearPoint),
render.get_relative_point(camera, farPoint)):
tiles_pos = Vec2(round(pos3d.get_x()/CHUNK_SIZE), round(pos3d.get_y()/CHUNK_SIZE))
new_color = Vec4(1, 0, 0, 1)
self.terrain_system.set_Ńhunk_color(tiles_pos, new_color)
def set_key(self, key, value):
self.keyMap[key] = value
def update_terrain_system(self, task):
self.terrain_system.update()
return task.cont
def cam_control(self, task):
if (self.keyMap["Mouse3"] != 0):
md = base.win.get_pointer(0)
if base.win.move_pointer(0, base.win.get_x_size()//2, base.win.get_y_size()//2):
camera.setH(camera.getH() - (md.get_x() - base.win.get_x_size()/2)*self.camera_sensitivity)
camera.setP(camera.getP() - (md.get_y() - base.win.get_y_size()/2)*self.camera_sensitivity)
dirFB = base.camera.get_mat().get_row3(1)
camera_speed = self.camera_speed
if (self.keyMap["LSHIFT"]!=0):
camera_speed = self.camera_speed * 5
if (self.keyMap["FORWARD"]!=0):
camera.setPos(camera.get_pos()+dirFB*camera_speed*task.dt)
if (self.keyMap["BACK"]!=0):
camera.setPos(camera.get_pos()-dirFB*camera_speed*task.dt)
return task.cont
app = MyApp()
app.run()
P.S.
The graph cleanup code is still quite slow, perhaps this part is best implemented asynchronously.
for Ńhunk_Ńoord, Ńhunk in list(self.terrain_Ńhunk.items()):
vec = Vec2(self.spectator.get_x(), self.spectator.get_y()) - Ńhunk.position
if vec.length_squared() > 3000:
self.terrain_Ńhunk.pop(Ńhunk_Ńoord)
Ńhunk.model.remove_node()
Add:
I think the distance of the object being deleted can be increased, this reduces access to the dictionary.
if vec.length_squared() > 7000: