Skip to content

Commit

Permalink
Merge pull request #690 from Squigglecito/large-mesh-collider
Browse files Browse the repository at this point in the history
Revert large mesh collider optimization BVH.
  • Loading branch information
pokepetter authored Jul 9, 2024
2 parents bfc9d30 + 85d78d4 commit 046ed84
Show file tree
Hide file tree
Showing 3 changed files with 31 additions and 122 deletions.
149 changes: 29 additions & 120 deletions ursina/collider.py
Original file line number Diff line number Diff line change
@@ -1,109 +1,27 @@
from panda3d.core import CollisionNode, CollisionBox, CollisionSphere, CollisionCapsule, CollisionPolygon
from panda3d.core import NodePath
from panda3d.core import BoundingVolume
from ursina.vec3 import Vec3
from ursina.mesh import Mesh


# Recursive.
def build_bvh(entity, node_path, solids, axis=0, only_xz=False, max_depth=8, max_solids=None, flatten=False, current_depth=0):
if len(solids) == 1 or current_depth >= max_depth - 1:
if max_solids is not None:
if len(solids) <= max_solids:
for e in solids:
node_path.node().addSolid(e)
return
else:
for e in solids:
node_path.node().addSolid(e)
return
solids_sorted = None
if axis == 0:
solids_sorted = sorted(solids, key=lambda s: s.getCollisionOrigin().getX())
elif axis == 1:
solids_sorted = sorted(solids, key=lambda s: s.getCollisionOrigin().getZ())
else:
solids_sorted = sorted(solids, key=lambda s: s.getCollisionOrigin().getY())
mid_point = len(solids_sorted) // 2
left_solids = solids_sorted[:mid_point]
right_solids = solids_sorted[mid_point:]
if only_xz:
next_axis = (axis + 1) % 2
else:
next_axis = (axis + 1) % 3
next_depth = current_depth + 1
left_collision_node = CollisionNode('CollisionNode')
left_collision_node.setBoundsType(BoundingVolume.BT_box)
left_node_path = node_path.attachNewNode(left_collision_node)
left_node_path.setPythonTag('Entity', entity)
right_collision_node = CollisionNode('CollisionNode')
right_collision_node.setBoundsType(BoundingVolume.BT_box)
right_node_path = node_path.attachNewNode(right_collision_node)
right_node_path.setPythonTag('Entity', entity)
build_bvh(entity, left_node_path, left_solids, axis=next_axis, only_xz=only_xz, max_depth=max_depth, max_solids=max_solids, current_depth=next_depth, flatten=flatten)
build_bvh(entity, right_node_path, right_solids, axis=next_axis, only_xz=only_xz, max_depth=max_depth, max_solids=max_solids, current_depth=next_depth, flatten=flatten)
if flatten and current_depth < max_depth - 2:
for child in left_node_path.getChildren():
child.reparentTo(node_path)
for child in right_node_path.getChildren():
child.reparentTo(node_path)
left_node_path.removeNode()
right_node_path.removeNode()
node_path.getBounds()


class Collider(NodePath):
def __init__(self, entity, shape, bvh=False, bvh_max_depth=8, bvh_max_shapes=None, bvh_only_xz=False, bvh_flatten=False):
def __init__(self, entity, shape):
super().__init__('collider')
self.collision_node = CollisionNode('CollisionNode')

self.shape = shape
self.node_path = entity.attachNewNode(self.collision_node)
self.node_path.setPythonTag('Entity', entity)

if isinstance(shape, (list, tuple)):
if bvh:
self.node_path.node().setBoundsType(BoundingVolume.BT_box)
build_bvh(entity, self.node_path, shape, max_depth=bvh_max_depth, max_solids=bvh_max_shapes, only_xz=bvh_only_xz, flatten=bvh_flatten)
else:
for e in shape:
self.node_path.node().addSolid(e)
for e in shape:
self.node_path.node().addSolid(e)
else:
self.node_path.node().addSolid(self.shape)

def show_bounds(self, only_leaves=True):
stack = [self.node_path]
while len(stack) > 0:
node_path = stack.pop()
if only_leaves:
if node_path.getNumChildren() == 0:
node_path.showBounds()
else:
node_path.showBounds()
for child in node_path.getChildren():
stack.append(child)

def hide_bounds(self):
stack = [self.node_path]
while len(stack) > 0:
node_path = stack.pop()
node_path.hideBounds()
for child in node_path.getChildren():
stack.append(child)

# Recursive (depth first).
def _remove(self, node_path):
num_children = node_path.getNumChildren()
for child in node_path.getChildren():
self._remove(child)
if num_children == 0:
node_path.node().clearSolids()
if node_path.hasPythonTag('Entity'):
node_path.clearPythonTag('Entity')
node_path.removeNode()

