def test_sphere_into_heightfield():
    root = NodePath("root")
    world = BulletWorld()
    # Create PNMImage to construct Heightfield with
    img = PNMImage(10, 10, 1)
    img.fill_val(255)
    # Make our nodes
    heightfield = make_node("Heightfield", BulletHeightfieldShape, img, 1, ZUp)
    sphere = make_node("Sphere", BulletSphereShape, 1)
    # Attach to world
    np1 = root.attach_new_node(sphere)
    np1.set_pos(0, 0, 1)
    world.attach(sphere)

    np2 = root.attach_new_node(heightfield)
    np2.set_pos(0, 0, 0)
    world.attach(heightfield)

    assert world.get_num_rigid_bodies() == 2
    test = world.contact_test_pair(sphere, heightfield)
    assert test.get_num_contacts() > 0
    assert test.get_contact(0).get_node0() == sphere
    assert test.get_contact(0).get_node1() == heightfield

    # Increment sphere's Z coordinate, no longer colliding
    np1.set_pos(0, 0, 2)
    test = world.contact_test_pair(sphere, heightfield)
    assert test.get_num_contacts() == 0
Beispiel #2
0
class World (object):
    """
    The World models basically everything about a map, including gravity, ambient light, the sky, and all map objects.
    """

    def __init__(self, camera, debug=False, audio3d=None, client=None, server=None):
        self.objects = {}
        self.incarnators = []
        self.collidables = set()
        self.updatables = set()
        self.updatables_to_add = set()
        self.garbage = set()
        self.render = NodePath('world')
        self.camera = camera
        self.audio3d = audio3d
        self.ambient = self._make_ambient()
        self.celestials = CompositeObject()
        self.sky = self.attach(Sky())
        # Set up the physics world. TODO: let maps set gravity.
        self.gravity = DEFAULT_GRAVITY
        self.physics = BulletWorld()
        self.physics.set_gravity(self.gravity)
        self.debug = debug
        self.client = client
        self.server = server
        if debug:
            debug_node = BulletDebugNode('Debug')
            debug_node.show_wireframe(True)
            debug_node.show_constraints(True)
            debug_node.show_bounding_boxes(False)
            debug_node.show_normals(False)
            np = self.render.attach_new_node(debug_node)
            np.show()
            self.physics.set_debug_node(debug_node)

    def _make_ambient(self):
        alight = AmbientLight('ambient')
        alight.set_color(VBase4(*DEFAULT_AMBIENT_COLOR))
        node = self.render.attach_new_node(alight)
        self.render.set_light(node)
        return node

    def attach(self, obj):
        assert hasattr(obj, 'world') and hasattr(obj, 'name')
        assert obj.name not in self.objects
        obj.world = self
        if obj.name.startswith('Incarnator'):
            self.incarnators.append(obj)
        if hasattr(obj, 'create_node') and hasattr(obj, 'create_solid'):
            # Let each object define it's own NodePath, then reparent them.
            obj.node = obj.create_node()
            obj.solid = obj.create_solid()
            if obj.solid:
                if isinstance(obj.solid, BulletRigidBodyNode):
                    self.physics.attach_rigid_body(obj.solid)
                elif isinstance(obj.solid, BulletGhostNode):
                    self.physics.attach_ghost(obj.solid)
            if obj.node:
                if obj.solid:
                    # If this is a solid visible object, create a new physics node and reparent the visual node to that.
                    phys_node = self.render.attach_new_node(obj.solid)
                    obj.node.reparent_to(phys_node)
                    obj.node = phys_node
                else:
                    # Otherwise just reparent the visual node to the root.
                    obj.node.reparent_to(self.render)
            elif obj.solid:
                obj.node = self.render.attach_new_node(obj.solid)
            if obj.solid and obj.collide_bits is not None:
                obj.solid.set_into_collide_mask(obj.collide_bits)
        self.objects[obj.name] = obj
        # Let the object know it has been attached.
        obj.attached()
        return obj

    def get_incarn(self):
        return random.choice(self.incarnators)

    def create_hector(self, name=None):
        # TODO: get random incarn, start there
        h = self.attach(Hector(name))
        h.move((0, 15, 0))
        return h

    def set_ambient(self, color):
        """
        Sets the ambient light to the given color.
        """
        self.ambient.node().set_color(VBase4(*color))

    def add_celestial(self, azimuth, elevation, color, intensity, radius, visible):
        """
        Adds a celestial light source to the scene. If it is a visible celestial, also add a sphere model.
        """
        if not self.camera:
            return
        location = Vec3(to_cartesian(azimuth, elevation, 1000.0 * 255.0 / 256.0))
        if intensity:
            dlight = DirectionalLight('celestial')
            dlight.set_color((color[0] * intensity, color[1] * intensity,
                color[2] * intensity, 1.0))
            node = self.render.attach_new_node(dlight)
            node.look_at(*(location * -1))
            self.render.set_light(node)
        if visible:
            if radius <= 2.0:
                samples = 6
            elif radius >= 36.0:
                samples = 40
            else:
                samples = int(round(((1.5 * radius) * (2 / 3.0)) + 3.75))
            celestial = Dome(radius * 1.5, samples, 2, color, 0, location,
                ((-(math.degrees(azimuth))), 90 + math.degrees(elevation), 0))
            self.celestials.attach(celestial)

    def create_celestial_node(self):
        bounds = self.camera.node().get_lens().make_bounds()
        self.celestials = self.celestials.create_node()
        self.celestials.set_transparency(TransparencyAttrib.MAlpha)
        self.celestials.set_light_off()
        self.celestials.set_effect(CompassEffect.make(self.camera, CompassEffect.PPos))
        self.celestials.node().set_bounds(bounds)
        self.celestials.node().set_final(True)
        self.celestials.reparent_to(self.render)

    def register_collider(self, obj):
        assert isinstance(obj, PhysicalObject)
        self.collidables.add(obj)

    def register_updater(self, obj):
        assert isinstance(obj, WorldObject)
        self.updatables.add(obj)

    def register_updater_later(self, obj):
        assert isinstance(obj, WorldObject)
        self.updatables_to_add.add(obj)

    def do_explosion(self, node, radius, force):
        center = node.get_pos(self.render);
        expl_body = BulletGhostNode("expl")
        expl_shape = BulletSphereShape(radius)
        expl_body.add_shape(expl_shape)
        expl_bodyNP = self.render.attach_new_node(expl_body)
        expl_bodyNP.set_pos(center)
        self.physics.attach_ghost(expl_body)
        result = self.physics.contact_test(expl_body)
        for contact in result.getContacts():
            n0_name = contact.getNode0().get_name()
            n1_name = contact.getNode1().get_name()
            obj = None
            try:
                obj = self.objects[n1_name]
            except:
                break
            if n0_name == "expl" and n1_name not in EXPLOSIONS_DONT_PUSH and not n1_name.startswith('Walker'):
                # repeat contact test with just this pair of objects
                # otherwise all manifold point values will be the same
                # for all objects in original result
                real_c = self.physics.contact_test_pair(expl_body, obj.solid)
                mpoint = real_c.getContacts()[0].getManifoldPoint()
                distance = mpoint.getDistance()
                if distance < 0:
                    if hasattr(obj, 'decompose'):
                        obj.decompose()
                    else:
                        expl_vec = Vec3(mpoint.getPositionWorldOnA() - mpoint.getPositionWorldOnB())
                        expl_vec.normalize()
                        magnitude = force * 1.0/math.sqrt(abs(radius - abs(distance)))
                        obj.solid.set_active(True)
                        obj.solid.apply_impulse(expl_vec*magnitude, mpoint.getLocalPointB())
                    if hasattr(obj, 'damage'):
                        obj.damage(magnitude/5)
        self.physics.remove_ghost(expl_body)
        expl_bodyNP.detach_node()
        del(expl_body, expl_bodyNP)

    def do_plasma_push(self, plasma, node, energy):
        obj = None
        try:
            obj = self.objects[node]
        except:
            raise
    
        if node not in EXPLOSIONS_DONT_PUSH and not node.startswith('Walker'):
            if hasattr(obj, 'decompose'):
                obj.decompose()
            else:
                solid = obj.solid
                dummy_node = NodePath('tmp')
                dummy_node.set_hpr(plasma.hpr)
                dummy_node.set_pos(plasma.pos)
                f_vec = render.get_relative_vector(dummy_node, Vec3(0,0,1))
                local_point = (obj.node.get_pos() - dummy_node.get_pos()) *-1
                f_vec.normalize()
                solid.set_active(True)
                try:
                    solid.apply_impulse(f_vec*(energy*35), Point3(local_point))
                except:
                    pass
                del(dummy_node)
        if hasattr(obj, 'damage'):
            obj.damage(energy*5)


    def update(self, task):
        """
        Called every frame to update the physics, etc.
        """
        dt = globalClock.getDt()
        for obj in self.updatables_to_add:
            self.updatables.add(obj)
        self.updatables_to_add = set()
        for obj in self.updatables:
            obj.update(dt)
        self.updatables -= self.garbage
        self.collidables -= self.garbage
        while True:
            if len(self.garbage) < 1:
                break;
            trash = self.garbage.pop()
            if(isinstance(trash.solid, BulletGhostNode)):
                self.physics.remove_ghost(trash.solid)
            if(isinstance(trash.solid, BulletRigidBodyNode)):
                self.physics.remove_rigid_body(trash.solid)
            if hasattr(trash, 'dead'):
                trash.dead()
            trash.node.remove_node()
            del(trash)
        self.physics.do_physics(dt)
        for obj in self.collidables:
            result = self.physics.contact_test(obj.node.node())
            for contact in result.get_contacts():
                obj1 = self.objects.get(contact.get_node0().get_name())
                obj2 = self.objects.get(contact.get_node1().get_name())
                if obj1 and obj2:
                    # Check the collision bits to see if the two objects should collide.
                    should_collide = obj1.collide_bits & obj2.collide_bits
                    if not should_collide.is_zero():
                        pt = contact.get_manifold_point()
                        if obj1 in self.collidables:
                            obj1.collision(obj2, pt, True)
                        if obj2 in self.collidables:
                            obj2.collision(obj1, pt, False)
        return task.cont
