Source code for arcade.gl.geometry

"""
A module providing commonly used geometry
"""

from __future__ import annotations

import math
from array import array

from arcade.gl import BufferDescription, Context
from arcade.gl.vertex_array import Geometry


def _get_active_context() -> Context:
    """
    Get the global active context.

    The gl module is forbidden to reach outside of its own module.
    so we can't use ``arcade.get_window().ctx`` here.
    """
    ctx = Context.active
    if not ctx:
        raise RuntimeError("No context is currently activated")
    return ctx


[docs] def quad_2d_fs() -> Geometry: """Creates a screen aligned quad using normalized device coordinates""" return quad_2d(size=(2.0, 2.0))
[docs] def quad_2d( size: tuple[float, float] = (1.0, 1.0), pos: tuple[float, float] = (0.0, 0.0) ) -> Geometry: """ Creates 2D quad Geometry using 2 triangle strip with texture coordinates. By default a unit quad is created with the center at (0, 0). Args: size: width and height of the quad pos: Center position x and y """ ctx = _get_active_context() width, height = size x_pos, y_pos = pos # fmt: off data = array('f', [ x_pos - width / 2.0, y_pos + height / 2.0, 0.0, 1.0, x_pos - width / 2.0, y_pos - height / 2.0, 0.0, 0.0, x_pos + width / 2.0, y_pos + height / 2.0, 1.0, 1.0, x_pos + width / 2.0, y_pos - height / 2.0, 1.0, 0.0, ]) # fmt: on return ctx.geometry( [ BufferDescription( ctx.buffer(data=data), "2f 2f", ["in_vert", "in_uv"], ) ], mode=ctx.TRIANGLE_STRIP, )
[docs] def screen_rectangle( bottom_left_x: float, bottom_left_y: float, width: float, height: float ) -> Geometry: """ Creates screen rectangle using 2 triangle strip with texture coordinates. This can create a rectangle in normalized device coordinates or projection space. Args: bottom_left_x: Bottom left x position bottom_left_y: Bottom left y position width: Width of the rectangle height: Height of the rectangle """ ctx = _get_active_context() # fmt: off data = array('f', [ bottom_left_x, bottom_left_y + height, 0.0, 1.0, bottom_left_x, bottom_left_y, 0.0, 0.0, bottom_left_x + width, bottom_left_y + height, 1.0, 1.0, bottom_left_x + width, bottom_left_y, 1.0, 0.0, ]) # fmt: on return ctx.geometry( [ BufferDescription( ctx.buffer(data=data), "2f 2f", ["in_vert", "in_uv"], ) ], mode=ctx.TRIANGLE_STRIP, )
[docs] def cube( size: tuple[float, float, float] = (1.0, 1.0, 1.0), center: tuple[float, float, float] = (0.0, 0.0, 0.0), ) -> Geometry: """ Creates a cube with normals and texture coordinates. By default a unit cube is created with the center at (0, 0, 0). Args: size: Size of the cube as a 3-component tuple center: Center of the cube as a 3-component tuple Returns: A Geometry instance containing a cube """ ctx = _get_active_context() width, height, depth = size width, height, depth = width / 2.0, height / 2.0, depth / 2.0 # fmt: off pos = array('f', [ center[0] + width, center[1] - height, center[2] + depth, center[0] + width, center[1] + height, center[2] + depth, center[0] - width, center[1] - height, center[2] + depth, center[0] + width, center[1] + height, center[2] + depth, center[0] - width, center[1] + height, center[2] + depth, center[0] - width, center[1] - height, center[2] + depth, center[0] + width, center[1] - height, center[2] - depth, center[0] + width, center[1] + height, center[2] - depth, center[0] + width, center[1] - height, center[2] + depth, center[0] + width, center[1] + height, center[2] - depth, center[0] + width, center[1] + height, center[2] + depth, center[0] + width, center[1] - height, center[2] + depth, center[0] + width, center[1] - height, center[2] - depth, center[0] + width, center[1] - height, center[2] + depth, center[0] - width, center[1] - height, center[2] + depth, center[0] + width, center[1] - height, center[2] - depth, center[0] - width, center[1] - height, center[2] + depth, center[0] - width, center[1] - height, center[2] - depth, center[0] - width, center[1] - height, center[2] + depth, center[0] - width, center[1] + height, center[2] + depth, center[0] - width, center[1] + height, center[2] - depth, center[0] - width, center[1] - height, center[2] + depth, center[0] - width, center[1] + height, center[2] - depth, center[0] - width, center[1] - height, center[2] - depth, center[0] + width, center[1] + height, center[2] - depth, center[0] + width, center[1] - height, center[2] - depth, center[0] - width, center[1] - height, center[2] - depth, center[0] + width, center[1] + height, center[2] - depth, center[0] - width, center[1] - height, center[2] - depth, center[0] - width, center[1] + height, center[2] - depth, center[0] + width, center[1] + height, center[2] - depth, center[0] - width, center[1] + height, center[2] - depth, center[0] + width, center[1] + height, center[2] + depth, center[0] - width, center[1] + height, center[2] - depth, center[0] - width, center[1] + height, center[2] + depth, center[0] + width, center[1] + height, center[2] + depth, ]) normal = array('f', [ -0, 0, 1, -0, 0, 1, -0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, -1, -0, 0, -1, -0, 0, -1, -0, 0, -1, -0, 0, -1, -0, 0, -1, -0, 0, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 0, -1, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, ]) uv = array('f', [ 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 0, 1, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 1, 0, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 1, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 1, 0, 1, 1, 0, 0, 1, 0, 0, 1, 0 ]) # fmt: on return ctx.geometry( [ BufferDescription(ctx.buffer(data=pos), "3f", ["in_position"]), BufferDescription(ctx.buffer(data=normal), "3f", ["in_normal"]), BufferDescription(ctx.buffer(data=uv), "2f", ["in_uv"]), ] )
def sphere( radius=0.5, sectors=32, rings=16, normals=True, uvs=True, ) -> Geometry: """ Creates a 3D sphere. Args: radius: Radius or the sphere rings: number or horizontal rings sectors: number of vertical segments normals: Include normals uvs: Include texture coordinates Returns: A Geometry instance containing a sphere """ ctx = _get_active_context() R = 1.0 / (rings - 1) S = 1.0 / (sectors - 1) vertices = [0.0] * (rings * sectors * 3) normals = [0.0] * (rings * sectors * 3) uvs = [0.0] * (rings * sectors * 2) v, n, t = 0, 0, 0 for r in range(rings): for s in range(sectors): y = math.sin(-math.pi / 2 + math.pi * r * R) x = math.cos(2 * math.pi * s * S) * math.sin(math.pi * r * R) z = math.sin(2 * math.pi * s * S) * math.sin(math.pi * r * R) uvs[t] = s * S uvs[t + 1] = r * R vertices[v] = x * radius vertices[v + 1] = y * radius vertices[v + 2] = z * radius normals[n] = x normals[n + 1] = y normals[n + 2] = z t += 2 v += 3 n += 3 indices = [0] * rings * sectors * 6 i = 0 for r in range(rings - 1): for s in range(sectors - 1): indices[i] = r * sectors + s indices[i + 1] = (r + 1) * sectors + (s + 1) indices[i + 2] = r * sectors + (s + 1) indices[i + 3] = r * sectors + s indices[i + 4] = (r + 1) * sectors + s indices[i + 5] = (r + 1) * sectors + (s + 1) i += 6 content = [ BufferDescription(ctx.buffer(data=array("f", vertices)), "3f", ["in_position"]), ] if normals: content.append(BufferDescription(ctx.buffer(data=array("f", normals)), "3f", ["in_normal"])) if uvs: content.append(BufferDescription(ctx.buffer(data=array("f", uvs)), "2f", ["in_uv"])) return ctx.geometry( content, index_buffer=ctx.buffer(data=array("I", indices)), mode=ctx.TRIANGLES, )