Skip to main content

Basic 3D world playground with animations and completely from scratch.

Project description

3D Playground - on Python from scratch.

Pygame integration example

TL;DR: Basic 3D world playground with animations and camera completely from scratch(only 2D pixels).

This implementation / API only for demonstration and playground purposes based on Perspective projection.
Can be used on top of any 2d graphics engine/lib(frame buffers, sdl and etc.)

Not implemented features due to low performance:

  • Face clipping not implemented, vertices clipping ignored too
  • Flat shading and Gouraud shading not implemented.
  • Z-buffering

models.Model API is open demonstration of MVP model and is definitely a good starting point/topic for 3D graphics.

Also you can plot any function on 3D scene.

How to use

There is only one requirement - to provide 2D pixel and line renderer(drawer)

As current example uses pygame:

from play3d.three_d import Device
import pygame

# our adapter will rely on pygame renderer
put_pixel = lambda x, y, color: pygame.draw.circle(screen, color, (x, y), 1)
# we certainly can draw lines ourselves using put_pixel three_d.drawline
# but implementation below - much faster
line_adapter = lambda p1, p2, color: pygame.draw.line(screen, color, (p1[x], p1[y]), (p2[x], p2[y]), 1)

width, height = 1024, 768 # should be same as 2D provider 
Device.viewport(width, height)
Device.set_renderer(put_pixel, line_adapter)
screen = pygame.display.set_mode(Device.get_resolution())

That's all we need for setting up environment. Now we can create and render model objects by calling Model.draw() at each frame update (See example)
To create model you can simply pass 3D world vertices as 2-d list Model(data=data)

It is possible to provide faces as 2d array Model(data=data, faces=faces). Face index starts from 1. Only triangles supported. For more information see below.

Simply by providing 3D (or 4D homogeneous where w=1) data vertices list - Model transforms this coordinates from 3D world space to projected screen space

from play3d.models import Model

# our 2D library renderer setup.. See above.

# Cube model. Already built-in `models.Cube`  
cube = Model(position=(0, 0, 0),
                 data=[
                    [-1, 1, 1, 1],
                    [1, 1, 1, 1],
                    [-1, -1, 1, 1],
                    [1, -1, 1, 1],
                    [-1, 1, -1, 1],
                    [1, 1, -1, 1],
                    [1, -1, -1, 1],
                    [-1, -1, -1, 1]
                ])
while True: # your render lib/method
    cube.draw()

Model View Projection

models.Model and three_d.Camera implements all MVP(See Model.draw).

Projection

Here we use perspective projection matrix
Z axis of clipped cube(from frustum) mapped to [-1, 1] and our camera directed to -z axis (OpenGL convention)
Projection Matrix can be tuned there (aspect ratio, FOV and etc.)
Camera.near = 1
Camera.far = 10
Camera.fov = 60
Camera.aspect_ratio = 3/4

World camera

By OpenGL standard we basically move our scene. Facing direction considered when we move our camera in case of rotations(direction vector will be transformed too)
Camera can be moved through three_d.Camera API:

from play3d.three_d import Camera
camera = Camera.get_instance()

# move camera to x, y, z with 0.5 step considering facing direction
camera['x'] += 0.5
camera['y'] += 0.5
camera['z'] += 0.5

camera.move(0.5, 0.5, 0.5) # identical above

# rotate camera to our left on XZ plane
camera.rotate('y', 2) # 

Camera keys example

Pygame integration example

Mesh and Wireframe

To exploit mesh one should provide both data and faces. Face represents triple group of vertices index referenced from data. Face index starts from 1.
By default object rendered as wireframe

from play3d.models import Model
triangle = Model(position=(-5, 3, -4),
                 data=[
                     [-3, 1, -7, 1],
                     [-2, 2, -7, 1],
                     [-1, 0, -7, 1],
                 ], faces=[[1, 2, 3]])

Triangle wireframe

Rasterization

