判断平面上一点是否在三角形内 Inside a triangle or not

本文介绍了一种通过面积法、射线法和矢量法来判断平面内给定点K是否位于已知三角形ABC内部的算法。详细解释了三种方法的原理,并提供了实现代码。

平面内有一个三角形,三个顶点ABC的坐标已经给出。现在给出一个坐标点K,要求判断这个点是在三角形内部还是外部。注意在三角形的三边上也算是在内部。

方法:

1、面积法

点K和三角形三顶点中的任意两点组合成三个不同的三角形,KAB、KBC、KAC,判断这三个三角形的面积之和是否等原三角的面积。如果相等的话,说明是在内部。

2、射线法

从K作水平向左射线,如果K在三角形内部,那么这条射线与三角形的交点个数必为奇数;如果P在外部,则交点个数必为偶数。

3、矢量法

由A点逆时针沿着三角形的边走一圈回到A,这一过程中若点K始终在前进方向的左侧,那么K在内部;否则是在外部。


方法一的实现:

根据三角形三点坐标求面积的公式:abs((x1*(y2-y3) + x2*(y3-y1)+ x3*(y1-y2))/2.0)

float area(int x1, int y1, int x2, int y2, int x3, int y3)
{
   return abs((x1*(y2-y3) + x2*(y3-y1)+ x3*(y1-y2))/2.0);
}
 
/* 根据三角形的三点坐标和点K的坐标判断 
   A(x1, y1), B(x2, y2) and C(x3, y3) */
bool isInside(int x1, int y1, int x2, int y2, int x3, int y3, int x, int y)
{   
   /* Calculate area of triangle ABC */
   float A = area (x1, y1, x2, y2, x3, y3);
 
   /* Calculate area of triangle PBC */  
   float A1 = area (x, y, x2, y2, x3, y3);
 
   /* Calculate area of triangle PAC */  
   float A2 = area (x1, y1, x, y, x3, y3);
 
   /* Calculate area of triangle PAB */   
   float A3 = area (x1, y1, x2, y2, x, y);
   
   return (A == A1 + A2 + A3);
}


