Module pyxyz.mesh

Mesh class definition

Expand source code
"""Mesh class definition"""
import math
import pygame
from pyxyz.vector3 import Vector3

class Mesh:
    """Mesh class.
    Stores a list of polygons to be drawn
    """
    stat_vertex_count = 0
    """Vertex count for statistics. This code that actually tracks the statistics
    is normally commented out for performance reasons (see render method)"""
    stat_transform_time = 0
    """Time spent on vertex transforming for statistics. This code that actually tracks
    the statistics is normally commented out for performance reasons (see render method)"""
    stat_render_time = 0
    """Time spent in rendering for statistics. This code that actually tracks the statistics
    is normally commented out for performance reasons (see render method)"""

    def __init__(self, name="UnknownMesh"):
        """
        Arguments:

            name {str} -- Name of the material, defaults to 'UnknownMesh'
        """
        self.name = name
        """ {str} Name of the mesh"""
        self.polygons = []
        """ {list[list[Vector3]]} List of lists of polygons. A polygon is a closed shape,
        hence the need for a list of lists, if we want more complex shapes."""

    def offset(self, v):
        """
        Offsets this mesh by a given vector. In practice, adds v to all vertex in all polygons

        Arguments:

            v {Vector3} -- Ammount to displace the mesh
        """
        new_polys = []
        for poly in self.polygons:
            new_poly = []
            for p in poly:
                new_poly.append(p + v)
            new_polys.append(new_poly)

        self.polygons = new_polys

    def render(self, screen, clip_matrix, material):
        """
        Renders the mesh.

        Arguments:

            screen {pygame.surface} -- Display surface on which to render the mesh

            clip_matrix {np.array} -- Clip matrix to use to convert the 3d local space coordinates
            of the vertices to screen coordinates.

            material {Material} -- Material to be used to render the mesh

            Note that this function has the code that tracks statistics for the vertex count and
            render times, but it is normally commented out, for performance reasons. If you want
            to use the statistics, uncomment the code on this funtion.
        """
        # Convert Color to the pygame format
        c = material.Color.tuple3()

        # For all polygons
        for poly in self.polygons:
            # Create the list that will store (temporarily) the transformed vertices
            tpoly = []
            # Uncomment next 2 lines for statistics
            #Mesh.stat_vertex_count += len(poly)
            #t0 = time.time()
            for v in poly:
                # Convert the vertex to numpy format and multiply it by the clip matrix
                vout = v.to_np4()
                vout = vout @ clip_matrix

                # Finalize the transformation by converting the point from homogeneous NDC to
                # screen coordinates (divide by w, scale it by the viewport resolution and
                # offset it)
                tpoly.append((screen.get_width() * 0.5 + vout[0] / vout[3],
                              screen.get_height() * 0.5 - vout[1] / vout[3]))

            # Uncomment next line for statistics
            #t1 = time.time()

            # Render
            pygame.draw.polygon(screen, c, tpoly, material.line_width)

            # Uncomment next 3 lines for statistics
            #t2 = time.time()
            #Mesh.stat_transform_time += (t1 - t0)
            #Mesh.stat_render_time += (t2 - t1)

    @staticmethod
    def create_cube(size, mesh=None):
        """
        Adds the 6 polygons necessary to form a cube with the given size. If a source mesh is
        not given, a new mesh is created.
        This cube will be centered on the origin (0,0,0).

        Arguments:

            size {3-tuple} -- (x,y,z) size of the cube

            mesh {Mesh} -- Mesh to add the polygons. If not given, create a new mesh

        Returns:
            {Mesh} - Mesh where the polygons were added
        """

        # Create mesh if one was not given
        if mesh is None:
            mesh = Mesh("UnknownCube")

        # Add the 6 quads that create a cube
        Mesh.create_quad(Vector3(size[0] * 0.5, 0, 0),
                         Vector3(0, -size[1] * 0.5, 0),
                         Vector3(0, 0, size[2] * 0.5), mesh)
        Mesh.create_quad(Vector3(-size[0] * 0.5, 0, 0),
                         Vector3(0, size[1] * 0.5, 0),
                         Vector3(0, 0, size[2] * 0.5), mesh)

        Mesh.create_quad(Vector3(0, size[1] * 0.5, 0),
                         Vector3(size[0] * 0.5, 0),
                         Vector3(0, 0, size[2] * 0.5), mesh)
        Mesh.create_quad(Vector3(0, -size[1] * 0.5, 0),
                         Vector3(-size[0] * 0.5, 0),
                         Vector3(0, 0, size[2] * 0.5), mesh)

        Mesh.create_quad(Vector3(0, 0, size[2] * 0.5),
                         Vector3(-size[0] * 0.5, 0),
                         Vector3(0, size[1] * 0.5, 0), mesh)
        Mesh.create_quad(Vector3(0, 0, -size[2] * 0.5),
                         Vector3(size[0] * 0.5, 0),
                         Vector3(0, size[1] * 0.5, 0), mesh)

        return mesh

    @staticmethod
    def create_sphere(size, res_lat, res_lon, mesh=None):
        """
        Adds the polygons necessary to form a sphere with the given size and resolution.
         If a source mesh is not given, a new mesh is created.
        This sphere will be centered on the origin (0,0,0).

        Arguments:

            size {3-tuple} -- (x,y,z) size of the sphere
            or
            size {number} -- radius of the sphere

            res_lat {int} -- Number of subdivisions in the latitude axis

            res_lon {int} -- Number of subdivisions in the longitudinal axis

            mesh {Mesh} -- Mesh to add the polygons. If not given, create a new mesh

        Returns:
            {Mesh} - Mesh where the polygons were added
        """
        # Create mesh if one was not given
        if mesh is None:
            mesh = Mesh("UnknownSphere")

        # Compute half-size
        if isinstance(size, Vector3):
            hs = size * 0.5
        else:
            hs = Vector3(size[0], size[1], size[2]) * 0.5

        # Sphere is going to be composed by quads in most of the surface, but triangles near the
        # poles, so compute the bottom and top vertex
        bottom_vertex = Vector3(0, -hs.y, 0)
        top_vertex = Vector3(0, hs.y, 0)

        lat_inc = math.pi / res_lat
        lon_inc = math.pi * 2 / res_lon
        # First row of triangles
        lat = -math.pi / 2
        lon = 0

        y = hs.y * math.sin(lat + lat_inc)
        c = math.cos(lat + lat_inc)
        for _ in range(0, res_lon):
            p1 = Vector3(c * math.cos(lon) * hs.x, y, c * math.sin(lon) * hs.z)
            p2 = Vector3(c * math.cos(lon + lon_inc) * hs.x, y, c * math.sin(lon + lon_inc) * hs.z)

            Mesh.create_tri(bottom_vertex, p1, p2, mesh)

            lon += lon_inc

        # Quads in the middle
        for _ in range(1, res_lat - 1):
            lat += lat_inc

            y1 = hs.y * math.sin(lat)
            y2 = hs.y * math.sin(lat + lat_inc)
            c1 = math.cos(lat)
            c2 = math.cos(lat + lat_inc)

            lon = 0
            for _ in range(0, res_lon):
                p1 = Vector3(c1 * math.cos(lon) * hs.x,
                             y1,
                             c1 * math.sin(lon) * hs.z)
                p2 = Vector3(c1 * math.cos(lon + lon_inc) * hs.x,
                             y1,
                             c1 * math.sin(lon + lon_inc) * hs.z)
                p3 = Vector3(c2 * math.cos(lon) * hs.x,
                             y2,
                             c2 * math.sin(lon) * hs.z)
                p4 = Vector3(c2 * math.cos(lon + lon_inc) * hs.x,
                             y2,
                             c2 * math.sin(lon + lon_inc) * hs.z)

                poly = []
                poly.append(p1)
                poly.append(p2)
                poly.append(p4)
                poly.append(p3)

                mesh.polygons.append(poly)

                lon += lon_inc

        # Last row of triangles
        lat += lat_inc
        y = hs.y * math.sin(lat)
        c = math.cos(lat)
        for _ in range(0, res_lon):
            p1 = Vector3(c * math.cos(lon) * hs.x, y, c * math.sin(lon) * hs.z)
            p2 = Vector3(c * math.cos(lon + lon_inc) * hs.x, y, c * math.sin(lon + lon_inc) * hs.z)

            Mesh.create_tri(top_vertex, p1, p2, mesh)

            lon += lon_inc

        return mesh


    @staticmethod
    def create_quad(origin, axis0, axis1, mesh):
        """
        Adds the vertices necessary to create a quad (4 sided coplanar rectangle).
        If a source mesh is not given, a new mesh is created.

        Arguments:

            origin {Vector3} -- Center of the quad

            axis0 {Vector3} -- One of the axis of the quad. This is not normalized, since the
            length specifies half the length of that side along that axis

            axis1 {Vector3} -- One of the axis of the quad. This is not normalized, since the
            length specifies half the length of that side along that axis

            mesh {Mesh} -- Mesh to add the polygons. If not given, create a new mesh

        Returns:
            {Mesh} - Mesh where the polygons were added
        """
        if mesh is None:
            mesh = Mesh("UnknownQuad")

        poly = []
        poly.append(origin + axis0 + axis1)
        poly.append(origin + axis0 - axis1)
        poly.append(origin - axis0 - axis1)
        poly.append(origin - axis0 + axis1)

        mesh.polygons.append(poly)

        return mesh

    @staticmethod
    def create_tri(p1, p2, p3, mesh):
        """
        Adds the vertices necessary to create a triangle
        If a source mesh is not given, a new mesh is created.

        Arguments:

            p1 {Vector3} -- First vertex of the triangle

            p2 {Vector3} -- Second vertex of the triangle

            p3 {Vector3} -- Third vertex of the triangle

            mesh {Mesh} -- Mesh to add the polygons. If not given, create a new mesh

        Returns:
            {Mesh} - Mesh where the polygons were added
        """
        if mesh is None:
            mesh = Mesh("UnknownQuad")

        poly = []
        poly.append(p1)
        poly.append(p2)
        poly.append(p3)

        mesh.polygons.append(poly)

        return mesh

