Collaborative Sci-Fantasy Tech Demo for an Official Panda3D Showcase

Portals the Lazy Way

I thought I’d knock off one of the harder problems, which is to create portal programming logic at a basic level.

I’ll provide the code after a short description here. Basically what’s going on is that I’m putting a texture buffer in a new render node, attaching a camera to that, and then projecting that scene-texture onto a plane in regular render space. Then, I’m defining a Task that updates the portal-camera’s H value and FOV based upon the camera handling logic of the player character.

The FOV increases with the decreasing distance of the player to the portal. Then, when the player is within a certain range, I use set_pos() to teleport the player character to the portal scene which is deceptively rendered far-off on the normal render space graph, as well as the portal render node.

portals_again

        # portal #1 begins
        # make a new texture buffer, render node, and attach a camera
        mirror_buffer = self.win.make_texture_buffer("mirror_buff", 4096, 4096)
        mirror_render = NodePath("mirror_render")
        mirror_render.set_shader(scene_shader)
        self.mirror_cam = self.make_camera(mirror_buffer)
        self.mirror_cam.reparent_to(mirror_render)
        self.mirror_cam.set_pos(0, -60, 5)
        self.mirror_cam.set_hpr(0, 25, 0)
        self.mirror_cam.node().get_lens().set_focal_length(10)
        self.mirror_cam.node().get_lens().set_fov(90)
        
        mirror_filters = CommonFilters(mirror_buffer, self.mirror_cam)
        # mirror_filters.set_high_dynamic_range()
        mirror_filters.set_exposure_adjust(1.1)
        # mirror_filters.set_gamma_adjust(1.3)
        
        # load in a mirror/display object model in normal render space
        self.mirror_model = self.loader.loadModel('models/wide_screen_video_display.egg')
        self.mirror_model.reparent_to(self.render)
        self.mirror_model.set_pos(-20, 0, 1)
        self.mirror_model.set_sz(3)
        # self.mirror_model.flatten_strong()
        
        # mirror scene model load-in
        # reparent to mirror render node
        house_uv = self.loader.load_model('models/hangar_1.gltf')
        house_uv.reparent_to(mirror_render)
        windows = house_uv.find('**/clear_arches')
        windows.hide()
        house_uv.set_pos(0, 0, 0)
        house_uv.set_scale(1)
        
        # the portal ramp
        house_uv = self.loader.load_model('models/ramp_1.gltf')
        house_uv.reparent_to(mirror_render)
        house_uv.set_h(180)
        house_uv.set_scale(1.5)
        house_uv.set_pos(0, -50, 0)

        # mirror scene lighting
        # point light generator
        for x in range(0, 1):
            plight_1 = PointLight('plight')
            # add plight props here
            plight_1_node = mirror_render.attach_new_node(plight_1)
            # group the lights close to each other to create a sun effect
            plight_1_node.set_pos(random.uniform(-21, -20), random.uniform(-21, -20), random.uniform(20, 21))
            mirror_render.set_light(plight_1_node)
        
        # set the live buffer texture to the mirror/display model in normal render space
        self.mirror_model.set_texture(mirror_buffer.get_texture())
        
        # secret hangar far off somewhere on the graph
        house_uv = self.loader.load_model('models/hangar_1.gltf')
        house_uv.reparent_to(self.render)
        windows = house_uv.find('**/clear_arches')
        windows.hide()
        house_uv.set_pos(400, 400, -1)
        
        # the portal ring
        house_uv = self.loader.load_model('models/ring_1.gltf')
        house_uv.reparent_to(self.render)
        house_uv.set_h(90)
        house_uv.set_pos(-20, 0, -2)
        
        # the portal ramp
        house_uv = self.loader.load_model('models/ramp_1.gltf')
        house_uv.reparent_to(self.render)
        house_uv.set_h(0)
        house_uv.set_pos(-20, -5.5, 0)
        r_pos = house_uv.get_pos()
        make_collision_from_model(house_uv, 0, 0, self.world, (r_pos[0], r_pos[1], r_pos[2] + 1), 0)
        
        self.count_frames_1 = 0
        self.screen_cap_num = 1
        
        def make_screenshot():
            # Ensure the frame is rendered.
            base.graphicsEngine.render_frame()

            # Grab the screenshot into a big image
            full = PNMImage()
            base.win.get_screenshot(full)

            # Now reduce it
            reduced = PNMImage(500, 300)
            reduced.gaussianFilterFrom(1, full)

            # And write it out.
            reduced.write(Filename('screen_cap_' + str(self.screen_cap_num) + '.jpg'))
            
            self.screen_cap_num += 1
        
        def update_portal_cam(Task):
            if self.count_frames_1 < 30:
                self.count_frames_1 += 1
            
            if self.count_frames_1 == 15:
                pass
                # make_screenshot()
                
            if self.count_frames_1 == 29:
                self.count_frames_1 = 0
            
            p_dist = (self.player.get_pos() - self.mirror_model.get_pos(base.render)).length()
            target_fov = 115

            if p_dist < 2.25:
                target_fov = 145
                self.player.set_pos(400, 400, 3)
                # adjust the ground plane
                self.ground_plane.set_pos(0, 0, 0)
                # move the normal point lights
                lights = self.render.find_all_matches('**/plight*')
                for l in lights:
                    l.set_pos(400, 400, 21)
            
            player_h = self.player.get_h()
            self.mirror_cam.set_h(player_h)
            self.mirror_cam.node().get_lens().set_fov(target_fov)

            return Task.cont
            
        self.task_mgr.add(update_portal_cam)
        
        # portal #2 begins
        # the portal ring
        house_uv = self.loader.load_model('models/ring_1.gltf')
        house_uv.reparent_to(self.render)
        house_uv.set_h(90)
        house_uv.set_pos(400, 400, -2)
        
        # the portal ramp
        house_uv = self.loader.load_model('models/ramp_1.gltf')
        house_uv.reparent_to(self.render)
        house_uv.set_h(180)
        house_uv.set_pos(400, 405.5, 0)
        r_pos = house_uv.get_pos()
        make_collision_from_model(house_uv, 0, 0, self.world, (r_pos[0], r_pos[1], r_pos[2] + 1), 180)

Here’s a bonus screen capture function:

        def make_screenshot():
            # Ensure the frame is rendered.
            base.graphicsEngine.render_frame()

            # Grab the screenshot into a big image
            full = PNMImage()
            base.win.get_screenshot(full)

            # Now reduce it
            reduced = PNMImage(500, 300)
            reduced.gaussianFilterFrom(1, full)

            # And write it out.
            reduced.write(Filename('screen_cap_' + str(self.screen_cap_num) + '.jpg'))
            
            self.screen_cap_num += 1
1 Like