""" Physics, a 2D Physics Playground for Kids Copyright (C) 2008 Alex Levenson and Brian Jordan 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 3 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, see . """ #================================================================== # Physics.activity # Tool Classes # By Alex Levenson #================================================================== import pygame import olpcgames from pygame.locals import * from helpers import * from inspect import getmro from gettext import gettext as _ # tools that can be used superlcass class Tool(object): name = 'Tool' icon = 'icon' toolTip = "Tool Tip" toolAccelerator = None def __init__(self,gameInstance): self.game = gameInstance self.name = self.__class__.name def handleEvents(self,event): handled = True # default event handling if event.type == QUIT: # bye bye! Hope you had fun! self.game.running = False elif event.type == USEREVENT: if hasattr(event,"action"): if event.action == "stop_start_toggle": # stop/start simulation self.game.world.run_physics = not self.game.world.run_physics elif self.game.toolList.has_key(event.action): self.game.setTool(event.action) elif hasattr(event,"code"): if event.code == olpcgames.FILE_WRITE_REQUEST: #saving to journal self.game.world.add.remove_mouseJoint() self.game.world.json_save(event.filename) elif event.code == olpcgames.FILE_READ_REQUEST: #loading from journal self.game.world.json_load(event.filename) elif event.type == MOUSEBUTTONDOWN and event.button == 1: self.game.canvas.grab_focus() handled = False else: handled = False if handled: return handled else: return self.handleToolEvent(event) def handleToolEvent(self,event): # overload to handel events for Tool subclasses pass def draw(self): # default drawing method is don't draw anything pass def cancel(self): # default cancel doesn't do anything pass # The circle creation tool class CircleTool(Tool): name = 'Circle' icon = 'circle' toolTip = _("Circle") toolAccelerator = _("c") def __init__(self,gameInstance): Tool.__init__(self,gameInstance) self.pt1 = None self.radius = 40 def handleToolEvent(self,event): if event.type == MOUSEBUTTONDOWN: if event.button == 1: self.pt1 = pygame.mouse.get_pos() elif event.type == MOUSEBUTTONUP: if event.button == 1: if self.radius > 1: # elements doesn't like tiny shapes :( self.game.world.add.ball(self.pt1,self.radius, dynamic=True, density=1.0, restitution=0.16, friction=0.5) self.pt1 = None def draw(self): # draw a circle from pt1 to mouse if self.pt1 != None: delta = distance(self.pt1, pygame.mouse.get_pos()) if delta > 0: self.radius = delta if self.radius > 3: thick = 3 else: thick = 0 pygame.draw.circle(self.game.screen, (100,180,255),self.pt1,self.radius,thick) pygame.draw.line(self.game.screen,(100,180,255),self.pt1,pygame.mouse.get_pos(),1) def cancel(self): self.pt1 = None # The box creation tool class BoxTool(Tool): name = 'Box' icon = 'box' toolTip = _("Box") toolAccelerator = _("b") def __init__(self,gameInstance): Tool.__init__(self,gameInstance) self.pt1 = None self.rect = None self.width = 80 self.height = 80 def handleToolEvent(self,event): if event.type == MOUSEBUTTONDOWN: if event.button == 1: self.pt1 = pygame.mouse.get_pos() elif event.type == MOUSEBUTTONUP: if event.button == 1 and self.pt1!=None: mouse_x_y = pygame.mouse.get_pos() if mouse_x_y[0] == self.pt1[0] and mouse_x_y[1] == self.pt1[1]: self.rect = pygame.Rect(self.pt1, (-self.width, -self.height)) self.rect.normalize() if self.rect.width > 10 and self.rect.height > 10: # elements doesn't like small shapes :( self.game.world.add.rect(self.rect.center, self.rect.width/2, self.rect.height/2, dynamic=True, density=1.0, restitution=0.16, friction=0.5) self.pt1 = None def draw(self): # draw a box from pt1 to mouse if self.pt1 != None: mouse_x_y = pygame.mouse.get_pos() if mouse_x_y[0] != self.pt1[0] or mouse_x_y[1] != self.pt1[1]: self.width = mouse_x_y[0] - self.pt1[0] self.height = mouse_x_y[1] - self.pt1[1] self.rect = pygame.Rect(self.pt1, (self.width, self.height)) self.rect.normalize() pygame.draw.rect(self.game.screen, (100,180,255),self.rect,3) def cancel(self): self.pt1 = None self.rect = None # The triangle creation tool class TriangleTool(Tool): name = 'Triangle' icon = 'triangle' toolTip = _("Triangle") toolAccelerator = _("t") def __init__(self,gameInstance): Tool.__init__(self,gameInstance) self.pt1 = None self.vertices = None self.line_delta = [0, -80] def handleToolEvent(self,event): if event.type == MOUSEBUTTONDOWN: if event.button == 1: self.pt1 = pygame.mouse.get_pos() elif event.type == MOUSEBUTTONUP: if event.button == 1 and self.pt1!= None: mouse_x_y = pygame.mouse.get_pos() if mouse_x_y[0] == self.pt1[0] and mouse_x_y[1] == self.pt1[1]: self.pt1 = [mouse_x_y[0] - self.line_delta[0], mouse_x_y[1] - self.line_delta[1]] self.vertices = constructTriangleFromLine(self.pt1, mouse_x_y) if distance(self.pt1,pygame.mouse.get_pos()) > 15: # elements doesn't like tiny shapes :( self.game.world.add.convexPoly(self.vertices, dynamic=True, density=1.0, restitution=0.16, friction=0.5) self.pt1 = None self.vertices = None def draw(self): # draw a triangle from pt1 to mouse if self.pt1 != None: mouse_x_y = pygame.mouse.get_pos() if mouse_x_y[0] != self.pt1[0] or mouse_x_y[1] != self.pt1[1]: self.vertices = constructTriangleFromLine(self.pt1, mouse_x_y) self.line_delta = [mouse_x_y[0] - self.pt1[0], mouse_x_y[1] - self.pt1[1]] pygame.draw.polygon(self.game.screen, (100,180,255),self.vertices, 3) pygame.draw.line(self.game.screen,(100,180,255),self.pt1,mouse_x_y,1) def cancel(self): self.pt1 = None self.vertices = None # The Polygon creation tool class PolygonTool(Tool): name = 'Polygon' icon = 'polygon' toolTip = _("Polygon") toolAccelerator = _("p") def __init__(self,gameInstance): Tool.__init__(self,gameInstance) self.vertices = None self.previous_vertices = None def handleToolEvent(self,event): if event.type == MOUSEBUTTONDOWN and event.button == 1 and self.vertices is None: self.vertices = [event.pos] self.safe = False if event.type == MOUSEBUTTONUP and event.button == 1 and self.vertices is not None and len(self.vertices) == 1 and event.pos[0] == self.vertices[0][0] and event.pos[1] == self.vertices[0][1]: if self.previous_vertices is not None: last_x_y = self.previous_vertices[-1] delta_x = last_x_y[0] - event.pos[0] delta_y = last_x_y[1] - event.pos[1] self.vertices = [[i[0] - delta_x, i[1] - delta_y] for i in self.previous_vertices] self.safe = True self.game.world.add.complexPoly(self.vertices, dynamic=True, density=1.0, restitution=0.16, friction=0.5) self.vertices = None elif (event.type == MOUSEBUTTONUP or event.type == MOUSEBUTTONDOWN) and event.button == 1: if self.vertices is None or (event.pos[0] == self.vertices[-1][0] and event.pos[1] == self.vertices[-1][1]): # Skip if coordinate is same as last one return if distance(event.pos,self.vertices[0]) < 15 and self.safe: self.vertices.append(self.vertices[0]) #connect the polygon self.game.world.add.complexPoly(self.vertices, dynamic=True, density=1.0, restitution=0.16, friction=0.5) self.previous_vertices = self.vertices[:] self.vertices = None elif distance(event.pos,self.vertices[0]) < 15: self.vertices = None else: self.vertices.append(event.pos) if distance(event.pos,self.vertices[0]) >= 55 and self.vertices: self.safe = True def draw(self): # draw the poly being created if self.vertices: for i in range(len(self.vertices)-1): pygame.draw.line(self.game.screen,(100,180,255),self.vertices[i],self.vertices[i+1],3) pygame.draw.line(self.game.screen,(100,180,255),self.vertices[-1],pygame.mouse.get_pos(),3) pygame.draw.circle(self.game.screen,(100,180,255),self.vertices[0],15,3) def cancel(self): self.vertices = None # The magic pen tool class MagicPenTool(Tool): name = 'Magicpen' icon = 'magicpen' toolTip = _("Draw") toolAccelerator = _("d") def __init__(self,gameInstance): Tool.__init__(self,gameInstance) self.vertices = None self.previous_vertices = None def handleToolEvent(self,event): if event.type == MOUSEBUTTONDOWN and event.button == 1: self.vertices=[event.pos] self.safe = False elif event.type == MOUSEBUTTONUP and event.button == 1: if len(self.vertices) == 1 and self.previous_vertices is not None: last_x_y = self.previous_vertices[-1] delta_x = last_x_y[0] - event.pos[0] delta_y = last_x_y[1] - event.pos[1] self.vertices = [[i[0] - delta_x, i[1] - delta_y] for i in self.previous_vertices] self.safe = True if self.vertices and self.safe: self.game.world.add.complexPoly(self.vertices, dynamic=True, density=1.0, restitution=0.16, friction=0.5) self.previous_vertices = self.vertices[:] self.vertices = None elif event.type == MOUSEMOTION and self.vertices: self.vertices.append(event.pos) if distance(event.pos,self.vertices[0]) >= 55 and len(self.vertices) > 3: self.safe = True def draw(self): # draw the poly being created if self.vertices: if len(self.vertices) > 1: for i in range(len(self.vertices)-1): pygame.draw.line(self.game.screen,(100,180,255),self.vertices[i],self.vertices[i+1],3) pygame.draw.line(self.game.screen,(100,180,255),self.vertices[-1],pygame.mouse.get_pos(),3) pygame.draw.circle(self.game.screen,(100,180,255),self.vertices[0],15,3) def cancel(self): self.vertices = None # The grab tool class GrabTool(Tool): name = 'Grab' icon = 'grab' toolTip = _("Grab") toolAccelerator = _("g") def __init__(self,gameInstance): Tool.__init__(self,gameInstance) self._current_body = None def handleToolEvent(self,event): # we handle two types of "grab" depending on simulation running or not if event.type == MOUSEBUTTONDOWN: if event.button == 1: # grab the first object at the mouse pointer bodylist = self.game.world.get_bodies_at_pos(event.pos, include_static=False) if bodylist and len(bodylist) > 0: if self.game.world.run_physics: self.game.world.add.mouseJoint(bodylist[0], event.pos) else: self._current_body = bodylist[0] elif event.type == MOUSEBUTTONUP: # let it go if event.button == 1: if self.game.world.run_physics: self.game.world.add.remove_mouseJoint() else: self._current_body = None elif event.type == MOUSEMOTION and event.buttons[0]: # move it around if self.game.world.run_physics: # use box2D mouse motion self.game.world.mouse_move(event.pos) else: # position directly (if we have a current body) if self._current_body is not None: x, y = self.game.world.to_world(event.pos) x /= self.game.world.ppm y /= self.game.world.ppm self._current_body.position = (x, y) def cancel(self): self.game.world.add.remove_mouseJoint() # The joint tool class JointTool(Tool): name = 'Joint' icon = 'joint' toolTip = _("Joint") toolAccelerator = "j" def __init__(self,gameInstance): Tool.__init__(self,gameInstance) self.jb1 = self.jb2 = self.jb1pos = self.jb2pos = None def handleToolEvent(self,event): if event.type == MOUSEBUTTONDOWN: if event.button >= 1: # grab the first body self.jb1pos = event.pos self.jb1 = self.game.world.get_bodies_at_pos(event.pos) self.jb2 = self.jb2pos = None elif event.type == MOUSEBUTTONUP: if event.button == 1: # grab the second body self.jb2pos = event.pos self.jb2 = self.game.world.get_bodies_at_pos(event.pos) # if we have two distinct bodies, add a distance joint! if self.jb1 and self.jb2 and str(self.jb1) != str(self.jb2): self.game.world.add.joint(self.jb1[0],self.jb2[0],self.jb1pos,self.jb2pos) #add joint to ground body #elif self.jb1: # groundBody = self.game.world.world.GetGroundBody() # self.game.world.add.joint(self.jb1[0],groundBody,self.jb1pos,self.jb2pos) # regardless, clean everything up self.jb1 = self.jb2 = self.jb1pos = self.jb2pos = None def draw(self): if self.jb1: pygame.draw.line(self.game.screen,(100,180,255),self.jb1pos,pygame.mouse.get_pos(),3) def cancel(self): self.jb1 = self.jb2 = self.jb1pos = self.jb2pos = None # The pin tool class PinTool(Tool): name = 'Pin' icon = 'pin' toolTip = _("Pin") toolAccelerator = _("o") def __init__(self,gameInstance): Tool.__init__(self,gameInstance) self.jb1 = self.jb1pos = None def handleToolEvent(self,event): if event.type == MOUSEBUTTONDOWN: self.jb1pos = event.pos self.jb1 = self.game.world.get_bodies_at_pos(event.pos) if self.jb1: self.game.world.add.joint(self.jb1[0],self.jb1pos) self.jb1 = self.jb1pos = None def cancel(self): self.jb1 = self.jb1pos = None # The motor tool class MotorTool(Tool): name = 'Motor' icon = 'motor' toolTip = _("Motor") toolAccelerator = _("m") def __init__(self,gameInstance): Tool.__init__(self,gameInstance) self.jb1 = self.jb1pos = None def handleToolEvent(self,event): if event.type == MOUSEBUTTONDOWN: if event.button >= 1: # grab the first body self.jb1pos = event.pos self.jb1 = self.game.world.get_bodies_at_pos(event.pos) if self.jb1: self.game.world.add.motor(self.jb1[0],self.jb1pos) self.jb1 = self.jb1pos = None def cancel(self): self.jb1 = self.jb1pos = None class RollTool(Tool): name = 'Roll' icon = 'roll' toolTip = _("Roll") toolAccelerator = _("r") def __init__(self,gameInstance): Tool.__init__(self,gameInstance) self.jb1 = self.jb1pos = None def handleToolEvent(self,event): if event.type == MOUSEBUTTONDOWN: if event.button == 1: self.jb1pos = event.pos self.jb1 = self.game.world.get_bodies_at_pos(event.pos) if self.jb1: if type(self.jb1[0].userData) == type({}): self.jb1[0].userData['rollMotor'] = {} self.jb1[0].userData['rollMotor']['targetVelocity'] = -10 self.jb1[0].userData['rollMotor']['strength'] = 40 self.jb1 = self.jb1pos = None def cancel(self): self.jb1 = self.jb1pos = None # The destroy tool class DestroyTool(Tool): name = 'Destroy' icon = 'destroy' toolTip = _("Erase") toolAccelerator = _("e") def __init__(self,gameInstance): Tool.__init__(self,gameInstance) self.vertices = None def handleToolEvent(self,event): if pygame.mouse.get_pressed()[0]: if not self.vertices: self.vertices = [] self.vertices.append(pygame.mouse.get_pos()) if len(self.vertices) > 10: self.vertices.pop(0) tokill = self.game.world.get_bodies_at_pos(pygame.mouse.get_pos()) if tokill: jointnode = tokill[0].GetJointList() if jointnode: joint = jointnode.joint self.game.world.world.DestroyJoint(joint) else: self.game.world.world.DestroyBody(tokill[0]) elif event.type == MOUSEBUTTONUP and event.button == 1: self.cancel() def draw(self): # draw the trail if self.vertices: if len(self.vertices) > 1: pygame.draw.lines(self.game.screen,(255,0,0),False,self.vertices,3) def cancel(self): self.vertices = None def getAllTools(): return [MagicPenTool, CircleTool, TriangleTool, BoxTool, PolygonTool, GrabTool, MotorTool, PinTool, JointTool, DestroyTool] allTools = getAllTools()