
# -*- indent-tabs-mode: t -*-
# Soya 3D tutorial
# Copyright (C) 2004 Jean-Baptiste 'Jiba' LAMY
# Copyright (C) 2001-2003 Bertrand 'blam!' LAMY
#
# This program is free software; you can redistribute it and/or modify
# it under the terms of the GNU General Public License as published by
# the Free Software Foundation; either version 2 of the License, or
# (at your option) any later version.
#
# This program is distributed in the hope that it will be useful,
# but WITHOUT ANY WARRANTY; without even the implied warranty of
# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the
# GNU General Public License for more details.
#
# You should have received a copy of the GNU General Public License
# along with this program; if not, write to the Free Software
# Foundation, Inc., 59 Temple Place, Suite 330, Boston, MA 02111-1307 USA
# portal-1: Portal
# A portal is a 2D rectangle used to link 2 worlds just as if the world
# beyond was seen through an open door.
# Portals are usefull to compute visibility and avoid renderering the
# world beyond when it is not visible.
import sys, os, os.path, soya, soya.cube, soya.widget, soya.sdlconst
soya.init()
soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "data"))
m1 = soya.Material(soya.Image.get("block2.png"))
m2 = soya.Material(soya.Image.get("metal1.png"))
scene = soya.World()
# Create world 1
w1 = soya.World(scene)
w1.set_xyz(0.0, 0.0, 0.0)
w1.atmosphere = soya.Atmosphere()
w1.atmosphere.bg_color = (0.0, 0.0, 1.0, 1.0)
w1.atmosphere.ambient = (0.0, 0.5, 0.0, 1.0)
w1.atmosphere.fog_color = (0.0, 0.0, 1.0, 1.0)
w1.atmosphere.fog_start = 10.0
w1.atmosphere.fog_end = 50.0
w1.atmosphere.fog = 1
w1.atmosphere.fog_type = 0
w1.set_model(soya.cube.Cube(None, m1).to_model())
# Create world 2
# Notice that w2 doesn't have the same atmosphere than w1
w2 = soya.World(scene)
w2.set_xyz(0.0, 0.0, -10.0)
w2.atmosphere = soya.SkyAtmosphere()
w2.atmosphere.bg_color = (1.0, 0.0, 0.0, 1.0)
w2.atmosphere.ambient = (0.5, 0.5, 0.0, 1.0)
w2.atmosphere.skyplane = 1
w2.atmosphere.sky_color = (1.0, 1.0, 0.0, 1.0)
w2.atmosphere.cloud = soya.Material(soya.Image.get("cloud.png"))
w2.set_model(soya.cube.Cube(None, m2).to_model())
# Add a light in world 2.
# The light will not light the objects in world 1 unless light.top_level is true.
light = soya.Light(w2)
light.top_level = 0
light.ambient = (1.0, 1.0, 0.0, 1.0)
light.set_xyz(0.0, 2.0, 0.0)
# Portal creation
# Create a portal that link to world 2. The attribute "beyond" is the world beyond
# the portal
portal1 = soya.Portal(w1)
portal1.beyond = w2
portal1.set_xyz(0.0, 0.0, -5.0)
# To change the size of the portal, we scale it (the z scale factor has no meaning)
portal1.scale(4.0, 4.0, 1.0)
# Attr bound_atm must be set to 1 if the 2 worlds linked doesn't have the
# same atmosphere (else set the value to 0).
#portal1.bound_atmosphere = 1
# Setting use_clip_plane to 1 will affect object rendered in world beyond,
# this means only the part of the objects that are visible through the portal
# 2D rectangle will be rendered.
#portal1.nb_clip_planes = 4
# Create a portal that link world 2 to world 1
portal2 = soya.Portal(w2)
portal2.rotate_y(180.0)
portal2.beyond = w1
portal2.scale(4.0, 4.0, 1.0)
portal2.set_xyz(0.0, 0.0, 5.0)
#portal1.nb_clip_planes = 0
#portal2.nb_clip_planes = 0
# ASCII art representation of the two world :
# \
# \ |
# +-+ | |
# | |\ | |
# +-+ + > world2 |
# \ \| | |
# +-+ | |
# | |
# <-portal2-> / |
#--------------+ |
# <-portal1-> \ |
# | > scene
# +-+ | |
# | |\ | |
# +-+ + | |
# \ \| | |
# +-+ > world1 |
# | |
# ___ | |
# \^/ | |
# V | |
# camera / |
# /
class MovableCamera(soya.Camera):
def __init__(self, parent):
soya.Camera.__init__(self, parent)
self.speed = soya.Vector(self)
self.rotation_y_speed = 0.0
self.rotation_x_speed = 0.0
def begin_round(self):
soya.Camera.begin_round(self)
for event in soya.MAIN_LOOP.events:
if event[0] == soya.sdlconst.KEYDOWN:
if event[1] == soya.sdlconst.K_UP: self.speed.z = -1.0
elif event[1] == soya.sdlconst.K_DOWN: self.speed.z = 1.0
elif event[1] == soya.sdlconst.K_LEFT: self.rotation_y_speed = 10.0
elif event[1] == soya.sdlconst.K_RIGHT: self.rotation_y_speed = -10.0
elif event[1] == soya.sdlconst.K_q: soya.MAIN_LOOP.stop()
elif event[1] == soya.sdlconst.K_ESCAPE: soya.MAIN_LOOP.stop()
if event[0] == soya.sdlconst.KEYUP:
if event[1] == soya.sdlconst.K_UP: self.speed.z = 0.0
elif event[1] == soya.sdlconst.K_DOWN: self.speed.z = 0.0
elif event[1] == soya.sdlconst.K_LEFT: self.rotation_y_speed = 0.0
elif event[1] == soya.sdlconst.K_RIGHT: self.rotation_y_speed = 0.0
# Checks if the camera has passed through a portal.
# First, collects all portals in the camera's root world.
# the World.search_all method take a predicat (a one argument callable), and
# returns a list of all items (recursively) in the world that satisfy the predicat.
portals = camera.to_render.search_all(lambda item: isinstance(item, soya.Portal))
# Then for each portal, checks if the camera has pass through it, and if so,
# transfers the camera in the world beyond the portal.
# The has_passed_through method takes two argument : the old position of the object
# and the new one or (as here) the speed vector.
for portal in portals:
if portal.has_passed_through(self, self.speed):
print "pass !", self.position(), self.speed
portal.pass_through(camera)
def advance_time(self, proportion):
self.add_mul_vector(proportion, self.speed)
self.turn_y(self.rotation_y_speed * proportion)
self.turn_x(self.rotation_x_speed * proportion)
# Creates a movable camera, that render the world 1 only (to_render attribute)
camera = MovableCamera(scene)
camera.to_render = w1
camera.set_xyz(0.0, 0.0, 8.0)
root = soya.widget.Group()
root.add(camera)
label = soya.widget.Label(root, " - Portal demo - use cursor keys to move.")
label.resize_style = soya.widget.WIDGET_RESIZE_MAXIMIZE
soya.set_root_widget(root)
soya.MainLoop(scene).main_loop()