闵可夫斯基差演示

两个多边形相交,其闵可夫斯基差必过原点

python 复制代码
import tkinter as tk
import math
import numpy as np

def idx2char(i):
    """0→A,1→B,2→C... 返回大写字母"""
    return chr(ord('A') + i)

class PolyDrawApp:
    def __init__(self, root):
        self.root = root
        self.root.title("闵可夫斯基差|Y轴向上为正(右上正)")
        self.root.geometry("1000x750")

        self.is_drawing = False
        self.current_points = []
        self.close_threshold = 8
        # polygons: {"points_canvas":[[x,y],...], "item_id":canvas_id, "vertex_text_ids":[]}
        self.polygons = []
        self.max_poly_count = 2

        self.drag_poly_idx = None
        self.drag_start_mx = 0
        self.drag_start_my = 0

        self.minkowski_item = None
        self.minkowski_text_ids = []       # 点标记 A1A2
        self.minkowski_formula_ids = []    # 计算公式文本

        frame_top = tk.Frame(root)
        frame_top.pack(fill=tk.X, padx=5, pady=5)
        self.btn_create = tk.Button(frame_top, text="创建多边形", command=self.start_create)
        self.btn_create.pack(side=tk.LEFT)
        self.btn_clear = tk.Button(frame_top, text="清空全部", command=self.clear_all)
        self.btn_clear.pack(side=tk.LEFT, padx=10)

        self.canvas = tk.Canvas(root, bg="white")
        self.canvas.pack(fill=tk.BOTH, expand=True, padx=5, pady=5)

        self.canvas.bind("<Button-1>", self.on_canvas_click)
        self.canvas.bind("<B1-Motion>", self.on_drag_move)
        self.canvas.bind("<ButtonRelease-1>", self.on_drag_release)

        self.root.update()
        self.redraw_axis()

    def redraw_axis(self):
        self.canvas.delete("axis")
        w = self.canvas.winfo_width()
        h = self.canvas.winfo_height()
        self.ox = w / 2   # 画布原点像素x
        self.oy = h / 2   # 画布原点像素y
        # X轴
        self.canvas.create_line(0, self.oy, w, self.oy, fill="#aaaaaa", dash=(4,2), tags="axis")
        # Y轴
        self.canvas.create_line(self.ox, 0, self.ox, h, fill="#aaaaaa", dash=(4,2), tags="axis")
        # 原点O标记
        self.canvas.create_oval(self.ox-5,self.oy-5,self.ox+5,self.oy+5,fill="black",tags="axis")
        self.canvas.create_text(self.ox+12,self.oy+12,text="O",font=("Consolas",11),tags="axis")
        # X Y箭头标签
        self.canvas.create_text(w-15, self.oy+12, text="+X", font=("Consolas",10), tags="axis")
        self.canvas.create_text(self.ox+12,15, text="+Y", font=("Consolas",10), tags="axis")

    def canvas_to_math(self,cx,cy):
        """画布像素坐标 →数学坐标系(Y向上为正)"""
        mx = cx - self.ox
        my = self.oy - cy
        return mx, my

    def math_to_canvas(self,mx,my):
        """数学坐标 →画布像素"""
        cx = mx + self.ox
        cy = self.oy - my
        return cx, cy

    def start_create(self):
        if len(self.polygons) >= self.max_poly_count:
            print("已经存在2个多边形,不能继续创建!")
            return
        self.is_drawing = True
        self.current_points.clear()
        print("开始绘制多边形,点击顶点,点回第一个顶点闭合")
        self.canvas.delete("temp")

    def on_canvas_click(self, event):
        x, y = event.x, event.y

        hit_idx = self.pick_polygon(x, y)
        if hit_idx is not None and not self.is_drawing:
            self.drag_poly_idx = hit_idx
            self.drag_start_mx = event.x
            self.drag_start_my = event.y
            return

        if not self.is_drawing:
            return

        if len(self.current_points) >= 1:
            sx, sy = self.current_points[0]
            dist = math.hypot(x - sx, y - sy)
            if dist < self.close_threshold and len(self.current_points) >=3:
                self.finish_polygon()
                return

        self.current_points.append([x, y])
        self.canvas.create_oval(x-4, y-4, x+4, y+4, fill="red", outline="red", tags="temp")
        if len(self.current_points) >=2:
            x0,y0 = self.current_points[-2]
            self.canvas.create_line(x0,y0, x,y, fill="blue", width=2, tags="temp")

    def finish_polygon(self):
        pts_canvas = self.current_points.copy()
        item_id = self.canvas.create_polygon(pts_canvas, outline="blue", fill="#cce5ff", width=2)
        poly_index = len(self.polygons)+1   # 1 /2
        text_ids = []
        for i,(px,py) in enumerate(pts_canvas):
            char = idx2char(i)
            label = f"{char}{poly_index}"
            tid = self.canvas.create_text(px+8, py-8, text=label, font=("Consolas",10), fill="#0000bb")
            text_ids.append(tid)

        self.polygons.append({
            "points_canvas": pts_canvas,
            "item_id": item_id,
            "vertex_text_ids": text_ids
        })
        self.is_drawing = False
        self.current_points.clear()
        self.canvas.delete("temp")
        print(f"已完成多边形{poly_index},当前数量:{len(self.polygons)}")
        if len(self.polygons)==2:
            self.draw_minkowski_diff()

    def pick_polygon(self, mx, my):
        for idx,poly in enumerate(self.polygons):
            pts = poly["points_canvas"]
            if self.point_in_polygon(mx,my,pts):
                return idx
        return None

    @staticmethod
    def point_in_polygon(x,y,polygon):
        inside=False
        n=len(polygon)
        for i in range(n):
            j=(i+1)%n
            xi,yi=polygon[i]
            xj,yj=polygon[j]
            intersect=((yi>y)!=(yj>y))
            if intersect:
                x_intersect= ((y-yi)*(xj-xi))/(yj-yi)+xi
                if x<x_intersect:
                    inside=not inside
        return inside

    def on_drag_move(self, event):
        if self.drag_poly_idx is None:
            return
        dx = event.x - self.drag_start_mx
        dy = event.y - self.drag_start_my
        poly = self.polygons[self.drag_poly_idx]
        self.canvas.move(poly["item_id"], dx, dy)
        for tid in poly["vertex_text_ids"]:
            self.canvas.move(tid, dx, dy)
        # 更新画布坐标
        for p in poly["points_canvas"]:
            p[0] += dx
            p[1] += dy
        self.drag_start_mx = event.x
        self.drag_start_my = event.y
        if len(self.polygons)==2:
            self.draw_minkowski_diff()

    def on_drag_release(self, event):
        self.drag_poly_idx = None
        self.drag_start_mx = 0
        self.drag_start_my = 0

    def get_math_poly(self,poly_data):
        """把多边形canvas像素点列表转为数学坐标系点列表(Y向上为正)"""
        pts_canvas = poly_data["points_canvas"]
        math_pts = []
        for cx,cy in pts_canvas:
            mx,my = self.canvas_to_math(cx,cy)
            math_pts.append([mx,my])
        return math_pts

    def minkowski_difference(self, A_math, B_math):
        """A⊖B = a−b,返回 (ia,ib,a_pt,b_pt,diff_pt),全部数学坐标"""
        diff_points = []
        for ia,a in enumerate(A_math):
            for ib,b in enumerate(B_math):
                diff = [a[0]-b[0], a[1]-b[1]]
                diff_points.append((ia, ib, a, b, diff))
        return diff_points

    def draw_minkowski_diff(self):
        if self.minkowski_item is not None:
            self.canvas.delete(self.minkowski_item)
        for tid in self.minkowski_text_ids:
            self.canvas.delete(tid)
        for tid in self.minkowski_formula_ids:
            self.canvas.delete(tid)
        self.minkowski_text_ids.clear()
        self.minkowski_formula_ids.clear()

        A_math = self.get_math_poly(self.polygons[0])
        B_math = self.get_math_poly(self.polygons[1])
        diff_all = self.minkowski_difference(A_math,B_math)
        diff_pts_only = [d[4] for d in diff_all]
        if len(diff_pts_only)<3:
            return

        hull_pts = self.convex_hull(diff_pts_only)
        # 凸包数学坐标转画布
        draw_hull = [self.math_to_canvas(p[0],p[1]) for p in hull_pts]
        self.minkowski_item = self.canvas.create_polygon(draw_hull, outline="#c82423", fill="#ffcccc", width=2)

        for ia, ib, a_pt, b_pt, diff_pt in diff_all:
            # diff_pt 数学坐标
            px_canvas, py_canvas = self.math_to_canvas(diff_pt[0], diff_pt[1])
            charA = idx2char(ia)
            charB = idx2char(ib)
            label_name = f"{charA}1{charB}2"

            ax, ay = round(a_pt[0],1), round(a_pt[1],1)
            bx, by = round(b_pt[0],1), round(b_pt[1],1)
            dx, dy = round(diff_pt[0],1), round(diff_pt[1],1)
            formula_text = f"{label_name}={charA}1-{charB}2=({ax},{ay})-({bx},{by})=({dx},{dy})"

            tid_name = self.canvas.create_text(px_canvas+6, py_canvas-8, text=label_name, font=("Consolas",9), fill="#bb0000")
            tid_formula = self.canvas.create_text(px_canvas+6, py_canvas+8, text=formula_text, font=("Consolas",7), fill="#600000")

            self.minkowski_text_ids.append(tid_name)
            self.minkowski_formula_ids.append(tid_formula)

    @staticmethod
    def convex_hull(points):
        pts = sorted(points)
        lower = []
        for p in pts:
            while len(lower)>=2:
                a = np.array(lower[-2])
                b = np.array(lower[-1])
                c = np.array(p)
                cross = np.cross(b-a, c-a)
                if cross <=0:
                    lower.pop()
                else:
                    break
            lower.append(p)
        upper = []
        for p in reversed(pts):
            while len(upper)>=2:
                a = np.array(upper[-2])
                b = np.array(upper[-1])
                c = np.array(p)
                cross = np.cross(b-a, c-a)
                if cross <=0:
                    upper.pop()
                else:
                    break
            upper.append(p)
        full = lower[:-1] + upper[:-1]
        if not full:
            return pts[:3]
        return full

    def clear_all(self):
        self.canvas.delete(tk.ALL)
        self.polygons.clear()
        self.is_drawing = False
        self.current_points.clear()
        self.minkowski_item = None
        self.minkowski_text_ids.clear()
        self.minkowski_formula_ids.clear()
        self.drag_poly_idx = None
        self.redraw_axis()


