Skip to content
A simple 3d renderer
Python APL
Branch: master
Clone or download
Fetching latest commit…
Cannot retrieve the latest commit at this time.
Permalink
Type Name Latest commit message Commit time
Failed to load latest commit information.
.gitignore
README.org
apl
apl.apl
renderer.py
window.png

README.org

General

I am trying to build a 3d renderer using: APL (GNU APL), Python, PySDL2 and SMC (the state machine Compiler). I will follow the guide build-your-own-raytracer .

Prerequisites

GNU APL

  • I’am using the Subversion repository:
svn co http://svn.savannah.gnu.org/svn/apl/trunk
  • GNU APL requires a specific Python (3.4) to compile the module (Python.h not found).
  • To get the python module, I had to follow the instructions of the file: ~/trunk/README-10-python
GNU APL can be compiled as a Python module.
The basic build procedure is this:

    configure --with-python [ other ./configure options ...]
    make
    sudo make install

This installs a shared object file named 'libgnu_apl.so' in the place where
GNU APL installs its libraries (typically /usr/local/lib/apl or
/usr/lib/apl)

The shared object file must then be copied to the directory where Python
keeps its libraries (e.g. /usr/lib/python3.4/lib-dynload/). For example (if
your Python version is 3.4, and NOTICE THE DIFFERENT NAME OF THE .so FILE):

    sudo cp /usr/local/lib/apl/lib_gnu_apl.so \
            /usr/lib/python3.4/lib-dynload/gnu_apl.cpython-34m.so

After that, one can import GNU APL in Python and call Python functions
provided by gnu_apl:

>>> import gnu_apl
>>> gnu_apl.exec("4 4⍴1+2")
3 3 3 3
3 3 3 3
3 3 3 3
3 3 3 3
0
>>> gnu_apl.command(")WSID")
'IS CLEAR WS\n'


The output above is a mix of APL output and Python output, which is more often
than not a bad idea. A better approach is to generate no APL output (i,e, the
results of APL computations should be assigned to variables and variables ⎕
and ⍞ should not be used). Alternatively, you can change the default print
behaviour in Python with gnu_apl.set_display(mode). See, for example,
gnu_apl.help('set_display').

After a sucessful installation you can display the documentation of all Python
functions in an interactive Python session like this:

>>> import gnu_apl
>>> gnu_apl.help('all')


  • My current GNU APL version
apl --version

SDL2 and PySDL2

TODO:

  • [ ] add commands to install SDL2 and PySDL2

GNU APL functions

getters

gnu_apl.get_shape

gnu_apl.get_ravel

gnu_apl.get_value

setter

gnu_apl.set_value(varname, value)

Code

Python

The idea is use APL to make all the math related stuff, SMC[0] for control flow. Python and PySDL2 seems easy to use.

The interesting part at the moment is this one:

# this load an apl-workspace
gnu_apl.command(")COPY ./apl")


# using the getters from GNU-APL

# not sure about the name, but good enough for now
# aplVar => list 
def makeImage(aplVar):
  data     = gnu_apl.get_ravel(aplVar)
  shape    = gnu_apl.get_shape(aplVar)
  shapeY   = shape[0] 
  shapeX   = shape[1] 
  arr      = [ [0 for x in range(shapeX)] for y in range(shapeY)]
  for y in range(shapeY):
   for x in range(shapeX):
    arr[y][x]=data[x]
  return arr

APL

canvas         10     10       0 0 0 0 1
topLeft         ¯3    ¯0.5    1
topRight      1.28    0.86 ¯0.5
bottomLeft   ¯1.28   ¯0.86 ¯0.5
bottomRight   1.28   ¯0.86 ¯0.5
imagePlane  topLeft topRight bottomLeft bottomRight
4 1  imagePlane
imagePlane topLeft
vector3  {} 
vector3 0 0 2
camera  vector3 0 0 2
camera
ray  {}
sphere  {}
newSphere  sphere ¯1.1 0.6 ¯1

Final code

import gnu_apl
import sys
import sdl2
import sdl2.ext

WHITE  = sdl2.ext.Color(255, 255, 255)
RED    = sdl2.ext.Color(255,   0, 0)
GREEN  = sdl2.ext.Color(0,   255, 0)
BLUE   = sdl2.ext.Color(0,     0, 255)
BLACK  = sdl2.ext.Color(0,     0, 0)

WIDTH  = 20
HEIGHT = 20

gnu_apl.command(")COPY ./apl")

# aplVar => list 
def makeImage(aplVar):
  data     = gnu_apl.get_ravel(aplVar)
  shape    = gnu_apl.get_shape(aplVar)
  shapeY   = shape[0] 
  shapeX   = shape[1] 
  arr      = [ [0 for x in range(shapeX)] for y in range(shapeY)]
  for y in range(shapeY):
   for x in range(shapeX):
    arr[y][x]=data[x]
  return arr

image = makeImage("canvas")

