def rotation_y(self, value): self.rotation = Vec3(self.rotation[0], value, self.rotation[2])
def raycast(self, origin, direction=(0, 0, 1), distance=inf, traverse_target=scene, ignore=list(), debug=False): self.position = origin self.look_at(self.position + direction) self._pickerNode.clearSolids() ray = CollisionRay() ray.setOrigin(Vec3(0, 0, 0)) ray.setDirection(Vec3(0, 0, 1)) self._pickerNode.addSolid(ray) if debug: temp = Entity(position=origin, model=Raycaster.line_model, scale=Vec3(1, 1, min(distance, 9999)), add_to_scene_entities=False) temp.look_at(self.position + direction) destroy(temp, 1 / 30) self._picker.traverse(traverse_target) if self._pq.get_num_entries() == 0: self.hit = HitInfo(hit=False, distance=distance) return self.hit ignore += tuple([e for e in scene.entities if not e.collision]) self._pq.sort_entries() self.entries = [ # filter out ignored entities e for e in self._pq.getEntries() if e.get_into_node_path().parent not in ignore and self.distance( self.world_position, Vec3( *e.get_surface_point(render))) <= distance ] if len(self.entries) == 0: self.hit = HitInfo(hit=False, distance=distance) return self.hit self.collision = self.entries[0] nP = self.collision.get_into_node_path().parent point = Vec3(*self.collision.get_surface_point(nP)) world_point = Vec3(*self.collision.get_surface_point(render)) hit_dist = self.distance(self.world_position, world_point) self.hit = HitInfo(hit=True, distance=distance) for e in scene.entities: if e == nP: # print('cast nP to Entity') self.hit.entity = e self.hit.point = point self.hit.world_point = world_point self.hit.distance = hit_dist self.hit.normal = Vec3(*self.collision.get_surface_normal( self.collision.get_into_node_path().parent).normalized()) self.hit.world_normal = Vec3( *self.collision.get_surface_normal(render).normalized()) return self.hit self.hit = HitInfo(hit=False, distance=distance) return self.hit
def __init__(self, add_to_scene_entities=True, **kwargs): super().__init__(self.__class__.__name__) self.name = camel_to_snake(self.type) self.enabled = True self.visible = True self.ignore = False # if True, will not try to run code self.eternal = False # eternal entities does not get destroyed on scene.clear() self.ignore_paused = False self.ignore_input = False self.parent = scene if add_to_scene_entities: scene.entities.append(self) self.model = None self.color = color.white self.texture = None self.reflection_map = scene.reflection_map self.reflectivity = 0 self.render_queue = 0 self.double_sided = False self.collision = False self.collider = None self.scripts = list() self.animations = list() self.hovered = False # self.origin = Vec3(0, 0, 0) self.position = Vec3(0, 0, 0) # can also set self.x, self.y, self.z self.rotation = Vec3( 0, 0, 0 ) # can also set self.rotation_x, self.rotation_y, self.rotation_z self.scale = Vec3( 1, 1, 1) # can also set self.scale_x, self.scale_y, self.scale_z self.line_definition = None if application.trace_entity_definition and add_to_scene_entities: from inspect import getframeinfo, stack _stack = stack() caller = getframeinfo(_stack[1][0]) if len(_stack) > 2 and _stack[ 1].code_context and 'super().__init__()' in _stack[ 1].code_context[0]: caller = getframeinfo(_stack[2][0]) self.line_definition = caller if caller.code_context: self.code_context = caller.code_context[0] if (self.code_context.count('(') == self.code_context.count(')') and ' = ' in self.code_context and not 'name=' in self.code_context and not 'Ursina()' in self.code_context): self.name = self.code_context.split( ' = ')[0].strip().replace('self.', '') # print('set name to:', self.code_context.split(' = ')[0].strip().replace('self.', '')) if application.print_entity_definition: print( f'{Path(caller.filename).name} -> {caller.lineno} -> {caller.code_context}' ) for key, value in kwargs.items(): setattr(self, key, value)
def set_position(self, value, relative_to=scene): self.setPos(relative_to, Vec3(value[0], value[2], value[1]))
def sum(l): try: return internal_sum(l) except: pass total = l[0].__class__() for e in l: total += e return total if __name__ == '__main__': from ursina import * app = Ursina() e1 = Entity(position = (0,0,0)) e2 = Entity(position = (0,1,1)) distance(e1, e2) distance_xz(e1, e2.position) between_color = lerp(color.lime, color.magenta, .5) print(between_color) print(lerp((0,0), (0,1), .5)) print(lerp(Vec2(0,0), Vec2(0,1), .5)) print(lerp([0,0], [0,1], .5)) print(round(Vec3(.38, .1351, 353.26), 2)) app.run()
def right(self): vec = render.getRelativeVector(self, (1, 0, 0)) return Vec3(vec[0], vec[2], vec[1])
def bounds(self): return Vec3(self.model_bounds[0] * self.scale_x, self.model_bounds[1] * self.scale_y, self.model_bounds[2] * self.scale_z)
for poly in self.collision_polygons: self.node.addSolid(poly) self.visible = False if __name__ == '__main__': from ursina import * app = Ursina() e = Entity(model='sphere', x=2) e.collider = 'box' # add BoxCollider based on entity's bounds. e.collider = 'sphere' # add SphereCollider based on entity's bounds. e.collider = 'mesh' # add MeshCollider based on entity's bounds. e.collider = BoxCollider(e, center=Vec3(0, 0, 0), size=Vec3( 1, 1, 1)) # add BoxCollider at custom positions and size. e.collider = SphereCollider( e, center=Vec3(0, 0, 0), radius=.75) # add SphereCollider at custom positions and size. e.collider = MeshCollider(e, mesh=e.model, center=Vec3( 0, 0, 0)) # add MeshCollider with custom shape and center. m = Prismatoid(base_shape=Circle(6), thicknesses=(1, .5)) e = Button(parent=scene, model=m, collider='mesh', color=color.red, highlight_color=color.yellow) EditorCamera()
def __init__(self, entity, mesh=None, center=(0, 0, 0)): super().__init__() center = Vec3(center) if mesh == None and entity.model: mesh = entity.model print('''auto generating mesh collider from entity's mesh''') # from ursina import Mesh # this part is obsolete if I use the old obj loading # if not isinstance(mesh, Mesh): # from ursina import mesh_importer # mesh = eval(mesh_importer.obj_to_ursinamesh(name=mesh.name, save_to_file=False)) # flipped_verts = [v*Vec3(-1,1,1) for v in mesh.vertices] # have to flip it on x axis for some reason # mesh.vertices = flipped_verts self.node_path = entity.attachNewNode(CollisionNode('CollisionNode')) self.node = self.node_path.node() self.collision_polygons = list() if mesh.triangles: for tri in mesh.triangles: if len(tri) == 3: poly = CollisionPolygon( Vec3(mesh.vertices[tri[2]]), Vec3(mesh.vertices[tri[1]]), Vec3(mesh.vertices[tri[0]]), ) self.collision_polygons.append(poly) elif len(tri) == 4: poly = CollisionPolygon(Vec3(mesh.vertices[tri[2]]), Vec3(mesh.vertices[tri[1]]), Vec3(mesh.vertices[tri[0]])) self.collision_polygons.append(poly) poly = CollisionPolygon(Vec3(mesh.vertices[tri[0]]), Vec3(mesh.vertices[tri[3]]), Vec3(mesh.vertices[tri[2]])) self.collision_polygons.append(poly) elif mesh.mode == 'triangle': for i in range(0, len(mesh.vertices), 3): poly = CollisionPolygon( Vec3(mesh.vertices[i + 2]), Vec3(mesh.vertices[i + 1]), Vec3(mesh.vertices[i]), ) self.collision_polygons.append(poly) else: print('error: mesh collider does not support', mesh.mode, 'mode') return None for poly in self.collision_polygons: self.node.addSolid(poly) self.visible = False
def model_center(self): if not self.model: return Vec3(0, 0, 0) return self.model.getTightBounds.getCenter()
def __init__(self, entity, mesh=None, center=(0, 0, 0)): super().__init__() center = Vec3(center) if mesh == None and entity.model: mesh = entity.model # print('''auto generating mesh collider from entity's mesh''') self.node_path = entity.attachNewNode(CollisionNode('CollisionNode')) self.node = self.node_path.node() self.collision_polygons = list() if isinstance(mesh, Mesh): if mesh.triangles: triangles = mesh.triangles if not isinstance(mesh.triangles[0], tuple): triangles = [ triangles[i:i + 3] for i in range(0, len(triangles), 3) ] # group into groups of three for tri in triangles: if len(tri) == 3: poly = CollisionPolygon( Vec3(mesh.vertices[tri[2]]), Vec3(mesh.vertices[tri[1]]), Vec3(mesh.vertices[tri[0]]), ) self.collision_polygons.append(poly) elif len(tri) == 4: poly = CollisionPolygon(Vec3(mesh.vertices[tri[2]]), Vec3(mesh.vertices[tri[1]]), Vec3(mesh.vertices[tri[0]])) self.collision_polygons.append(poly) poly = CollisionPolygon(Vec3(mesh.vertices[tri[0]]), Vec3(mesh.vertices[tri[3]]), Vec3(mesh.vertices[tri[2]])) self.collision_polygons.append(poly) elif mesh.mode == 'triangle': for i in range(0, len(mesh.vertices), 3): poly = CollisionPolygon( Vec3(mesh.vertices[i + 2]), Vec3(mesh.vertices[i + 1]), Vec3(mesh.vertices[i]), ) self.collision_polygons.append(poly) else: print('error: mesh collider does not support', mesh.mode, 'mode') return None elif isinstance(mesh, NodePath): from panda3d.core import GeomVertexReader verts = [] geomNodeCollection = mesh.findAllMatches('**/+GeomNode') for nodePath in geomNodeCollection: geomNode = nodePath.node() for i in range(geomNode.getNumGeoms()): geom = geomNode.getGeom(i) vdata = geom.getVertexData() for i in range(geom.getNumPrimitives()): prim = geom.getPrimitive(i) vertex_reader = GeomVertexReader(vdata, 'vertex') prim = prim.decompose() for p in range(prim.getNumPrimitives()): s = prim.getPrimitiveStart(p) e = prim.getPrimitiveEnd(p) for i in range(s, e): vi = prim.getVertex(i) vertex_reader.setRow(vi) verts.append(vertex_reader.getData3()) for i in range(0, len(verts) - 3, 3): p = CollisionPolygon(Vec3(verts[i + 2]), Vec3(verts[i + 1]), Vec3(verts[i])) self.collision_polygons.append(p) for poly in self.collision_polygons: self.node.addSolid(poly) self.visible = False
def world_scale_x(self, value): self.setScale(base.render, Vec3(value, self.world_scale_y, self.world_scale_z))
def world_scale(self): return Vec3(*self.getScale(base.render))
def rotation_z(self, value): self.rotation = Vec3(self.rotation[0], self.rotation[1], value)
def scale(self): scale = self.getScale() return Vec3(scale[0], scale[2], scale[1])
def __init__(self, add_to_scene_entities=True, **kwargs): super().__init__(self.__class__.__name__) self.name = camel_to_snake(self.type) self.enabled = True # disabled entities wil not be visible nor run code self.visible = True self.ignore = False # if True, will not try to run code self.eternal = False # eternal entities does not get destroyed on scene.clear() self.ignore_paused = False self.ignore_input = False self.parent = scene self.add_to_scene_entities = add_to_scene_entities # set to False to be ignored by the engine, but still get rendered. if add_to_scene_entities: scene.entities.append(self) self.model = None # set model with model='model_name' (without file type extention) self.color = color.white self.texture = None # set model with texture='texture_name'. requires a model to be set beforehand. self.reflection_map = scene.reflection_map self.reflectivity = 0 self.render_queue = 0 self.double_sided = False self.always_on_top = False self.collision = False # toggle collision without changing collider. self.collider = None # set to 'box'/'sphere'/'mesh' for auto fitted collider. self.scripts = list( ) # add with add_script(class_instance). will assign an 'entity' variable to the script. self.animations = list() self.hovered = False # will return True if mouse hovers entity. self.origin = Vec3(0, 0, 0) self.position = Vec3( 0, 0, 0) # right, up, forward. can also set self.x, self.y, self.z self.rotation = Vec3( 0, 0, 0 ) # can also set self.rotation_x, self.rotation_y, self.rotation_z self.scale = Vec3( 1, 1, 1) # can also set self.scale_x, self.scale_y, self.scale_z self.line_definition = None # returns a Traceback(filename, lineno, function, code_context, index). if application.trace_entity_definition and add_to_scene_entities: from inspect import getframeinfo, stack _stack = stack() caller = getframeinfo(_stack[1][0]) if len(_stack) > 2 and _stack[ 1].code_context and 'super().__init__()' in _stack[ 1].code_context[0]: caller = getframeinfo(_stack[2][0]) self.line_definition = caller if caller.code_context: self.code_context = caller.code_context[0] if (self.code_context.count('(') == self.code_context.count(')') and ' = ' in self.code_context and not 'name=' in self.code_context and not 'Ursina()' in self.code_context): self.name = self.code_context.split( ' = ')[0].strip().replace('self.', '') # print('set name to:', self.code_context.split(' = ')[0].strip().replace('self.', '')) if application.print_entity_definition: print( f'{Path(caller.filename).name} -> {caller.lineno} -> {caller.code_context}' ) for key, value in kwargs.items(): setattr(self, key, value)
def forward(self): vec = render.getRelativeVector(self, (0, 1, 0)) return Vec3(vec[0], vec[2], vec[1])
def position(self): return Vec3(self.getX(), self.getZ(), self.getY())
def up(self): vec = render.getRelativeVector(self, (0, 0, 1)) return Vec3(vec[0], vec[2], vec[1])
def world_rotation(self): rotation = self.getHpr(base.render) return Vec3(-rotation[1], -rotation[0], rotation[2])
def get_position(self, relative_to=scene): pos = self.getPos(relative_to) return Vec3(pos[0], pos[2], pos[1])
def rotation(self): rotation = self.getHpr() return Vec3(-rotation[1], -rotation[0], rotation[2])
verts = ((0, 0, 0), (1, 0, 0), (.5, 1, 0), (-.5, 1, 0)) tris = (1, 2, 0, 2, 3, 0) uvs = ((1.0, 0.0), (0.0, 1.0), (0.0, 0.0), (1.0, 1.0)) norms = ((0, 0, -1), ) * len(verts) colors = (color.red, color.blue, color.lime, color.black) e = Entity(model=Mesh(vertices=verts, triangles=tris, uvs=uvs, normals=norms, colors=colors), scale=2) # line mesh test verts = (Vec3(0, 0, 0), Vec3(0, 1, 0), Vec3(1, 1, 0), Vec3(2, 2, 0), Vec3(0, 3, 0), Vec3(-2, 3, 0)) tris = ((0, 1), (3, 4, 5)) lines = Entity(model=Mesh(vertices=verts, triangles=tris, mode='line', thickness=4), color=color.cyan, z=-1) points = Entity(model=Mesh(vertices=verts, mode='point', thickness=.05), color=color.red, z=-1.01) # points.model.mode = MeshModes.point # can also use the MeshMode enum print(e.model.recipe) # e.model.save('bam_test', application.compressed_models_folder, 'bam')
def world_scale(self): scale = self.getScale(base.render) return Vec3(scale[0], scale[2], scale[1])
class Raycaster(Entity): line_model = Mesh(vertices=[Vec3(0, 0, 0), Vec3(0, 0, 1)], mode='line') def __init__(self): super().__init__(name='raycaster', eternal=True) self._picker = CollisionTraverser() # Make a traverser self._pq = CollisionHandlerQueue() # Make a handler self._pickerNode = CollisionNode('raycaster') self._pickerNode.set_into_collide_mask(0) self._pickerNP = self.attach_new_node(self._pickerNode) self._picker.addCollider(self._pickerNP, self._pq) self._pickerNP.show() def distance(self, a, b): return sqrt(sum((a - b)**2 for a, b in zip(a, b))) def raycast(self, origin, direction=(0, 0, 1), distance=inf, traverse_target=scene, ignore=list(), debug=False): self.position = origin self.look_at(self.position + direction) self._pickerNode.clearSolids() ray = CollisionRay() ray.setOrigin(Vec3(0, 0, 0)) ray.setDirection(Vec3(0, 0, 1)) self._pickerNode.addSolid(ray) if debug: temp = Entity(position=origin, model=Raycaster.line_model, scale=Vec3(1, 1, min(distance, 9999)), add_to_scene_entities=False) temp.look_at(self.position + direction) destroy(temp, 1 / 30) self._picker.traverse(traverse_target) if self._pq.get_num_entries() == 0: self.hit = HitInfo(hit=False, distance=distance) return self.hit ignore += tuple([e for e in scene.entities if not e.collision]) self._pq.sort_entries() self.entries = [ # filter out ignored entities e for e in self._pq.getEntries() if e.get_into_node_path().parent not in ignore and self.distance( self.world_position, Vec3( *e.get_surface_point(render))) <= distance ] if len(self.entries) == 0: self.hit = HitInfo(hit=False, distance=distance) return self.hit self.collision = self.entries[0] nP = self.collision.get_into_node_path().parent point = Vec3(*self.collision.get_surface_point(nP)) world_point = Vec3(*self.collision.get_surface_point(render)) hit_dist = self.distance(self.world_position, world_point) self.hit = HitInfo(hit=True, distance=distance) for e in scene.entities: if e == nP: # print('cast nP to Entity') self.hit.entity = e self.hit.point = point self.hit.world_point = world_point self.hit.distance = hit_dist self.hit.normal = Vec3(*self.collision.get_surface_normal( self.collision.get_into_node_path().parent).normalized()) self.hit.world_normal = Vec3( *self.collision.get_surface_normal(render).normalized()) return self.hit self.hit = HitInfo(hit=False, distance=distance) return self.hit def boxcast(self, origin, direction=(0, 0, 1), distance=9999, thickness=(1, 1), traverse_target=scene, ignore=list(), debug=False): if isinstance(thickness, (int, float, complex)): thickness = (thickness, thickness) temp = Entity(position=origin, model='cube', origin_z=-.5, scale=Vec3(abs(thickness[0]), abs(thickness[1]), abs(distance)), collider='box', color=color.white33, always_on_top=debug, visible=debug) temp.look_at(origin + direction) hit_info = temp.intersects(traverse_target=traverse_target, ignore=ignore) if hit_info.world_point: hit_info.distance = ursinamath.distance(origin, hit_info.world_point) else: hit_info.distance = distance if debug: temp.collision = False temp.scale_z = hit_info.distance destroy(temp, delay=.2) else: destroy(temp) return hit_info
def world_scale(self, value): if isinstance(value, (int, float, complex)): value = (value, value, value) self.setScale(base.render, Vec3(value[0], value[2], value[1]))
def __setattr__(self, name, value): if name == 'enabled': try: # try calling on_enable() on classes inheriting from Entity if value == True: self.on_enable() else: self.on_disable() except: pass if value == True: if not self.is_singleton(): self.unstash() else: if not self.is_singleton(): self.stash() if name == 'eternal': for c in self.children: c.eternal = value if name == 'world_parent': self.reparent_to(value) if name == 'model': if value is None: if hasattr(self, 'model') and self.model: self.model.removeNode() # print('removed model') object.__setattr__(self, name, value) return None if isinstance(value, NodePath): # pass procedural model if self.model is not None and value != self.model: self.model.removeNode() object.__setattr__(self, name, value) elif isinstance(value, str): # pass model asset name m = load_model(value, application.asset_folder) if not m: m = load_model(value, application.internal_models_folder) if m: if self.model is not None: self.model.removeNode() object.__setattr__(self, name, m) if isinstance(m, Mesh): m.recipe = value # print('loaded model successively') else: print('missing model:', value) return if self.model: self.model.reparentTo(self) self.model.setTransparency(TransparencyAttrib.M_dual) self.color = self.color # reapply color after changing model self.texture = self.texture # reapply texture after changing model self._vert_cache = None if isinstance(value, Mesh): if hasattr(value, 'on_assign'): value.on_assign(assigned_to=self) return if name == 'color' and value is not None: if not isinstance(value, Vec4): value = Vec4(value[0], value[1], value[2], value[3]) if self.model: self.model.setColorScale(value) object.__setattr__(self, name, value) if name == 'texture_scale': if self.model and self.texture: self.model.setTexScale(TextureStage.getDefault(), value[0], value[1]) if name == 'texture_offset': if self.model and self.texture: self.model.setTexOffset(TextureStage.getDefault(), value[0], value[1]) self.texture = self.texture if name == 'position': # automatically add position instead of extending the tuple new_value = Vec3() if len(value) % 2 == 0: for i in range(0, len(value), 2): new_value.add_x(value[i]) new_value.add_y(value[i + 1]) new_value.add_z(self.getY()) if len(value) % 3 == 0: for i in range(0, len(value), 3): new_value.add_x(value[i]) new_value.add_y(value[i + 1]) new_value.add_z(value[i + 2]) try: self.setPos(new_value[0], new_value[2], new_value[1]) except: pass # can't set position if name == 'x': self.setX(value) if name == 'y': self.setZ(value) if name == 'z': self.setY(value) if name == 'origin' and self.model: new_value = Vec3() if len(value) % 2 == 0: for i in range(0, len(value), 2): new_value.add_x(value[i]) new_value.add_y(value[i + 1]) new_value.add_z(self.model.getY()) if len(value) % 3 == 0: for i in range(0, len(value), 3): new_value.add_x(value[i]) new_value.add_y(value[i + 1]) new_value.add_z(value[i + 2]) self.model.setPos(-new_value[0], -new_value[2], -new_value[1]) object.__setattr__(self, name, new_value) return if name == 'rotation': new_value = Vec3() if len(value) % 2 == 0: for i in range(0, len(value), 2): new_value.add_x(value[i]) new_value.add_y(value[i + 1]) new_value.add_z(self.getR()) if len(value) % 3 == 0: for i in range(0, len(value), 3): new_value.add_x(value[i]) new_value.add_y(value[i + 1]) new_value.add_z(value[i + 2]) self.setHpr(Vec3(-new_value[1], -new_value[0], new_value[2])) if name == 'rotation_x': self.setP(-value) if name == 'rotation_y': self.setH(-value) if name == 'rotation_z': self.setR(value) if name == 'scale': if isinstance(value, (int, float, complex)): value = (value, value, value) new_value = Vec3() if len(value) % 2 == 0: for i in range(0, len(value), 2): new_value.add_x(value[i]) new_value.add_y(value[i + 1]) new_value.add_z(self.getSy()) if len(value) % 3 == 0: for i in range(0, len(value), 3): new_value.add_x(value[i]) new_value.add_y(value[i + 1]) new_value.add_z(value[i + 2]) for e in new_value: if e == 0: e = .001 self.setScale(new_value[0], new_value[2], new_value[1]) if name == 'scale_x': self.set_scale(value, self.scale_z, self.scale_y) if name == 'scale_y': self.set_scale(self.scale_x, self.scale_z, value) if name == 'scale_z': self.set_scale(self.scale_x, value, self.scale_y) if name == 'collision' and hasattr(self, 'collider') and self.collider: if value: self.collider.node_path.unstash() else: self.collider.node_path.stash() object.__setattr__(self, name, value) return if name == 'render_queue': if self.model: self.model.setBin('fixed', value) if name == 'double_sided': self.setTwoSided(value) if name == 'always_on_top': self.set_bin("fixed", 0) self.set_depth_write(False) self.set_depth_test(False) try: super().__setattr__(name, value) except: pass
def world_scale_z(self, value): self.setScale(base.render, Vec3(self.world_scale_x, value, self.world_scale_y))
def world_position(self): return Vec3(self.get_position(render))
def rotation_x(self, value): self.rotation = Vec3(value, self.rotation[1], self.rotation[2])