
# -*- indent-tabs-mode: t -*-
# Soya 3D tutorial
# Copyright (C) 2004 Jean-Baptiste 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
# raypicking-2: Drag-droppable 3D objects
# In this lesson, you'll learn how to use raypicking to grab the object under the mouse.
# Raypicking consists in casting a ray in a 3D World, and it returns information abour
# the object the ray hit.
# Soya provides 2 raypicking functions: raypick and raypick_b ("b" stands for boolean).
# The first version returns a (IMPACT, NORMAL) tuple. IMPACT is the impact Point, and
# IMPACT.parent is the object hit. NORMAL is the normal Vector of the object at the
# impact (usefull e.g. for reflection).
# The boolean version simply returns true if something is hit.
# Both take the same arguments:
# - ORIGIN: the origin of the ray (a Position)
# - DIRECTION: the direction of the ray (a Vector)
# - DISTANCE: the maximum distance of the ray; -1.0 (default) for no distance limit
# - HALF_LINE: if true (default), the ray goes only in the direction of DIRECTION.
# if false, the ray goes both in DIRECTION and -DIRECTION, and so can hit
# objects backward.
# - CULL_FACE if true (default), does not take into account invisible sides of non-double
# sided faces (Face.double_sided = 0).
# For speeding up, raypick has 2 optional arguments, a Point and a Vector. If given,
# these Point and Vector will be returned in the tuple, instead of creating new objects.
import sys, os, os.path, soya, soya.cube, soya.sphere, soya.sdlconst
soya.init()
soya.path.append(os.path.join(os.path.dirname(sys.argv[0]), "data"))
# Creates the scene.
scene = soya.World()
# DragDropWorld is a world that allows to dragdrop its content with the mouse.
class DragDropWorld(soya.World):
def __init__(self, parent):
soya.World.__init__(self, parent)
# The object we are currently dragdroping (None => no dragdrop).
self.dragdroping = None
# The impact point
self.impact = None
def begin_round(self):
soya.World.begin_round(self)
# Processes the events
for event in soya.MAIN_LOOP.events:
# Mouse down initiates the dragdrop.
if event[0] == soya.sdlconst.MOUSEBUTTONDOWN:
# The event give us the 2D mouse coordinates in pixel. The camera.coord2d_to_3d
# convert these 2D pixel coordinates into a soy.Point object.
mouse = camera.coord2d_to_3d(event[2], event[3])
# Performs a raypicking, starting at the camera and going toward the mouse.
# The vector_to method returns the vector between 2 positions.
# This raypicking grabs anything that is under the mouse. Raypicking returns
# None if nothing is encountered, or a (impact, normal) tuple, where impact is the
# position of the impact and normal is the normal vector at this position.
# The object encountered is impact.parent ; here, we don't need the normal.
result = self.raypick(camera, camera.vector_to(mouse))
if result:
self.impact, normal = result
self.dragdroping = self.impact.parent
# Converts impact into the camera coordinate system, in order to get its Z value.
# camera.coord2d_to_3d cannot choose a Z value for you, so you need to pass it
# as a third argument (it defaults to -1.0). Then, we computes the old mouse
# position, which has the same Z value than impact.
self.impact.convert_to(camera)
self.old_mouse = camera.coord2d_to_3d(event[2], event[3], self.impact.z)
# Mouse up ends the dragdrop.
elif event[0] == soya.sdlconst.MOUSEBUTTONUP:
self.dragdroping = None
# Mouse motion moves the dragdroping object, if there is one.
elif event[0] == soya.sdlconst.MOUSEMOTION:
if self.dragdroping:
# Computes the new mouse position, at the same Z value than impact.
new_mouse = camera.coord2d_to_3d(event[1], event[2], self.impact.z)
# Translates dragdroping by a vector starting at old_mouse and ending at
# new_mouse.
self.dragdroping.add_vector(self.old_mouse.vector_to(new_mouse))
# Store the current mouse position.
self.old_mouse = new_mouse
# Creates a dragdrop world.
world = DragDropWorld(scene)
# Adds some bodys with different models, at different positions.
red = soya.Material(); red .diffuse = (1.0, 0.0, 0.0, 1.0)
green = soya.Material(); green.diffuse = (0.0, 1.0, 0.0, 1.0)
blue = soya.Material(); blue .diffuse = (0.0, 0.0, 1.0, 1.0)
soya.Body(world, soya.cube.Cube(None, red ).to_model()).set_xyz(-1.0, -1.0, 1.0)
soya.Body(world, soya.cube.Cube(None, green).to_model()).set_xyz( 0.0, -1.0, 0.0)
soya.Body(world, soya.cube.Cube(None, blue ).to_model()).set_xyz( 1.0, -1.0, -1.0)
soya.Body(world, soya.sphere.Sphere().to_model()).set_xyz(1.0, 1.0, 0.0)
# Adds a light.
light = soya.Light(scene)
light.set_xyz(0.0, 0.2, 1.0)
# Creates a camera.
camera = soya.Camera(scene)
camera.set_xyz(0.0, 0.0, 4.0)
camera.fov = 100.0
soya.set_root_widget(camera)
# Main loop
soya.MainLoop(scene).main_loop()
# TODO / exercice : turn this demo into a puzzle game !