def run():
 sdl2.ext.init()
 window = sdl2.ext.Window("This is a WINDOW", size=(200, 300))
 window.show()

 surface  = sdl2.ext.Window.get_surface(window)
 renderer = sdl2.ext.Renderer(window)
 running  =  True

 renderer.clear(0)

 for y,a in enumerate(image):
  for x,b in enumerate(a):
   if image[y][x] == 1:
    renderer.fill(((x*WIDTH),(y*HEIGHT), WIDTH, HEIGHT), RED)
   else:
    renderer.draw_rect(((x*WIDTH),(y*HEIGHT), WIDTH, HEIGHT), WHITE)
 renderer.present()


 while running:
    for event in sdl2.ext.get_events():
      if event.type == sdl2.SDL_QUIT:
           running = False
           break
      if event.type == sdl2.SDL_KEYDOWN:
           running = False
           break
      if event.type == sdl2.SDL_MOUSEBUTTONDOWN:
           running = False
           break
    sdl2.SDL_Delay(10)



if __name__ == "__main__":
  sys.exit(run())

Result

The above code create this window:

./window.png

Run

python3.4 renderer.py 

nodemon

Funny, nodemon can be used with python files (nodemon --exec python3.4 renderer.py)

GNU APL documentation

general

Synopsis: gnu_apl.help(topic) topic is an UTF8 string containing a help topic.

Examples: gnu_apl.help() (this text) gnu_apl.help(‘all’) (long) gnu_apl.help(‘help’) gnu_apl.help(‘command’) gnu_apl.help(‘exec’) gnu_apl.help(‘fix_function’) gnu_apl.help(‘get_ravel’) gnu_apl.help(‘get_shape’) gnu_apl.help(‘get_value’) gnu_apl.help(‘set_value’) gnu_apl.help(‘APL-values’)


gnu_apl.command() : execute an APL command.

Synopsis: Result = gnu_apl.command(cmd) cmd is an UTF8 string containing a valid APL expression

Result: a UTF8 string that contains the output of the command (which depends on the command that was executed).


gnu_apl.exec() : execute an APL expression.

Synopsis: Result = gnu_apl.exec(expr) expr is an UTF8 string containing a valid APL expression

Result: tuple(0, error_code) if the expression caused an error tuple(1, value) if the expression returned an uncommitted value tuple(2, value) if the expression returned a committed value tuple(3, None) if the expression returned no value tuple(4, value) if the expression returned a branch (e.g. →N) tuple(5, value) if the expression returned branch escape (→)

Note: Result has the same format as ⎕EC (execute controlled)


gnu_apl.fix_function()

gnu_apl.fix_function() : create (aka. ⎕FX) a new defined APL function, or change the definition of an existing definition.

Synopsis: Result = gnu_apl.fix_function(text) text is a UTF8 string containing the function lines (separated by \n). The first line in text is the function signature, e.g. Z←A myfun B The subsequent lines in text comprise the function body.

Result: None if the function could be created, int(0) if the function signature was incorrect, int(N) if the body line N ≥ 1 was incorrect. int(-1) if something else went wrong.

NOTE: creating or modifying defined APL functions with ⎕FX (in APL) or by gnu_apl.fix_function() (in Python) is not the usual way of creating or modifying defined APL functions. Most APL programmers will prefer to use the standard APL ∇-editor (in interactive APL sessions) or to write APL scripts in order to create an APL workspace that contains the desired defined functions. The workspace is then loaded with APL commands )LOAD or )COPY (in APL) resp. gnu_apl.command(‘)LOAD …’) or gnu_apl.command(‘)COPY …’) in Python.


gnu_apl.get_ravel()

gnu_apl.get_ravel() : get the ravel of an APL variable.

Synopsis: Result = gnu_apl.get_ravel(text) varname is a string containing the name of an APL variable.

Result: on sucess: list [ i₁, i₂, … iN ] where N is the length of the ravel (aka, ,varname in APL) of the value of the variable, and iK is the K’th ravel element: int(iK): an APL integer float(iK): an APL float complex(iK): an APL complex value chr(iK): an APL character (Unicode) list(iK): a nested APL sub-value tuple(R, S): a nested APL sub-value with ravel R and shape S

otherwise: int(error_code)

Example: gnu_apl.exec(‘Var←4 4⍴⍳16’) (2, ([4, 4], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])) gnu_apl.get_ravel(‘Var’) [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16]

gnu_apl.get_shape() : get the shape of an APL variable.

Synopsis: Result = gnu_apl.get_shape(text) varname is a string containing the name of an APL variable.

Result: on sucess: list [ r₁, r₂, … rR ] where R is the number of axes (aka. the rank in APL) of the value of the variable and rK is the length of the Kth axis. otherwise: int(error_code)

Example: gnu_apl.exec(‘Var←4 4⍴⍳16’) (2, ([4, 4], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])) gnu_apl.get_shape(‘Var’) [4, 4]

