From efc9bf97c82b53d8acd3445bcbac83266a0d4b87 Mon Sep 17 00:00:00 2001 From: pmoxhay Date: Wed, 30 Dec 2009 23:04:08 +0000 Subject: Animation added! --- diff --git a/compare3lesson.py b/compare3lesson.py index 93c54d7..ed16676 100644 --- a/compare3lesson.py +++ b/compare3lesson.py @@ -86,17 +86,25 @@ class Compare3Lesson(ObjectArea): self.nextarrow = None self.pose_problem_stage1() - + + self.solution_pending = False + #TODO- Put this code in the ShapeObjects themselves. def check_problem_solved(self): + #print "Compare3Lesson: check_problem_solved called" if self.stage == 1: + if self.solution_pending: + if not self.is_animating(): + #print "Stage 1 Complete" + self.solution_pending = False + self.finish_problem_stage1() + self.pose_problem_stage2() + #print " check_problem_solved calling problem_solved_stage1" - if self.problem_solved_stage1(): - #print "Stage 1 Complete" - self.finish_problem_stage1() - self.pose_problem_stage2() - + elif self.problem_solved_stage1(): + self.solution_pending = True + def register_error(self): #print '\a' self.n_errors += 1 @@ -168,7 +176,7 @@ class Compare3Lesson(ObjectArea): #print "self.recently_used = ", self.recently_used # Uncomment this to choose a particular problem type. - #self.problem_type = 'cutting' + self.problem_type = 'volume' if self.problem_type == 'length': self.problem = LengthProblem(self, self.color_scheme, (self.alphabetical_letter1, self.alphabetical_letter2) ) @@ -251,13 +259,6 @@ class Compare3Lesson(ObjectArea): self.problem.shape2.draggable = False elif self.problem_type == 'volume': self.problem.finish_problem_stage1() - self.problem.shape1.calculate_bounds() - self.problem.shape2.calculate_bounds() - # Make the ShapeObjects inactive. - self.problem.shape1.selected = False - self.problem.shape1.draggable = False - self.problem.shape2.selected = False - self.problem.shape2.draggable = False def pose_problem_stage2(self): #print "pose_problem_stage2 called" diff --git a/faucetobject.py b/faucetobject.py index 8abf39a..8921d07 100644 --- a/faucetobject.py +++ b/faucetobject.py @@ -23,6 +23,10 @@ FAUCET_SVG = rsvg.Handle('faucet.svg') class FaucetObject(Object): """Faucet for filling volumes with water.""" + STREAM_WIDTH = 30 + STREAM_X = 185 + STREAM_Y = 235 + def __init__(self, pos): Object.__init__(self) diff --git a/objectarea.py b/objectarea.py index f2c7640..0b53fad 100644 --- a/objectarea.py +++ b/objectarea.py @@ -15,7 +15,7 @@ # along with Math. If not, see . from vector import Vector -import gtk, math +import gtk, gobject, math GRID_VISIBLE = False @@ -53,6 +53,8 @@ class Object: self.container = None + self.animating = False + self.GRID_SIZE = 50 self.DRAGGING_RECT_WIDTH = 24*self.GRID_SIZE self.DRAGGING_RECT_HEIGHT = 16*self.GRID_SIZE @@ -103,6 +105,17 @@ class Object: self.scale = scale self.queue_draw() + def start_animating(self): + self.container.start_animating_object(self) + self.animating = True + + def stop_animating(self): + self.container.stop_animating_object(self) + self.animating = False + + def animate(self): + pass + def on_key(self, event): pass @@ -119,6 +132,9 @@ class ObjectArea(gtk.Layout): self.objects = [] + # Sub-list of objects that are currently animating. + self.animating_objects = [] + # Object currently selected. self.selected_object = None @@ -149,6 +165,8 @@ class ObjectArea(gtk.Layout): self.modify_bg(gtk.STATE_NORMAL, self.get_colormap().alloc_color('#ffffff')) self.connect('expose-event', self.expose_cb) + self.timer_id = None + def check_problem_solved(self): pass @@ -197,6 +215,30 @@ class ObjectArea(gtk.Layout): old_selected_object.queue_draw() object.queue_draw() + def start_animating_object(self, object): + if self.animating_objects.count(object) == 0: + self.animating_objects.append(object) + + if self.timer_id is None: + self.timer_id = gobject.timeout_add(50, self.timer_cb) + self.window.set_cursor(gtk.gdk.Cursor(gtk.gdk.WATCH)) + + def stop_animating_object(self, object): + self.animating_objects.remove(object) + + if len(self.animating_objects) == 0: + if self.timer_id is not None: + gobject.source_remove(self.timer_id) + self.timer_id = None + + self.window.set_cursor(gtk.gdk.Cursor(self.cursor)) + + # Give a chance to solve the problem when animation finishes. + self.check_problem_solved() + + def is_animating(self): + return len(self.animating_objects) > 0 + def configure_dragging_area(self, grid_size, dragging_rect_width, dragging_rect_height, radial_grid_size): self.GRID_SIZE = grid_size self.DRAGGING_RECT_WIDTH = dragging_rect_width * self.GRID_SIZE @@ -225,6 +267,10 @@ class ObjectArea(gtk.Layout): self.queued_cursor = cursor def on_mouse(self, widget, event): + # Wait for animation to finish before accepting input. + if self.is_animating(): + return + # Any mouse movement over the canvas grabs focus, so we can receive keyboard events. if not widget.is_focus(): widget.grab_focus() @@ -263,6 +309,10 @@ class ObjectArea(gtk.Layout): gtk.gdk.event_request_motions(event) def on_key(self, widget, event): + # Wait for animation to finish before accepting input. + if self.is_animating(): + return + key_name = gtk.gdk.keyval_name(event.keyval) # Useful print for determining the names of keys. @@ -359,5 +409,9 @@ class ObjectArea(gtk.Layout): o.draw(cr) cr.restore() + def timer_cb(self): + for o in self.animating_objects: + o.animate() + return True diff --git a/volumeobject.py b/volumeobject.py index 90c43aa..de86f5d 100644 --- a/volumeobject.py +++ b/volumeobject.py @@ -16,12 +16,15 @@ from objectarea import Object from vector import Vector from movableobject import MovableObject +from faucetobject import FaucetObject import gtk, math class VolumeObject(MovableObject): """Quasi three-dimensional container object.""" + FILL_RATE = 50000 + def __init__(self, symbol, pos, height = 400, lower_radius = 50, upper_radius = 100): MovableObject.__init__(self) @@ -45,11 +48,15 @@ class VolumeObject(MovableObject): self.water_height = 0 self.water_lower_radius = lower_radius - self.water_upper_radius = lower_radius self.pos = pos self.volume = self.calculate_volume() - self.water_volume = self.calculate_water_volume() + self.water_volume = 0 + + self.animated_water_volume = 0 + self.animated_water_height = 0 + + self.filling_from_faucet = False self.selectable = True @@ -66,11 +73,11 @@ class VolumeObject(MovableObject): return (math.pi * self.height / 3.0) * \ (self.lower_radius * self.lower_radius + self.lower_radius * self.upper_radius + self.upper_radius * self.upper_radius) - def calculate_water_volume(self): - return (math.pi * self.water_height / 3.0) * \ - (self.lower_radius * self.lower_radius + self.lower_radius * self.water_upper_radius + self.water_upper_radius * self.water_upper_radius) - - def fill_to_given_volume(self, volume): + #def calculate_water_volume(self): + # return (math.pi * self.water_height / 3.0) * \ + # (self.lower_radius * self.lower_radius + self.lower_radius * self.water_upper_radius + self.water_upper_radius * self.water_upper_radius) + + def calculate_water_height(self, volume): a = (self.upper_radius - self.lower_radius)**2 b = 3.0 * self.lower_radius * (self.upper_radius - self.lower_radius) c = 3.0 * self.lower_radius ** 2 @@ -80,14 +87,18 @@ class VolumeObject(MovableObject): #if volume <= 0.0: if volume <= 0.0001: - self.water_height = 0.0 - self.water_upper_radius = self.lower_radius + return 0.0 else: - self.water_height = solution[0] * self.height - self.water_upper_radius = self.lower_radius + self.water_height * (self.upper_radius - self.lower_radius)/self.height - - self.water_volume = self.calculate_water_volume() + return solution[0] * self.height + def fill_to_given_volume(self, volume): + self.water_height = self.calculate_water_height(volume) + + #self.water_volume = self.calculate_water_volume() + self.water_volume = volume + + self.start_animating() + # Solve the cubic equation. def cubic(self, a, b, c, d=None): from math import cos @@ -160,12 +171,33 @@ class VolumeObject(MovableObject): cr.fill() cr.restore(); + def animate(self): + if self.animated_water_volume < self.water_volume: + self.animated_water_volume = min(self.animated_water_volume + VolumeObject.FILL_RATE, self.water_volume) + self.animated_water_height = self.calculate_water_height(self.animated_water_volume) + self.calculate_bounds() + self.queue_draw() + + elif self.animated_water_volume > self.water_volume: + self.animated_water_volume = max(self.animated_water_volume - VolumeObject.FILL_RATE, self.water_volume) + self.animated_water_height = self.calculate_water_height(self.animated_water_volume) + self.calculate_bounds() + self.queue_draw() + + else: + self.calculate_bounds() + self.queue_draw() + self.stop_animating() + self.filling_from_faucet = False + def draw(self, cr): + cr.save() + #cr.set_source_rgb(1, 0, 1) #self_bounds_mn, self_bounds_mx = self.get_bounds() #cr.rectangle(self_bounds_mn.x, self_bounds_mn.y, self_bounds_mx.x - self_bounds_mn.x, self_bounds_mx.y - self_bounds_mn.y) #cr.fill() - + cr.scale(self.scale, self.scale) points = [ self.pos + Vector(-self.upper_radius, -self.height/2), @@ -176,11 +208,11 @@ class VolumeObject(MovableObject): # Transform the points. water_points = points[:] - water_points[0] = Vector(water_points[0].x + (self.upper_radius - self.lower_radius) * (self.height - self.water_height)/float(self.height), water_points[0].y - self.height + self.water_height) - water_points[1] = Vector(water_points[1].x - (self.upper_radius - self.lower_radius) * (self.height - self.water_height)/float(self.height), water_points[1].y - self.height + self.water_height) - - water_points[0] = Vector(water_points[0].x, water_points[3].y - self.water_height) - water_points[1] = Vector(water_points[1].x, water_points[2].y - self.water_height) + water_points[0] = Vector(water_points[0].x + (self.upper_radius - self.lower_radius) * (self.height - self.animated_water_height)/float(self.height), water_points[0].y - self.height + self.animated_water_height) + water_points[1] = Vector(water_points[1].x - (self.upper_radius - self.lower_radius) * (self.height - self.animated_water_height)/float(self.height), water_points[1].y - self.height + self.animated_water_height) + + water_points[0] = Vector(water_points[0].x, water_points[3].y - self.animated_water_height) + water_points[1] = Vector(water_points[1].x, water_points[2].y - self.animated_water_height) water_points[2] = Vector(water_points[2].x, water_points[2].y) water_points[3] = Vector(water_points[3].x, water_points[3].y) @@ -200,35 +232,49 @@ class VolumeObject(MovableObject): # Fill in the lower ellipse. cr.save() - if not self.water_height == 0: + if not self.animated_water_height == 0: #self.fill_ellipse(cr, self.pos.x - self.lower_radius, self.pos.y + self.height/2.0 - self.lower_radius/4.0, \ # 2.0 * self.lower_radius, self.lower_radius/2.0) self.fill_ellipse(cr, points[3].x, points[3].y, \ 2.0 * self.lower_radius, self.lower_radius/2.0) cr.restore() + water_upper_radius = self.lower_radius + self.animated_water_height * (self.upper_radius - self.lower_radius)/self.height + # Fill in the upper ellipse. cr.save() - if not self.water_height == 0: - #self.fill_ellipse(cr, self.pos.x - self.water_upper_radius, self.pos.y + self.height/2.0 - self.water_height - self.water_upper_radius/4.0, \ - #2.0 * self.water_upper_radius, self.water_upper_radius/2.0) + if not self.animated_water_height == 0: + #self.fill_ellipse(cr, self.pos.x - water_upper_radius, self.pos.y + self.height/2.0 - self.animated_water_height - water_upper_radius/4.0, \ + #2.0 * water_upper_radius, water_upper_radius/2.0) self.fill_ellipse(cr, water_points[0].x, water_points[0].y, \ - 2.0 * self.water_upper_radius, self.water_upper_radius/2.0) + 2.0 * water_upper_radius, water_upper_radius/2.0) cr.restore() # Draw the upper ellipse - if not self.water_height == 0: + if not self.animated_water_height == 0: + cr.save() cr.set_source_rgb(0.0, 0.0, 1.0) cr.set_line_width(4.0) - #self.draw_ellipse(cr, self.pos.x - self.water_upper_radius, \ - # self.pos.y + self.height/2.0 - self.water_height - self.water_upper_radius/4.0, - # 2.0 * self.water_upper_radius, self.water_upper_radius/2.0) + #self.draw_ellipse(cr, self.pos.x - water_upper_radius, \ + # self.pos.y + self.height/2.0 - self.animated_water_height - water_upper_radius/4.0, + # 2.0 * water_upper_radius, water_upper_radius/2.0) self.draw_ellipse(cr, water_points[0].x, water_points[0].y, \ - 2.0 * self.water_upper_radius, self.water_upper_radius/2.0) + 2.0 * water_upper_radius, water_upper_radius/2.0) cr.stroke() cr.restore() + + # Draw the faucet filling. + if self.filling_from_faucet: + stream_y = self.container.problem.faucet_object.pos.y + FaucetObject.STREAM_Y + cr.rectangle(self.container.problem.faucet_object.pos.x + FaucetObject.STREAM_X, + stream_y, + FaucetObject.STREAM_WIDTH, self.pos.y + self.height/2 - self.animated_water_height - stream_y) + #Draw the fill. + cr.set_source_rgb(0.37, 0.74, 1.0) + cr.fill() + # Done drawing the shape of the water. # Now draw the shape of the container. # Generate the shape. @@ -282,6 +328,8 @@ class VolumeObject(MovableObject): x_bearing, y_bearing, width, height = cr.text_extents(self.symbol)[:4] cr.move_to(self.pos.x - x_bearing - width/2, self.pos.y - y_bearing - height/2) cr.show_text(self.symbol) + + cr.restore() def calculate_bounds(self): # Get the current width and height of the bounding rectangle. @@ -297,6 +345,11 @@ class VolumeObject(MovableObject): self.bounds_min -= Vector(2, 2 + self.upper_radius/4.) self.bounds_max += Vector(2, 2 + self.lower_radius/4.) + # Include the stream when animating. + if self.filling_from_faucet: + stream_y = self.container.problem.faucet_object.pos.y + FaucetObject.STREAM_Y + self.bounds_min.y = stream_y + def get_bounds(self): return self.bounds_min, self.bounds_max diff --git a/volumeproblem.py b/volumeproblem.py index 7b9858a..b5a5318 100644 --- a/volumeproblem.py +++ b/volumeproblem.py @@ -294,7 +294,7 @@ class VolumeProblem(Problem): def check_problem_solved(self): #print "Volume Problem: check_problem_solved called" - + if self.shape1.pos.approx_equal(self.under_faucet_position, tolerance=100): #print " first volume is under the faucet" if not self.shape1.full and not self.shape2.full: @@ -304,7 +304,8 @@ class VolumeProblem(Problem): self.shape1.contains_water = True self.shape1.full = True self.shape1.water_height = self.shape1.height - self.shape1.fill_to_given_volume(self.shape1.volume) + self.shape1.filling_from_faucet = True + self.shape1.fill_to_given_volume(self.shape1.volume) elif self.shape2.pos.approx_equal(self.under_faucet_position, tolerance=100): #print " second volume is under the faucet" if not self.shape1.full and not self.shape2.full: @@ -314,6 +315,7 @@ class VolumeProblem(Problem): self.shape2.contains_water = True self.shape2.full = True self.shape2.water_height = self.shape2.height + self.shape2.filling_from_faucet = True self.shape2.fill_to_given_volume(self.shape2.volume) if self.shape1.pos.approx_equal(self.shape2.pos, tolerance=150): @@ -340,15 +342,13 @@ class VolumeProblem(Problem): #print " volume2 =", self.shape2.water_volume if self.shape2.pos.x > 250: - self.shape1.pos = Vector(self.shape2.pos.x - 250, self.shape2.pos.y) + self.shape1.move(Vector(self.shape2.pos.x - 250, self.shape2.pos.y)) else: - self.shape1.pos = Vector(self.shape2.pos.x + 250, self.shape2.pos.y) + self.shape1.move(Vector(self.shape2.pos.x + 250, self.shape2.pos.y)) #self.shape1.calculate_bounds() #self.shape2.calculate_bounds() - self.place_objects_in_final_positions() - return True elif self.shape2.full and not self.shape1.full: @@ -374,12 +374,10 @@ class VolumeProblem(Problem): #print " After: volume2 =", self.shape2.water_volume #print " volume1 =", self.shape1.water_volume - #if self.shape1.pos.x > 250: - # self.shape2.pos = Vector(self.shape1.pos.x - 250, self.shape1.pos.y) - #else: - # self.shape2.pos = Vector(self.shape1.pos.x + 250, self.shape1.pos.y) - - self.place_objects_in_final_positions() + if self.shape1.pos.x > 250: + self.shape2.move(Vector(self.shape1.pos.x - 250, self.shape1.pos.y)) + else: + self.shape2.move(Vector(self.shape1.pos.x + 250, self.shape1.pos.y)) return True @@ -388,8 +386,16 @@ class VolumeProblem(Problem): def finish_problem_stage1(self): #print "VolumeProblem: finish_problem_stage1 called" - pass + self.place_objects_in_final_positions() + self.shape1.calculate_bounds() + self.shape2.calculate_bounds() + # Make ShapeObjects inactive. + self.shape1.selected = False + self.shape1.draggable = False + self.shape2.selected = False + self.shape2.draggable = False + def place_objects_in_final_positions(self): #print "VolumeProblem: place_objects_in_final_positions called" -- cgit v0.9.1