By default if data and faces provided, rasterization will be enabled.
For rasterization we use - standard slope algorithm with horizontal filling lines.

from play3d.models import Model

white = (230, 230, 230)
suzanne = Model.load_OBJ('suzanne.obj.txt', position=(-4, 2, -6), color=white, rasterize=True)
suzanne_wireframe = Model.load_OBJ('suzanne.obj.txt', position=(-4, 2, -6), color=white)
suzanne.rotate(0, -14)
suzanne_wireframe.rotate(0, 14)

Suzanne wireframe and rasterized

3D plotting

You can plot any function you want by providing parametric equation as func(*parameters) -> [x, y, z]. For example, sphere and some awesome wave both polar and parametric equations(Sphere built-in as Models.Sphere):

import math
from play3d.models import Plot

def fn(phi, theta):

    return [
        math.sin(phi * math.pi / 180) * math.cos(theta * math.pi / 180),
        math.sin(theta * math.pi / 180) * math.sin(phi * math.pi / 180),
        math.cos(phi * math.pi / 180)
    ]

sphere_model = Plot(func=fn, allrange=[0, 360], position=(-4, 2, 1), color=(0, 64, 255))

blow_your_head = Plot(
    position=(-4, 2, 1), color=(0, 64, 255),
    func=lambda x, t: [x, math.cos(x) * math.cos(t), math.cos(t)], allrange=[0, 2*math.pi], interpolate=75
)

Plots

OBJ format

Wawefront format is widely used as a standard in 3D graphics

You can import your model here. Only vertices and faces supported.
Model.load_OBJ(cls, path, wireframe=False, **all_model_kwargs)

You can find examples here github.com/alecjacobson/common-3d-test-models

Model.load_OBJ('beetle.obj.txt', wireframe=True, color=white, position=(-2, 2, -4), scale=3)

Beetle object

Models API

Models.Model

Fields Description
position tuple=(0, 0, 0) with x, y, z world coordinates
scale integer(=1)
color tuple (255, 255, 255)
data list[[x, y, z, [w=1]]] - Model vertices(points)
faces list[[A, B, C]] - Defines triangles See: Mesh and Wireframe
rasterize bool(=True) - Rasterize - "fill" an object
shimmering bool(=False) - color flickering/dancing
# Initial Model Matrix
model.matrix = Matrix([
                          [1 * scale, 0, 0, 0],
                          [0, 1 * scale, 0, 0],
                          [0, 0, 1 * scale, 0],
                          [*position, 1]
                      ])

Trajectory API

Models.Trajectory

Fields Description
func func Parametrized math function which takes *args and returns world respective coordinates tuple=(x, y, z)

To move our object through defined path we can build Trajectory for our object. You can provide any parametric equation with args.
World coordinates defined by func(*args) tuple output.

model_obj @ translate(x, y, z)

translates object's model matrix (in world space)

rotate(self, angle_x, angle_y=0, angle_z=0)

Rotates object relative to particular axis plane. First object translated from the world space back to local origin, then we rotate the object

route(self, trajectory: 'Trajectory', enable_trace=False)

Set the function-based trajectory routing for the object.

  • trajectory Trajectory - trajectory state
  • enable_trace bool - Keep track of i.e. draw trajectory path (breadcrumbs)

Example

import math

from play3d.models import Sphere, Trajectory
white = (230, 230, 230)
moving_sphere = Sphere(position=(1, 3, -5), color=white, interpolate=50)
moving_sphere.route(Trajectory.ToAxis.Z(speed=0.02).backwards())

whirling_sphere = Sphere(position=(1, 3, -5), color=white, interpolate=50)
# Already built-in as Trajectory.SineXY(speed=0.1)
whirling_sphere.route(Trajectory(lambda x: [x, math.sin(x)], speed=0.1))


while True: # inside your "render()"
    moving_sphere.draw()
    whirling_sphere.draw()

Pygame example

import logging
import os
import sys

import pygame