Classes

class Mesh (name='UnknownMesh')

Mesh class. Stores a list of polygons to be drawn

Arguments

name {str} – Name of the material, defaults to 'UnknownMesh'

Expand source code
class Mesh:
    """Mesh class.
    Stores a list of polygons to be drawn
    """
    stat_vertex_count = 0
    """Vertex count for statistics. This code that actually tracks the statistics
    is normally commented out for performance reasons (see render method)"""
    stat_transform_time = 0
    """Time spent on vertex transforming for statistics. This code that actually tracks
    the statistics is normally commented out for performance reasons (see render method)"""
    stat_render_time = 0
    """Time spent in rendering for statistics. This code that actually tracks the statistics
    is normally commented out for performance reasons (see render method)"""

    def __init__(self, name="UnknownMesh"):
        """
        Arguments:

            name {str} -- Name of the material, defaults to 'UnknownMesh'
        """
        self.name = name
        """ {str} Name of the mesh"""
        self.polygons = []
        """ {list[list[Vector3]]} List of lists of polygons. A polygon is a closed shape,
        hence the need for a list of lists, if we want more complex shapes."""

    def offset(self, v):
        """
        Offsets this mesh by a given vector. In practice, adds v to all vertex in all polygons

        Arguments:

            v {Vector3} -- Ammount to displace the mesh
        """
        new_polys = []
        for poly in self.polygons:
            new_poly = []
            for p in poly:
                new_poly.append(p + v)
            new_polys.append(new_poly)

        self.polygons = new_polys

    def render(self, screen, clip_matrix, material):
        """
        Renders the mesh.

        Arguments:

            screen {pygame.surface} -- Display surface on which to render the mesh

            clip_matrix {np.array} -- Clip matrix to use to convert the 3d local space coordinates
            of the vertices to screen coordinates.

            material {Material} -- Material to be used to render the mesh

            Note that this function has the code that tracks statistics for the vertex count and
            render times, but it is normally commented out, for performance reasons. If you want
            to use the statistics, uncomment the code on this funtion.
        """
        # Convert Color to the pygame format
        c = material.Color.tuple3()

        # For all polygons
        for poly in self.polygons:
            # Create the list that will store (temporarily) the transformed vertices
            tpoly = []
            # Uncomment next 2 lines for statistics
            #Mesh.stat_vertex_count += len(poly)
            #t0 = time.time()
            for v in poly:
                # Convert the vertex to numpy format and multiply it by the clip matrix
                vout = v.to_np4()
                vout = vout @ clip_matrix

                # Finalize the transformation by converting the point from homogeneous NDC to
                # screen coordinates (divide by w, scale it by the viewport resolution and
                # offset it)
                tpoly.append((screen.get_width() * 0.5 + vout[0] / vout[3],
                              screen.get_height() * 0.5 - vout[1] / vout[3]))

            # Uncomment next line for statistics
            #t1 = time.time()

            # Render
            pygame.draw.polygon(screen, c, tpoly, material.line_width)

            # Uncomment next 3 lines for statistics
            #t2 = time.time()
            #Mesh.stat_transform_time += (t1 - t0)
            #Mesh.stat_render_time += (t2 - t1)

    @staticmethod
    def create_cube(size, mesh=None):
        """
        Adds the 6 polygons necessary to form a cube with the given size. If a source mesh is
        not given, a new mesh is created.
        This cube will be centered on the origin (0,0,0).

        Arguments:

            size {3-tuple} -- (x,y,z) size of the cube

            mesh {Mesh} -- Mesh to add the polygons. If not given, create a new mesh

        Returns:
            {Mesh} - Mesh where the polygons were added
        """

        # Create mesh if one was not given
        if mesh is None:
            mesh = Mesh("UnknownCube")

        # Add the 6 quads that create a cube
        Mesh.create_quad(Vector3(size[0] * 0.5, 0, 0),
                         Vector3(0, -size[1] * 0.5, 0),
                         Vector3(0, 0, size[2] * 0.5), mesh)
        Mesh.create_quad(Vector3(-size[0] * 0.5, 0, 0),
                         Vector3(0, size[1] * 0.5, 0),
                         Vector3(0, 0, size[2] * 0.5), mesh)

        Mesh.create_quad(Vector3(0, size[1] * 0.5, 0),
                         Vector3(size[0] * 0.5, 0),
                         Vector3(0, 0, size[2] * 0.5), mesh)
        Mesh.create_quad(Vector3(0, -size[1] * 0.5, 0),
                         Vector3(-size[0] * 0.5, 0),
                         Vector3(0, 0, size[2] * 0.5), mesh)

        Mesh.create_quad(Vector3(0, 0, size[2] * 0.5),
                         Vector3(-size[0] * 0.5, 0),
                         Vector3(0, size[1] * 0.5, 0), mesh)
        Mesh.create_quad(Vector3(0, 0, -size[2] * 0.5),
                         Vector3(size[0] * 0.5, 0),
                         Vector3(0, size[1] * 0.5, 0), mesh)

        return mesh

    @staticmethod
    def create_sphere(size, res_lat, res_lon, mesh=None):
        """
        Adds the polygons necessary to form a sphere with the given size and resolution.
         If a source mesh is not given, a new mesh is created.
        This sphere will be centered on the origin (0,0,0).

        Arguments:

            size {3-tuple} -- (x,y,z) size of the sphere
            or
            size {number} -- radius of the sphere

            res_lat {int} -- Number of subdivisions in the latitude axis

            res_lon {int} -- Number of subdivisions in the longitudinal axis

            mesh {Mesh} -- Mesh to add the polygons. If not given, create a new mesh

        Returns:
            {Mesh} - Mesh where the polygons were added
        """
        # Create mesh if one was not given
        if mesh is None:
            mesh = Mesh("UnknownSphere")

        # Compute half-size
        if isinstance(size, Vector3):
            hs = size * 0.5
        else:
            hs = Vector3(size[0], size[1], size[2]) * 0.5

        # Sphere is going to be composed by quads in most of the surface, but triangles near the
        # poles, so compute the bottom and top vertex
        bottom_vertex = Vector3(0, -hs.y, 0)
        top_vertex = Vector3(0, hs.y, 0)

        lat_inc = math.pi / res_lat
        lon_inc = math.pi * 2 / res_lon
        # First row of triangles
        lat = -math.pi / 2
        lon = 0

        y = hs.y * math.sin(lat + lat_inc)
        c = math.cos(lat + lat_inc)
        for _ in range(0, res_lon):
            p1 = Vector3(c * math.cos(lon) * hs.x, y, c * math.sin(lon) * hs.z)
            p2 = Vector3(c * math.cos(lon + lon_inc) * hs.x, y, c * math.sin(lon + lon_inc) * hs.z)

            Mesh.create_tri(bottom_vertex, p1, p2, mesh)

            lon += lon_inc

        # Quads in the middle
        for _ in range(1, res_lat - 1):
            lat += lat_inc

            y1 = hs.y * math.sin(lat)
            y2 = hs.y * math.sin(lat + lat_inc)
            c1 = math.cos(lat)
            c2 = math.cos(lat + lat_inc)

            lon = 0
            for _ in range(0, res_lon):
                p1 = Vector3(c1 * math.cos(lon) * hs.x,
                             y1,
                             c1 * math.sin(lon) * hs.z)
                p2 = Vector3(c1 * math.cos(lon + lon_inc) * hs.x,
                             y1,
                             c1 * math.sin(lon + lon_inc) * hs.z)
                p3 = Vector3(c2 * math.cos(lon) * hs.x,
                             y2,
                             c2 * math.sin(lon) * hs.z)
                p4 = Vector3(c2 * math.cos(lon + lon_inc) * hs.x,
                             y2,
                             c2 * math.sin(lon + lon_inc) * hs.z)

                poly = []
                poly.append(p1)
                poly.append(p2)
                poly.append(p4)
                poly.append(p3)

                mesh.polygons.append(poly)

                lon += lon_inc

        # Last row of triangles
        lat += lat_inc
        y = hs.y * math.sin(lat)
        c = math.cos(lat)
        for _ in range(0, res_lon):
            p1 = Vector3(c * math.cos(lon) * hs.x, y, c * math.sin(lon) * hs.z)
            p2 = Vector3(c * math.cos(lon + lon_inc) * hs.x, y, c * math.sin(lon + lon_inc) * hs.z)

            Mesh.create_tri(top_vertex, p1, p2, mesh)

            lon += lon_inc

        return mesh


    @staticmethod
    def create_quad(origin, axis0, axis1, mesh):
        """
        Adds the vertices necessary to create a quad (4 sided coplanar rectangle).
        If a source mesh is not given, a new mesh is created.

        Arguments:

            origin {Vector3} -- Center of the quad

            axis0 {Vector3} -- One of the axis of the quad. This is not normalized, since the
            length specifies half the length of that side along that axis

            axis1 {Vector3} -- One of the axis of the quad. This is not normalized, since the
            length specifies half the length of that side along that axis

            mesh {Mesh} -- Mesh to add the polygons. If not given, create a new mesh

        Returns:
            {Mesh} - Mesh where the polygons were added
        """
        if mesh is None:
            mesh = Mesh("UnknownQuad")

        poly = []
        poly.append(origin + axis0 + axis1)
        poly.append(origin + axis0 - axis1)
        poly.append(origin - axis0 - axis1)
        poly.append(origin - axis0 + axis1)

        mesh.polygons.append(poly)

        return mesh

    @staticmethod
    def create_tri(p1, p2, p3, mesh):
        """
        Adds the vertices necessary to create a triangle
        If a source mesh is not given, a new mesh is created.

        Arguments:

            p1 {Vector3} -- First vertex of the triangle

            p2 {Vector3} -- Second vertex of the triangle

            p3 {Vector3} -- Third vertex of the triangle

            mesh {Mesh} -- Mesh to add the polygons. If not given, create a new mesh

        Returns:
            {Mesh} - Mesh where the polygons were added
        """
        if mesh is None:
            mesh = Mesh("UnknownQuad")

        poly = []
        poly.append(p1)
        poly.append(p2)
        poly.append(p3)

        mesh.polygons.append(poly)

        return mesh