7-12 线形系列4-凸四边形的计算 分数 70 作者 蔡轲 单位 南昌航空大学 用户输入一组选项和数据,进行与四边形有关的计算。 以下四边形顶的坐标要求按顺序依次输入,连续输入的两个顶是相邻顶,第一个和最后一个输入的顶相邻。 选项包括: 1:输入四个坐标,判断是否是四边形、平行四边形,判断结果输出true/false,结果之间以一个英文空格符分隔。 2:输入四个坐标,判断是否是菱形、矩形、正方形,判断结果输出true/false,结果之间以一个英文空格符分隔。 若四个坐标无法构成四边形,输出"not a quadrilateral" 3:输入四个坐标,判断是凹四边形(false)还是凸四边形(true),输出四边形周长、面积,结果之间以一个英文空格符分隔。 若四个坐标无法构成四边形,输出"not a quadrilateral" 4:输入六个坐标,前两个构成一条直线,后四个构成一个四边形或三角形,输出直线与四边形(也可能是三角形)相交的交数量。如果交有两个,再按面积从小到大输出四边形(或三角形)被直线分割成两部分的面积(不换行)。若直线与四边形或三角形的一条边线重合,输出"The line is coincide with one of the lines"。若后四个不符合四边形或三角形的输入,输出"not a quadrilateral or triangle"。 后四个构成三角形的情况:假设三角形一条边上两个端分别是x、y,边线中间有一点z,另一顶s: 1)符合要求的输入:顶重复或者z与xy都相邻,如x x y s、x z y s、x y x s、s x y y。此时去除冗余,保留一个x、一个y。 2) 不符合要求的输入:z 不与xy都相邻,如z x y s、x z s y、x s z y 5:输入五个坐标,输出第一个是否在后四个所构成的四边形(限定为凸四边形,不考虑凹四边形)或三角形(判定方法见选项4)的内部(若是四边形输出in the quadrilateral/outof the quadrilateral,若是三角形输出in the triangle/outof the triangle)。如果在多边形的某条边上,输出"on the triangle或者on the quadrilateral"。若后四个不符合四边形或三角形,输出"not a quadrilateral or triangle"。 输入格式: 基本格式:选项+":"+坐标x+","+坐标y+" "+坐标x+","+坐标y。的x、y坐标之间以英文","分隔,之间以一个英文空格分隔。 输出格式: 基本输出格式见每种选项的描述。 异常情况输出: 如果不符合基本格式,输出"Wrong Format"。 如果符合基本格式,但输入的数量不符合要求,输出"wrong number of points"。 注意:输出的数据若小数后超过3位,只保留小数后3位,多余部分采用四舍五入规则进到最低位。小数后若不足3位,按原始位数显示,不必补齐。例如:1/3的结果按格式输出为 0.333,1.0按格式输出为1.0 选项1、2、3中,若四边形四个中有重合,输出"points coincide"。 选项4中,若前两个输入线的重合,输出"points coincide"。 输入样例1: 选项1,重合。例如: 1:-1,-1 -1,-1 1,2 1,-2 输出样例: 在这里给出相应的输出。例如: points coincide 输入样例2: 不符合基本格式。例如: 1:-1,-1 1,2 -1,1 ++1,0 输出样例: 在这里给出相应的输出。例如: Wrong Format 输入样例3: 选项1,输入数量不对。例如: 1:-1,-1 -1,2 输出样例: 在这里给出相应的输出。例如: wrong number of points 输入样例4: 选项1,正确输入判断。例如: 1:-1,-1 -1,1 1,2 1,-2 输出样例: 在这里给出相应的输出。例如: true false 输入样例5: 选项2,输入不构成四边形。例如: 2:10,10 1,1 0,0 1,20 输出样例: 在这里给出相应的输出。例如: not a quadrilateral 输入样例6: 选项2,正方形。例如: 2:0,0 0,80 80,80 80,0 输出样例: 在这里给出相应的输出。例如: true true true 输入样例7: 选项2。例如: 2:0,0 -10,80 0,160 -10,80 输出样例: 在这里给出相应的输出。例如: not a quadrilateral 输入样例8: 选项3,凸四边形。例如: 3:-1,-1 -1,1 1,2 1,-2 输出样例: 在这里给出相应的输出。例如: true 10.472 6.0 输入样例9: 选项3,。例如: 3:0,0 -10,100 0,99 10,100 输出样例: 在这里给出相应的输出。例如: false 221.097 990.0 其余样例,详见附件: 线形系列4-四边形题目说明.pdf 代码长度限制 50 KB 时间限制 400 ms 内存限制 64 MB 栈限制 8192 KB
07-07
代码有未定义的get_vertices:import math from collections import defaultdict from pathlib import Path def process_folder(input_dir, output_dir): """保持原有文件夹处理逻辑不变""" Path(output_dir).mkdir(parents=True, exist_ok=True) valid_extensions = {'.txt', '.obj', '.geo'} for input_path in Path(input_dir).rglob('*'): if input_path.is_dir() or input_path.suffix.lower() not in valid_extensions: continue relative_path = input_path.relative_to(input_dir) output_path = Path(output_dir) / relative_path.with_name(f"{input_path.stem}_mesh.obj") output_path.parent.mkdir(parents=True, exist_ok=True) try: process_single_file(input_path, output_path) print(f"成功处理:{input_path} -> {output_path}") except Exception as e: print(f"处理失败:{input_path}\n错误详情:{str(e)}\n{'='*50}") def process_single_file(input_path, output_path): """保持原有单文件处理逻辑不变""" with open(input_path, 'r') as f: raw_lines = [line.strip() for line in f if line.strip()] vertices, lines = parse_input_data(raw_lines) obj_lines = enhanced_convert_lines_to_mesh(vertices, lines) with open(output_path, 'w') as f: f.write("\n".join(obj_lines)) def parse_input_data(raw_lines): """保持原有数据解析逻辑不变""" vertices = [] lines = [] for line in raw_lines: if line.startswith('#') or not line.split(): continue parts = line.split() try: if parts[0].lower() == 'v': vertices.append(' '.join(parts[1:4])) elif parts[0].lower() == 'l': start = int(parts[1]) - 1 end = int(parts[2]) - 1 lines.append((start, end)) except (IndexError, ValueError) as e: raise ValueError(f"解析错误:{line}") from e return vertices, lines def enhanced_convert_lines_to_mesh(vertex_data, line_data): """增强型网格生成算法""" # 构建邻接表并缓存坐标 adj, coords = build_adjacency_with_coords(line_data, vertex_data) # 第一步:查找严格闭合的平面多边形 planar_faces = find_planar_faces(adj, coords) # 第二步:处理非闭合结构 edge_faces = process_open_structures(adj, coords) # 合并结果并去重 all_faces = deduplicate_faces(planar_faces + edge_faces) return generate_obj_content(vertex_data, all_faces) def build_adjacency_with_coords(line_data, vertex_data): """构建邻接表并缓存坐标""" adj = defaultdict(set) coord_cache = {} for idx in range(len(vertex_data)): parts = vertex_data[idx].split() coord_cache[idx] = tuple(map(float, parts)) for s, e in line_data: adj[s].add(e) adj[e].add(s) return adj, coord_cache def find_planar_faces(adj, coord_cache, max_length=6): """查找平面多边形""" def dfs(current, path, edges): cycles = [] if len(path) > 2 and current == path[0]: cycle = path[:-1] # 去除重复起 if validate_planar_cycle(cycle, coord_cache): return [cycle] return [] if len(path) >= max_length: return [] for neighbor in adj[current]: edge = tuple(sorted((current, neighbor))) if edge not in edges: new_edges = edges.copy() new_edges.add(edge) cycles += dfs(neighbor, path + [neighbor], new_edges) return cycles seen = set() planar_faces = [] for start in adj: for cycle in dfs(start, [start], set()): normalized = normalize_cycle(cycle) if normalized not in seen: seen.add(normalized) planar_faces.extend(triangulate_planar_face(normalized, coord_cache)) return planar_faces def validate_planar_cycle(cycle, coord_cache, tolerance=1e-3): """验证多边形平面性(放宽容差)""" if len(cycle) < 3: return False # 计算平均法线 normals = [] for i in range(len(cycle)): a = coord_cache[cycle[i-2]] b = coord_cache[cycle[i-1]] c = coord_cache[cycle[i]] normals.append(calculate_normal(a, b, c)) # 检查法线一致性 ref_normal = normals[0] for n in normals[1:]: if angle_between(ref_normal, n) > math.radians(5): # 允许5度偏差 return False return True def triangulate_planar_face(cycle, coord_cache): """平面多边形三角剖分""" try: # 将三维坐标投影到二维平面 points_3d = [coord_cache[v] for v in cycle] normal = calculate_normal(*points_3d[:3]) basis = create_projection_basis(normal) points_2d = [project_point(p, basis) for p in points_3d] # 执行二维耳切法 triangles = ear_clipping_2d(points_2d) return [(cycle[i], cycle[j], cycle[k]) for i, j, k in triangles] except: return [] def process_open_structures(adj, coord_cache): """处理开放式结构(线段连接)""" faces = [] visited = set() # 查找所有三角形结构 for a in adj: for b in adj[a]: if b in visited: continue for c in adj[b]: if c in adj[a] and c != a and c != b: # 检查是否形成有效三角形 if validate_triangle(a, b, c, coord_cache): faces.append((a, b, c)) visited.update({a, b, c}) return faces def validate_triangle(a, b, c, coord_cache): """验证三角形有效性""" try: p1 = coord_cache[a] p2 = coord_cache[b] p3 = coord_cache[c] # 检查面积 area = 0.5 * math.sqrt(sum(x**2 for x in calculate_normal(p1, p2, p3))) return area > 1e-6 except: return False # ---------- 几何计算辅助函数 ---------- def calculate_normal(a, b, c): """计算法线向量""" v1 = [b[i]-a[i] for i in range(3)] v2 = [c[i]-a[i] for i in range(3)] return cross_product(v1, v2) def cross_product(v1, v2): return [ v1[1]*v2[2] - v1[2]*v2[1], v1[2]*v2[0] - v1[0]*v2[2], v1[0]*v2[1] - v1[1]*v2[0] ] def angle_between(v1, v2): """计算两向量夹角""" dot = sum(x*y for x, y in zip(v1, v2)) mag1 = math.sqrt(sum(x**2 for x in v1)) mag2 = math.sqrt(sum(y**2 for y in v2)) return math.acos(dot/(mag1*mag2 + 1e-9)) def create_projection_basis(normal): """创建投影坐标系""" axis_x = [1, 0, 0] if abs(normal[1]) > 0.5 else [0, 1, 0] tangent = cross_product(normal, axis_x) binormal = cross_product(normal, tangent) return (tangent, binormal) def project_point(p, basis): """三维坐标投影到二维平面""" return ( p[0]*basis[0][0] + p[1]*basis[0][1] + p[2]*basis[0][2], p[0]*basis[1][0] + p[1]*basis[1][1] + p[2]*basis[1][2] ) def ear_clipping_2d(points): """二维耳切法实现""" indices = list(range(len(points))) triangles = [] while len(indices) > 3: ear_found = False for i in range(len(indices)): a, b, c = get_vertices(indices, i) if is_ear(a, b, c, points, indices): triangles.append((a, b, c)) del indices[i] ear_found = True break if not ear_found: break if len(indices) == 3: triangles.append(tuple(indices)) return triangles def is_ear(a, b, c, points, indices): """检查是否为耳朵""" if not is_convex(points[a], points[b], points[c]): return False return not any(point_in_triangle(points[i], points[a], points[b], points[c]) for i in indices if i not in {a, b, c}) def point_in_triangle(p, a, b, c, tol=1e-6): """精确包含检查""" area = triangle_area(a, b, c) return abs(area - (triangle_area(a, b, p) + triangle_area(b, c, p) + triangle_area(c, a, p))) < tol def triangle_area(a, b, c): """计算三角形面积""" return 0.5 * abs( (b[0]-a[0])*(c[1]-a[1]) - (c[0]-a[0])*(b[1]-a[1]) ) # ---------- 辅助函数 ---------- def normalize_cycle(cycle): """优化的环标准化""" min_val = min(cycle) rotations = [cycle[i:] + cycle[:i] for i, v in enumerate(cycle) if v == min_val] reversed_rot = [list(reversed(r)) for r in rotations] all_candidates = rotations + reversed_rot return min(all_candidates) def deduplicate_faces(faces): """面片去重""" seen = set() unique = [] for f in faces: key = tuple(sorted(f)) if key not in seen and len(set(key)) == 3: seen.add(key) unique.append(f) return unique def generate_obj_content(vertex_data, faces): """生成OBJ文件内容""" output = ["# Generated Mesh"] output += [f"v {v}" for v in vertex_data] output.append("\n# Faces") output += [f"f {' '.join(str(v+1) for v in f)}" for f in faces] return output if __name__ == "__main__": INPUT_DIR = "D:/idmdownload/Compressed/Tallinn/train/wireframe" OUTPUT_DIR = "D:/idmdownload/Compressed/Tallinn/train/mesh" if not Path(INPUT_DIR).exists(): raise FileNotFoundError(f"输入目录不存在:{INPUT_DIR}") if INPUT_DIR == OUTPUT_DIR: raise ValueError("输入输出目录不能相同") try: process_folder(INPUT_DIR, OUTPUT_DIR) print(f"\n处理完成!输出文件数:{len(list(Path(OUTPUT_DIR).rglob('*.obj')))}") except KeyboardInterrupt: print("\n操作已中断")
05-27
评论
添加红包

请填写红包祝福语或标题

红包个数最小为10个

红包金额最低5元

当前余额3.43前往充值 >
需支付:10.00
成就一亿技术人!
领取后你会自动成为博主和红包主的粉丝 规则
hope_wisdom
发出的红包
实付
使用余额支付
点击重新获取
扫码支付
钱包余额 0

抵扣说明:

1.余额是钱包充值的虚拟货币,按照1:1的比例进行支付金额的抵扣。
2.余额无法直接购买下载,可以购买VIP、付费专栏及课程。

余额充值