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.
# 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