Class variables

var stat_render_time

Time spent in rendering for statistics. This code that actually tracks the statistics is normally commented out for performance reasons (see render method)

var stat_transform_time

Time spent on vertex transforming for statistics. This code that actually tracks the statistics is normally commented out for performance reasons (see render method)

var stat_vertex_count

Vertex count for statistics. This code that actually tracks the statistics is normally commented out for performance reasons (see render method)

Static methods

def create_cube(size, mesh=None)

Adds the 6 polygons necessary to form a cube with the given size. If a source mesh is not given, a new mesh is created. This cube will be centered on the origin (0,0,0).

Arguments

size {3-tuple} – (x,y,z) size of the cube

mesh {Mesh} – Mesh to add the polygons. If not given, create a new mesh

Returns

{Mesh} - Mesh where the polygons were added

Expand source code
@staticmethod
def create_cube(size, mesh=None):
    """
    Adds the 6 polygons necessary to form a cube with the given size. If a source mesh is
    not given, a new mesh is created.
    This cube will be centered on the origin (0,0,0).

    Arguments:

        size {3-tuple} -- (x,y,z) size of the cube

        mesh {Mesh} -- Mesh to add the polygons. If not given, create a new mesh

    Returns:
        {Mesh} - Mesh where the polygons were added
    """

    # Create mesh if one was not given
    if mesh is None:
        mesh = Mesh("UnknownCube")

    # Add the 6 quads that create a cube
    Mesh.create_quad(Vector3(size[0] * 0.5, 0, 0),
                     Vector3(0, -size[1] * 0.5, 0),
                     Vector3(0, 0, size[2] * 0.5), mesh)
    Mesh.create_quad(Vector3(-size[0] * 0.5, 0, 0),
                     Vector3(0, size[1] * 0.5, 0),
                     Vector3(0, 0, size[2] * 0.5), mesh)

    Mesh.create_quad(Vector3(0, size[1] * 0.5, 0),
                     Vector3(size[0] * 0.5, 0),
                     Vector3(0, 0, size[2] * 0.5), mesh)
    Mesh.create_quad(Vector3(0, -size[1] * 0.5, 0),
                     Vector3(-size[0] * 0.5, 0),
                     Vector3(0, 0, size[2] * 0.5), mesh)

    Mesh.create_quad(Vector3(0, 0, size[2] * 0.5),
                     Vector3(-size[0] * 0.5, 0),
                     Vector3(0, size[1] * 0.5, 0), mesh)
    Mesh.create_quad(Vector3(0, 0, -size[2] * 0.5),
                     Vector3(size[0] * 0.5, 0),
                     Vector3(0, size[1] * 0.5, 0), mesh)

    return mesh