gnu_apl.get_value() : get the value of an APL variable.

Synopsis: Result = gnu_apl.get_value(varname) varname is a string containing the name of an APL variable.

Result: on sucess: tuple( get_ravel(varname), get_shape(varname) ) otherwise: int(error_code)

Example: gnu_apl.exec(‘Var←4 4⍴⍳16’) (2, ([4, 4], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])) gnu_apl.get_value(‘Var’) ([4, 4], [1, 2, 3, 4, 5, 6, 7, 8, 9, 10, 11, 12, 13, 14, 15, 16])

gnu_apl.set_value() : set the value of an APL variable.

The variable is created if necessary.

Synopsis: gnu_apl.set_value(varname, value) varname is a UTF8 string containing the name of an APL variable. value is the new value of the variable, see gnu_apl.help(‘APL-values’).

Examples: gnu_apl.set_value(‘Var’, 65) # Var ← 65 gnu_apl.set_value(‘Var’, 6.5) # Var ← 6.5 gnu_apl.set_value(‘Var’, 6J5) # Var ← 6J5 gnu_apl.set_value(‘Var’, [6, 5, ‘A’]) # Var ← 6 5 ‘A’ gnu_apl.set_value(‘Var’, [1, [2, 3], 4]) # Var ← 1 (2 3) 4

gnu_apl.set_display() : set the APL display mode.

Synopsis: Result = gnu_apl.set_display(mode) mode is an integer: 0: display neither committed values nor not committed values 1: display only not committed values (standard APL behavior) 2: display both committed values and not committed values

Result: the previous display mode

APL-values : The mapping between Python values and APL values (examples):

╔═══════════════════════════╤══╤════════════╤═══════════════════════════════╗ ║ Python value │ │ APL value │ Remark ║ ╠═══════════════════════════╪══╪════════════╪════════════╤══════════════════╣ ║ int(65) │ →│ 65 │ │ Integer ║ ╟───────────────────────────┼──┼────────────┤ ├──────────────────╢ ║ float(6.5) │ →│ 6.5 │ │ Floating point ║ ╟───────────────────────────┼──┼────────────┤ APL Scalar ├──────────────────╢ ║ complex(6, 5) │ →│ 6J5 │ │ Complex ║ ╟───────────────────────────┼──┼────────────┤ ├──────────────────╢ ║ chr(65) or ‘A’ │ →│ ‘A’ │ │ single Unicode ║ ╠═══════════════════════════╪══╪════════════╪════════════╪══════════════════╣ ║ list([6, 5, ‘A’]) │ →│ 6 5 ‘A’ │ │ Mixed ║ ╟───────────────────────────┼──┼────────────┤ APL Vector ├──────────────────╢ ║ str(‘string’) │ →│ ‘string’ │ │ Character ║ ╠═══════════════════════════╪══╪════════════╪════════════╪══════════════════╣ ║ tuple([65], []) │←→│ 65 │ APL Scalar │ Integer ║ ║ tuple([6.5], []) │←→│ 6.5 │ (as above) │ Floating point ║ ║ tuple([6J5], []) │←→│ 6J5 │ in tuple() │ Complex ║ ║ tuple([‘A’], []) │←→│ ‘A’ │ format │ single Unicode ║ ╟───────────────────────────┼──┼────────────┼────────────┴──────────────────╢ ║ tuple([6, 5, ‘A’], [3]) │←→│ 6 5 ‘A’ │ Vector of length 3 (as above) ║ ╟───────────────────────────┼──┼────────────┼───────────────────────────────╢ ║ ravel = [6, 5, ‘A’] │ │ 6 5 ‘A’ │ 2×3 Matrix. The ravel is ║ ║ shape = [2, 3] │←→│ 6 5 ‘A’ │ truncated or repeated as ║ ║ tuple(ravel, shape) │ │ │ needed to fill the shape ║ ╟───────────────────────────┼──┼────────────┼───────────────────────────────╢ ║ any non-scalar array item │ │ nested APL │ 3-element vector with nested ║ ║ (such as str() or list() │ │ array │ second element which is the ║ ║ or tuple() above) │ │ │ 2-element sub-vector 2 3 ║ ║ [1 [2, 3] 4] │←→│ 1 (2 3) 4 │ ║ ╚═══════════════════════════╧══╧════════════╧═══════════════════════════════╝

NOTE: Every non-tuple() variant (like str(‘string’) or list([i₁, i₂, …]) above is a convenience shortcut that can also be expressed in the form of the more general tuple() variant. For performance reasons, but also to simplify the decoding of APL values in Python, these convenience shortcuts work only in the Python → APL direction; values in the APL → Python direction are always returned in the _tuple()_ format.

Notes

SMC generates finite state machines for objects - not processes or applications but for an individual object. If you have objects that receive asynchronous callbacks and how objects respond to those callbacks are based on the object state, then SMC provides a powerful solution.

You can’t perform that action at this time.