179 lines
6.2 KiB
Python
179 lines
6.2 KiB
Python
import numpy as np
|
|
import sys
|
|
import os
|
|
|
|
def compute_vertex_normals(vertices, faces):
|
|
"""
|
|
计算顶点法线
|
|
Args:
|
|
vertices: 顶点列表,每个顶点是[x, y, z]
|
|
faces: 面列表,每个面是顶点索引列表(三角形面为3个索引)
|
|
|
|
Returns:
|
|
normals: 顶点法线列表,每个法线是[nx, ny, nz]
|
|
"""
|
|
# 初始化法线数组
|
|
normals = np.zeros_like(vertices, dtype=np.float64)
|
|
|
|
# 计算每个面的法线并累加到对应的顶点
|
|
for face in faces:
|
|
if len(face) < 3:
|
|
continue
|
|
|
|
# 获取三角形的三个顶点
|
|
v0 = vertices[face[0]]
|
|
v1 = vertices[face[1]]
|
|
v2 = vertices[face[2]]
|
|
|
|
# 计算两条边向量
|
|
edge1 = v1 - v0
|
|
edge2 = v2 - v0
|
|
|
|
# 计算面法线(叉积)
|
|
face_normal = np.cross(edge1, edge2)
|
|
|
|
# 面法线长度(用于加权平均)
|
|
face_area = np.linalg.norm(face_normal)
|
|
|
|
# 如果面法线长度不为零,则归一化并加权
|
|
if face_area > 1e-10:
|
|
face_normal_normalized = face_normal / face_area
|
|
|
|
# 将面法线累加到每个顶点
|
|
for vertex_index in face:
|
|
normals[vertex_index] += face_normal_normalized
|
|
|
|
# 归一化所有顶点法线
|
|
for i in range(len(normals)):
|
|
length = np.linalg.norm(normals[i])
|
|
if length > 1e-10:
|
|
normals[i] /= length
|
|
else:
|
|
# 如果法线长度为零(可能是孤立顶点),设置为默认向上法线
|
|
normals[i] = np.array([0.0, 1.0, 0.0])
|
|
|
|
return normals
|
|
|
|
def parse_obj_file(filename):
|
|
"""
|
|
解析OBJ文件
|
|
Returns:
|
|
vertices: 顶点列表
|
|
faces: 面索引列表
|
|
has_normals: 是否已经包含法线
|
|
"""
|
|
vertices = []
|
|
faces = []
|
|
existing_normals = []
|
|
has_normals = False
|
|
|
|
try:
|
|
with open(filename, 'r', encoding='utf-8') as file:
|
|
for line in file:
|
|
line = line.strip()
|
|
if not line or line.startswith('#'):
|
|
continue
|
|
|
|
parts = line.split()
|
|
if not parts:
|
|
continue
|
|
|
|
if parts[0] == 'v': # 顶点
|
|
if len(parts) >= 4:
|
|
vertex = [float(parts[1]), float(parts[2]), float(parts[3])]
|
|
vertices.append(vertex)
|
|
|
|
elif parts[0] == 'vn': # 法线
|
|
if len(parts) >= 4:
|
|
normal = [float(parts[1]), float(parts[2]), float(parts[3])]
|
|
existing_normals.append(normal)
|
|
has_normals = True
|
|
|
|
elif parts[0] == 'f': # 面
|
|
face_vertices = []
|
|
for part in parts[1:]:
|
|
# 处理 f v1/vt1/vn1 v2/vt2/vn2 格式
|
|
vertex_data = part.split('/')
|
|
if vertex_data[0]: # 顶点索引
|
|
# OBJ索引从1开始,转换为从0开始
|
|
vertex_index = int(vertex_data[0]) - 1
|
|
face_vertices.append(vertex_index)
|
|
|
|
if len(face_vertices) >= 3:
|
|
# 如果是四边形或多边形,分解为三角形
|
|
for i in range(1, len(face_vertices) - 1):
|
|
faces.append([face_vertices[0], face_vertices[i], face_vertices[i + 1]])
|
|
|
|
except UnicodeDecodeError:
|
|
print(f"错误: 无法读取文件 {filename},请确保文件是UTF-8编码。")
|
|
sys.exit(1)
|
|
|
|
return np.array(vertices), faces, has_normals
|
|
|
|
def write_obj_file(filename, vertices, faces, normals, original_has_normals=False):
|
|
"""
|
|
写入OBJ文件
|
|
"""
|
|
with open(filename, 'w', encoding='utf-8') as file:
|
|
file.write("# OBJ文件 - 自动计算法线\n")
|
|
file.write(f"# 顶点数: {len(vertices)}\n")
|
|
file.write(f"# 面数: {len(faces)}\n\n")
|
|
|
|
# 写入顶点
|
|
for i, vertex in enumerate(vertices):
|
|
file.write(f"v {vertex[0]:.6f} {vertex[1]:.6f} {vertex[2]:.6f}\n")
|
|
|
|
file.write("\n")
|
|
|
|
# 写入法线
|
|
for i, normal in enumerate(normals):
|
|
file.write(f"vn {normal[0]:.6f} {normal[1]:.6f} {normal[2]:.6f}\n")
|
|
|
|
file.write("\n")
|
|
|
|
# 写入面(使用顶点索引/法线索引格式)
|
|
for face in faces:
|
|
face_line = "f"
|
|
for vertex_index in face:
|
|
# 如果原始文件有法线或者我们计算了法线,使用 v//vn 格式
|
|
face_line += f" {vertex_index + 1}//{vertex_index + 1}"
|
|
file.write(face_line + "\n")
|
|
|
|
def process_obj_file(input_file, output_file=None):
|
|
"""
|
|
处理OBJ文件的主要函数
|
|
"""
|
|
if output_file is None:
|
|
base_name = os.path.splitext(input_file)[0]
|
|
output_file = f"{base_name}_with_normals.obj"
|
|
|
|
print(f"读取文件: {input_file}")
|
|
vertices, faces, has_normals = parse_obj_file(input_file)
|
|
|
|
print(f"找到 {len(vertices)} 个顶点, {len(faces)} 个面")
|
|
|
|
if has_normals:
|
|
print("警告: 输入文件已包含法线数据,将被重新计算")
|
|
|
|
if len(vertices) == 0:
|
|
print("错误: 没有找到顶点数据")
|
|
return False
|
|
|
|
if len(faces) == 0:
|
|
print("错误: 没有找到面数据")
|
|
return False
|
|
|
|
print("计算顶点法线中...")
|
|
normals = compute_vertex_normals(vertices, faces)
|
|
|
|
print("写入输出文件...")
|
|
write_obj_file(output_file, vertices, faces, normals, has_normals)
|
|
|
|
print(f"完成! 输出文件: {output_file}")
|
|
return True
|
|
|
|
|
|
if __name__ == "__main__":
|
|
input_file = "face.obj" # 默认输入文件
|
|
output_file = "face_with_normals.obj" # 默认输出文件
|
|
process_obj_file(input_file, output_file) |