def create_quad(origin, axis0, axis1, mesh)

Adds the vertices necessary to create a quad (4 sided coplanar rectangle). If a source mesh is not given, a new mesh is created.

Arguments

origin {Vector3} – Center of the quad

axis0 {Vector3} – One of the axis of the quad. This is not normalized, since the length specifies half the length of that side along that axis

axis1 {Vector3} – One of the axis of the quad. This is not normalized, since the length specifies half the length of that side along that axis

mesh {Mesh} – Mesh to add the polygons. If not given, create a new mesh

Returns

{Mesh} - Mesh where the polygons were added

Expand source code
@staticmethod
def create_quad(origin, axis0, axis1, mesh):
    """
    Adds the vertices necessary to create a quad (4 sided coplanar rectangle).
    If a source mesh is not given, a new mesh is created.

    Arguments:

        origin {Vector3} -- Center of the quad

        axis0 {Vector3} -- One of the axis of the quad. This is not normalized, since the
        length specifies half the length of that side along that axis

        axis1 {Vector3} -- One of the axis of the quad. This is not normalized, since the
        length specifies half the length of that side along that axis

        mesh {Mesh} -- Mesh to add the polygons. If not given, create a new mesh

    Returns:
        {Mesh} - Mesh where the polygons were added
    """
    if mesh is None:
        mesh = Mesh("UnknownQuad")

    poly = []
    poly.append(origin + axis0 + axis1)
    poly.append(origin + axis0 - axis1)
    poly.append(origin - axis0 - axis1)
    poly.append(origin - axis0 + axis1)

    mesh.polygons.append(poly)

    return mesh