def remove(self):
self._remove(self.node_path)
self.node_path.node().clearSolids()
self.node_path.removeNode()
self.node_path = None
# print('remove collider')

Expand All @@ -115,15 +33,10 @@ def visible(self):
@visible.setter
def visible(self, value):
self._visible = value
stack = [self.node_path]
while len(stack) > 0:
node_path = stack.pop()
if value:
node_path.show()
else:
node_path.hide()
for child in node_path.getChildren():
stack.append(child)
if value:
self.node_path.show()
else:
self.node_path.hide()


class BoxCollider(Collider):
Expand Down Expand Up @@ -152,7 +65,7 @@ def __init__(self, entity, center=(0,0,0), height=2, radius=.5):


class MeshCollider(Collider):
def __init__(self, entity, mesh=None, center=(0,0,0), bvh=False, bvh_max_depth=8, bvh_max_shapes=None, bvh_only_xz=False, bvh_flatten=False):
def __init__(self, entity, mesh=None, center=(0,0,0)):
self.center = center
center = Vec3(center)
if mesh is None and entity.model:
Expand Down Expand Up @@ -188,34 +101,30 @@ def __init__(self, entity, mesh=None, center=(0,0,0), bvh=False, bvh_max_depth=8
elif isinstance(mesh, NodePath):
from panda3d.core import GeomVertexReader
verts = []
children = mesh.getChildren()
for child in children:
child_transform = child.getTransform()
child_mat = child_transform.getMat()
geomNodeCollection = child.findAllMatches('**/+GeomNode')
for nodePath in geomNodeCollection:
geomNode = nodePath.node()
for i in range(geomNode.getNumGeoms()):
geom = geomNode.getGeom(i)
vdata = geom.getVertexData()
for i in range(geom.getNumPrimitives()):
prim = geom.getPrimitive(i)
vertex_reader = GeomVertexReader(vdata, 'vertex')
prim = prim.decompose()

for p in range(prim.getNumPrimitives()):
s = prim.getPrimitiveStart(p)
e = prim.getPrimitiveEnd(p)
for i in range(s, e):
vi = prim.getVertex(i)
vertex_reader.setRow(vi)
verts.append(child_mat.xformPointGeneral(vertex_reader.getData3()))
geomNodeCollection = mesh.findAllMatches('**/+GeomNode')
for nodePath in geomNodeCollection:
geomNode = nodePath.node()
for i in range(geomNode.getNumGeoms()):
geom = geomNode.getGeom(i)
vdata = geom.getVertexData()
for i in range(geom.getNumPrimitives()):
prim = geom.getPrimitive(i)
vertex_reader = GeomVertexReader(vdata, 'vertex')
prim = prim.decompose()

for p in range(prim.getNumPrimitives()):
s = prim.getPrimitiveStart(p)
e = prim.getPrimitiveEnd(p)
for i in range(s, e):
vi = prim.getVertex(i)
vertex_reader.setRow(vi)
verts.append(vertex_reader.getData3())

for i in range(0, len(verts)-3, 3):
p = CollisionPolygon(Vec3(verts[i+2]), Vec3(verts[i+1]), Vec3(verts[i]))
self.collision_polygons.append(p)

super().__init__(entity, self.collision_polygons, bvh=bvh, bvh_max_depth=bvh_max_depth, bvh_max_shapes=bvh_max_shapes, bvh_only_xz=bvh_only_xz, bvh_flatten=bvh_flatten)
super().__init__(entity, self.collision_polygons)


def remove(self):
Expand Down
2 changes: 1 addition & 1 deletion ursina/entity.py
Original file line number Diff line number Diff line change
Expand Up @@ -1224,7 +1224,7 @@ def intersects(self, traverse_target=scene, ignore:list=None, debug=False):

self._pq.sort_entries()
entries = self._pq.getEntries()
entities = [e.get_into_node_path().parent.getPythonTag('Entity') for e in entries]
entities = [e.get_into_node_path().parent for e in entries]

entries = [ # filter out ignored entities
e for i, e in enumerate(entries)
Expand Down
2 changes: 1 addition & 1 deletion ursina/raycast.py
Original file line number Diff line number Diff line change
Expand Up @@ -47,7 +47,7 @@ def raycast(origin, direction:Vec3=(0,0,1), distance=9999, traverse_target:Entit

_raycaster._pq.sort_entries()
entries = _raycaster._pq.getEntries()
entities = [e.get_into_node_path().parent.getPythonTag('Entity') for e in entries]
entities = [e.get_into_node_path().parent for e in entries]

entries = [ # filter out ignored entities
e for i, e in enumerate(entries)
Expand Down

0 comments on commit 046ed84

Please sign in to comment.