Beispiel #3
0
class World(object):
    """
    The World models basically everything about a map, including gravity, ambient light, the sky, and all map objects.
    """
    def __init__(self,
                 camera,
                 debug=False,
                 audio3d=None,
                 client=None,
                 server=None):
        self.objects = {}
        self.incarnators = []
        self.collidables = set()
        self.updatables = set()
        self.updatables_to_add = set()
        self.garbage = set()
        self.render = NodePath('world')
        self.camera = camera
        self.audio3d = audio3d
        self.ambient = self._make_ambient()
        self.celestials = CompositeObject()
        self.sky = self.attach(Sky())
        # Set up the physics world. TODO: let maps set gravity.
        self.gravity = DEFAULT_GRAVITY
        self.physics = BulletWorld()
        self.physics.set_gravity(self.gravity)
        self.debug = debug
        self.client = client
        self.server = server
        if debug:
            debug_node = BulletDebugNode('Debug')
            debug_node.show_wireframe(True)
            debug_node.show_constraints(True)
            debug_node.show_bounding_boxes(False)
            debug_node.show_normals(False)
            np = self.render.attach_new_node(debug_node)
            np.show()
            self.physics.set_debug_node(debug_node)

    def _make_ambient(self):
        alight = AmbientLight('ambient')
        alight.set_color(VBase4(*DEFAULT_AMBIENT_COLOR))
        node = self.render.attach_new_node(alight)
        self.render.set_light(node)
        return node

    def attach(self, obj):
        assert hasattr(obj, 'world') and hasattr(obj, 'name')
        assert obj.name not in self.objects
        obj.world = self
        if obj.name.startswith('Incarnator'):
            self.incarnators.append(obj)
        if hasattr(obj, 'create_node') and hasattr(obj, 'create_solid'):
            # Let each object define it's own NodePath, then reparent them.
            obj.node = obj.create_node()
            obj.solid = obj.create_solid()
            if obj.solid:
                if isinstance(obj.solid, BulletRigidBodyNode):
                    self.physics.attach_rigid_body(obj.solid)
                elif isinstance(obj.solid, BulletGhostNode):
                    self.physics.attach_ghost(obj.solid)
            if obj.node:
                if obj.solid:
                    # If this is a solid visible object, create a new physics node and reparent the visual node to that.
                    phys_node = self.render.attach_new_node(obj.solid)
                    obj.node.reparent_to(phys_node)
                    obj.node = phys_node
                else:
                    # Otherwise just reparent the visual node to the root.
                    obj.node.reparent_to(self.render)
            elif obj.solid:
                obj.node = self.render.attach_new_node(obj.solid)
            if obj.solid and obj.collide_bits is not None:
                obj.solid.set_into_collide_mask(obj.collide_bits)
        self.objects[obj.name] = obj
        # Let the object know it has been attached.
        obj.attached()
        return obj

    def get_incarn(self):
        return random.choice(self.incarnators)

    def create_hector(self, name=None):
        # TODO: get random incarn, start there
        h = self.attach(Hector(name))
        h.move((0, 15, 0))
        return h

    def set_ambient(self, color):
        """
        Sets the ambient light to the given color.
        """
        self.ambient.node().set_color(VBase4(*color))

    def add_celestial(self, azimuth, elevation, color, intensity, radius,
                      visible):
        """
        Adds a celestial light source to the scene. If it is a visible celestial, also add a sphere model.
        """
        if not self.camera:
            return
        location = Vec3(
            to_cartesian(azimuth, elevation, 1000.0 * 255.0 / 256.0))
        if intensity:
            dlight = DirectionalLight('celestial')
            dlight.set_color((color[0] * intensity, color[1] * intensity,
                              color[2] * intensity, 1.0))
            node = self.render.attach_new_node(dlight)
            node.look_at(*(location * -1))
            self.render.set_light(node)
        if visible:
            if radius <= 2.0:
                samples = 6
            elif radius >= 36.0:
                samples = 40
            else:
                samples = int(round(((1.5 * radius) * (2 / 3.0)) + 3.75))
            celestial = Dome(
                radius * 1.5, samples, 2, color, 0, location,
                ((-(math.degrees(azimuth))), 90 + math.degrees(elevation), 0))
            self.celestials.attach(celestial)

    def create_celestial_node(self):
        bounds = self.camera.node().get_lens().make_bounds()
        self.celestials = self.celestials.create_node()
        self.celestials.set_transparency(TransparencyAttrib.MAlpha)
        self.celestials.set_light_off()
        self.celestials.set_effect(
            CompassEffect.make(self.camera, CompassEffect.PPos))
        self.celestials.node().set_bounds(bounds)
        self.celestials.node().set_final(True)
        self.celestials.reparent_to(self.render)

    def register_collider(self, obj):
        assert isinstance(obj, PhysicalObject)
        self.collidables.add(obj)

    def register_updater(self, obj):
        assert isinstance(obj, WorldObject)
        self.updatables.add(obj)

    def register_updater_later(self, obj):
        assert isinstance(obj, WorldObject)
        self.updatables_to_add.add(obj)

    def do_explosion(self, node, radius, force):
        center = node.get_pos(self.render)
        expl_body = BulletGhostNode("expl")
        expl_shape = BulletSphereShape(radius)
        expl_body.add_shape(expl_shape)
        expl_bodyNP = self.render.attach_new_node(expl_body)
        expl_bodyNP.set_pos(center)
        self.physics.attach_ghost(expl_body)
        result = self.physics.contact_test(expl_body)
        for contact in result.getContacts():
            n0_name = contact.getNode0().get_name()
            n1_name = contact.getNode1().get_name()
            obj = None
            try:
                obj = self.objects[n1_name]
            except:
                break
            if n0_name == "expl" and n1_name not in EXPLOSIONS_DONT_PUSH and not n1_name.startswith(
                    'Walker'):
                # repeat contact test with just this pair of objects
                # otherwise all manifold point values will be the same
                # for all objects in original result
                real_c = self.physics.contact_test_pair(expl_body, obj.solid)
                mpoint = real_c.getContacts()[0].getManifoldPoint()
                distance = mpoint.getDistance()
                if distance < 0:
                    if hasattr(obj, 'decompose'):
                        obj.decompose()
                    else:
                        expl_vec = Vec3(mpoint.getPositionWorldOnA() -
                                        mpoint.getPositionWorldOnB())
                        expl_vec.normalize()
                        magnitude = force * 1.0 / math.sqrt(
                            abs(radius - abs(distance)))
                        obj.solid.set_active(True)
                        obj.solid.apply_impulse(expl_vec * magnitude,
                                                mpoint.getLocalPointB())
                    if hasattr(obj, 'damage'):
                        obj.damage(magnitude / 5)
        self.physics.remove_ghost(expl_body)
        expl_bodyNP.detach_node()
        del (expl_body, expl_bodyNP)

    def do_plasma_push(self, plasma, node, energy):
        obj = None
        try:
            obj = self.objects[node]
        except:
            raise

        if node not in EXPLOSIONS_DONT_PUSH and not node.startswith('Walker'):
            if hasattr(obj, 'decompose'):
                obj.decompose()
            else:
                solid = obj.solid
                dummy_node = NodePath('tmp')
                dummy_node.set_hpr(plasma.hpr)
                dummy_node.set_pos(plasma.pos)
                f_vec = render.get_relative_vector(dummy_node, Vec3(0, 0, 1))
                local_point = (obj.node.get_pos() - dummy_node.get_pos()) * -1
                f_vec.normalize()
                solid.set_active(True)
                try:
                    solid.apply_impulse(f_vec * (energy * 35),
                                        Point3(local_point))
                except:
                    pass
                del (dummy_node)
        if hasattr(obj, 'damage'):
            obj.damage(energy * 5)

    def update(self, task):
        """
        Called every frame to update the physics, etc.
        """
        dt = globalClock.getDt()
        for obj in self.updatables_to_add:
            self.updatables.add(obj)
        self.updatables_to_add = set()
        for obj in self.updatables:
            obj.update(dt)
        self.updatables -= self.garbage
        self.collidables -= self.garbage
        while True:
            if len(self.garbage) < 1:
                break
            trash = self.garbage.pop()
            if (isinstance(trash.solid, BulletGhostNode)):
                self.physics.remove_ghost(trash.solid)
            if (isinstance(trash.solid, BulletRigidBodyNode)):
                self.physics.remove_rigid_body(trash.solid)
            if hasattr(trash, 'dead'):
                trash.dead()
            trash.node.remove_node()
            del (trash)
        self.physics.do_physics(dt)
        for obj in self.collidables:
            result = self.physics.contact_test(obj.node.node())
            for contact in result.get_contacts():
                obj1 = self.objects.get(contact.get_node0().get_name())
                obj2 = self.objects.get(contact.get_node1().get_name())
                if obj1 and obj2:
                    # Check the collision bits to see if the two objects should collide.
                    should_collide = obj1.collide_bits & obj2.collide_bits
                    if not should_collide.is_zero():
                        pt = contact.get_manifold_point()
                        if obj1 in self.collidables:
                            obj1.collision(obj2, pt, True)
                        if obj2 in self.collidables:
                            obj2.collision(obj1, pt, False)
        return task.cont