def create_sphere(size, res_lat, res_lon, mesh=None)

Adds the polygons necessary to form a sphere with the given size and resolution. If a source mesh is not given, a new mesh is created. This sphere will be centered on the origin (0,0,0).

Arguments

size {3-tuple} – (x,y,z) size of the sphere or size {number} – radius of the sphere

res_lat {int} – Number of subdivisions in the latitude axis

res_lon {int} – Number of subdivisions in the longitudinal axis

mesh {Mesh} – Mesh to add the polygons. If not given, create a new mesh

Returns

{Mesh} - Mesh where the polygons were added

Expand source code
@staticmethod
def create_sphere(size, res_lat, res_lon, mesh=None):
    """
    Adds the polygons necessary to form a sphere with the given size and resolution.
     If a source mesh is not given, a new mesh is created.
    This sphere will be centered on the origin (0,0,0).

    Arguments:

        size {3-tuple} -- (x,y,z) size of the sphere
        or
        size {number} -- radius of the sphere

        res_lat {int} -- Number of subdivisions in the latitude axis

        res_lon {int} -- Number of subdivisions in the longitudinal axis

        mesh {Mesh} -- Mesh to add the polygons. If not given, create a new mesh

    Returns:
        {Mesh} - Mesh where the polygons were added
    """
    # Create mesh if one was not given
    if mesh is None:
        mesh = Mesh("UnknownSphere")

    # Compute half-size
    if isinstance(size, Vector3):
        hs = size * 0.5
    else:
        hs = Vector3(size[0], size[1], size[2]) * 0.5

    # Sphere is going to be composed by quads in most of the surface, but triangles near the
    # poles, so compute the bottom and top vertex
    bottom_vertex = Vector3(0, -hs.y, 0)
    top_vertex = Vector3(0, hs.y, 0)

    lat_inc = math.pi / res_lat
    lon_inc = math.pi * 2 / res_lon
    # First row of triangles
    lat = -math.pi / 2
    lon = 0

    y = hs.y * math.sin(lat + lat_inc)
    c = math.cos(lat + lat_inc)
    for _ in range(0, res_lon):
        p1 = Vector3(c * math.cos(lon) * hs.x, y, c * math.sin(lon) * hs.z)
        p2 = Vector3(c * math.cos(lon + lon_inc) * hs.x, y, c * math.sin(lon + lon_inc) * hs.z)

        Mesh.create_tri(bottom_vertex, p1, p2, mesh)

        lon += lon_inc

    # Quads in the middle
    for _ in range(1, res_lat - 1):
        lat += lat_inc

        y1 = hs.y * math.sin(lat)
        y2 = hs.y * math.sin(lat + lat_inc)
        c1 = math.cos(lat)
        c2 = math.cos(lat + lat_inc)

        lon = 0
        for _ in range(0, res_lon):
            p1 = Vector3(c1 * math.cos(lon) * hs.x,
                         y1,
                         c1 * math.sin(lon) * hs.z)
            p2 = Vector3(c1 * math.cos(lon + lon_inc) * hs.x,
                         y1,
                         c1 * math.sin(lon + lon_inc) * hs.z)
            p3 = Vector3(c2 * math.cos(lon) * hs.x,
                         y2,
                         c2 * math.sin(lon) * hs.z)
            p4 = Vector3(c2 * math.cos(lon + lon_inc) * hs.x,
                         y2,
                         c2 * math.sin(lon + lon_inc) * hs.z)

            poly = []
            poly.append(p1)
            poly.append(p2)
            poly.append(p4)
            poly.append(p3)

            mesh.polygons.append(poly)

            lon += lon_inc

    # Last row of triangles
    lat += lat_inc
    y = hs.y * math.sin(lat)
    c = math.cos(lat)
    for _ in range(0, res_lon):
        p1 = Vector3(c * math.cos(lon) * hs.x, y, c * math.sin(lon) * hs.z)
        p2 = Vector3(c * math.cos(lon + lon_inc) * hs.x, y, c * math.sin(lon + lon_inc) * hs.z)

        Mesh.create_tri(top_vertex, p1, p2, mesh)

        lon += lon_inc

    return mesh
