圆锥与球体相交立体角计算异常:Blender实现问题排查
圆锥与球体相交立体角计算错误排查
问题背景
需要计算圆锥与球体相交区域的立体角,通过Blender辅助实现。圆锥通过顶点apex、半张角halfOpeningDeg、方位角azimuth、仰角elevation参数化定义,球体通过球心centreSphere和半径radiusSphere定义,示例代码如下:
from Cone import Cone from Sphere import Sphere apex: tuple[float, float, float] = (0.0, 0.0, 0.0) height: float = 1.0e1 halfOpeningDeg: float = 15.0 azimuth: float = 45.0 elevation: float = 15.0 cone: Cone = Cone( apex = apex, height = height, halfOpeningDeg = halfOpeningDeg, azDeg = azimuth, elDeg = elevation, ) centreSphere: tuple[float, float, float] = (5.0, 3.0, 0.0) radiusSphere: float = 3.5 sphere: Sphere = Sphere( radius = radiusSphere, centre = centreSphere, )
基于上述对象编写了相交检测、相交体积计算、立体角计算函数,但运行后Blender视图显示圆锥完全穿过球体,理论上相交区域立体角应等于圆锥本身的立体角,实际结果却不符,需要排查计算错误。
核心错误分析
1. calculate_solid_angle函数的逻辑错误
错误的球内圆锥判断条件
当前代码中(distToIntersec + apexBaseDist) <= sphereRadius的判断完全不符合几何意义:
distToIntersec是锥顶到球心的直线距离,apexBaseDist是锥顶到锥底的斜距,两者相加与球半径比较无法判断圆锥是否完全在球内。
错误的立体角缩放逻辑
代码中distToIntersec -= sphereRadius和(sphereRadius - distToIntersec) / coneHeight的计算没有几何依据:
- 立体角是从锥顶出发的射线与球体相交区域对应的角度,当圆锥完全穿过球体时,所有圆锥射线都穿过球体,立体角应等于圆锥本身的立体角,而非按高度比例缩放。
2. 球体半径计算错误
代码中sphereRadius: float = (0.5 * sphereBlend.dimensions.x) * sphere.sphere.scale.x存在属性调用错误,正确写法应为sphereBlend.scale.x(sphere是自定义Sphere对象,其sphere属性才是Blender对象):
sphereRadius: float = (0.5 * sphereBlend.dimensions.x) * sphereBlend.scale.x
3. check_intersection函数的圆锥半径计算错误
coneRadiusHeight的计算中,(0.5 * coneScales[0]) * coneScales[0]应为(0.5 * coneDims[0] * coneScales[0]),需结合原始尺寸与缩放比例计算实际半径:
coneRadiusHeight = (projLength / (coneDims[2] * coneScales[2])) * (0.5 * coneDims[0] * coneScales[0])
修正后的立体角计算函数
def calculate_solid_angle(cone: Cone, sphere: Sphere) -> float: """ Compute the solid angle underlying the intersection of a cone and a sphere Parameters ---------- cone : Cone Cone sphere : Sphere Sphere Return ------ solidAngIntersec : float Solid angle of the intersection, in steradians """ coneSolidAng: float = cone.get_solidAngle() coneBlend: bpy.types.Object = cone.cone sphereBlend: bpy.types.Object = sphere.sphere # 修正球体半径计算 sphereRadius: float = (0.5 * sphereBlend.dimensions.x) * sphereBlend.scale.x coneHeight: float = coneBlend.dimensions.z * coneBlend.scale.z halfOpeningRad: float = np.radians(cone.halfOpeningDeg) # 获取圆锥轴线方向 cone_axis = mathutils.Vector((0.0, 0.0, 1.0)) cone_axis.rotate(coneBlend.rotation_quaternion) # 锥顶到球心的向量及距离 apex_to_centre = sphereBlend.location - coneBlend.location d = apex_to_centre.length # 球心到圆锥轴线的垂直距离 h = apex_to_centre.cross(cone_axis).length # 圆锥底半径 cone_base_radius = coneHeight * np.tan(halfOpeningRad) # 情况1:圆锥与球不相交 if h > sphereRadius + cone_base_radius: return 0.0 # 情况2:整个圆锥在球内 if d + coneHeight <= sphereRadius: return coneSolidAng # 情况3:圆锥完全穿过球体(所有圆锥射线均穿过球) base_centre = coneBlend.location + coneHeight * cone_axis base_dist_to_centre = (base_centre - sphereBlend.location).length if (d - sphereRadius) * (base_dist_to_centre - sphereRadius) < 0: return coneSolidAng # 情况4:圆锥部分伸入球内,通过Blender计算相交区域在单位球上的表面积(即立体角) bpy.ops.object.select_all(action='DESELECT') # 复制圆锥和球体 cone_copy = coneBlend.copy() cone_copy.data = coneBlend.data.copy() sphere_copy = sphereBlend.copy() sphere_copy.data = sphereBlend.data.copy() bpy.context.collection.objects.link(cone_copy) bpy.context.collection.objects.link(sphere_copy) # 计算圆锥与球体的交集 cone_copy.select_set(True) bpy.context.view_layer.objects.active = cone_copy bpy.ops.object.modifier_add(type='BOOLEAN') bpy.context.object.modifiers["Boolean"].operation = 'INTERSECT' bpy.context.object.modifiers["Boolean"].object = sphere_copy bpy.ops.object.modifier_apply(modifier="Boolean") # 创建以锥顶为中心的单位球 bpy.ops.mesh.primitive_uv_sphere_add(radius=1, location=coneBlend.location) unit_sphere = bpy.context.active_object # 计算交集与单位球的重叠区域(对应立体角的表面) cone_copy.select_set(True) bpy.context.view_layer.objects.active = cone_copy bpy.ops.object.modifier_add(type='BOOLEAN') bpy.context.object.modifiers["Boolean.001"].operation = 'INTERSECT' bpy.context.object.modifiers["Boolean.001"].object = unit_sphere bpy.ops.object.modifier_apply(modifier="Boolean.001") # 计算重叠区域的表面积,即立体角(单位球表面积对应立体角) bm = bmesh.new() bm.from_mesh(cone_copy.data) solid_angle = bm.calc_surface_area() bm.free() # 清理临时对象 bpy.ops.object.select_all(action='DESELECT') cone_copy.select_set(True) sphere_copy.select_set(True) unit_sphere.select_set(True) bpy.ops.object.delete() return solid_angle
内容的提问来源于stack exchange,提问作者aheuchamps
相关产品推荐
相关产品推荐

