# tk_softbody.py # still has an instability issues? import tkinter as tk import math WIDTH, HEIGHT = 600, 600 softbodies = [] dragged_body = None drag_offset_x = 0 drag_offset_y = 0 def create_softbody(x, y, radius, points_count): body_points = [] body_springs = [] for i in range(points_count): angle = 2 * math.pi * i / points_count px = x + radius * math.cos(angle) py = y + radius * math.sin(angle) body_points.append({'x': px, 'y': py, 'oldx': px, 'oldy': py, 'pinned': False}) for i in range(points_count): p1 = body_points[i] p2 = body_points[(i + 1) % points_count] body_springs.append({'p1': p1, 'p2': p2, 'length': math.hypot(p2['x']-p1['x'], p2['y']-p1['y'])}) for i in range(points_count // 2): p1 = body_points[i] p2 = body_points[i + points_count // 2] body_springs.append({'p1': p1, 'p2': p2, 'length': math.hypot(p2['x']-p1['x'], p2['y']-p1['y'])}) return { 'points': body_points, 'springs': body_springs, 'radius': radius, 'flip_count': 0, 'last_area': 0, 'original_points': [{'x': px, 'y': py} for px, py in [(p['x'], p['y']) for p in body_points]] } def calculate_area(points): area = 0.0 n = len(points) for i in range(n): x_i = points[i]['x'] y_i = points[i]['y'] x_j = points[(i+1)%n]['x'] y_j = points[(i+1)%n]['y'] area += (x_i * y_j) - (x_j * y_i) return abs(area) / 2.0 def reset_body(body): cx = sum(p['x'] for p in body['points']) / len(body['points']) cy = sum(p['y'] for p in body['points']) / len(body['points']) for i, point in enumerate(body['points']): original = body['original_points'][i] point['x'] = cx + (original['x'] - body['original_points'][0]['x']) point['y'] = cy + (original['y'] - body['original_points'][0]['y']) point['oldx'] = point['x'] point['oldy'] = point['y'] body['flip_count'] = 0 body['last_area'] = calculate_area(body['points']) def update_physics(): for body in softbodies: current_area = calculate_area(body['points']) if body['last_area'] > 0 and current_area < body['last_area'] * 0.3: # Significant area reduction body['flip_count'] += 1 else: body['flip_count'] = max(0, body['flip_count'] - 0.1) body['last_area'] = current_area if body['flip_count'] >= 2: reset_body(body) continue for point in body['points']: if not point['pinned']: vx = (point['x'] - point['oldx']) * 0.99 vy = (point['y'] - point['oldy']) * 0.99 point['oldx'] = point['x'] point['oldy'] = point['y'] point['x'] += vx point['y'] += vy point['y'] += 0.5 if point['x'] < 5: point['x'], point['oldx'] = 5, point['x'] + vx*0.5 if point['x'] > WIDTH-5: point['x'], point['oldx'] = WIDTH-5, point['x'] + vx*0.5 if point['y'] < 5: point['y'], point['oldy'] = 5, point['y'] + vy*0.5 if point['y'] > HEIGHT-5: point['y'], point['oldy'] = HEIGHT-5, point['y'] + vy*0.5 for _ in range(3): for body in softbodies: for spring in body['springs']: p1 = spring['p1'] p2 = spring['p2'] dx = p2['x'] - p1['x'] dy = p2['y'] - p1['y'] distance = math.hypot(dx, dy) diff = 0 if distance: diff = (spring['length'] - distance) / distance if not p1['pinned']: p1['x'] -= dx * diff * 0.5 p1['y'] -= dy * diff * 0.5 if not p2['pinned']: p2['x'] += dx * diff * 0.5 p2['y'] += dy * diff * 0.5 for i, body1 in enumerate(softbodies): for j, body2 in enumerate(softbodies): if i != j: for point in body1['points']: for other_point in body2['points']: dx = other_point['x'] - point['x'] dy = other_point['y'] - point['y'] distance = math.hypot(dx, dy) if distance < 10: if distance == 0: distance = 0.001 force = (10 - distance) / distance * 0.5 point['x'] -= dx * force point['y'] -= dy * force def draw(): canvas.delete("all") for body in softbodies: for spring in body['springs']: p1 = spring['p1'] p2 = spring['p2'] canvas.create_line(p1['x'], p1['y'], p2['x'], p2['y'], fill="#d0d0ff", width=2) for point in body['points']: color = "red" if point['pinned'] else "#4444ff" canvas.create_oval(point['x']-3, point['y']-3, point['x']+3, point['y']+3, fill=color, outline="") def physics_loop(): update_physics() draw() root.after(1, physics_loop) def on_click(event): global dragged_body, drag_offset_x, drag_offset_y for body in softbodies: cx = sum(p['x'] for p in body['points']) / len(body['points']) cy = sum(p['y'] for p in body['points']) / len(body['points']) if math.hypot(event.x - cx, event.y - cy) < body['radius']: dragged_body = body drag_offset_x = event.x - cx drag_offset_y = event.y - cy for point in body['points']: point['pinned'] = True return def on_drag(event): global dragged_body, drag_offset_x, drag_offset_y if dragged_body: cx = sum(p['x'] for p in dragged_body['points']) / len(dragged_body['points']) cy = sum(p['y'] for p in dragged_body['points']) / len(dragged_body['points']) new_cx = event.x - drag_offset_x new_cy = event.y - drag_offset_y dx = new_cx - cx dy = new_cy - cy for point in dragged_body['points']: point['x'] += dx point['y'] += dy point['oldx'] = point['x'] point['oldy'] = point['y'] def on_release(event): global dragged_body if dragged_body: for point in dragged_body['points']: point['pinned'] = False dragged_body = None root = tk.Tk() canvas = tk.Canvas(root, width=WIDTH, height=HEIGHT, bg='white') canvas.pack() softbodies = [ create_softbody(200, 200, 50, 12), create_softbody(400, 400, 70, 16) ] canvas.bind("", on_click) canvas.bind("", on_drag) canvas.bind("", on_release) physics_loop() root.mainloop()