def create_tri(p1, p2, p3, mesh)

Adds the vertices necessary to create a triangle If a source mesh is not given, a new mesh is created.

Arguments

p1 {Vector3} – First vertex of the triangle

p2 {Vector3} – Second vertex of the triangle

p3 {Vector3} – Third vertex of the triangle

mesh {Mesh} – Mesh to add the polygons. If not given, create a new mesh

Returns

{Mesh} - Mesh where the polygons were added

Expand source code
@staticmethod
def create_tri(p1, p2, p3, mesh):
    """
    Adds the vertices necessary to create a triangle
    If a source mesh is not given, a new mesh is created.

    Arguments:

        p1 {Vector3} -- First vertex of the triangle

        p2 {Vector3} -- Second vertex of the triangle

        p3 {Vector3} -- Third vertex of the triangle

        mesh {Mesh} -- Mesh to add the polygons. If not given, create a new mesh

    Returns:
        {Mesh} - Mesh where the polygons were added
    """
    if mesh is None:
        mesh = Mesh("UnknownQuad")

    poly = []
    poly.append(p1)
    poly.append(p2)
    poly.append(p3)

    mesh.polygons.append(poly)

    return mesh

Instance variables

var name

{str} Name of the mesh

var polygons

{list[list[Vector3]]} List of lists of polygons. A polygon is a closed shape, hence the need for a list of lists, if we want more complex shapes.

