def collide_shape_details(left, right, details=True): sleft = shapeOf(left) sright = shapeOf(right) if type(sleft) is Circle and type(sright) is Circle: v = delta(sleft.pos, sright.pos) if hypot(*v) < sleft.radius + sright.radius: if details: c = sright.containsPoint(sleft.pos) or sleft.containsPoint(sright.pos) return delta(v, mag=(-1 if c else 1)) else: return True return False
def _bounce(self, cv): "Bounce sprite from the edge of its canvas" vx, vy = self.vel w, h = cv.size b = self.bounce update = 0 if self.bounceType == 0: x, y = delta(self.rect.center, cv.rect.topleft) r = self.radius if b & HORIZONTAL and (x < r and vx < 0 or x > w - r and vx > 0): self.vel = -vx, vy update += HORIZONTAL if b & VERTICAL and (y < r and vy < 0 or y > h - r and vy > 0): self.vel = vx, -vy update += VERTICAL else: r = self.rect if b & HORIZONTAL and (r.left < 0 and vx < 0 or r.right >= w and vx > 0): self.vel = -vx, vy update += HORIZONTAL if b & VERTICAL and (r.top < 0 and vy < 0 or r.bottom >= h and vy > 0): self.vel = vx, -vy update += VERTICAL return update
def ondraw(self): "Update robot sprite each frame" if not self.active: raise InactiveError() # Target wheel speed... sk = self.sketch v = self.maxSpeed * sk.width v1, v2 = self._motors v1 *= v v2 *= v # Angular speed and turning radius... w = (v1 - v2) / (2 * self.radius) self.spin = w / DEG if w: R = (v1 + v2) / (2 * w) v = w * R else: v = v1 # Acceleration... v = vec2d(v, self.angle) a = delta(v, self.vel) if hypot(*a) > 0.05: self.acc = delta(a, mag=0.05) else: self.vel = v self.acc = 0, 0 # Adjust wheel speed... p = self.power self.costumeTime = 0 if p == 0 else round(36 / (1 + 5 * p)) # Update position and angle... super().ondraw() # Update sensors if requested... if self._updateSensors: try: self._checkDown() self._checkFront() self._drawLEDs() self._updateSensors = False except: logError() self._startup = False
def ondraw(self): # Calculate electric force sk = self.sketch dr = delta(self.pos, sk["blue"].pos) r = hypot(*dr) / sk.scale / 100 F = delta(dr, mag=8.99e-3 * sk.q1 * sk.q2 / (r * r)) # Add electric plus gravitational forces F = F[0], F[1] + sk.mass * 9.81e-3 # Tangential acceleration s = sk["string"] u = s.u t = 1 / sk.frameRate F = (F[0] * u[1] - F[1] * u[0]) / (sk.mass / 1000) * (sk.scale / 100) / t**2 ax, ay = F * u[1], -F * u[0] # Kinematics v1x, v1y = tuple(0.95 * v for v in self.vel) v2x = v1x + ax * t v2y = v1y + ay * t self.vel = v2x, v2y x, y = self.pos x += (v1x + v2x) * t / 2 y += (v1y + v2y) * t / 2 x, y = delta((x, y), s.pos, 20 * sk.scale) self.pos = s.pos[0] + x, s.pos[1] + y s.__init__(s.pos, self.pos) # Protractor if s.u[1] > 0: a = round(2 * degrees(asin(s.u[0]))) / 2 a = "Angle = {:.1f}°".format(abs(a)) else: a = "Angle > 90°" sk["angle"].config(data=a)
def _knobDrag(knob, ev): "Handle drag events on the slider's 'knob' object" slider = knob.canvas if knob._dragRel is None: knob._dragRel = delta(knob.rect.center, ev.pos) if slider._lastButton in slider.allowButton: x = pygame.event.Event(pygame.USEREVENT, pos=sigma(ev.pos, knob._dragRel)) v = slider._val slider.val = slider._eventValue(x) x = slider.val setattr(ev, "target", slider) if x != v: setattr(ev, "method", DRAG) slider.bubble("onchange", ev)
def dragDrop(gr, ev): "Drag a Graphic, possibly into a different canvas" gr.hoverable = False pos = sigma(gr.pos, ev.rel) drop = False try: cv = [gr for gr in gr.sketch.objectAt(ev.pos).path if getattr(gr, "allowDrop", False)][0] if cv is not gr.canvas: pos = sigma(pos, delta(gr.canvas.rect.topleft, cv.rect.topleft)) drop = True gr.setCanvas(cv) except: pass gr.config(pos=pos, hoverable=True) if drop: gr.bubble("ondrop", ev)
def ondraw(self): # Calculate electric force sk = self.sketch dr = delta(self.pos, sk["blue"].pos) r = hypot(*dr) / sk.scale / 100 F = delta(dr, mag = 8.99e-3 * sk.q1 * sk.q2 / (r * r)) # Add electric plus gravitational forces F = F[0], F[1] + sk.mass * 9.81e-3 # Tangential acceleration s = sk["string"] u = s.u t = 1 / sk.frameRate F = (F[0] * u[1] - F[1] * u[0]) / (sk.mass / 1000) * (sk.scale / 100) / t ** 2 ax, ay = F * u[1], -F * u[0] # Kinematics v1x, v1y = tuple(0.95 * v for v in self.vel) v2x = v1x + ax * t v2y = v1y + ay * t self.vel = v2x, v2y x, y = self.pos x += (v1x + v2x) * t / 2 y += (v1y + v2y) * t / 2 x, y = delta((x,y), s.pos, 20 * sk.scale) self.pos = s.pos[0] + x, s.pos[1] + y s.__init__(s.pos, self.pos) # Protractor if s.u[1] > 0: a = round(2 * degrees(asin(s.u[0]))) / 2 a = "Angle = {:.1f}°".format(abs(a)) else: a = "Angle > 90°" sk["angle"].config(data=a)
def intersect(self, other): "Find the intersection(s) of two circles as list of points" R = self.radius r = other.radius d = dist(self.pos, other.pos) if d > r + R or d == 0 or d < abs(r - R): return [] r2 = r * r x = (d*d + r2 - R*R) / (2*d) ux, uy = delta(self.pos, other.pos, 1) x0, y0 = other.pos x0 += x * ux y0 += x * uy if x < r: y = sqrt(r2 - x*x) return [(x0 - y * uy, y0 + y * ux), (x0 + y * uy, y0 - y * ux)] else: return [(x0, y0)]
def __init__(self, ship): "Fire a new missile from the ship" super().__init__(self.original) # Initial position of missile u = vec2d(1.3 * ship.radius, ship.angle) pos = ship.pos[0] + u[0], ship.pos[1] + u[1] # Initial velocity of missile u = delta(u, mag=ship.sketch.height / 135) vel = ship.vel[0] + u[0], ship.vel[1] + u[1] self.config(width=ship.width / 4, pos=pos, vel=vel, spin=ship.spin, angle=ship.angle)
def _checkFront(self): "Update the front color sensor" # Sensor info sw = self.sensorWidth res = 0.5 * self.sensorResolution pos = delta(self.pos, vec2d(-self.radius, self.angle)) # Distance from sensor to edge of sketch obj = prox = None sk = self.sketch if sk.weight: prox = _distToWall(pos, self.angle, self.sensorWidth, *sk.size) if prox: obj = sk # Find closest object within "sensor cone" for gr in self.sensorObjects(sk): if gr is not self and gr.avgColor and hasattr(gr, "rect"): r = gr.radius view = subtend(pos, gr.rect.center, r, None if prox is None else prox + r) if view: # Object may be closer than the current closest object sep, direct, half = view if not res or half > res: # Object size meets sensor resolution threshold beta = abs(angleDifference(self.angle, direct)) - sw if beta < half or sep < r: # Object is in sensor cone pr = sep - r if beta > 0: # CLOSEST point is NOT in sensor cone dr = r + sep * (cos(half * DEG) - 1) pr += dr * (beta / half)**2 if prox is None or pr < prox: # Object is closest (so far) prox = pr obj = gr # Save data self.closestObject = obj c = rgba(sk.border if obj is sk else obj.avgColor if obj else (0, 0, 0)) self.sensorFront = noise(divAlpha(c), self.sensorNoise, 255) self.proximity = None if prox is None else max(0, round(prox))
def _checkFront(self): "Update the front color sensor" # Sensor info sw = self.sensorWidth res = 0.5 * self.sensorResolution pos = delta(self.pos, vec2d(-self.radius, self.angle)) # Distance from sensor to edge of sketch obj = prox = None sk = self.sketch if sk.weight: prox = _distToWall(pos, self.angle, self.sensorWidth, *sk.size) if prox: obj = sk # Find closest object within "sensor cone" for gr in self.sensorObjects(sk): if gr is not self and gr.avgColor and hasattr(gr, "rect"): r = gr.radius view = subtend(pos, gr.rect.center, r, None if prox is None else prox + r) if view: # Object may be closer than the current closest object sep, direct, half = view if not res or half > res: # Object size meets sensor resolution threshold beta = abs(angleDifference(self.angle, direct)) - sw if beta < half or sep < r: # Object is in sensor cone pr = sep - r if beta > 0: # CLOSEST point is NOT in sensor cone dr = r + sep * (cos(half * DEG) - 1) pr += dr * (beta / half) ** 2 if prox is None or pr < prox: # Object is closest (so far) prox = pr obj = gr # Save data self.closestObject = obj c = rgba(sk.border if obj is sk else obj.avgColor if obj else (0,0,0)) self.sensorFront = noise(divAlpha(c), self.sensorNoise, 255) self.proximity = None if prox is None else max(0, round(prox))
def _bounce(self, cv): "Bounce sprite from the edge of its canvas" vx, vy = self.vel w, h = cv.size b = self.bounce update = 0 if self.bounceType == 0: x, y = delta(self.rect.center, cv.rect.topleft) r = self.radius if b & HORIZONTAL and (x < r and vx < 0 or x > w-r and vx > 0): self.vel = -vx, vy update += HORIZONTAL if b & VERTICAL and (y < r and vy < 0 or y > h-r and vy > 0): self.vel = vx, -vy update += VERTICAL else: r = self.rect if b & HORIZONTAL and (r.left < 0 and vx < 0 or r.right >= w and vx > 0): self.vel = -vx, vy update += HORIZONTAL if b & VERTICAL and (r.top < 0 and vy < 0 or r.bottom >= h and vy > 0): self.vel = vx, -vy update += VERTICAL return update
def between(tail, tip, width=0.1, head=0.1, flatness=2): r, a = polar2d(*delta(tip, tail)) return Arrow(r, width, head, flatness).config(pos=tip, angle=a)
def toward(self, pos, mag=None): "Return a vector directed toward the specified position" if mag is None: mag = hypot(*self.vel) return delta(pos, self.pos, mag)
def px(self, *pt): return delta(self._px(pt), self._scroll)
def contains(self, pos): "Determine if the point is within the shape, accounting for canvas offset" cv = self.canvas cvPos = delta(pos, cv.rect.topleft) if cv else pos return self.containsPoint(cvPos)
def between(tail, tip, width=0.1, head=0.1, flatness=2): r, a = polar2d(*delta(tip, tail)) return ArrowSprite(r, width, head, flatness).config(pos=tip, angle=a)