# # CMSC 491R/691R - Spring 2012 # # Homework #1 - SumoBot simulations using breve and steve # # This file is heavily documented to give you a head start on writing the # steve code that will be required to complete the homework assignment. # # # Include class files required for simulation. The @include directive behaves # much like the #include directive in the C programming language. The default # location for @include files is specified by the BREVE_CLASS_PATH environment # variable. # @include "PhysicalControl.tz" @include "MultiBody.tz" @include "Link.tz" @include "Stationary.tz" # # To run breve from the command line you give it the name of a .tz file. # For example, "breve SumoBot.tz". In that file there must be a line of # code that looks like this: # # Controller CLASS-NAME. # # This tells breve that to run the simulation it should create an object of # type class-name. That object must contain the top level code required to # run the simulation. The line of code below says that the top level class # for the SumoBot simulation is SumoBotArena. # Controller SumoBotArena. # # This class is the driver for the SumoBot simulation. It creates a "floor" # for the SumoBots to exist on, creates the SumoBots, puts then in # appropriate locations, and then does nothing. When the simulation begins, # the SumoBot objects that were created will be run by breve. The line of # code below defines a new class (SumoBotArena) that inherits from the # PhysicalControl class. # PhysicalControl : SumoBotArena { # # Each instance of a class has the variables declared here. The syntax for # variable declarations is as follows: # # VAR-NAME (CLASS-NAME). # # For the SumoBot simulation, we need to SumoBots, called bot1 and bot2, # and some variables used when creating the floor for the robots. # + variables: bot1, bot2 (object). floor, floorShape (object). # # Every class has an init method that is called by breve when an instance # of the class is created, but before the instance is "run" in the # simulation by calling its iterate method (see below for information on # the iterate method). # + to init: # # Create a floor for the bots to move on # floorShape = new Shape. floorShape init-with-cube size (75, 10, 75). floor = new Stationary. floor register with-shape floorShape at-location (0, 0, 0). floor catch-shadows. # # Set some display properties # self enable-lighting. self enable-smooth-drawing. self move-light to (0, 40, 0). self enable-shadow-volumes. self set-background-color to (.4, .6, .9). self set-background-texture-image to (new Image load from "images/clouds.png"). # # Create a bot and put it on the left side of the arena # bot1 = new SumoBot. bot1 move to (-25, 20, 0). # # Create a bot and put it on the right side of the arena, rotating it # to face the first bot. # bot2 = new SumoBot. bot2 move to (25, 20, 0). bot2 rotate around-axis (0, 1, 0) by 3.14. # Position the camera so that we can watch the action self watch item bot2. self set-camera-target to (25, 20, 0). self offset-camera by (65, 65, 65). # These two methods make it possible to zoom the camers in /out with +/-. + to catch-key-0x2B-down: self set-camera-offset to (self get-camera-offset) * 0.95. + to catch-key-0x2D-down: self set-camera-offset to (self get-camera-offset) * 1.05. } # # The Servo class inherits from RevoluteJoint, which is a joint that can # continuously rotate in a plane. RevoluteJoints can be used to model motors # for wheels. # RevoluteJoint : Servo { + variables: target (double). # The target angle for the servo epsilon (double). # The largest acceptable difference between target # and actual angle + to init: target = 0. # Initialize instance variables epsilon = 0.05. # # The next two lines of code define a method named set-target-angle. # It takes a single argument named angle which is of type double. # Given a Servo object named, for example, servo, the following line # of code sets the target angle to -1 radians: # # servo set-target-angle to -1 # # Note that in the method declaration we have "to angle (double)". That # says that there is an argument referred to inside the method as angle # which is of type double, and the value specified for that argument when # the method is called must be preceded by the word to. + to set-target-angle to angle (double): target = angle. # # This method determines whether the servo's current position is close # enough to the target angle. If it is, the method returns 1, otherwise it # returns 0. # + to at-target-angle : difference (double). difference = target - (self get-joint-angle). if (max(difference, -difference) > epsilon) : return 0. else return 1. # # Every class can have an iterate method that defines how instances of that # class behave. When breve runs a simulation, it repeatedly loops over all # objects that have been created and calls their iterate method. # + to iterate: # Test to see if servo is at target angle already if ((self at-target-angle) == 0) : { # If it isn't, we need to move joint if (target > (self get-joint-angle)) : self set-joint-velocity to 1. else self set-joint-velocity to -1. } else # If it is, don't move the joint self set-joint-velocity to 0. } # # My SumoBot has a body, struts, legs, and a lift. Strust are small parts # fixed to the body. Legs are driven by a servo and are attached to struts. # The lift is just a long leg that lifts the body high enough for the other # legs to swing free. # # The @define directives below (that behave like #define directives in # the C programming language) define constants relevant to the size and mass # of these parts. # # For example, the volume of the body is BODY_X * BODY_Y * BODY_Z. Note that # in breve one unit of volume equals one unit of mass, and that joints # connecting parent and child parts can exert a maximum torque that is # proportional to the mass of the child. Therefore, small legs might not # be able to generate enough torque to move a large body. The way to get # around this is to call the set-mass method for the legs. See below for an # example of this. # @define BODY_X 10.00. @define BODY_Y 0.50. @define BODY_Z 5.00. @define BODY_MASS 25.00. @define STRUT_X 0.25. @define STRUT_Y 1.00. @define STRUT_Z 0.25. @define STRUT_MASS 0.05. @define LEG_X 0.25. @define LEG_Y 2.00. @define LEG_Z 0.25. @define LEG_MASS 5.0. @define LIFT_X 0.25. @define LIFT_Y 2.20. @define LIFT_Z 0.25. @define LIFT_MASS 10.00. # # The SumoBot class inherits from the MultiBody class, which makes it possible # to join the parts of the body together in a logically coherent way. # MultiBody : SumoBot { + variables: # Links are body parts, and joints are connections between body parts bodyLink (object). # The main body link (i.e., the chassis) links (list). # A list of other links for struts, legs, etc. joints (list). # A list of joints that will connect the links gait-state (int). # A state variable for gait control + to init: # Variables can be declared at the start of the method. The variables # declared here are local and cease to exist when the method call exits. strutShape, legShape, bodyShape, liftShape (object). gait-state = 0. # Create shapes for all body parts strutShape = (new Cube init-with size (STRUT_X, STRUT_Y, STRUT_Z)). legShape = (new Cube init-with size (LEG_X, LEG_Y, LEG_Z)). bodyShape = (new Cube init-with size (BODY_X, BODY_Y, BODY_Z)). liftShape = (new Cube init-with size (LIFT_X, LIFT_Y, LIFT_Z)). # Set shape masses strutShape set-mass to STRUT_MASS. legShape set-mass to LEG_MASS. bodyShape set-mass to BODY_MASS. liftShape set-mass to LIFT_MASS. # Initialize body/chassis link bodyLink = new Link. bodyLink set-shape to bodyShape. # Allocate and initialize all other links (body parts) links = 3 new Links. links{0} set-shape to strutShape. links{1} set-shape to strutShape. links{2} set-shape to legShape. # Aloocate and initialize joints joints = 3 new Joints. joints{0} = new FixedJoint. joints{1} = new FixedJoint. joints{2} = new Servo. # # The code below actually connects links via joints. A few notes are in # order. When joining two links, one is the parent and one is the child. # The mass of the child determines how much torque the joint can # generate. You must specify the points on the parent and child where # they are attached. Note that when you allocate a cube, the center of # that cube has coordinates (0, 0, 0). Suppose the dimensions of the # cube are (X, Y, Z). Then the center of the upper face of the cube is # (0, Y/2, 0). Keep that in mind when trying to understand the code that # computes attachment points below. # # Join a strut (to-child links{0}) to the chassis (parent bodyLink). joints{0} link parent bodyLink to-child links{0} with-parent-point (-(BODY_X/2) + (STRUT_X/2), -(BODY_Y/2), (BODY_Z/2) - (STRUT_Z/2)) with-child-point (0, STRUT_Y/2, 0). # Join a strut (to-child links{1}) to the chassis (parent bodyLink). joints{1} link parent bodyLink to-child links{1} with-parent-point (-(BODY_X/2) + (STRUT_X/2), -(BODY_Y/2), -(BODY_Z/2) + (STRUT_Z/2)) with-child-point (0, STRUT_Y/2, 0). # Join a leg (to-child links{2}) to a strut (parent links{0}). # Note that joints{2} is a Servo, which inherits from RevoluteJoint. # Therefore, it can rotate in a plain. The "with-normal (0, 0, 1)" # below says that the joint rotates around the Z axis, and lies in the # X/Y plain. joints{2} link parent links{0} to-child links{2} with-normal (0, 0, 1) with-parent-point (0, -STRUT_Y/2, 0) with-child-point (0, LEG_Y/2, 0). # Color the different types of links differently links{0} set-color to (1.0, 0.0, 0.0). links{1} set-color to (1.0, 0.0, 0.0). links{2} set-color to (0.0, 0.0, 1.0). # Register the chassis with breve self register with-link bodyLink. joints set-strength-limit to 1000. # # The iterate method should move the servos so that the SumoBot moves # forward. # + to iterate: if (gait-state == 0) : { joints{2} set-target-angle to 2.0. } if (gait-state == 1) : { joints{2} set-target-angle to -2.0. } if (joints{2} at-target-angle) : { gait-state++. gait-state = gait-state % 2. } }