Methods

def offset(self, v)

Offsets this mesh by a given vector. In practice, adds v to all vertex in all polygons

Arguments

v {Vector3} – Ammount to displace the mesh

Expand source code
def offset(self, v):
    """
    Offsets this mesh by a given vector. In practice, adds v to all vertex in all polygons

    Arguments:

        v {Vector3} -- Ammount to displace the mesh
    """
    new_polys = []
    for poly in self.polygons:
        new_poly = []
        for p in poly:
            new_poly.append(p + v)
        new_polys.append(new_poly)

    self.polygons = new_polys
def render(self, screen, clip_matrix, material)

Renders the mesh.

Arguments

screen {pygame.surface} – Display surface on which to render the mesh

clip_matrix {np.array} – Clip matrix to use to convert the 3d local space coordinates of the vertices to screen coordinates.

material {Material} – Material to be used to render the mesh

Note that this function has the code that tracks statistics for the vertex count and render times, but it is normally commented out, for performance reasons. If you want to use the statistics, uncomment the code on this funtion.

Expand source code
def render(self, screen, clip_matrix, material):
    """
    Renders the mesh.

    Arguments:

        screen {pygame.surface} -- Display surface on which to render the mesh

        clip_matrix {np.array} -- Clip matrix to use to convert the 3d local space coordinates
        of the vertices to screen coordinates.

        material {Material} -- Material to be used to render the mesh

        Note that this function has the code that tracks statistics for the vertex count and
        render times, but it is normally commented out, for performance reasons. If you want
        to use the statistics, uncomment the code on this funtion.
    """
    # Convert Color to the pygame format
    c = material.Color.tuple3()

    # For all polygons
    for poly in self.polygons:
        # Create the list that will store (temporarily) the transformed vertices
        tpoly = []
        # Uncomment next 2 lines for statistics
        #Mesh.stat_vertex_count += len(poly)
        #t0 = time.time()
        for v in poly:
            # Convert the vertex to numpy format and multiply it by the clip matrix
            vout = v.to_np4()
            vout = vout @ clip_matrix

            # Finalize the transformation by converting the point from homogeneous NDC to
            # screen coordinates (divide by w, scale it by the viewport resolution and
            # offset it)
            tpoly.append((screen.get_width() * 0.5 + vout[0] / vout[3],
                          screen.get_height() * 0.5 - vout[1] / vout[3]))

        # Uncomment next line for statistics
        #t1 = time.time()

        # Render
        pygame.draw.polygon(screen, c, tpoly, material.line_width)