Beispiel #4
0
class PandaPhysicsSystem(DelegateByNetmode, SignalListener):
    subclasses = {}

    def __init__(self):
        self.register_signals()

        self.world = BulletWorld()
        self.world.setGravity((0, 0, -9.81))

        # # Seems that this does not function
        # on_contact_added = PythonCallbackObject(self._on_contact_added)
        # self.world.set_contact_added_callback(on_contact_added)

        # on_filter = PythonCallbackObject(self._filter_collision)
        # self.world.set_filter_callback(on_filter)

        self.listener = DirectObject()
        self.listener.accept('bullet-contact-added', self._on_contact_added)
        self.listener.accept('bullet-contact-destroyed', self._on_contact_removed)

        debug_node = BulletDebugNode('Debug')
        debug_node.showWireframe(True)
        debug_node.showConstraints(True)
        debug_node.showBoundingBoxes(False)
        debug_node.showNormals(False)

        self.debug_nodepath = render.attachNewNode(debug_node)
        self.world.set_debug_node(debug_node)

        self.tracked_contacts = defaultdict(int)
        self.existing_collisions = set()

    def _create_contacts_from_result(self, requesting_node, contact_result):
        """Return collision contacts between two nodes"""
        contacts = []

        for contact in contact_result.get_contacts():
            if contact.get_node0() == requesting_node:
                manifold = contact.get_manifold_point()

                position = manifold.get_position_world_on_a()
                normal = -manifold.get_normal_world_on_b()

            elif contact.get_node1() == requesting_node:
                manifold = contact.get_manifold_point()

                position = manifold.get_position_world_on_b()
                normal = manifold.get_normal_world_on_b()

            impulse = manifold.get_applied_impulse()
            contact_ = CollisionContact(position, normal, impulse)
            contacts.append(contact_)

        return contacts

    def _on_contact_removed(self, node_a, node_b):
        self.tracked_contacts[(node_a, node_b)] -= 1

    def _on_contact_added(self, node_a, node_b):
        self.tracked_contacts[(node_a, node_b)] += 1

    def _filter_collision(self, filter_data):
        filter_data.set_collide(True)

    @RegisterPhysicsNode.on_global
    def register_node(self, node):
        self.world.attachRigidBody(node)
        node.set_python_tag("world", self.world)

    @DeregisterPhysicsNode.on_global
    def deregister_node(self, node):
        self.world.removeRigidBody(node)
        node.clear_python_tag("world")

    def dispatch_collisions(self):
        # Dispatch collisions
        existing_collisions = self.existing_collisions
        for pair, contact_count in self.tracked_contacts.items():
            if contact_count > 0 and pair not in existing_collisions:
                existing_collisions.add(pair)

                # Dispatch collision
                node_a, node_b = pair

                entity_a = entity_from_nodepath(node_a)
                entity_b = entity_from_nodepath(node_b)

                contact_result = None

                if entity_a is not None:
                    def contact_getter():
                        nonlocal contact_result
                        if contact_result is None:
                            contact_result = self.world.contact_test_pair(node_a, node_b)

                        return self._create_contacts_from_result(node_a, contact_result)

                    collision_result = LazyCollisionResult(entity_b, CollisionState.started, contact_getter)
                    CollisionSignal.invoke(collision_result, target=entity_a)

                if entity_b is not None:
                    def contact_getter():
                        nonlocal contact_result
                        if contact_result is None:
                            contact_result = self.world.contact_test_pair(node_a, node_b)

                        return self._create_contacts_from_result(node_b, contact_result)

                    collision_result = LazyCollisionResult(entity_a, CollisionState.started, contact_getter)
                    CollisionSignal.invoke(collision_result, target=entity_b)

            elif contact_count == 0 and pair in existing_collisions:
                existing_collisions.remove(pair)

                # Dispatch collision
                node_a, node_b = pair

                entity_a = entity_from_nodepath(node_a)
                entity_b = entity_from_nodepath(node_b)

                # Don't send contacts for ended collisions
                contact_getter = lambda: None

                if entity_a is not None:
                    collision_result = LazyCollisionResult(entity_b, CollisionState.ended, contact_getter)
                    CollisionSignal.invoke(collision_result, target=entity_a)

                if entity_b is not None:
                    collision_result = LazyCollisionResult(entity_a, CollisionState.ended, contact_getter)
                    CollisionSignal.invoke(collision_result, target=entity_b)

    def update(self, delta_time):
        self.world.doPhysics(delta_time)
        self.dispatch_collisions()
