# -*- coding:utf-8 -*- # title :ursina天体视图 # description :ursina天体视图(天体效果展示用,需要安装 ursina) # author :Python超人 # date :2023-02-11 # link :https://gitcode.net/pythoncr/ # python_version :3.8 # ============================================================================== # pip install -i http://pypi.douban.com/simple/ --trusted-host=pypi.douban.com ursina from ursina import Ursina, window, Entity, Mesh, EditorCamera, color, mouse, Vec2, Vec3, load_texture, Texture, Text, \ Cylinder import ursina.color as ursina_color from math import pi, sin, cos import numpy as np import math from common.color_utils import conv_to_vec4_color from common.func import calculate_distance from simulators.ursina.entities.circle_line import CircleLine from simulators.ursina.entities.orbit_line import OrbitLine from simulators.ursina.entities.label import Label from simulators.ursina.ursina_config import UrsinaConfig def create_sphere(radius, subdivisions): """ 创建一个球体 @param radius: @param subdivisions: @return: """ # 生成球体的顶点、UV坐标uvs、法线tris和三角面 verts = [] tris = [] normals = [] uvs = [] for y in range(subdivisions + 1): for x in range(subdivisions + 1): x_segment = x / subdivisions y_segment = -y / subdivisions x_pos = cos(x_segment * 2 * pi) * sin(y_segment * pi) y_pos = cos(y_segment * pi) z_pos = sin(x_segment * 2 * pi) * sin(y_segment * pi) verts.append(Vec3(x_pos, y_pos, z_pos) * radius) uvs.append(Vec2(x_segment, y_segment)) normals.append(Vec3(x_pos, y_pos, z_pos)) for y in range(subdivisions): for x in range(subdivisions): first = (y * (subdivisions + 1)) + x second = first + subdivisions + 1 # tris.append((first, second + 1, second)) # tris.append((first, first + 1, second + 1)) tris.append((second, second + 1, first)) tris.append((second + 1, first + 1, first)) # 反转面法线 # for i in range(len(tris)): # a, b, c = tris[i] # tris[i] = (c, b, a) # normals[a], normals[b], normals[c] = -Vec3(*normals[c]), -Vec3(*normals[b]), -Vec3(*normals[a]) # normals[a], normals[b], normals[c] = -Vec3(*normals[a]), -Vec3(*normals[b]), -Vec3(*normals[c]) # 翻转球体 # for i in range(len(normals)): # normals[i] = -normals[i] return Mesh(vertices=verts, triangles=tris, normals=normals, uvs=uvs, mode='triangle') # def create_label(text, position=(0, 0, 0)): # label = Label(text=text, position=position) # return label def create_cone(radius, height, subdivisions, r=0.1): """ 创建一个圆锥 @param radius: 圆锥底部的半径 @param height: 圆锥的高度 @param subdivisions: 细分数,用于控制圆锥的光滑度 @return: Mesh对象,表示创建的圆锥 """ verts = [] # 顶点列表 tris = [] # 三角面索引列表 normals = [] # 法线列表 uvs = [] # UV坐标列表 p_h = r * height d_h = (1 - r) * height # 生成圆锥底部的顶点和UV坐标 for i in range(subdivisions): angle = 2 * math.pi * i / subdivisions x = radius * math.cos(angle) z = radius * math.sin(angle) verts.append(Vec3(x, d_h, z)) uvs.append(Vec2(i / subdivisions, 0)) # 圆锥尖端的顶点 verts.append(Vec3(0, -p_h, 0)) uvs.append(Vec2(0.5, 1)) # 生成圆锥侧面的顶点、法线和三角面 for i in range(subdivisions): p1 = verts[i] p2 = verts[(i + 1) % subdivisions] # 侧面三角形 tris.append((i, (i + 1) % subdivisions, subdivisions)) # 侧面法线 normal = Vec3(0, 1, 0).cross(p2 - p1).normalized() normals.append(normal) return Mesh(vertices=verts, triangles=tris, normals=normals, uvs=uvs, mode='triangle') import math def create_frustum(bottom_radius, top_radius, height, subdivisions): """ 创建一个圆台 @param bottom_radius: 圆台底部的半径 @param top_radius: 圆台顶部的半径 @param height: 圆台的高度 @param subdivisions: 细分数,用于控制圆台的光滑度 @return: Mesh对象,表示创建的圆台 """ verts = [] # 顶点列表 tris = [] # 三角面索引列表 normals = [] # 法线列表 uvs = [] # UV坐标列表 # 生成圆台底部的顶点和UV坐标 for i in range(subdivisions): angle = 2 * math.pi * i / subdivisions x = bottom_radius * math.cos(angle) z = bottom_radius * math.sin(angle) verts.append(Vec3(x, 0, z)) uvs.append(Vec2(i / subdivisions, 0)) # 生成圆台顶部的顶点和UV坐标 for i in range(subdivisions): angle = 2 * math.pi * i / subdivisions x = top_radius * math.cos(angle) z = top_radius * math.sin(angle) verts.append(Vec3(x, height, z)) uvs.append(Vec2(i / subdivisions, 1)) # 生成圆台侧面的顶点、法线和三角面 for i in range(subdivisions): # 底部顶点索引 bottom_index = i # 顶部顶点索引 top_index = i + subdivisions # 下一个底部顶点索引 next_bottom_index = (i + 1) % subdivisions # 下一个顶部顶点索引 next_top_index = (i + 1) % subdivisions + subdivisions # 侧面三角形 tris.append((bottom_index, next_bottom_index, top_index)) tris.append((next_bottom_index, next_top_index, top_index)) # 侧面法线 normal = (verts[top_index] - verts[bottom_index]).cross( verts[next_bottom_index] - verts[bottom_index]).normalized() normals.append(normal) normals.append(normal) return Mesh(vertices=verts, triangles=tris, normals=normals, uvs=uvs, mode='triangle') def create_cylinder(radius, height, subdivisions): """ 创建一个圆柱体 @param radius: 圆柱体的底部半径 @param height:圆柱体的高度 @param subdivisions: 细分数,用于控制圆柱体的光滑度 @return: Mesh对象,表示创建的圆柱体 """ # 生成圆锥的顶点、UV坐标uvs、法线normals和三角面tris verts = [] tris = [] normals = [] uvs = [] # 生成底面顶点和法线 verts.append(Vec3(0, 0, 0)) normals.append(Vec3(0, -1, 0)) for x in range(subdivisions + 1): angle = x / subdivisions * 2 * pi x_pos = cos(angle) * radius z_pos = sin(angle) * radius verts.append(Vec3(x_pos, 0, z_pos)) normals.append(Vec3(0, -1, 0)) # 生成侧面顶点、法线和纹理坐标 for y in range(subdivisions + 1): for x in range(subdivisions + 1): angle = x / subdivisions * 2 * pi x_pos = cos(angle) * radius * (1 - y / subdivisions) y_pos = -height + (y / subdivisions) * height z_pos = sin(angle) * radius * (1 - y / subdivisions) verts.append(Vec3(x_pos, y_pos, z_pos)) normals.append(Vec3(cos(angle), radius / height, sin(angle))) uvs.append(Vec2(x / subdivisions, y / subdivisions)) # 生成底面三角面 for x in range(subdivisions): tris.append((0, x + 2, x + 1)) # 生成侧面三角面 for y in range(subdivisions): for x in range(subdivisions): first = (y * (subdivisions + 1)) + x + 1 second = first + subdivisions + 1 tris.append((first, second, first + 1)) tris.append((second, second + 1, first + 1)) return Mesh(vertices=verts, triangles=tris, normals=normals, uvs=uvs, mode='triangle') def create_arrow(height=0.5, width=0.1): # 创建金字塔的顶点 r = width / 2 verts = [ (0, height, 0), # 顶点 (-r, 0, -r), # 左后底点 (r, 0, -r), # 右后底点 (r, 0, r), # 右前底点 (-r, 0, r), # 左前底点 ] # 修改指向 verts = [(z, x, y) for x, y, z in verts] # 定义金字塔的面 faces = [ (0, 1, 2), # 底面1 (0, 2, 3), # 底面2 (0, 3, 4), # 底面3 (0, 4, 1), # 底面4 (4, 2, 1), # 侧面1 (4, 3, 2), # 侧面1 ] # 创建一个金字塔的Mesh对象 arrow_mesh = Mesh(vertices=verts, triangles=faces, mode='triangle') return arrow_mesh def create_label(parent, label, pos, label_color, scale=50, alpha=1.0, background=False, font=None): if isinstance(label_color, tuple) or isinstance(label_color, list): label_color = conv_to_vec4_color(label_color) if alpha < 1: label_color[3] = alpha if font is None: font = UrsinaConfig.CN_FONT elif str(font).startswith("fonts/"): from common.func import find_file font = find_file(f"{font}", UrsinaConfig.CN_FONT) text = Text(label, parent=parent, scale=scale, billboard=True, color=label_color, position=Vec3(pos) + Vec3(1, 1, 1), alpha=alpha, font=font, background=background) return text def create_label__(parent, label, pos, label_color, scale=50, alpha=1.0, bg_color=None): if isinstance(label_color, tuple) or isinstance(label_color, list): label_color = conv_to_vec4_color(label_color) if alpha < 1: label_color[3] = alpha text = Text(label, parent=parent, scale=scale, billboard=True, color=label_color, position=Vec3(pos) + Vec3(1, 1, 1), alpha=alpha, font=UrsinaConfig.CN_FONT, background=bg_color is not None) if bg_color is not None: b_color = conv_to_vec4_color(bg_color) text.background.color = b_color # text.background.parent text.background.position = Vec3(0, 0, 0) text.background.model.radius /= 2 text.set_light_off(True) return text def create_line(from_pos, to_pos, parent=None, alpha=1.0, len_scale=1, set_light_off=True, color=color.white, thickness=1): line = Entity(parent=parent, model=Mesh(vertices=(from_pos * len_scale, to_pos * len_scale), mode='line', thickness=thickness), color=color, alpha=alpha) if set_light_off: line.set_light_off() return line def create_circle(parent=None, pos=Vec3(0, 0, 0), thickness=1, scale=1, color=color.white, alpha=1): circle = Entity(parent=parent, model="circle", thickness=thickness, scale=scale, position=pos, color=color, alpha=alpha) return circle def create_circle_line(parent=None, radius=1, position=None, segments=100, thickness=0.1, color=color.white, alpha=1): if isinstance(color, tuple) or isinstance(color, list): color = conv_to_vec4_color(color) if alpha < 1: color[3] = alpha circle_line = CircleLine(position=position, radius=radius, segments=segments, thickness=thickness, color=color, alpha=alpha) circle_line.set_light_off(True) if parent is not None: if hasattr(parent, "planet"): parent = parent.planet circle_line.parent = parent return circle_line def create_orbit_line(parent=None, position=None, points=100, thickness=0.1, color=color.white, alpha=1): if isinstance(color, tuple) or isinstance(color, list): color = conv_to_vec4_color(color) if alpha < 1: color[3] = alpha orbit_line = OrbitLine(position=position, points=points, thickness=thickness, color=color, alpha=alpha) orbit_line.set_light_off(True) if parent is not None: if hasattr(parent, "planet"): parent = parent.planet orbit_line.parent = parent return orbit_line def create_arrow_line(from_pos, to_pos, parent=None, label=None, set_light_off=True, alpha=1.0, len_scale=0.5, color=color.white, thickness=2, arrow_scale=1, text_scale=50): """ 创建箭头和箭头线段 @param from_pos: 箭头线段开始位置 @param to_pos: 箭头线段结束位置 @param parent: @param label: 箭头显示的文字 @param set_light_off: 是否设置为灯光关闭状态 @param alpha: 透明度 @param len_scale: 长度缩放 @param color: 箭头线颜色 @param thickness: 线段粗细 @param arrow_scale: 箭头缩放 @return: """ height = 0.5 * thickness width = 0.1 * thickness arrow_mesh = create_arrow(height, width) from_pos, to_pos = (Vec3(from_pos), Vec3(to_pos)) line = Entity(parent=parent, model=Mesh(vertices=(from_pos * len_scale, to_pos * len_scale), mode='line', thickness=thickness), color=color, alpha=alpha) arrow = Entity(parent=line, model=arrow_mesh, position=to_pos * len_scale, scale=thickness * arrow_scale, color=color, alpha=alpha) arrow.look_at(to_pos * 100) if set_light_off: line.set_light_off() arrow.set_light_off() if label is not None: text = create_label(parent=line, label=label, pos=Vec3(to_pos) * len_scale * 1.2, label_color=color, scale=text_scale, alpha=alpha) if set_light_off: text.set_light_off() else: text = None return arrow, line, text def get_obj_planet_position(obj): """ 获取对象的位置坐标 @param obj: @return: """ if hasattr(obj, "planet"): if hasattr(obj.planet, "main_entity"): pos1 = obj.planet.main_entity.position else: pos1 = obj.planet.position else: pos1 = obj.position return pos1 def adj_orbit_line_scale(center_obj, orbiting_obj, orbit_line, adj_center_pos=True, adj_scale=True): """ 由于天体运行不是标准的圆形,则需要动态调整轨道的大小,保证轨道线始终在天体的中心位置 @param center_obj: 中心天体,一般为恒星天体 @param orbiting_obj: 绕行天体,一般为行星天体 @param orbit_line: 绕行天体轨道,一般为行星轨道 @param adj_center_pos: 是否调整轨道线的中心位置 @param adj_scale: 是否调整轨道线的缩放 @return: """ # if adj_scale: # 根据距离调整缩放 distance = calculate_distance(center_obj.position - orbiting_obj.position) scale_rate = distance * UrsinaConfig.SCALE_FACTOR / orbit_line.radius orbit_line.scale = scale_rate if adj_center_pos: # 保证轨道中心的位置始终是中心天体的位置 orbit_line.position = center_obj.planet.position def create_orbit_line(center_obj, orbiting_obj, thickness=5, line_color=None, alpha=0.6, rotation_x=90, rotation_y=None, rotation_z=None): """ 创建轨道 @param center_obj: 中心天体,一般为恒星天体 @param orbiting_obj: 绕行天体,一般为行星天体 @param thickness: 轨道线的粗细 @param line_color: 轨道线颜色,默认为绕行天体的拖尾颜色 @param alpha: 轨道线的透明度 @param rotation_x: @param rotation_y: @param rotation_z: @return: """ if isinstance(line_color, tuple) or isinstance(line_color, list): line_color = color.rgba(line_color[0] / 255, line_color[1] / 255, line_color[2] / 255, alpha) if line_color is None: line_color = orbiting_obj.trail_color if line_color is None: line_color = color.white else: line_color = color.rgba(line_color[0], line_color[1], line_color[2], alpha) distance = (center_obj.planet.position - orbiting_obj.planet.position).length() orbit_line = create_circle_line(parent=None, radius=distance, position=center_obj.planet.position, thickness=thickness, color=line_color, alpha=alpha) if rotation_x is not None: orbit_line.rotation_x = rotation_x if rotation_y is not None: orbit_line.rotation_y = rotation_y if rotation_z is not None: orbit_line.rotation_z = rotation_z # orbit_line.look_at(orbiting_obj.planet, axis='back') def auto_adjust(adj_center_pos=True, adj_scale=True): adj_orbit_line_scale(center_obj, orbiting_obj, orbit_line, adj_center_pos, adj_scale) orbit_line.auto_adjust = auto_adjust # orbit_line.enabled = False # 默认不显示 return orbit_line def create_orbit_by_points(center_pos, orbiting_points, thickness=5, line_color=color.white, alpha=0.6, scale_factor=None): if isinstance(line_color, tuple) or isinstance(line_color, list): line_color = conv_to_vec4_color(line_color) if alpha < 1: line_color[3] = alpha if scale_factor is None: scale_factor = UrsinaConfig.SCALE_FACTOR if scale_factor != 1: center_pos = center_pos * scale_factor for i, point in enumerate(orbiting_points): orbiting_points[i] = np.array([point[0] * scale_factor, point[1] * scale_factor, point[2] * scale_factor]) orbit_line = OrbitLine(points=orbiting_points, position=center_pos, thickness=thickness, color=line_color, alpha=alpha) orbit_line.set_light_off(True) # if parent is not None: # if hasattr(parent, "planet"): # parent = parent.planet # circle_line.parent = parent return orbit_line def create_orbit_line2(center_obj, orbiting_obj, line_color=color.white, alpha=0.3): if isinstance(line_color, tuple) or isinstance(line_color, list): line_color = color.rgba(line_color[0] / 255, line_color[1] / 255, line_color[2] / 255, alpha) # center_pos = get_obj_planet_position(center_obj) # orbiting_pos = get_obj_planet_position(orbiting_obj) distance1 = (center_obj.planet.position - orbiting_obj.planet.position).length() distance = calculate_distance(center_obj.position - orbiting_obj.position) distance2 = distance * UrsinaConfig.SCALE_FACTOR * 0.0125 orbit_line = create_circle_line(parent=center_obj, radius=distance2, thickness=2, color=line_color, alpha=alpha) orbit_line.rotation_x = 90 # orbit_line.enabled = False # 默认不显示 return orbit_line def create_connecting_line(obj1, obj2, line_color=color.white, alpha=0.3): """ 创建物体之间的连接线 @param obj1: @param obj2: @param line_color: 线条颜色 @param alpha: 透明度(默认0.3) @return: """ if isinstance(line_color, tuple) or isinstance(line_color, list): line_color = color.rgba(line_color[0] / 255, line_color[1] / 255, line_color[2] / 255, alpha) pos1 = get_obj_planet_position(obj1) pos2 = get_obj_planet_position(obj2) line = create_line(from_pos=pos1, color=line_color, to_pos=pos2, alpha=alpha) return line def create_connecting_lines(objs_list, line_color=color.white, alpha=0.3): """ 创建物体之间的连接线。(多条线) 样例如下:
create_connecting_lines([ [obj1, obj2], [obj3, obj4], ...... ]) @param objs_list: @param color: 线条颜色 @param alpha: 透明度(默认0.3) @return: 被创建的连接线列表 """ if isinstance(line_color, tuple) or isinstance(line_color, list): line_color = color.rgba(line_color[0] / 255, line_color[1] / 255, line_color[2] / 254, alpha) lines = [] for objs in objs_list: line = create_connecting_line(objs[0], objs[1], line_color=line_color, alpha=alpha) lines.append(line) return lines def create_pyramid(): # 创建金字塔的顶点 verts = [ (0, 2, 0), # 顶点 (-1, 0, -1), # 左后底点 (1, 0, -1), # 右后底点 (1, 0, 1), # 右前底点 (-1, 0, 1), # 左前底点 ] # 定义金字塔的面 faces = [ (0, 1, 2), # 底面1 (0, 2, 3), # 底面2 (0, 3, 4), # 底面3 (0, 4, 1), # 底面4 (4, 2, 1), # 侧面1 (4, 3, 2), # 侧面1 ] # 创建一个金字塔的Mesh对象 # verts = [(0, 1, 0), (1, 0, 1), (1, 0, -1), (-1, 0, -1), (-1, 0, 1)] # faces = [(0, 1, 2), (0, 2, 3), (0, 3, 4), (0, 4, 1), (1, 3, 2)] pyramid_mesh = Mesh(vertices=verts, triangles=faces, mode='triangle') return pyramid_mesh def create_body_torus(inner_radius, outer_radius, subdivisions): vertices = [] uvs = [] normals = [] triangles = [] # 计算圆环顶点、法向量和纹理坐标 for i in range(subdivisions): for j in range(subdivisions): # 计算纹理坐标 u = i / subdivisions v = j / subdivisions # 计算球面坐标系下的角度 theta = u * math.pi * 2 phi = v * math.pi * 2 # 计算圆环顶点位置 x = (outer_radius + inner_radius * math.cos(phi)) * math.cos(theta) y = inner_radius * math.sin(phi) * (inner_radius) / 2 z = (outer_radius + inner_radius * math.cos(phi)) * math.sin(theta) # 计算圆环顶点法向量 nx = math.cos(theta) * math.cos(phi) ny = math.sin(phi) nz = math.sin(theta) * math.cos(phi) vertices.append((x, y, z)) normals.append((nx, ny, nz)) uvs.append((u, v)) # 计算圆环三角形面片 for i in range(subdivisions): for j in range(subdivisions): i1 = i j1 = j i2 = (i + 1) % subdivisions j2 = (j + 1) % subdivisions p1 = i1 * subdivisions + j1 p2 = i2 * subdivisions + j1 p3 = i2 * subdivisions + j2 p4 = i1 * subdivisions + j2 triangles.append((p1, p2, p3)) triangles.append((p1, p3, p4)) # uvs = [[u * 2, v] for u, v in uvs] # 创建 mesh 对象 mesh = Mesh(vertices=vertices, uvs=uvs, normals=normals, triangles=triangles, mode='triangle') return mesh def create_torus(inner_radius, outer_radius, subdivisions, repeat=1): verts = [] tris = [] uvs = [] for i in range(subdivisions): angle = i * (360 / subdivisions) x = np.cos(angle * np.pi / 180) y = np.sin(angle * np.pi / 180) # create vertices for inner radius inner_x = x * inner_radius inner_y = y * inner_radius # create vertices for outer radius outer_x = x * outer_radius outer_y = y * outer_radius if i % int(subdivisions / repeat) == 0: verts.append((inner_x, inner_y, 0)) verts.append((outer_x, outer_y, 0)) uvs.append((0.999, 0.0)) uvs.append((0.999, 0.999)) verts.append((inner_x, inner_y, 0)) verts.append((outer_x, outer_y, 0)) uvs.append((0.001, 0.0)) uvs.append((0.001, 0.999)) else: verts.append((inner_x, inner_y, 0)) verts.append((outer_x, outer_y, 0)) # create uvs u = angle * repeat / 360 % 1 uvs.append((u, 0.0)) uvs.append((u, 0.999)) # create triangles first_index = i * 2 second_index = (i * 2 + 2) % (subdivisions * 2) third_index = (i * 2 + 1) % (subdivisions * 2) fourth_index = (i * 2 + 3) % (subdivisions * 2) tris.append((first_index, second_index, third_index)) tris.append((third_index, second_index, fourth_index)) # create normals normals = [] for i in range(len(verts)): angle = i * (360 / subdivisions) x = np.cos(angle * np.pi / 180) y = np.sin(angle * np.pi / 180) normals.append((x, y, 0)) # create mesh mesh = Mesh(vertices=verts, triangles=tris, uvs=uvs, normals=normals, mode='triangle') # add color attribute # mesh.colorize() return mesh if __name__ == '__main__': app = Ursina() # # 使用 Mesh 类创建球体 texture = "../../textures/saturn.jpg" textureRings = '../../textures/saturnRings.jpg' textureAsteroids = '../../textures/asteroids.png' # 创建球体 # sphere = create_sphere(1, 32) # entity = Entity(model=sphere, texture=texture, color=color.white) # 创建光晕 # glow_entity = Entity(parent=entity, model='sphere', color=color.rgb(1,1,1,0.1), # scale=2.1, alpha=0.1) # # torus = create_body_torus(0.8, 2, 64) # textureRings = load_texture(textureRings) # entity = Entity(model=torus, texture=textureRings, rotation=(0, 0, 0), double_sided=True) # torus = create_torus(1.5, 3, 64) # entity = Entity(model=torus, texture=textureRings, rotation=(85, 0, 0), double_sided=True) # body_torus = create_torus(9, 10, 64) # entities = Entity(model=body_torus, texture=textureAsteroids, rotation=(85, 0, 0), double_sided=True) # entities.set_light_off() # 创建金字塔的实体对象 # pyramid = Entity(model=create_pyramid(), texture='brick', color=color.yellow) # pyramid.set_light_off() # arrow = Entity(model=create_arrow(), color=color.yellow) # arrow.set_light_off() # arrow, line, text = create_arrow_line((0, 0, 0), (10, 0, 0)) create_circle_line() EditorCamera() app.run()