# Copyright 2008 by Peter Moxhay and Wade Brainerd. # This file is part of Math. # # Math 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. # # Math 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 Math. If not, see . 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) self.symbol = symbol self.area = 0 self.centroid = Vector(0, 0) self.bounds_min = Vector(0, 0) self.bounds_max = Vector(0, 0) self.height = height self.lower_radius = lower_radius self.upper_radius = upper_radius #if upper_radius > lower_radius: self.points = [ Vector(-self.upper_radius, -self.height/2.), Vector(self.upper_radius-self.lower_radius, self.height/2.), \ Vector(self.lower_radius, self.height/2.), Vector(-self.lower_radius, self.height/2.) ] #else: # self.points = [ Vector(-self.upper_radius, -self.height/2.), Vector(self.upper_radius-self.lower_radius, self.height/2.), \ # Vector(self.lower_radius, self.height/2.), Vector(self.upper_radius, self.height/2.) ] self.water_height = 0 self.water_lower_radius = lower_radius self.pos = pos self.volume = self.calculate_volume() self.water_volume = 0 self.animated_water_volume = 0 self.animated_water_height = 0 self.filling_from_faucet = False self.selectable = True self.symbol_visible = True self.rotatable = False self.contains_water = False self.full = False # Get the current bounding rectangle. self.calculate_bounds() def calculate_volume(self): 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 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 d = -3.0 * volume/(math.pi * self.height) solution = self.cubic(a, b, c, d) #if volume <= 0.0: if volume <= 0.0001: return 0.0 else: 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 if a == 0: return self.quadratic(b, c, d)[0], self.quadratic(b, c, d)[1], 0 if d: # (ax^3 + bx^2 + cx + d = 0) a, b, c = b / float(a), c / float(a), d / float(a) t = a / 3.0 p, q = b - 3 * t**2, c - b * t + 2 * t**3 u, v = self.quadratic(q, -(p/3.0)**3) if type(u) == type(0j): # Complex cube root. r, w = polar(u.real, u.imag) y1 = 2 * self.cbrt(r) * cos(w / 3.0) else: # Real root. y1 = self.cbrt(u) + self.cbrt(v) y2, y3 = self.quadratic(y1, p + y1**2) return y1 - t, y2 - t, y3 - t # Solve the squadratic equation. def quadratic(self, a, b, c=None): import math, cmath if a == 0: return -c/float(b), 0 if c: # (ax^2 + bx + c = 0) a, b = b / float(a), c / float(a) t = a / 2.0 r = t**2 - b if r >= 0: # Real roots. y1 = math.sqrt(r) else: # Complex roots. y1 = cmath.sqrt(r) y2 = -y1 return y1 - t, y2 - t # Calculate a cube root. def cbrt(self, x): from math import pow if x >= 0: return pow(x, 1.0/3.0) else: return -pow(abs(x), 1.0/3.0) def draw_ellipse(self, cr, x, y, width, height): cr.save() cr.translate (x + width / 2., y) cr.scale(width / 2., height / 2.) cr.arc(0., 0., 1., 0., 2 * math.pi) cr.restore() def fill_ellipse(self, cr, x, y, width, height): cr.save() cr.translate (x + width / 2., y) cr.scale(width / 2., height / 2.) cr.arc(0., 0., 1., 0., 2 * math.pi) cr.set_source_rgb(0.37, 0.74, 1.0) 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), self.pos + Vector(self.upper_radius, -self.height/2), self.pos + Vector(self.lower_radius, self.height/2), self.pos + Vector(-self.lower_radius, self.height/2) ] # Transform the points. water_points = points[:] 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) # Draw the water, if any. cr.save() # Generate the shape. cr.move_to(water_points[0].x, water_points[0].y) for p in water_points: cr.line_to(p.x, p.y) cr.line_to(water_points[0].x, water_points[0].y) cr.close_path() #Draw the fill. cr.set_source_rgb(0.37, 0.74, 1.0) cr.fill() cr.restore() # Fill in the lower ellipse. cr.save() 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.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 * water_upper_radius, water_upper_radius/2.0) cr.restore() # Draw the upper ellipse 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 - 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 * 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. cr.move_to(points[1].x, points[1].y) cr.line_to(points[2].x, points[2].y) # Draw the outline. if self.selected: cr.set_dash((10, 10), 0) cr.set_source_rgb(0.0, 0.0, 0.0) cr.set_line_width(4.0) cr.stroke() # Generate the shape. cr.move_to(points[0].x, points[0].y) cr.line_to(points[3].x, points[3].y) # Draw the outline. if self.selected: cr.set_dash((10, 10), 0) cr.set_source_rgb(0.0, 0.0, 0.0) cr.set_line_width(4.0) cr.stroke() # Draw the lower ellipse cr.save() if self.selected: cr.set_dash((10, 10), 0) cr.set_source_rgb(0.0, 0.0, 0.0) cr.set_line_width(4.0) self.draw_ellipse(cr, points[3].x, points[3].y, \ 2.0 * self.lower_radius, self.lower_radius/2.0) cr.stroke() cr.restore() # Draw the upper ellipse cr.save() if self.selected: cr.set_dash((10, 10), 0) cr.set_source_rgb(0.0, 0.0, 0.0) cr.set_line_width(4.0) self.draw_ellipse(cr, points[0].x, points[0].y, \ 2.0 * self.upper_radius, self.upper_radius/2.0) cr.stroke() cr.restore() # Draw the symbol (capital letter representing the shape's area). if self.symbol_visible: cr.set_source_rgb(0, 0, 0) cr.set_font_size(50) 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): r = max(self.upper_radius, self.lower_radius) halfsize = Vector(r + 2, self.height/2 + r/2 + 2) * self.scale self.bounds_min = self.pos * self.scale - halfsize self.bounds_max = self.pos * self.scale + halfsize # 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