Beispiel #5
0
class PandaPhysicsSystem(DelegateByNetmode, SignalListener):
    subclasses = {}

    def __init__(self):
        self.register_signals()

        self.world = BulletWorld()
        self.world.setGravity((0, 0, -9.81))

        # # Seems that this does not function
        # on_contact_added = PythonCallbackObject(self._on_contact_added)
        # self.world.set_contact_added_callback(on_contact_added)

        # on_filter = PythonCallbackObject(self._filter_collision)
        # self.world.set_filter_callback(on_filter)

        self.listener = DirectObject()
        self.listener.accept('bullet-contact-added', self._on_contact_added)
        self.listener.accept('bullet-contact-destroyed',
                             self._on_contact_removed)

        debug_node = BulletDebugNode('Debug')
        debug_node.showWireframe(True)
        debug_node.showConstraints(True)
        debug_node.showBoundingBoxes(False)
        debug_node.showNormals(False)

        self.debug_nodepath = render.attachNewNode(debug_node)
        self.world.set_debug_node(debug_node)

        self.tracked_contacts = defaultdict(int)
        self.existing_collisions = set()

    def _create_contacts_from_result(self, requesting_node, contact_result):
        """Return collision contacts between two nodes"""
        contacts = []

        for contact in contact_result.get_contacts():
            if contact.get_node0() == requesting_node:
                manifold = contact.get_manifold_point()

                position = manifold.get_position_world_on_a()
                normal = -manifold.get_normal_world_on_b()

            elif contact.get_node1() == requesting_node:
                manifold = contact.get_manifold_point()

                position = manifold.get_position_world_on_b()
                normal = manifold.get_normal_world_on_b()

            impulse = manifold.get_applied_impulse()
            contact_ = CollisionContact(position, normal, impulse)
            contacts.append(contact_)

        return contacts

    def _on_contact_removed(self, node_a, node_b):
        self.tracked_contacts[(node_a, node_b)] -= 1

    def _on_contact_added(self, node_a, node_b):
        self.tracked_contacts[(node_a, node_b)] += 1

    def _filter_collision(self, filter_data):
        filter_data.set_collide(True)

    @RegisterPhysicsNode.on_global
    def register_node(self, node):
        self.world.attachRigidBody(node)
        node.set_python_tag("world", self.world)

    @DeregisterPhysicsNode.on_global
    def deregister_node(self, node):
        self.world.removeRigidBody(node)
        node.clear_python_tag("world")

    def dispatch_collisions(self):
        # Dispatch collisions
        existing_collisions = self.existing_collisions
        for pair, contact_count in self.tracked_contacts.items():
            if contact_count > 0 and pair not in existing_collisions:
                existing_collisions.add(pair)

                # Dispatch collision
                node_a, node_b = pair

                entity_a = entity_from_nodepath(node_a)
                entity_b = entity_from_nodepath(node_b)

                contact_result = None

                if entity_a is not None:

                    def contact_getter():
                        nonlocal contact_result
                        if contact_result is None:
                            contact_result = self.world.contact_test_pair(
                                node_a, node_b)

                        return self._create_contacts_from_result(
                            node_a, contact_result)

                    collision_result = LazyCollisionResult(
                        entity_b, CollisionState.started, contact_getter)
                    CollisionSignal.invoke(collision_result, target=entity_a)

                if entity_b is not None:

                    def contact_getter():
                        nonlocal contact_result
                        if contact_result is None:
                            contact_result = self.world.contact_test_pair(
                                node_a, node_b)

                        return self._create_contacts_from_result(
                            node_b, contact_result)

                    collision_result = LazyCollisionResult(
                        entity_a, CollisionState.started, contact_getter)
                    CollisionSignal.invoke(collision_result, target=entity_b)

            elif contact_count == 0 and pair in existing_collisions:
                existing_collisions.remove(pair)

                # Dispatch collision
                node_a, node_b = pair

                entity_a = entity_from_nodepath(node_a)
                entity_b = entity_from_nodepath(node_b)

                # Don't send contacts for ended collisions
                contact_getter = lambda: None

                if entity_a is not None:
                    collision_result = LazyCollisionResult(
                        entity_b, CollisionState.ended, contact_getter)
                    CollisionSignal.invoke(collision_result, target=entity_a)

                if entity_b is not None:
                    collision_result = LazyCollisionResult(
                        entity_a, CollisionState.ended, contact_getter)
                    CollisionSignal.invoke(collision_result, target=entity_b)

    def update(self, delta_time):
        self.world.doPhysics(delta_time)
        self.dispatch_collisions()