from play3d.models import Model, Grid
from pygame_utils import handle_camera_with_keys  # your keyboard control management
from play3d.three_d import Device, Camera
from play3d.utils import capture_fps

logging.basicConfig(stream=sys.stdout, level=logging.INFO)

os.environ["SDL_VIDEO_CENTERED"] = '1'
black, white = (20, 20, 20), (230, 230, 230)


Device.viewport(1024, 768)
pygame.init()
screen = pygame.display.set_mode(Device.get_resolution())

# just for simplicity - array access, we should avoid that
x, y, z = 0, 1, 2

# pygame sdl line is faster than default one
line_adapter = lambda p1, p2, color: pygame.draw.line(screen, color, (p1[x], p1[y]), (p2[x], p2[y]), 1)
put_pixel = lambda x, y, color: pygame.draw.circle(screen, color, (x, y), 1)

Device.set_renderer(put_pixel, line_renderer=line_adapter)

grid = Grid(color=(30, 140, 200), dimensions=(30, 30))
suzanne = Model.load_OBJ('suzanne.obj.txt', position=(3, 2, -7), color=white, rasterize=True)
beetle = Model.load_OBJ('beetle.obj.txt', wireframe=False, color=white, position=(0, 2, -11), scale=3)
beetle.rotate(0, 45, 50)

camera = Camera.get_instance()
# move our camera up and back a bit, from origin
camera.move(y=1, z=2)


@capture_fps
def frame():
    if pygame.event.get(pygame.QUIT):
        sys.exit(0)

    screen.fill(black)
    handle_camera_with_keys()  # we can move our camera
    grid.draw()
    beetle.draw()
    suzanne.rotate(0, 1, 0).draw()
    pygame.display.flip()


while True:

    frame()

Project details


Download files

Download the file for your platform. If you're not sure which to choose, learn more about installing packages.

Source Distribution

play3d-0.1.0.tar.gz (19.2 kB view details)

Uploaded Source

Built Distribution

play3d-0.1.0-py3-none-any.whl (16.5 kB view details)

Uploaded Python 3

File details

Details for the file play3d-0.1.0.tar.gz.

File metadata

  • Download URL: play3d-0.1.0.tar.gz
  • Upload date:
  • Size: 19.2 kB
  • Tags: Source
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/4.0.1 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.60.0 CPython/3.8.2

File hashes

Hashes for play3d-0.1.0.tar.gz
Algorithm Hash digest
SHA256 07e51e581a8e086220b2cfc7ab48703c10cc1b33c9934a9bfbbd57039746ddbf
MD5 a0069ded169bfa6cb151d40c5da3bc51
BLAKE2b-256 66b354fe158755063b6a1eeabba84d4025b0d725f28925a30190dec31294d612

See more details on using hashes here.

File details

Details for the file play3d-0.1.0-py3-none-any.whl.

File metadata

  • Download URL: play3d-0.1.0-py3-none-any.whl
  • Upload date:
  • Size: 16.5 kB
  • Tags: Python 3
  • Uploaded using Trusted Publishing? No
  • Uploaded via: twine/3.4.1 importlib_metadata/4.0.1 pkginfo/1.7.0 requests/2.25.1 requests-toolbelt/0.9.1 tqdm/4.60.0 CPython/3.8.2

File hashes

Hashes for play3d-0.1.0-py3-none-any.whl
Algorithm Hash digest
SHA256 6b05644689f08e41a46597e3209c54b14ded0f8f7ebb08bbf47ac673c97e9ee5
MD5 90c0d7981c8e146cba1e236e02f9b88e
BLAKE2b-256 03fa8aba819f5dbf128b9b97ef114fa6a2fce37de3d8eec61fc28a47024bca9f

See more details on using hashes here.

Supported by

AWS AWS Cloud computing and Security Sponsor Datadog Datadog Monitoring Fastly Fastly CDN Google Google Download Analytics Microsoft Microsoft PSF Sponsor Pingdom Pingdom Monitoring Sentry Sentry Error logging StatusPage StatusPage Status page