if __name__ == "__main__":
    win = tk.Tk()
    app = PolyDrawApp(win)
    win.mainloop()
相关推荐
智购科技无人售货机工厂店1 小时前
2026自动售货机大屏交互设计原则:从信息层级到视觉动线的工程实践~YH
运维·python·单片机·嵌入式硬件·自动化·交互
quantdash_cc1 小时前
量化数据源怎么选:稳定性、实时性和数据完整性到底该如何权衡?
开发语言·python·数据分析·量化交易·股票数据·quantdash
hfywmsj1 小时前
广州餐饮铺位招租数据分析:选址权重模型与租金评估实操
人工智能·python·数据分析·广州餐饮铺位招租
cui_ruicheng2 小时前
FastAPI 应用开发(一):FastAPI、路由参数与交互式 API 文档
python·fastapi
小柯南敲键盘2 小时前
跨境电商翻译工具,AI批量图片及视频字幕翻译
大数据·人工智能·python·音视频
天天喝旺仔2 小时前
Python asyncio 异步编程实战:用协程与事件循环构建高性能网络服务
服务器·网络·python·性能优化·fastapi
2601_962298092 小时前
零基础学大数据分析全栈
hadoop·python·spark·实战·大数据分析
quantdash_cc3 小时前
Python 股票 K 线数据质量校验:字段、缺失值、重复行和价格异常
开发语言·python·数据分析·量化交易·股票数据·quantdash
长空任鸟飞_阿康3 小时前
三、《从零手撸 Agent》 · system prompt 与核心参数:调好你的旋钮
人工智能·python·ai·prompt