]> Some of my projects - openlase-old.git/commitdiff
Initial commit
authorHector Martin <hector@marcansoft.com>
Wed, 24 Nov 2010 01:10:10 +0000 (02:10 +0100)
committerHector Martin <hector@marcansoft.com>
Wed, 24 Nov 2010 01:10:10 +0000 (02:10 +0100)
30 files changed:
.gitignore [new file with mode: 0644]
CMakeLists.txt [new file with mode: 0644]
Modules/FindJACK.cmake [new file with mode: 0644]
Modules/LibFindMacros.cmake [new file with mode: 0644]
examples/CMakeLists.txt [new file with mode: 0644]
examples/circlescope.c [new file with mode: 0644]
examples/pong.c [new file with mode: 0644]
examples/simple.c [new file with mode: 0644]
include/ilda.h [new file with mode: 0644]
include/libol.h [new file with mode: 0644]
include/text.h [new file with mode: 0644]
libol/CMakeLists.txt [new file with mode: 0644]
libol/ilda.c [new file with mode: 0644]
libol/laserfont.svg [new file with mode: 0644]
libol/libol.c [new file with mode: 0644]
libol/text.c [new file with mode: 0644]
output/CMakeLists.txt [new file with mode: 0644]
output/output.cpp [new file with mode: 0644]
output/output.h [new file with mode: 0644]
output/output_settings.cpp [new file with mode: 0644]
output/output_settings.h [new file with mode: 0644]
output/output_settings.ui [new file with mode: 0644]
test_patterns/ILDA12K.ild [new file with mode: 0644]
tools/CMakeLists.txt [new file with mode: 0644]
tools/genfont.py [new file with mode: 0644]
tools/playilda.c [new file with mode: 0644]
tools/playvid.c [new file with mode: 0644]
tools/svg2ild.py [new file with mode: 0644]
tools/trace.c [new file with mode: 0644]
tools/trace.h [new file with mode: 0644]

diff --git a/.gitignore b/.gitignore
new file mode 100644 (file)
index 0000000..dee53c5
--- /dev/null
@@ -0,0 +1,2 @@
+build
+*~
diff --git a/CMakeLists.txt b/CMakeLists.txt
new file mode 100644 (file)
index 0000000..8a1f1f4
--- /dev/null
@@ -0,0 +1,33 @@
+#         OpenLase - a realtime laser graphics toolkit
+#
+# Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 or version 3.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+
+PROJECT(openlase)
+
+cmake_minimum_required(VERSION 2.6)
+
+set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/Modules/")
+
+find_package(Qt4 REQUIRED)
+find_package(JACK REQUIRED)
+
+set(CMAKE_C_FLAGS "-Wall -O3 -g")
+
+add_subdirectory (libol)
+add_subdirectory (output)
+add_subdirectory (tools)
+add_subdirectory (examples)
diff --git a/Modules/FindJACK.cmake b/Modules/FindJACK.cmake
new file mode 100644 (file)
index 0000000..d5970b6
--- /dev/null
@@ -0,0 +1,32 @@
+# - Try to find JACK
+# Once done, this will define
+#
+#  JACK_FOUND - system has JACK
+#  JACK_INCLUDE_DIRS - the JACK include directories
+#  JACK_LIBRARIES - link these to use JACK
+
+include(LibFindMacros)
+
+# Dependencies
+
+set(JACK_PKGCONF_INCLUDE_DIRS /usr/include)
+set(JACK_PKGCONF_LIBRARY_DIRS /usr/lib)
+set(JACK_LIBRARY_NAME jack)
+
+# Include dir
+find_path(JACK_INCLUDE_DIR
+  NAMES jack/jack.h
+  PATHS ${JACK_PKGCONF_INCLUDE_DIRS}
+)
+
+# Finally the library itself
+find_library(JACK_LIBRARY
+  NAMES ${JACK_LIBRARY_NAME}
+  PATHS ${JACK_PKGCONF_LIBRARY_DIRS}
+)
+
+# Set the include dir variables and the libraries and let libfind_process do the rest.
+# NOTE: Singular variables for this library, plural for libraries this this lib depends on.
+set(JACK_PROCESS_INCLUDES JACK_INCLUDE_DIR)
+set(JACK_PROCESS_LIBS JACK_LIBRARY)
+libfind_process(JACK)
diff --git a/Modules/LibFindMacros.cmake b/Modules/LibFindMacros.cmake
new file mode 100644 (file)
index 0000000..795d6b7
--- /dev/null
@@ -0,0 +1,99 @@
+# Works the same as find_package, but forwards the "REQUIRED" and "QUIET" arguments
+# used for the current package. For this to work, the first parameter must be the
+# prefix of the current package, then the prefix of the new package etc, which are
+# passed to find_package.
+macro (libfind_package PREFIX)
+  set (LIBFIND_PACKAGE_ARGS ${ARGN})
+  if (${PREFIX}_FIND_QUIETLY)
+    set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} QUIET)
+  endif (${PREFIX}_FIND_QUIETLY)
+  if (${PREFIX}_FIND_REQUIRED)
+    set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} REQUIRED)
+  endif (${PREFIX}_FIND_REQUIRED)
+  find_package(${LIBFIND_PACKAGE_ARGS})
+endmacro (libfind_package)
+
+# Damn CMake developers made the UsePkgConfig system deprecated in the same release (2.6)
+# where they added pkg_check_modules. Consequently I need to support both in my scripts
+# to avoid those deprecated warnings. Here's a helper that does just that.
+# Works identically to pkg_check_modules, except that no checks are needed prior to use.
+macro (libfind_pkg_check_modules PREFIX PKGNAME)
+  if (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4)
+    include(UsePkgConfig)
+    pkgconfig(${PKGNAME} ${PREFIX}_INCLUDE_DIRS ${PREFIX}_LIBRARY_DIRS ${PREFIX}_LDFLAGS ${PREFIX}_CFLAGS)
+  else (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4)
+    find_package(PkgConfig)
+    if (PKG_CONFIG_FOUND)
+      pkg_check_modules(${PREFIX} ${PKGNAME})
+    endif (PKG_CONFIG_FOUND)
+  endif (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4)
+endmacro (libfind_pkg_check_modules)
+
+# Do the final processing once the paths have been detected.
+# If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain
+# all the variables, each of which contain one include directory.
+# Ditto for ${PREFIX}_PROCESS_LIBS and library files.
+# Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES.
+# Also handles errors in case library detection was required, etc.
+macro (libfind_process PREFIX)
+  # Skip processing if already processed during this run
+  if (NOT ${PREFIX}_FOUND)
+    # Start with the assumption that the library was found
+    set (${PREFIX}_FOUND TRUE)
+
+    # Process all includes and set _FOUND to false if any are missing
+    foreach (i ${${PREFIX}_PROCESS_INCLUDES})
+      if (${i})
+        set (${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIRS} ${${i}})
+        mark_as_advanced(${i})
+      else (${i})
+        set (${PREFIX}_FOUND FALSE)
+      endif (${i})
+    endforeach (i)
+
+    # Process all libraries and set _FOUND to false if any are missing
+    foreach (i ${${PREFIX}_PROCESS_LIBS})
+      if (${i})
+        set (${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARIES} ${${i}})
+        mark_as_advanced(${i})
+      else (${i})
+        set (${PREFIX}_FOUND FALSE)
+      endif (${i})
+    endforeach (i)
+
+    # Print message and/or exit on fatal error
+    if (${PREFIX}_FOUND)
+      if (NOT ${PREFIX}_FIND_QUIETLY)
+        message (STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}")
+      endif (NOT ${PREFIX}_FIND_QUIETLY)
+    else (${PREFIX}_FOUND)
+      if (${PREFIX}_FIND_REQUIRED)
+        foreach (i ${${PREFIX}_PROCESS_INCLUDES} ${${PREFIX}_PROCESS_LIBS})
+          message("${i}=${${i}}")
+        endforeach (i)
+        message (FATAL_ERROR "Required library ${PREFIX} NOT FOUND.\nInstall the library (dev version) and try again. If the library is already installed, use ccmake to set the missing variables manually.")
+      endif (${PREFIX}_FIND_REQUIRED)
+    endif (${PREFIX}_FOUND)
+  endif (NOT ${PREFIX}_FOUND)
+endmacro (libfind_process)
+
+macro(libfind_library PREFIX basename)
+  set(TMP "")
+  if(MSVC80)
+    set(TMP -vc80)
+  endif(MSVC80)
+  if(MSVC90)
+    set(TMP -vc90)
+  endif(MSVC90)
+  set(${PREFIX}_LIBNAMES ${basename}${TMP})
+  if(${ARGC} GREATER 2)
+    set(${PREFIX}_LIBNAMES ${basename}${TMP}-${ARGV2})
+    string(REGEX REPLACE "\\." "_" TMP ${${PREFIX}_LIBNAMES})
+    set(${PREFIX}_LIBNAMES ${${PREFIX}_LIBNAMES} ${TMP})
+  endif(${ARGC} GREATER 2)
+  find_library(${PREFIX}_LIBRARY
+    NAMES ${${PREFIX}_LIBNAMES}
+    PATHS ${${PREFIX}_PKGCONF_LIBRARY_DIRS}
+  )
+endmacro(libfind_library)
+
diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt
new file mode 100644 (file)
index 0000000..60dd6c1
--- /dev/null
@@ -0,0 +1,29 @@
+#         OpenLase - a realtime laser graphics toolkit
+#
+# Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 or version 3.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+
+include_directories (${CMAKE_SOURCE_DIR}/include)
+link_directories (${CMAKE_BINARY_DIR}/libol)
+
+add_executable(circlescope circlescope.c)
+target_link_libraries(circlescope ${JACK_LIBRARIES} m)
+
+add_executable(simple simple.c)
+target_link_libraries(simple openlase)
+
+add_executable(pong pong.c)
+target_link_libraries(pong openlase)
diff --git a/examples/circlescope.c b/examples/circlescope.c
new file mode 100644 (file)
index 0000000..ce6b1a0
--- /dev/null
@@ -0,0 +1,134 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#define _BSD_SOURCE
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+
+#include <jack/jack.h>
+
+#include <stdint.h>
+#include <math.h>
+
+typedef jack_default_audio_sample_t sample_t;
+typedef jack_nframes_t nframes_t;
+
+jack_port_t *in_l;
+jack_port_t *in_r;
+jack_port_t *out_x;
+jack_port_t *out_y;
+jack_port_t *out_w;
+
+nframes_t rate;
+
+sample_t max_size = 1.0f;
+sample_t min_size = 0.2f;
+sample_t boost = 8;
+
+//float w = 110 * (2*M_PI);
+float w = 523.251131f / 4.0f * (2*M_PI) / 1;
+float pos = 0.0f;
+
+#define MAX(a,b) (((a)<(b))?(b):(a))
+#define MIN(a,b) (((a)>(b))?(b):(a))
+
+int process (nframes_t nframes, void *arg)
+{
+       sample_t *i_l = (sample_t *) jack_port_get_buffer (in_l, nframes);
+       sample_t *i_r = (sample_t *) jack_port_get_buffer (in_r, nframes);
+       sample_t *o_x = (sample_t *) jack_port_get_buffer (out_x, nframes);
+       sample_t *o_y = (sample_t *) jack_port_get_buffer (out_y, nframes);
+       sample_t *o_w = (sample_t *) jack_port_get_buffer (out_w, nframes);
+
+       nframes_t frm;
+       for (frm = 0; frm < nframes; frm++) {
+               sample_t val = (*i_l++ + *i_r++) / 2;
+
+               val *= boost;
+               val = MAX(MIN(val,1.0f),-1.0f);
+               val = val * 0.5f + 0.5f;
+               val *= (max_size - min_size);
+               val += min_size;
+
+               *o_w++ = 1.0f;
+               *o_x++ = cosf(pos) * val;
+               *o_y++ = sinf(pos) * val;
+
+               pos += w / rate;
+               while(pos >= (2*M_PI)) {
+                       pos -= (2*M_PI);
+               }
+       }
+
+       return 0;
+}
+
+int bufsize (nframes_t nframes, void *arg)
+{
+       printf ("the maximum buffer size is now %u\n", nframes);
+       return 0;
+}
+
+int srate (nframes_t nframes, void *arg)
+{
+       rate = nframes;
+       printf ("Sample rate: %u/sec\n", nframes);
+       return 0;
+}
+
+void jack_shutdown (void *arg)
+{
+       exit (1);
+}
+
+int main (int argc, char *argv[])
+{
+       jack_client_t *client;
+
+       if ((client = jack_client_new ("circlescope")) == 0) {
+               fprintf (stderr, "jack server not running?\n");
+               return 1;
+       }
+
+       jack_set_process_callback (client, process, 0);
+       jack_set_buffer_size_callback (client, bufsize, 0);
+       jack_set_sample_rate_callback (client, srate, 0);
+       jack_on_shutdown (client, jack_shutdown, 0);
+
+       in_l = jack_port_register (client, "in_l", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
+       in_r = jack_port_register (client, "in_r", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
+       out_x = jack_port_register (client, "out_x", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_y = jack_port_register (client, "out_y", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_w = jack_port_register (client, "out_w", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+
+       if (jack_activate (client)) {
+               fprintf (stderr, "cannot activate client");
+               return 1;
+       }
+
+       while (1)
+               sleep(1);
+       jack_client_close (client);
+       exit (0);
+}
+
diff --git a/examples/pong.c b/examples/pong.c
new file mode 100644 (file)
index 0000000..d939d30
--- /dev/null
@@ -0,0 +1,317 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "libol.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <jack/jack.h>
+#include <math.h>
+
+#include <linux/input.h>
+#include <sys/types.h>
+#include <sys/stat.h>
+#include <fcntl.h>
+
+#define DIGW 0.06
+#define DIGH 0.12
+
+/*
+Pong! Input is yet another hack. It's meant to be used with one or two PS3
+controllers connected via event devices. Dealing with permissions is left as
+an exercise to the reader. Will probably work with other similar input devices.
+
+Also, the digit code dates to before I had fonts going. However, the default
+font currently has no digits, so it has a reason to exist for now.
+*/
+
+void digit(float x, float y, int d, uint32_t color)
+{
+       olPushMatrix();
+       olTranslate(x,y);
+       olScale(DIGW*0.8,DIGH*0.4);
+       olBegin(OL_LINESTRIP);
+       switch(d) {
+               case 0:
+                       olVertex(0,0,color);
+                       olVertex(0,2,color);
+                       olVertex(1,2,color);
+                       olVertex(1,0,color);
+                       olVertex(0,0,color);
+                       break;
+               case 1:
+                       olVertex(0.5,0,color);
+                       olVertex(0.5,2,color);
+                       break;
+               case 2:
+                       olVertex(0,0,color);
+                       olVertex(1,0,color);
+                       olVertex(1,1,color);
+                       olVertex(0,1,color);
+                       olVertex(0,2,color);
+                       olVertex(1,2,color);
+                       break;
+               case 3:
+                       olVertex(0,0,color);
+                       olVertex(1,0,color);
+                       olVertex(1,1,color);
+                       olVertex(0,1,color);
+                       olVertex(1,1,C_BLACK);
+                       olVertex(1,2,color);
+                       olVertex(0,2,color);
+                       break;
+               case 4:
+                       olVertex(0,0,color);
+                       olVertex(0,1,color);
+                       olVertex(1,1,color);
+                       olVertex(1,0,C_BLACK);
+                       olVertex(1,2,color);
+                       break;
+               case 5:
+                       olVertex(1,0,color);
+                       olVertex(0,0,color);
+                       olVertex(0,1,color);
+                       olVertex(1,1,color);
+                       olVertex(1,2,color);
+                       olVertex(0,2,color);
+                       break;
+               case 6:
+                       olVertex(1,0,color);
+                       olVertex(0,0,color);
+                       olVertex(0,2,color);
+                       olVertex(1,2,color);
+                       olVertex(1,1,color);
+                       olVertex(0,1,color);
+                       break;
+               case 7:
+                       olVertex(0,0,color);
+                       olVertex(1,0,color);
+                       olVertex(1,2,color);
+                       break;
+               case 8:
+                       olVertex(0,0,color);
+                       olVertex(0,2,color);
+                       olVertex(1,2,color);
+                       olVertex(1,0,color);
+                       olVertex(0,0,color);
+                       olVertex(0,1,C_BLACK);
+                       olVertex(1,1,color);
+                       break;
+               case 9:
+                       olVertex(1,1,color);
+                       olVertex(0,1,color);
+                       olVertex(0,0,color);
+                       olVertex(1,0,color);
+                       olVertex(1,2,color);
+                       break;
+       }
+       olEnd();
+       olPopMatrix();
+}
+
+int main (int argc, char *argv[])
+{
+       OLRenderParams params;
+
+       int in1_fd;
+       int in2_fd;
+
+       in1_fd = open(argv[1], O_RDONLY | O_NONBLOCK);
+       in2_fd = open(argv[2], O_RDONLY | O_NONBLOCK);
+
+       memset(&params, 0, sizeof params);
+       params.rate = 48000;
+       params.on_speed = 2.0/100.0;
+       params.off_speed = 2.0/20.0;
+       params.start_wait = 12;
+       params.start_dwell = 3;
+       params.curve_dwell = 0;
+       params.corner_dwell = 12;
+       params.curve_angle = cosf(30.0*(M_PI/180.0)); // 30 deg
+       params.end_dwell = 3;
+       params.end_wait = 10;
+       params.snap = 1/100000.0;
+       params.render_flags = RENDER_GRAYSCALE;
+
+       if(olInit(3, 30000) < 0)
+               return 1;
+       olSetRenderParams(&params);
+
+       float time = 0;
+       float ftime;
+
+       int frames = 0;
+
+#define TOP 0.2
+#define BOTTOM 0.8
+#define LEFT 0.07
+#define RIGHT 0.93
+#define WIDTH (RIGHT-LEFT)
+#define HEIGHT (BOTTOM-TOP)
+#define PH 0.12
+#define PW 0.03
+#define BW 0.02
+#define BH 0.02
+
+       float p1 = (HEIGHT-PH)/2;
+       float p2 = (HEIGHT-PH)/2;
+       float bx = 0;
+       float by = HEIGHT/2;
+       float bdx = 0.4;
+       float bdy = 0.2;
+
+       int score1 = 0;
+       int score2 = 0;
+
+       float b1pos = 0, b2pos = 0;
+       float eb1pos,eb2pos;
+
+       while(1) {
+               while(1) {
+                       struct input_event ev;
+                       if (read(in1_fd, &ev, sizeof(ev)) != sizeof(ev))
+                               break;
+                       if (ev.type != EV_ABS)
+                               continue;
+                       if (ev.code == ABS_Y)
+                               b1pos = (ev.value - 128) / 255.0;
+                       else if (ev.code == 5)
+                               b2pos = (ev.value - 128) / 255.0;
+               }
+               while(1) {
+                       struct input_event ev;
+                       if (read(in2_fd, &ev, sizeof(ev)) != sizeof(ev))
+                               break;
+                       if (ev.type != EV_ABS)
+                               continue;
+                       if (ev.code == ABS_Y)
+                               b2pos = (ev.value - 128) / 255.0;
+               }
+
+               if (b1pos > 0.1)
+                       eb1pos = b1pos - 0.1;
+               else if (b1pos < -0.1)
+                       eb1pos = b1pos + 0.1;
+               else
+                       eb1pos = 0;
+
+               if (b2pos > 0.1)
+                       eb2pos = b2pos - 0.1;
+               else if (b2pos < -0.1)
+                       eb2pos = b2pos + 0.1;
+               else
+                       eb2pos = 0;
+
+               eb1pos *= 2;
+               eb2pos *= 2;
+
+               printf("ebpos:%f %f\n", eb1pos, eb2pos);
+
+
+               olLoadIdentity();
+               olTranslate(-1,1);
+               olScale(2,-2);
+               // window is now 0.0-1.0 X and Y, Y going down)
+               olRect(0, TOP, 1, BOTTOM, C_WHITE);
+               olRect(LEFT-PW, p1+TOP, LEFT, p1+TOP+PH, C_WHITE);
+               olRect(RIGHT, p2+TOP, RIGHT+PW, p2+TOP+PH, C_WHITE);
+               olRect(LEFT+bx, TOP+by, LEFT+bx+BW, TOP+by+BW, C_WHITE);
+               olLine((LEFT+RIGHT)/2, TOP, (LEFT+RIGHT)/2, BOTTOM, C_GREY(50));
+
+               olTranslate(0,0.08);
+               if (score1 >= 100)
+                       digit(0,0,score1/100,C_WHITE);
+               if (score1 >= 10)
+                       digit(DIGW,0,score1/10%10,C_WHITE);
+               digit(2*DIGW,0,score1%10,C_WHITE);
+
+               if (score2 >= 100)
+                       digit(1-3*DIGW,0,score2/100,C_WHITE);
+               if (score2 >= 10)
+                       digit(1-2*DIGW,0,score2/10%10,C_WHITE);
+               digit(1-1*DIGW,0,score2%10,C_WHITE);
+
+               ftime = olRenderFrame(60);
+
+               bx += ftime*bdx;
+               by += ftime*bdy;
+
+               if (by > HEIGHT - BH) {
+                       bdy = -bdy;
+                       by += 2*ftime*bdy;
+               } else if (by < 0) {
+                       bdy = -bdy;
+                       by += 2*ftime*bdy;
+               }
+
+               p1 += ftime*eb1pos;
+               if (p1 < 0)
+                       p1 = 0;
+               if (p1 > HEIGHT-PH)
+                       p1 = HEIGHT-PH;
+
+
+               p2 += ftime*eb2pos;
+               if (p2 < 0)
+                       p2 = 0;
+               if (p2 > HEIGHT-PH)
+                       p2 = HEIGHT-PH;
+
+               if (bx < 0) {
+                       if (by < p1-BH || by > p1+PH) {
+                               if (bx < -BW) {
+                                       by = p2 + PH/2 - BH/2;
+                                       bx = WIDTH - BW;
+                                       bdx = -0.4;
+                                       bdy = 0.2;
+                                       score2++;
+                               }
+                       } else {
+                               bdx = -bdx;
+                               bx += 2*ftime*bdx;
+                       }
+               } else if (bx > WIDTH - BW) {
+                       if (by < p2-BH || by > p2+PH) {
+                               if (bx > WIDTH) {
+                                       by = p1 + PH/2 - BH/2;
+                                       bx = 0;
+                                       bdx = 0.4;
+                                       bdy = 0.2;
+                                       score1++;
+                               }
+                       } else {
+                               bdx = -bdx;
+                               bx += 2*ftime*bdx;
+                       }
+               }
+
+               bdx *= powf(1.1, ftime);
+
+               frames++;
+               time += ftime;
+               printf("Frame time: %f, FPS:%f\n", ftime, frames/time);
+       }
+
+       olShutdown();
+       exit (0);
+}
+
diff --git a/examples/simple.c b/examples/simple.c
new file mode 100644 (file)
index 0000000..3cc7f3a
--- /dev/null
@@ -0,0 +1,115 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "libol.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <jack/jack.h>
+#include <math.h>
+
+/*
+Simple demonstration, shows two cubes in perspective 3D.
+*/
+
+int main (int argc, char *argv[])
+{
+       OLRenderParams params;
+
+       memset(&params, 0, sizeof params);
+       params.rate = 48000;
+       params.on_speed = 2.0/100.0;
+       params.off_speed = 2.0/20.0;
+       params.start_wait = 8;
+       params.start_dwell = 3;
+       params.curve_dwell = 0;
+       params.corner_dwell = 8;
+       params.curve_angle = cosf(30.0*(M_PI/180.0)); // 30 deg
+       params.end_dwell = 3;
+       params.end_wait = 7;
+       params.snap = 1/100000.0;
+       params.render_flags = RENDER_GRAYSCALE;
+
+       if(olInit(3, 30000) < 0)
+               return 1;
+
+       olSetRenderParams(&params);
+
+       float time = 0;
+       float ftime;
+       int i,j;
+
+       int frames = 0;
+
+       while(1) {
+               olLoadIdentity3();
+               olLoadIdentity();
+               olPerspective(24, 1, 1, 100);
+               olTranslate3(0, 0, 4);
+
+               for(i=0; i<2; i++) {
+                       olScale3(0.6, 0.6, 0.6);
+
+                       olRotate3Z(time * M_PI * 0.1);
+                       olRotate3Y(time * M_PI * 0.8);
+                       olRotate3X(time * M_PI * 0.73);
+
+                       olBegin(OL_LINESTRIP);
+                       olVertex3(-1, -1, -1, C_WHITE);
+                       olVertex3( 1, -1, -1, C_WHITE);
+                       olVertex3( 1,  1, -1, C_WHITE);
+                       olVertex3(-1,  1, -1, C_WHITE);
+                       olVertex3(-1, -1, -1, C_WHITE);
+                       olVertex3(-1, -1,  1, C_WHITE);
+                       olEnd();
+
+                       olBegin(OL_LINESTRIP);
+                       olVertex3( 1,  1,  1, C_WHITE);
+                       olVertex3(-1,  1,  1, C_WHITE);
+                       olVertex3(-1, -1,  1, C_WHITE);
+                       olVertex3( 1, -1,  1, C_WHITE);
+                       olVertex3( 1,  1,  1, C_WHITE);
+                       olVertex3( 1,  1, -1, C_WHITE);
+                       olEnd();
+
+                       olBegin(OL_LINESTRIP);
+                       olVertex3( 1, -1, -1, C_WHITE);
+                       olVertex3( 1, -1,  1, C_WHITE);
+                       olEnd();
+
+                       olBegin(OL_LINESTRIP);
+                       olVertex3(-1,  1,  1, C_WHITE);
+                       olVertex3(-1,  1, -1, C_WHITE);
+                       olEnd();
+
+               }
+
+               ftime = olRenderFrame(60);
+               frames++;
+               time += ftime;
+               printf("Frame time: %f, FPS:%f\n", ftime, frames/time);
+       }
+
+       olShutdown();
+       exit (0);
+}
+
diff --git a/include/ilda.h b/include/ilda.h
new file mode 100644 (file)
index 0000000..a6aff4e
--- /dev/null
@@ -0,0 +1,49 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef ILD_H
+#define ILD_H
+
+#include <stdint.h>
+
+typedef struct {
+       float x;
+       float y;
+       float z;
+       int is_blank;
+       uint8_t color;
+} IldaPoint;
+
+typedef struct {
+       int count;
+       float min_x;
+       float max_x;
+       float min_y;
+       float max_y;
+       float min_z;
+       float max_z;
+       IldaPoint *points;
+} IldaFile;
+
+IldaFile *olLoadIlda(const char *filename);
+void olDrawIlda(IldaFile *ild);
+void olDrawIlda3D(IldaFile *ild);
+void olFreeIlda(IldaFile *ild);
+
+#endif
\ No newline at end of file
diff --git a/include/libol.h b/include/libol.h
new file mode 100644 (file)
index 0000000..8c6e3a5
--- /dev/null
@@ -0,0 +1,133 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef LIBOL_H
+#define LIBOL_H
+
+#include <stdint.h>
+
+enum {
+       OL_LINESTRIP,
+       OL_BEZIERSTRIP,
+       OL_POINTS
+};
+
+#define C_RED   0xff0000
+#define C_GREEN 0x00ff00
+#define C_BLUE  0x0000ff
+#define C_WHITE 0xffffff
+#define C_BLACK 0x000000
+
+#define C_GREY(x)   (0x010101 * ((int)(x)))
+
+enum {
+       RENDER_GRAYSCALE = 1,
+       RENDER_NOREORDER = 2,
+};
+
+typedef struct {
+       int rate;
+       float on_speed;
+       float off_speed;
+       int start_wait;
+       int start_dwell;
+       int curve_dwell;
+       int corner_dwell;
+       int end_dwell;
+       int end_wait;
+       float curve_angle;
+       float flatness;
+       float snap;
+       int render_flags;
+       int min_length;
+       int max_framelen;
+} OLRenderParams;
+
+typedef struct {
+       int points;
+       int resampled_points;
+       int resampled_blacks;
+       int padding_points;
+} OLFrameInfo;
+
+int olInit(int buffer_count, int max_points);
+
+void olSetRenderParams(OLRenderParams *params);
+
+typedef void (*AudioCallbackFunc)(float *leftbuf, float *rightbuf, int samples);
+
+void olSetAudioCallback(AudioCallbackFunc f);
+
+void olLoadIdentity(void);
+void olPushMatrix(void);
+void olPopMatrix(void);
+
+void olMultMatrix(float m[9]);
+void olRotate(float theta);
+void olTranslate(float x, float y);
+void olScale(float sx, float sy);
+
+void olLoadIdentity3(void);
+void olPushMatrix3(void);
+void olPopMatrix3(void);
+
+void olMultMatrix3(float m[16]);
+void olRotate3X(float theta);
+void olRotate3Y(float theta);
+void olRotate3Z(float theta);
+void olTranslate3(float x, float y, float z);
+void olScale3(float sx, float sy, float sz);
+
+void olFrustum (float left, float right, float bot, float ttop, float near, float far);
+void olPerspective(float fovy, float aspect, float zNear, float zFar);
+
+void olResetColor(void);
+void olMultColor(uint32_t color);
+void olPushColor(void);
+void olPopColor(void);
+
+void olBegin(int prim);
+void olVertex(float x, float y, uint32_t color);
+void olVertex3(float x, float y, float z, uint32_t color);
+void olEnd(void);
+
+void olTransformVertex3(float *x, float *y, float *z);
+
+typedef void (*ShaderFunc)(float *x, float *y, uint32_t *color);
+typedef void (*Shader3Func)(float *x, float *y, float *z, uint32_t *color);
+
+void olSetVertexPreShader(ShaderFunc f);
+void olSetVertexShader(ShaderFunc f);
+void olSetVertex3Shader(Shader3Func f);
+
+void olSetPixelShader(ShaderFunc f);
+
+void olRect(float x1, float y1, float x2, float y2, uint32_t color);
+void olLine(float x1, float y1, float x2, float y2, uint32_t color);
+void olDot(float x, float y, int points, uint32_t color);
+
+float olRenderFrame(int max_fps);
+
+void olGetFrameInfo(OLFrameInfo *info);
+
+void olShutdown(void);
+
+void olSetScissor (float x0, float y0, float x1, float y1);
+
+#endif
diff --git a/include/text.h b/include/text.h
new file mode 100644 (file)
index 0000000..b47da83
--- /dev/null
@@ -0,0 +1,50 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef TEXT_H
+#define TEXT_H
+
+#include <stdint.h>
+#include <stddef.h>
+
+typedef struct {
+       int flag;
+       float x;
+       float y;
+} FontPoint;
+
+typedef struct {
+       float width;
+       const FontPoint *points;
+} FontChar;
+
+typedef struct {
+       float height;
+       float overlap;
+       const FontChar *chars;
+} Font;
+
+Font *olGetDefaultFont(void);
+float olGetCharWidth(Font *fnt, char c);
+float olGetStringWidth(Font *fnt, float height, const char *s);
+float olGetCharOverlap(Font *font, float height);
+float olDrawChar(Font *fnt, float x, float y, float height, uint32_t color, char c);
+float olDrawString(Font *fnt, float x, float y, float height, uint32_t color, const char *s);
+
+#endif
diff --git a/libol/CMakeLists.txt b/libol/CMakeLists.txt
new file mode 100644 (file)
index 0000000..455cf9c
--- /dev/null
@@ -0,0 +1,29 @@
+#         OpenLase - a realtime laser graphics toolkit
+#
+# Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 or version 3.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+
+include_directories (${CMAKE_SOURCE_DIR}/include)
+find_package(Threads)
+
+add_library (openlase libol.c text.c ilda.c ${CMAKE_CURRENT_BINARY_DIR}/fontdef.c)
+find_library (PTHREAD pthread)
+target_link_libraries (openlase ${CMAKE_THREAD_LIBS_INIT} m jack)
+
+add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/fontdef.c
+    DEPENDS ${CMAKE_SOURCE_DIR}/tools/genfont.py
+    MAIN_DEPENDENCY laserfont.svg
+    COMMAND python ${CMAKE_SOURCE_DIR}/tools/genfont.py ${CMAKE_CURRENT_SOURCE_DIR}/laserfont.svg ${CMAKE_CURRENT_BINARY_DIR}/fontdef.c default_font)
diff --git a/libol/ilda.c b/libol/ilda.c
new file mode 100644 (file)
index 0000000..d6528e8
--- /dev/null
@@ -0,0 +1,221 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "libol.h"
+#include "ilda.h"
+
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+static inline uint16_t swapshort(uint16_t v) {
+       return (v >> 8) | (v << 8);
+}
+# define MAGIC 0x41444C49
+#else
+static inline uint16_t swapshort(uint16_t v) {
+       return v;
+}
+# define MAGIC 0x494C4441
+#endif
+
+#include <stdint.h>
+
+struct ilda_hdr {
+       uint32_t magic;
+       uint8_t pad1[3];
+       uint8_t format;
+       char name[8];
+       char company[8];
+       uint16_t count;
+       uint16_t frameno;
+       uint16_t framecount;
+       uint8_t scanner;
+       uint8_t pad2;
+} __attribute__((packed));
+
+
+struct icoord3d {
+       int16_t x;
+       int16_t y;
+       int16_t z;
+       uint8_t state;
+       uint8_t color;
+} __attribute__((packed));
+
+struct icoord2d {
+       int16_t x;
+       int16_t y;
+       uint8_t state;
+       uint8_t color;
+} __attribute__((packed));
+
+IldaFile *olLoadIlda(const char *filename)
+{
+       int i;
+       FILE *fd = fopen(filename, "rb");
+       IldaFile *ild;
+
+       if (!fd) {
+               return NULL;
+       }
+
+       ild = malloc(sizeof(*ild));
+
+       memset(ild, 0, sizeof(*ild));
+
+       ild->min_x = 1.0;
+       ild->min_y = 1.0;
+       ild->min_z = 1.0;
+       ild->max_x = -1.0;
+       ild->max_y = -1.0;
+       ild->max_z = -1.0;
+
+       while(!ild->count) {
+
+               struct ilda_hdr hdr;
+
+               if (fread(&hdr, sizeof(hdr), 1, fd) != 1) {
+                       fprintf(stderr, "ILDA: error while reading header\n");
+                       olFreeIlda(ild);
+                       return NULL;
+               }
+
+               if (hdr.magic != MAGIC) {
+                       fprintf(stderr, "ILDA: Invalid magic 0x%08x\n", hdr.magic);
+                       olFreeIlda(ild);
+                       return NULL;
+               }
+
+               hdr.count = swapshort(hdr.count);
+               hdr.frameno = swapshort(hdr.frameno);
+               hdr.framecount = swapshort(hdr.framecount);
+
+               switch (hdr.format) {
+               case 0:
+                       printf("ILD: Got 3D frame, %d points\n", hdr.count);
+                       ild->points = malloc(sizeof(IldaPoint) * hdr.count);
+                       struct icoord3d *tmp3d = malloc(sizeof(struct icoord3d) * hdr.count);
+                       if (fread(tmp3d, sizeof(struct icoord3d), hdr.count, fd) != hdr.count) {
+                               fprintf(stderr, "ILDA: error while reading frame\n");
+                               olFreeIlda(ild);
+                               return NULL;
+                       }
+                       for(i=0; i<hdr.count; i++) {
+                               ild->points[i].x = ((int16_t)swapshort(tmp3d[i].x)) / 32768.0f;
+                               ild->points[i].y = ((int16_t)swapshort(tmp3d[i].y)) / 32768.0f;
+                               ild->points[i].z = ((int16_t)swapshort(tmp3d[i].z)) / 32768.0f;
+                               ild->points[i].is_blank = !!(tmp3d[i].state & 0x40);
+                               ild->points[i].color = tmp3d[i].color;
+                       }
+                       free(tmp3d);
+                       ild->count = hdr.count;
+                       break;
+               case 1:
+                       printf("Got 2D frame, %d points\n", hdr.count);
+                       ild->points = malloc(sizeof(IldaPoint) * hdr.count);
+                       struct icoord2d *tmp2d = malloc(sizeof(struct icoord2d) * hdr.count);
+                       if (fread(tmp2d, sizeof(struct icoord2d), hdr.count, fd) != hdr.count) {
+                               fprintf(stderr, "ILDA: error while reading frame\n");
+                               olFreeIlda(ild);
+                               return NULL;
+                       }
+                       for(i=0; i<hdr.count; i++) {
+                               ild->points[i].x = ((int16_t)swapshort(tmp2d[i].x)) / 32768.0f;
+                               ild->points[i].y = ((int16_t)swapshort(tmp2d[i].y)) / 32768.0f;
+                               ild->points[i].z = 0;
+                               ild->points[i].is_blank = !!(tmp2d[i].state & 0x40);
+                               ild->points[i].color = tmp2d[i].color;
+                       }
+                       free(tmp2d);
+                       ild->count = hdr.count;
+                       break;
+               case 2:
+                       printf("ILDA: Got color palette section, %d entries\n", hdr.count);
+                       printf("ILDA: NOT SUPPORTED\n");
+                       olFreeIlda(ild);
+                       return NULL;
+               }
+       }
+
+       fclose(fd);
+
+       for(i=0; i<ild->count; i++) {
+               if(ild->points[i].x > ild->max_x)
+                       ild->max_x = ild->points[i].x;
+               if(ild->points[i].y > ild->max_y)
+                       ild->max_y = ild->points[i].y;
+               if(ild->points[i].z > ild->max_z)
+                       ild->max_z = ild->points[i].z;
+               if(ild->points[i].x < ild->min_x)
+                       ild->min_x = ild->points[i].x;
+               if(ild->points[i].y < ild->min_y)
+                       ild->min_y = ild->points[i].y;
+               if(ild->points[i].z < ild->min_z)
+                       ild->min_z = ild->points[i].z;
+       }
+
+       return ild;
+}
+
+void olDrawIlda(IldaFile *ild)
+{
+       if (!ild)
+               return;
+       IldaPoint *p = ild->points;
+       int i;
+       olBegin(OL_POINTS);
+       for (i = 0; i < ild->count; i++) {
+               //printf("%f %f %f %d\n", p->x, p->y, p->z, p->is_blank);
+               if (p->is_blank)
+                       olVertex(p->x, p->y, C_BLACK);
+               else
+                       olVertex(p->x, p->y, C_WHITE);
+               p++;
+       }
+       olEnd();
+}
+
+void olDrawIlda3D(IldaFile *ild)
+{
+       if (!ild)
+               return;
+       IldaPoint *p = ild->points;
+       int i;
+       olBegin(OL_POINTS);
+       for (i = 0; i < ild->count; i++) {
+               if (p->is_blank)
+                       olVertex3(p->x, p->y, p->z, C_BLACK);
+               else
+                       olVertex3(p->x, p->y, p->z, C_WHITE);
+       }
+       olEnd();
+}
+
+void olFreeIlda(IldaFile *ild)
+{
+       if(ild->points)
+               free(ild->points);
+       free(ild);
+}
diff --git a/libol/laserfont.svg b/libol/laserfont.svg
new file mode 100644 (file)
index 0000000..02dfb4f
--- /dev/null
@@ -0,0 +1,1328 @@
+<?xml version="1.0" encoding="UTF-8" standalone="no"?>
+<!-- Created with Inkscape (http://www.inkscape.org/) -->
+
+<svg
+   xmlns:dc="http://purl.org/dc/elements/1.1/"
+   xmlns:cc="http://creativecommons.org/ns#"
+   xmlns:rdf="http://www.w3.org/1999/02/22-rdf-syntax-ns#"
+   xmlns:svg="http://www.w3.org/2000/svg"
+   xmlns="http://www.w3.org/2000/svg"
+   xmlns:xlink="http://www.w3.org/1999/xlink"
+   xmlns:sodipodi="http://sodipodi.sourceforge.net/DTD/sodipodi-0.dtd"
+   xmlns:inkscape="http://www.inkscape.org/namespaces/inkscape"
+   width="1100"
+   height="1100"
+   id="svg2"
+   version="1.1"
+   inkscape:version="0.47 r22583"
+   sodipodi:docname="laserfont.svg">
+  <defs
+     id="defs4">
+    <inkscape:perspective
+       id="perspective4814"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective4948"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective4972"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective4994"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5016"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5041"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5063"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5088"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5110"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5132"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5156"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5182"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5204"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5204-5"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5235"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5260"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5282"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5307"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5371"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5397"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5419"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5441"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5478"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5532"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5596"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5621"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5643"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5670"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5698"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5720"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5745"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5770"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5803"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5831"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5855"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5880"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5905"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5930"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5958"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective5983"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective6008"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective6038"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective6060"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective6085"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective6110"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective6134"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective6162"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective6194"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+    <inkscape:perspective
+       id="perspective6245"
+       inkscape:persp3d-origin="0.5 : 0.33333333 : 1"
+       inkscape:vp_z="1 : 0.5 : 1"
+       inkscape:vp_y="0 : 1000 : 0"
+       inkscape:vp_x="0 : 0.5 : 1"
+       sodipodi:type="inkscape:persp3d" />
+  </defs>
+  <sodipodi:namedview
+     id="base"
+     pagecolor="#ffffff"
+     bordercolor="#666666"
+     borderopacity="1.0"
+     inkscape:pageopacity="0.0"
+     inkscape:pageshadow="2"
+     inkscape:zoom="5.6"
+     inkscape:cx="151.86098"
+     inkscape:cy="466.76866"
+     inkscape:document-units="px"
+     inkscape:current-layer="layer1"
+     showgrid="true"
+     inkscape:window-width="1912"
+     inkscape:window-height="1011"
+     inkscape:window-x="0"
+     inkscape:window-y="0"
+     inkscape:window-maximized="1"
+     inkscape:snap-global="true">
+    <inkscape:grid
+       type="xygrid"
+       id="grid2816"
+       empspacing="2"
+       visible="true"
+       enabled="true"
+       snapvisiblegridlinesonly="true"
+       spacingx="5px"
+       spacingy="5px" />
+  </sodipodi:namedview>
+  <metadata
+     id="metadata7">
+    <rdf:RDF>
+      <cc:Work
+         rdf:about="">
+        <dc:format>image/svg+xml</dc:format>
+        <dc:type
+           rdf:resource="http://purl.org/dc/dcmitype/StillImage" />
+        <dc:title></dc:title>
+      </cc:Work>
+    </rdf:RDF>
+  </metadata>
+  <g
+     inkscape:label="Layer 1"
+     inkscape:groupmode="layer"
+     id="layer1">
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 70,60 0,20 c 0,10.00002 -10,20.00002 -20,20.00002 -10,0 -20,-10 -20,-20.00002 0,-10 10,-20 20,-20 10,0 20,10 20,20 l 0,20.00002"
+       id="path2934"
+       sodipodi:nodetypes="csssssc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 100,100 0,-20.00002 c 0,-10 10,-20 20,-20 10,0 20,10 20,20 C 140,90 130,100 120,100 110,100 100,90 100,79.99998 l 0,-50"
+       id="path2936"
+       sodipodi:nodetypes="cssssss" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="M 203.75,93.75 C 200,97.5 195,100 190,100 c -10,0 -20,-10 -20,-20.00002 0,-10 10,-20 20,-20 5,0 10,2.5 13.75,6.25"
+       id="path3712"
+       sodipodi:nodetypes="cscsc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 310,80 40,0 c 0,-10 -10,-20 -20,-20 -10,0 -20,10 -20,20 0,10 10,20 20,20 5,0 10,-2.5 13.75,-6.25"
+       id="path3769"
+       sodipodi:nodetypes="ccssss" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 390,100 0,-50 c 0,-10 10,-20 20,-20"
+       id="path3773-0"
+       sodipodi:nodetypes="csc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 380,60 20,0"
+       id="path3775-6" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 480,80 c 0,10 -10,20 -20,20 -10,0 -20,-10 -20,-20 0,-10.00002 10,-20.00002 20,-20.00002 10,0 20,10 20,20.00002 l 0,30 c 0,10 -10,20 -20,20 -10,0 -20,-10 -20,-20"
+       id="path2936-9-9"
+       sodipodi:nodetypes="csssscsc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 510,30 0,70 0,-25 c 0,-10 10,-15 15,-15 5,0 15,5 15,15 l 0,25"
+       id="path3846"
+       sodipodi:nodetypes="ccsssc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 575,60 0,40"
+       id="path3848" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 615,60 0,70"
+       id="path3848-9"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 650,100 0,-70"
+       id="path3892" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 680,60 -30,20 30,20"
+       id="path3896" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 712.49492,100 0,-70"
+       id="path3898" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 750,60 0,40 0,-25 c 0,-10 10,-15 15,-15 5,0 15,5 15,15 l 0,25 0,-25 c 0,-10 10,-15 15,-15 5,0 15,5 15,15 l 0,25"
+       id="path3900"
+       sodipodi:nodetypes="ccssscsssc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 30,220 c 0,-10 10,-20 20,-20 10,0 20,10 20,20 0,10 -10,20 -20,20 -10,0 -20,-10.13547 -20,-20 z"
+       id="path3948"
+       sodipodi:nodetypes="csssz" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 240,215 c 0,-10 10,-15 20,-15 m -20,40 0,-40"
+       id="path3994"
+       sodipodi:nodetypes="cccc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 315,205 c 0,0 -5,-5 -10,-5 -20,0 -20,20 0,20 20,0 20,20 0,20 -10,0 -15,-10 -15,-10"
+       id="path3996"
+       sodipodi:nodetypes="csssc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 355,170 0,70"
+       id="path4020" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 345,200 20,0"
+       id="path4022" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 390,200 0,25 c 0,10 10,15 15,15 5,0 15,-5 15,-15 l 0,-25"
+       id="path3846-2"
+       sodipodi:nodetypes="csssc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 445,200 20,40 20,-40"
+       id="path4061"
+       sodipodi:nodetypes="ccc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 505,200 15,40 15,-40 15,40 15,-40"
+       id="path4063" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:round;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 700,200 -35,70"
+       id="path4069"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 660,200 20,40"
+       id="path4071"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 735,200 35,0 -40,40 40,0"
+       id="path4073"
+       sodipodi:nodetypes="cccc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 575,45 0,-5"
+       id="path4117"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 615,45 0,-5"
+       id="path4119"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 280,100 0,-20.00002 c 0,-10 -10,-20 -20,-20 -10,0 -20,10 -20,20 C 240,90 250,100 260,100 c 10,0 20,-10 20,-20.00002 l 0,-50"
+       id="path2936-1"
+       sodipodi:nodetypes="cssssss" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="M 100,199.99998 100,220 c 0,10 10,20 20,20 10,0 20,-10 20,-20 0,-10.00002 -10,-20.00002 -20,-20.00002 -10,0 -20,10 -20,20.00002 l 0,50"
+       id="path2936-1-3"
+       sodipodi:nodetypes="cssssss" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="M 210,199.99998 210,220 c 0,10 -10,20 -20,20 -10,0 -20,-10 -20,-20 0,-10.00002 10,-20.00002 20,-20.00002 10,0 20,10 20,20.00002 l 0,50"
+       id="path2936-1-3-2"
+       sodipodi:nodetypes="cssssss" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_a"
+       width="60"
+       height="120"
+       x="20"
+       y="20.000032"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_b"
+       width="60"
+       height="120"
+       x="90"
+       y="20.000002"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_c"
+       width="60"
+       height="120"
+       x="160"
+       y="20.000002"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_d"
+       width="60"
+       height="120"
+       x="230"
+       y="20.000002"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_e"
+       width="60"
+       height="120"
+       x="300"
+       y="20.000002"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_f"
+       width="45"
+       height="120"
+       x="370"
+       y="20.000002"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_g"
+       width="60"
+       height="120"
+       x="430"
+       y="20.000002"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_h"
+       width="50"
+       height="120"
+       x="500"
+       y="20.000002"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_i"
+       width="30"
+       height="120"
+       x="560"
+       y="20.000002"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_j"
+       width="30"
+       height="120"
+       x="600"
+       y="20.000002"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_k"
+       width="50"
+       height="120"
+       x="640"
+       y="20.000002"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_l"
+       width="25"
+       height="120"
+       x="700"
+       y="20.000002"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_m"
+       width="80"
+       height="120"
+       x="740"
+       y="20.000002"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 840,60 0,40 0,-25 c 0,-10 10,-15 15,-15 5,0 15,5 15,15 l 0,25"
+       id="path3900-9-8"
+       sodipodi:nodetypes="ccsssc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_n"
+       width="50"
+       height="120"
+       x="830"
+       y="20.000002"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_o"
+       width="60"
+       height="120"
+       x="20"
+       y="160"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_p"
+       width="60"
+       height="120"
+       x="90"
+       y="160"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_q"
+       width="60"
+       height="120"
+       x="160"
+       y="160"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_r"
+       width="35"
+       height="120"
+       x="230"
+       y="160"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_s"
+       width="50"
+       height="120"
+       x="280"
+       y="160"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_t"
+       width="30"
+       height="120"
+       x="340"
+       y="160"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_u"
+       width="50"
+       height="120"
+       x="380"
+       y="160"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_v"
+       width="50"
+       height="120"
+       x="440"
+       y="160"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_w"
+       width="70"
+       height="120"
+       x="500"
+       y="160"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_x"
+       width="60"
+       height="120"
+       x="580"
+       y="160"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 590,240 40,-40"
+       id="path4065-5" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 590,200 40,40"
+       id="path4067-0" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_y"
+       width="50"
+       height="120"
+       x="655"
+       y="160"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_z"
+       width="60"
+       height="120"
+       x="720"
+       y="160"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_0x20"
+       width="40"
+       height="120"
+       x="20"
+       y="580"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="overlap"
+       width="10"
+       height="120"
+       x="80"
+       y="580"
+       inkscape:label="#rect4828" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_0x3f"
+       width="55"
+       height="120"
+       x="795"
+       y="160"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 800,185 c 0,-10 10,-20 20,-20 10,0 20,10 20,20 0,10 -20,15 -20,25 0,10 0,10 0,10"
+       id="path4962"
+       sodipodi:nodetypes="csssc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 820,240 0,-5"
+       id="path4119-9"
+       sodipodi:nodetypes="cc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_0x21"
+       width="30"
+       height="120"
+       x="860"
+       y="160"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 875,220 0,-55"
+       id="path3848-9-4"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 875,235 0,5"
+       id="path4119-6"
+       sodipodi:nodetypes="cc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_0x3a"
+       width="20"
+       height="120"
+       x="110"
+       y="580"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 120,630 0,-5"
+       id="path4119-9-7"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 120,660 0,-5"
+       id="path4119-9-7-0"
+       sodipodi:nodetypes="cc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_0x2d"
+       width="50"
+       height="120"
+       x="330"
+       y="580"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 340,640 30,0"
+       id="path5146" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_0x29"
+       width="40"
+       height="120"
+       x="280"
+       y="580"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 290,680 c 10,-5 15,-25.23403 15,-45 0,-20 -5,-40 -15,-45"
+       id="path5172"
+       sodipodi:nodetypes="csc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_0x2c"
+       width="20"
+       height="120"
+       x="170"
+       y="580"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 175,670 5,-15"
+       id="path4119-9-7-0-4"
+       sodipodi:nodetypes="cc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_0x3b"
+       width="20"
+       height="120"
+       x="140"
+       y="580"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 145,670 5,-15"
+       id="path4119-9-7-0-4-4"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 150,630 0,-5"
+       id="path4119-9-7-05"
+       sodipodi:nodetypes="cc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_0x28"
+       width="40"
+       height="120"
+       x="230"
+       y="580"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 260,680 c -10,-5 -15,-25.23403 -15,-45 0,-20 5,-40 15,-45"
+       id="path5172-1"
+       sodipodi:nodetypes="csc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_0x2e"
+       width="20"
+       height="120"
+       x="200"
+       y="580"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 210,660 0,-5"
+       id="path4119-9-7-0-2"
+       sodipodi:nodetypes="cc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_0x5f"
+       width="50"
+       height="120"
+       x="390"
+       y="580"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 420,345 30,0"
+       id="path5459-9"
+       sodipodi:nodetypes="cc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_A"
+       width="80"
+       height="119.99997"
+       x="20"
+       y="300.00003"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_B"
+       width="60"
+       height="120"
+       x="110"
+       y="300"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 30,380 30,-70 30,70"
+       id="path5357"
+       sodipodi:nodetypes="ccc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 40,355 40,0"
+       id="path5359"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 120,380 0,-70 15,0 c 10,0 15,8.43559 15,20 0,10 -5,15 -15,15 l -15,0 20,0 c 13.92645,0 20,10 20,20 0,5 -5,15 -20,15 l -20,0 z"
+       id="path5361"
+       sodipodi:nodetypes="ccssscsssc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_C"
+       width="80"
+       height="120"
+       x="180"
+       y="300"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 250,370 c -6.25,6.25 -15,10 -25,10 -20.35714,0 -35,-15.71098 -35,-35 0,-20 15,-35 35,-35 9.28743,0 18.03743,3.75 24.46557,10"
+       id="path5385"
+       sodipodi:nodetypes="cszsc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_D"
+       width="65"
+       height="120"
+       x="270"
+       y="300"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 290,310 -10,0 0,70 10,0 c 20.35714,0 35,-15.71098 35,-35 0,-20 -15,-35 -35,-35 z"
+       id="path5385-1"
+       sodipodi:nodetypes="cccczc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_E"
+       width="55"
+       height="120"
+       x="345"
+       y="300"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 390,310 -35,0 0,70 35,0"
+       id="path5455" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 355,345 30,0"
+       id="path5459"
+       sodipodi:nodetypes="cc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_F"
+       width="55"
+       height="120"
+       x="410"
+       y="300"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 455,310 -35,0 0,70"
+       id="path5455-9"
+       sodipodi:nodetypes="ccc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_G"
+       width="85"
+       height="120"
+       x="475"
+       y="300"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="M 544.46557,320 C 538.03743,313.75 529.28743,310 520,310 c -20,0 -35,15 -35,35 0,19.28902 14.64286,35 35,35 16.60141,0 30,-9.785 30,-25 l 0,-10 -30,0"
+       id="path5385-0"
+       sodipodi:nodetypes="cszsscc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 395,665 40,0"
+       id="path5660" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_H"
+       width="65"
+       height="120"
+       x="570"
+       y="300"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 580,310 0,70"
+       id="path5455-1"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 580,345 45,0"
+       id="path5459-2"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 625,310 0,70"
+       id="path5455-1-6"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 657.49493,380 0,-70"
+       id="path3898-2" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_I"
+       width="25"
+       height="120"
+       x="645"
+       y="300"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 685,375 c 5,15 20,10 20,-5 l 0,-60"
+       id="path3898-2-4"
+       sodipodi:nodetypes="ccc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_J"
+       width="35"
+       height="120"
+       x="680"
+       y="300"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_K"
+       width="60"
+       height="120"
+       x="725"
+       y="300"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 735,310 0,70"
+       id="path5455-1-2"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 775,310 -40,35 40,35"
+       id="path5793" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_L"
+       width="45"
+       height="120"
+       x="795"
+       y="300"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 830,380 -25,0 0,-70"
+       id="path5455-9-7"
+       sodipodi:nodetypes="ccc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_M"
+       width="90"
+       height="120"
+       x="850"
+       y="300"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 855,380 15,-70 25,65 25,-65 15,70"
+       id="path5845"
+       sodipodi:nodetypes="ccccc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_N"
+       width="80"
+       height="120"
+       x="950"
+       y="300"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 960,380 0,-70 60,70 0,-70"
+       id="path5845-9"
+       sodipodi:nodetypes="cccc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_O"
+       width="90"
+       height="120"
+       x="20"
+       y="430"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 100,475 c 0,20 -15,35 -35,35 -20.0008,0 -35,-15.71098 -35,-35 0,-20 13.74914,-35 35,-35 20,0 35,14.10237 35,35 z"
+       id="path5385-6"
+       sodipodi:nodetypes="czzzz" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_P"
+       width="55"
+       height="120"
+       x="120"
+       y="430"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 130,475 15,0 c 13.92645,0 20,-10.22716 20,-17.72716 C 165,449.77284 160,440 145,440 l -15,0 0,70"
+       id="path5361-9"
+       sodipodi:nodetypes="cszscc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_Q"
+       width="90"
+       height="120"
+       x="190"
+       y="430"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 270,475 c 0,20 -15,35 -35,35 -20.0008,0 -35,-15.71098 -35,-35 0,-20 13.74914,-35 35,-35 20,0 35,14.10237 35,35 z"
+       id="path5385-6-1"
+       sodipodi:nodetypes="czzzz" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="M 265,510 245,490"
+       id="path5947"
+       sodipodi:nodetypes="cc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_R"
+       width="55"
+       height="120"
+       x="290"
+       y="430"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="M 335,510 307.50508,475 300,475 l 15,0 c 13.92645,0 20,-10.22716 20,-17.72716 C 335,449.77284 330,440 315,440 l -15,0 0,70"
+       id="path5361-9-4"
+       sodipodi:nodetypes="cccszscc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 405,450 c -5,-10 -15,-10 -20,-10 -20,0 -30,30 0,35 30,5 25,35 0,35 -10,0 -19.28571,-5 -20,-10"
+       id="path3996-9"
+       sodipodi:nodetypes="csssc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_S"
+       width="60"
+       height="120"
+       x="355"
+       y="430"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 430,440 40,0"
+       id="path5459-9-1"
+       sodipodi:nodetypes="cc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_T"
+       width="50"
+       height="120"
+       x="425"
+       y="430"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 450,440 0,70"
+       id="path5455-9-6"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 495,440 0,48.03571 c 0,15 11.25,21.93891 22.5,21.93891 11.25,0 22.5,-6.93891 22.5,-21.93891 L 540,440"
+       id="path6028"
+       sodipodi:nodetypes="ccscc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_U"
+       width="65"
+       height="120"
+       x="485"
+       y="430"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="M 565.01578,440 590,510 615,440"
+       id="path4061-1"
+       sodipodi:nodetypes="ccc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_V"
+       width="60"
+       height="120"
+       x="560"
+       y="430"
+       inkscape:label="#rect4201" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_W"
+       width="100"
+       height="120"
+       x="630"
+       y="430"
+       inkscape:label="#rect4201" />
+    <path
+       id="path4061-1-9-3"
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="M 630.01578,440 655,510 l 25,-70 25,70 25,-70"
+       sodipodi:nodetypes="ccccc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_X"
+       width="60"
+       height="120"
+       x="740"
+       y="430"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 790,440 -40,70"
+       id="path5455-1-2-7"
+       sodipodi:nodetypes="cc" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 750,440 40,70"
+       id="path5793-4"
+       sodipodi:nodetypes="cc" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_Y"
+       width="60"
+       height="120"
+       x="810"
+       y="430"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 820,440 20,40 20,-40"
+       id="path6182" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 840,480 0,30"
+       id="path6184" />
+    <rect
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:1;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       id="char_Z"
+       width="70"
+       height="120"
+       x="880"
+       y="430"
+       inkscape:label="#rect4201" />
+    <path
+       style="color:#000000;fill:none;stroke:#000000;stroke-width:3;stroke-linecap:butt;stroke-linejoin:bevel;stroke-miterlimit:3.97361732;stroke-opacity:1;stroke-dasharray:none;stroke-dashoffset:0;marker:none;visibility:visible;display:inline;overflow:visible;enable-background:accumulate"
+       d="m 895,440 45,0 -50,70 50,0"
+       id="path6214"
+       sodipodi:nodetypes="cccc" />
+    <image
+       y="580"
+       x="460"
+       id="image2926"
+       height="160"
+       width="240"
+       xlink:href="file:///home/marcansoft/inkscape_pasted_image_20100718_003225.png" />
+  </g>
+</svg>
diff --git a/libol/libol.c b/libol/libol.c
new file mode 100644 (file)
index 0000000..0b471f4
--- /dev/null
@@ -0,0 +1,1138 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "libol.h"
+#include <jack/jack.h>
+#include <stdio.h>
+#include <stdlib.h>
+#include <string.h>
+#include <math.h>
+#include <unistd.h>
+
+typedef jack_default_audio_sample_t sample_t;
+typedef jack_nframes_t nframes_t;
+
+static jack_port_t *out_x;
+static jack_port_t *out_y;
+static jack_port_t *out_r;
+static jack_port_t *out_g;
+static jack_port_t *out_b;
+static jack_port_t *out_al;
+static jack_port_t *out_ar;
+
+static jack_client_t *client;
+
+typedef struct {
+       float x,y;
+       uint32_t color;
+} Point;
+
+typedef struct {
+       int pointcnt;
+       Point *points;
+} Object;
+
+typedef struct {
+       int objcnt;
+       int objmax;
+       Object *objects;
+       int psmax;
+       int psnext;
+       Point *points;
+} Frame;
+
+typedef struct {
+       int pmax;
+       int pnext;
+       Point *points;
+       float *audio_l;
+       float *audio_r;
+} RenderedFrame;
+
+static RenderedFrame *frames;
+static nframes_t jack_rate;
+
+static OLFrameInfo last_info;
+
+static Frame wframe;
+
+typedef struct {
+       Object *curobj;
+       Point last_point;
+       Point last_slope;
+       Point c1, c2;
+       int prim;
+       int state;
+       int points;
+} DrawState;
+
+static DrawState dstate;
+
+static OLRenderParams params;
+
+static Point last_render_point;
+
+static volatile int crbuf;
+static volatile int cwbuf;
+static int fbufs;
+static int buflag;
+static int out_point;
+static int first_time_full;
+static int first_output_frame;
+
+float bbox[2][2];
+
+#define MTX_STACK_DEPTH 16
+
+int mtx2dp = 0;
+float mtx2ds[MTX_STACK_DEPTH][3][3];
+float mtx2d[3][3];
+
+int mtx3dp = 0;
+float mtx3ds[MTX_STACK_DEPTH][4][4];
+float mtx3d[4][4];
+
+int coldp = 0;
+uint32_t cols[MTX_STACK_DEPTH];
+uint32_t curcol;
+
+#define POINT(x, y, color) ((Point){x,y,color})
+
+ShaderFunc vpreshader;
+ShaderFunc vshader;
+Shader3Func v3shader;
+ShaderFunc pshader;
+
+AudioCallbackFunc audiocb;
+
+static uint32_t colmul(uint32_t a, uint32_t b)
+{
+    uint32_t out = 0;
+    out |= ((a&0xff)*(b&0xff)) / 255;
+    out |= ((((a>>8)&0xff)*((b>>8)&0xff)) / 255)<<8;
+    out |= ((((a>>16)&0xff)*((b>>16)&0xff)) / 255)<<16;
+    return out;
+}
+
+static Point *ps_alloc(int count)
+{
+       Point *ret;
+       if ((count + wframe.psnext) > wframe.psmax) {
+               fprintf(stderr, "Point buffer overflow (temp): need %d points, have %d\n", count + wframe.psnext, wframe.psmax);
+               exit(1);
+       }
+       ret = wframe.points + wframe.psnext;
+       wframe.psnext += count;
+       return ret;
+}
+
+static int bufsize (nframes_t nframes, void *arg)
+{
+       printf ("the maximum buffer size is now %u\n", nframes);
+       return 0;
+}
+
+static int srate (nframes_t nframes, void *arg)
+{
+       jack_rate = nframes;
+       printf ("Playing back at %u Hz\n", jack_rate);
+       return 0;
+}
+
+static void jack_shutdown (void *arg)
+{
+       printf ("jack_shutdown\n");
+}
+
+static int process (nframes_t nframes, void *arg)
+{
+       sample_t *o_x = (sample_t *) jack_port_get_buffer (out_x, nframes);
+       sample_t *o_y = (sample_t *) jack_port_get_buffer (out_y, nframes);
+       sample_t *o_r = (sample_t *) jack_port_get_buffer (out_r, nframes);
+       sample_t *o_g = (sample_t *) jack_port_get_buffer (out_g, nframes);
+       sample_t *o_b = (sample_t *) jack_port_get_buffer (out_b, nframes);
+       sample_t *o_al = (sample_t *) jack_port_get_buffer (out_al, nframes);
+       sample_t *o_ar = (sample_t *) jack_port_get_buffer (out_ar, nframes);
+
+       if (!first_time_full) {
+               printf("Dummy frame!\n");
+               memset(o_x, 0, nframes * sizeof(sample_t));
+               memset(o_y, 0, nframes * sizeof(sample_t));
+               memset(o_r, 0, nframes * sizeof(sample_t));
+               memset(o_g, 0, nframes * sizeof(sample_t));
+               memset(o_b, 0, nframes * sizeof(sample_t));
+               memset(o_al, 0, nframes * sizeof(sample_t));
+               memset(o_ar, 0, nframes * sizeof(sample_t));
+               return 0;
+       }
+
+       while(nframes) {
+               if (out_point == -1) {
+                       if (!first_output_frame) {
+                               printf("First frame! %d\n", crbuf);
+                               first_output_frame = 1;
+                       } else {
+                               if ((crbuf+1)%fbufs == cwbuf) {
+                                       printf("Duplicated frame! %d\n", crbuf);
+                               } else {
+                                       crbuf = (crbuf+1)%fbufs;
+                                       //printf("Normal frame! %d\n", crbuf);
+                               }
+                       }
+                       out_point = 0;
+               }
+               int count = nframes;
+               int left = frames[crbuf].pnext - out_point;
+               if (count > left)
+                       count = left;
+               int i;
+               for (i=0; i<count; i++) {
+                       Point *p = &frames[crbuf].points[out_point];
+                       *o_x++ = p->x;
+                       *o_y++ = p->y;
+
+                       *o_r++ = ((p->color >> 16) & 0xff) / 255.0f;
+                       *o_g++ = ((p->color >> 8) & 0xff) / 255.0f;
+                       *o_b++ = (p->color & 0xff) / 255.0f;
+
+                       *o_al++ = frames[crbuf].audio_l[out_point];
+                       *o_ar++ = frames[crbuf].audio_r[out_point];
+                       out_point++;
+                       //printf("%06x %f %f\n", p->x, p->y, p->color);
+               }
+               if (out_point == frames[crbuf].pnext)
+                       out_point = -1;
+               nframes -= count;
+       }
+       return 0;
+}
+
+int olInit(int buffer_count, int max_points)
+{
+       int i;
+       if (buffer_count < 2)
+               return -1;
+
+       memset(&dstate, 0, sizeof(dstate));
+       memset(&last_render_point, 0, sizeof(last_render_point));
+
+       buflag = buffer_count;
+       fbufs = buffer_count+1;
+
+       cwbuf = 0;
+       crbuf = 0;
+       out_point = -1;
+       first_time_full = 0;
+       first_output_frame = 0;
+       memset(&wframe, 0, sizeof(Frame));
+       wframe.objmax = 16;
+       wframe.objects = malloc(wframe.objmax * sizeof(Object));
+       wframe.psmax = max_points;
+       wframe.points = malloc(wframe.psmax * sizeof(Point));
+       frames = malloc(fbufs * sizeof(RenderedFrame));
+       for (i=0; i<fbufs; i++) {
+               memset(&frames[i], 0, sizeof(RenderedFrame));
+               frames[i].pmax = max_points;
+               frames[i].points = malloc(frames[i].pmax * sizeof(Point));
+               frames[i].audio_l = malloc(frames[i].pmax * sizeof(float));
+               frames[i].audio_r = malloc(frames[i].pmax * sizeof(float));
+       }
+
+       if ((client = jack_client_new ("libol")) == 0) {
+               fprintf (stderr, "jack server not running?\n");
+               return -1;
+       }
+
+       jack_set_process_callback (client, process, 0);
+       jack_set_buffer_size_callback (client, bufsize, 0);
+       jack_set_sample_rate_callback (client, srate, 0);
+       jack_on_shutdown (client, jack_shutdown, 0);
+
+       out_x = jack_port_register (client, "out_x", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_y = jack_port_register (client, "out_y", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_r = jack_port_register (client, "out_r", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_g = jack_port_register (client, "out_g", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_b = jack_port_register (client, "out_b", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_al = jack_port_register (client, "out_al", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_ar = jack_port_register (client, "out_ar", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+
+       if (jack_activate (client)) {
+               fprintf (stderr, "cannot activate client");
+               return -1;
+       }
+
+       olLoadIdentity();
+       for(i=0; i<MTX_STACK_DEPTH; i++)
+               olPushMatrix();
+       mtx2dp = 0;
+
+       olLoadIdentity3();
+       for(i=0; i<MTX_STACK_DEPTH; i++)
+               olPushMatrix3();
+       mtx3dp = 0;
+
+       olResetColor();
+       for(i=0; i<MTX_STACK_DEPTH; i++)
+               olPushColor();
+       coldp = 0;
+
+       bbox[0][0] = -1;
+       bbox[0][1] = -1;
+       bbox[1][0] = 1;
+       bbox[1][1] = 1;
+
+       vpreshader = NULL;
+       vshader = NULL;
+       v3shader = NULL;
+       pshader = NULL;
+       audiocb = NULL;
+
+       return 0;
+}
+
+void olSetRenderParams(OLRenderParams *sp)
+{
+       params = *sp;
+}
+
+void olShutdown(void)
+{
+       jack_client_close (client);
+}
+
+void olBegin(int prim)
+{
+       if (dstate.curobj)
+               return;
+       if (wframe.objmax == wframe.objcnt) {
+               wframe.objmax *= 2;
+               wframe.objects = realloc(wframe.objects, wframe.objmax * sizeof(Object));
+       }
+       dstate.curobj = wframe.objects + wframe.objcnt;
+       memset(dstate.curobj, 0, sizeof(Object));
+       dstate.curobj->points = wframe.points + wframe.psnext;
+       dstate.prim = prim;
+       dstate.state = 0;
+       dstate.points = 0;
+}
+
+static int near(Point a, Point b)
+{
+       float dx = a.x - b.x;
+       float dy = a.y - b.y;
+       return sqrtf(dx*dx+dy*dy) <= params.snap;
+}
+
+static void addpoint(float x, float y, uint32_t color)
+{
+       Point *pnt = ps_alloc(1);
+       pnt->x = x;
+       pnt->y = y;
+       pnt->color = color;
+       dstate.curobj->pointcnt++;
+}
+
+static int get_dwell(float x, float y)
+{
+       if (dstate.points == 1) {
+               return params.start_dwell;
+       } else {
+               float ex = dstate.last_point.x;
+               float ey = dstate.last_point.y;
+               float ecx = dstate.last_slope.x;
+               float ecy = dstate.last_slope.y;
+               float sx = ex;
+               float sy = ey;
+               float scx = x;
+               float scy = y;
+               float dex = ecx-ex;
+               float dey = ecy-ey;
+               float dsx = sx-scx;
+               float dsy = sy-scy;
+               float dot = dex*dsx + dey*dsy;
+               float lens = sqrtf(dex*dex+dey*dey) * sqrtf(dsx*dsx+dsy*dsy);
+               //printf("%f,%f -> %f,%f -> %f,%f\n", ecx,ecy,ex,ey,x,y);
+               if (lens == 0) {
+                       //printf("deg cor\n");
+                       return params.corner_dwell;
+               } else {
+                       dot = dot / lens;
+                       if (dot > params.curve_angle) {
+                               //printf("curve\n");
+                               return params.curve_dwell;
+                       } else {
+                               //printf("cor\n");
+                               return params.corner_dwell;
+                       }
+               }
+       }
+}
+
+static void line_to(float x, float y, uint32_t color)
+{
+       int dwell, i;
+       //printf("points: %d %d\n", dstate.points, dstate.curobj->pointcnt      );
+       if (dstate.points == 0) {
+               addpoint(x,y,color);
+               dstate.points++;
+               dstate.last_point = POINT(x,y,color);
+               return;
+       }
+       dwell = get_dwell(x, y);
+       Point last = dstate.last_point;
+       for (i=0; i<dwell; i++)
+               addpoint(last.x,last.y,last.color);
+       float dx = x - last.x;
+       float dy = y - last.y;
+       float distance = fmaxf(fabsf(dx),fabsf(dy));
+       int points = ceilf(distance/params.on_speed);
+       for (i=1; i<=points; i++) {
+               addpoint(last.x + (dx/(float)points) * i, last.y + (dy/(float)points) * i, color);
+       }
+       dstate.last_slope = dstate.last_point;
+       dstate.last_point = POINT(x,y,color);
+       dstate.points++;
+}
+
+static void recurse_bezier(float x1, float y1, float x2, float y2, float x3, float y3, uint32_t color)
+{
+       float x0 = dstate.last_point.x;
+       float y0 = dstate.last_point.y;
+       int subdivide = 0;
+
+       float dx = x3-x0;
+       float dy = y3-y0;
+       float distance = fmaxf(fabsf(dx),fabsf(dy));
+       if (distance > params.on_speed) {
+               subdivide = 1;
+       } else {
+               float ux = (3.0*x1 - 2.0*x0 - x3); ux = ux * ux;
+               float uy = (3.0*y1 - 2.0*y0 - y3); uy = uy * uy;
+               float vx = (3.0*x2 - 2.0*x3 - x0); vx = vx * vx;
+               float vy = (3.0*y2 - 2.0*y3 - y0); vy = vy * vy;
+               if (ux < vx)
+                       ux = vx;
+               if (uy < vy)
+                       uy = vy;
+               if ((ux+uy) > params.flatness)
+                       subdivide = 1;
+       }
+
+       if (subdivide) {
+               //de Casteljau at t=0.5
+               float mcx = (x1 + x2) * 0.5;
+               float mcy = (y1 + y2) * 0.5;
+               float ax1 = (x0 + x1) * 0.5;
+               float ay1 = (y0 + y1) * 0.5;
+               float ax2 = (ax1 + mcx) * 0.5;
+               float ay2 = (ay1 + mcy) * 0.5;
+               float bx2 = (x2 + x3) * 0.5;
+               float by2 = (y2 + y3) * 0.5;
+               float bx1 = (bx2 + mcx) * 0.5;
+               float by1 = (by2 + mcy) * 0.5;
+               float xm = (ax2 + bx1) * 0.5;
+               float ym = (ay2 + by1) * 0.5;
+               recurse_bezier(ax1, ay1, ax2, ay2, xm, ym, color);
+               recurse_bezier(bx1, by1, bx2, by2, x3, y3, color);
+       } else {
+               addpoint(x3, y3, color);
+               dstate.last_point = POINT(x3,y3,color);
+       }
+}
+
+static void bezier_to(float x, float y, uint32_t color)
+{
+       int dwell, i;
+
+       if (dstate.points == 0) {
+               addpoint(x,y,color);
+               dstate.points++;
+               dstate.last_point = POINT(x,y,color);
+               return;
+       }
+
+       switch(dstate.state) {
+               case 0:
+                       dstate.c1 = POINT(x,y,color);
+                       dstate.state++;
+                       return;
+               case 1:
+                       dstate.c2 = POINT(x,y,color);
+                       dstate.state++;
+                       return;
+               case 2:
+                       break;
+       }
+
+       if (near(dstate.last_point, dstate.c1))
+               dwell = get_dwell(dstate.c2.x, dstate.c2.y);
+       else
+               dwell = get_dwell(dstate.c1.x, dstate.c1.y);
+
+       Point last = dstate.last_point;
+       for (i=0; i<dwell; i++)
+               addpoint(last.x,last.y,last.color);
+
+       recurse_bezier(dstate.c1.x, dstate.c1.y, dstate.c2.x, dstate.c2.y, x, y, color);
+
+       dstate.last_point = POINT(x,y,color);
+       if (near(dstate.c2, dstate.last_point))
+               dstate.last_slope = dstate.c1;
+       else
+               dstate.last_slope = dstate.c2;
+       dstate.points++;
+       dstate.state = 0;
+}
+
+
+static void point_to(float x, float y, uint32_t color)
+{
+       addpoint(x,y,color);
+       dstate.points++;
+       return;
+}
+
+void olVertex(float x, float y, uint32_t color)
+{
+       if (!dstate.curobj)
+               return;
+
+       float nx, ny;
+
+       if(vpreshader)
+               vpreshader(&x, &y, &color);
+
+       color = colmul(color,curcol);
+
+       nx = mtx2d[0][0] * x + mtx2d[0][1] * y + mtx2d[0][2];
+       ny = mtx2d[1][0] * x + mtx2d[1][1] * y + mtx2d[1][2];
+
+       if(vshader)
+               vshader(&nx, &ny, &color);
+
+       switch (dstate.prim) {
+               case OL_LINESTRIP:
+                       line_to(nx,ny,color);
+                       break;
+               case OL_BEZIERSTRIP:
+                       bezier_to(nx,ny,color);
+                       break;
+               case OL_POINTS:
+                       point_to(nx,ny,color);
+                       break;
+       }
+}
+
+void olEnd(void)
+{
+       int i;
+       if (!dstate.curobj)
+               return;
+       if (dstate.points < 2) {
+               dstate.curobj = NULL;
+               return;
+       }
+       Point *last = dstate.curobj->points + dstate.curobj->pointcnt - 1;
+       for (i=0; i<params.end_dwell; i++)
+               addpoint(last->x,last->y,last->color);
+
+       if(pshader) {
+               for (i=0; i<dstate.curobj->pointcnt; i++) {
+                       pshader(&dstate.curobj->points[i].x, &dstate.curobj->points[i].y, &dstate.curobj->points[i].color);
+               }
+       }
+
+       wframe.objcnt++;
+       dstate.curobj = NULL;
+}
+
+static void chkpts(int count)
+{
+       if (frames[cwbuf].pnext + count > frames[cwbuf].pmax) {
+               fprintf(stderr, "Point buffer overflow (final): need %d points, have %d\n",
+                               count + frames[cwbuf].pnext, frames[cwbuf].pmax);
+               exit(1);
+       }
+}
+
+static void addrndpoint(float x, float y, uint32_t color)
+{
+       frames[cwbuf].points[frames[cwbuf].pnext].x = x;
+       frames[cwbuf].points[frames[cwbuf].pnext].y = y;
+       frames[cwbuf].points[frames[cwbuf].pnext].color = color;
+       frames[cwbuf].pnext++;
+}
+
+static void render_object(Object *obj)
+{
+       int i,j;
+       Point *start = &obj->points[0];
+       Point *end = &obj->points[obj->pointcnt-1];
+       float dx = start->x - last_render_point.x;
+       float dy = start->y - last_render_point.y;
+       float distance = fmaxf(fabsf(dx),fabsf(dy));
+       int points = ceilf(distance/params.off_speed);
+       chkpts(2 * (obj->pointcnt + params.start_wait + params.end_wait + points));
+       Point *out_start = NULL;
+       int skip_out_start_wait = 0;
+
+       Point *ip = obj->points;
+       for (i=0; i<obj->pointcnt; i++, ip++) {
+               if (ip->x < bbox[0][0] || ip->x > bbox[1][0] ||
+                       ip->y < bbox[0][1] || ip->y > bbox[1][1])
+                       continue;
+               if (ip->color != C_BLACK)
+                       break;
+       }
+       if (i == obj->pointcnt) // null object
+               return;
+
+       if (start->x < bbox[0][0] || start->x > bbox[1][0] ||
+               start->y < bbox[0][1] || start->y > bbox[1][1]) {
+               out_start = &last_render_point;
+               skip_out_start_wait = 1;
+       } else if (distance > params.snap) {
+               for (i=0; i<points; i++) {
+                       addrndpoint(last_render_point.x + (dx/(float)points) * i,
+                                               last_render_point.y + (dy/(float)points) * i,
+                                               C_BLACK);
+               }
+               for (i=0; i<params.start_wait; i++) {
+                       addrndpoint(start->x, start->y, C_BLACK);
+               }
+       }
+       Point *op = &frames[cwbuf].points[frames[cwbuf].pnext];
+       ip = obj->points;
+       for (i=0; i<obj->pointcnt; i++, ip++) {
+               int inside = 1;
+               if (ip->x < bbox[0][0] || ip->x > bbox[1][0] ||
+                       ip->y < bbox[0][1] || ip->y > bbox[1][1])
+                       inside = 0;
+               if (!out_start) {
+                       if (inside) {
+                               *op++ = *ip;
+                               frames[cwbuf].pnext++;
+                       } else {
+                               out_start = ip;
+                               last_render_point = *ip;
+                       }
+               } else if (inside) {
+                       if (!skip_out_start_wait) {
+                               for (j=0; j<params.end_wait; j++) {
+                                       *op = *out_start;
+                                       op->color = C_BLACK;
+                                       op++;
+                                       frames[cwbuf].pnext++;
+                               }
+                       }
+                       skip_out_start_wait = 0;
+                       float dx = ip->x - out_start->x;
+                       float dy = ip->y - out_start->y;
+                       float distance = fmaxf(fabsf(dx),fabsf(dy));
+                       int points = ceilf(distance/params.off_speed);
+                       if (distance > params.snap) {
+                               for (j=0; j<points; j++) {
+                                       op->x = out_start->x + (dx/(float)points) * j;
+                                       op->y = out_start->y + (dy/(float)points) * j;
+                                       op->color = C_BLACK;
+                                       op++;
+                                       frames[cwbuf].pnext++;
+                               }
+                               for (j=0; j<params.start_wait; j++) {
+                                       *op = *ip;
+                                       op->color = C_BLACK;
+                                       op++;
+                                       frames[cwbuf].pnext++;
+                               }
+                       }
+                       *op++ = *ip;
+                       frames[cwbuf].pnext++;
+                       out_start = NULL;
+               }
+       }
+       if(!out_start) {
+               for (i=0; i<params.end_wait; i++) {
+                       addrndpoint(end->x, end->y, C_BLACK);
+               }
+               last_render_point = *end;
+       } else {
+               for (i=0; i<params.end_wait; i++) {
+                       addrndpoint(out_start->x, out_start->y, C_BLACK);
+               }
+               last_render_point = *out_start;
+       }
+}
+
+float olRenderFrame(int max_fps)
+{
+       int i;
+       int count = 0;
+
+       int min_points = params.rate / max_fps;
+
+       memset(&last_info, 0, sizeof(last_info));
+
+       while (((cwbuf+1)%fbufs) == crbuf) {
+               //printf("Waiting %d %d\n", cwbuf, crbuf);
+               usleep(1000);
+               first_time_full = 1;
+       }
+       frames[cwbuf].pnext=0;
+       int cnt = wframe.objcnt;
+       float dclosest = 0;
+       int clinv = 0;
+
+       if (!(params.render_flags & RENDER_NOREORDER)) {
+               while(cnt) {
+                       Object *closest = NULL;
+                       for (i=0; i<wframe.objcnt; i++) {
+                               if (!wframe.objects[i].pointcnt)
+                                       continue;
+                               if (wframe.objects[i].pointcnt < params.min_length)
+                                       continue;
+                               float dx = wframe.objects[i].points[0].x - last_render_point.x;
+                               float dy = wframe.objects[i].points[0].y - last_render_point.y;
+                               if (frames[cwbuf].pnext == 0) {
+                                       dx = wframe.objects[i].points[0].x + 1;
+                                       dy = wframe.objects[i].points[0].y + 1;
+                               }
+                               float distance = fmaxf(fabsf(dx),fabsf(dy));
+                               if (!closest || distance < dclosest) {
+                                       closest = &wframe.objects[i];
+                                       clinv = 0;
+                                       dclosest = distance;
+                               }/* XXX Support reversing objects. Commented out due to framerate artifacting when successive frames are traversed in opposite order.
+                                   Should fix this and instead force object traversion to begin at the same point each frame to achieve the same effect, maybe. 
+                               dx = wframe.objects[i].points[wframe.objects[i].pointcnt-1].x - last_render_point.x;
+                               dy = wframe.objects[i].points[wframe.objects[i].pointcnt-1].y - last_render_point.y;
+                               distance = fmaxf(fabsf(dx),fabsf(dy));
+                               if(!closest || distance < dclosest) {
+                                       closest = &wframe.objects[i];
+                                       clinv = 1;
+                                       dclosest = distance;
+                               }*/
+                       }
+                       if (clinv) {
+                               Point *pt = closest->points;
+                               int cnt = closest->pointcnt;
+                               for (i=0; i<cnt/2; i++) {
+                                       Point tmp = pt[i];
+                                       pt[i] = pt[cnt-i-1];
+                                       pt[cnt-i-1] = tmp;
+                               }
+                       }
+                       if (!closest)
+                               break;
+                       //printf("%d (%d) ", closest - wframe.objects, closest->pointcnt);
+                       render_object(closest);
+                       //printf("[%d] ", frames[cwbuf].pnext);
+                       //printf("[LRP:%f %f]\n", last_render_point.x, last_render_point.y);
+                       closest->pointcnt = 0;
+                       cnt--;
+               }
+               //printf("\n");
+       } else {
+               for (i=0; i<wframe.objcnt; i++) {
+                       if (wframe.objects[i].pointcnt < params.min_length)
+                               continue;
+                       render_object(&wframe.objects[i]);
+               }
+       }
+       wframe.psnext = 0;
+       wframe.objcnt = 0;
+       count = frames[cwbuf].pnext;
+       last_info.points = count;
+
+       if (params.max_framelen && count > params.max_framelen)
+       {
+               int in_count = count;
+               int out_count = params.max_framelen;
+               chkpts(count);
+
+               Point *pin = frames[cwbuf].points;
+               Point *pout = &pin[in_count];
+
+               float pos = 0;
+               float delta = count / (float)out_count;
+
+               count = 0;
+               while (pos < (in_count - 1)) {
+                       int ipos = pos;
+                       float rest = pos - ipos;
+
+                       pout->x = pin[ipos].x * (1-rest) + pin[ipos+1].x * rest;
+                       pout->y = pin[ipos].y * (1-rest) + pin[ipos+1].y * rest;
+
+                       if (pin[ipos].color == C_BLACK || pin[ipos+1].color == C_BLACK) {
+                               pout->color = C_BLACK;
+                               pos += 1;
+                               last_info.resampled_blacks++;
+                       } else {
+                               pout->color = pin[ipos].color;
+                               pos += delta;
+                       }
+
+                       pout++;
+                       count++;
+               }
+
+               memcpy(pin, &pin[in_count], count * sizeof(*pin));
+               frames[cwbuf].pnext = count;
+               chkpts(0);
+               last_info.resampled_points = count;
+       }
+
+       float last_x = frames[cwbuf].points[count-1].x;
+       float last_y = frames[cwbuf].points[count-1].y;
+       while(count < min_points) {
+               frames[cwbuf].points[count].x = last_x;
+               frames[cwbuf].points[count].y = last_y;
+               frames[cwbuf].points[count].color = C_BLACK;
+               count++;
+               last_info.padding_points++;
+       }
+       frames[cwbuf].pnext = count;
+
+       if (audiocb) {
+               audiocb(frames[cwbuf].audio_l, frames[cwbuf].audio_r, count);
+       } else {
+               memset(frames[cwbuf].audio_l, 0, sizeof(float)*count);
+               memset(frames[cwbuf].audio_r, 0, sizeof(float)*count);
+       }
+
+       //printf("Rendered frame! %d\n", cwbuf);
+       cwbuf = (cwbuf + 1) % fbufs;
+
+       return count / (float)params.rate;
+}
+
+void olLoadIdentity(void)
+{
+       static const float identity[3][3] = {
+               {1,0,0},
+               {0,1,0},
+               {0,0,1}
+       };
+       memcpy(&mtx2d[0][0], &identity[0][0], sizeof(mtx2d));
+}
+
+void olRotate(float theta)
+{
+       float rot[9] = {
+               cosf(theta),-sinf(theta),0,
+               sinf(theta),cosf(theta),0,
+               0,0,1,
+       };
+       olMultMatrix(rot);
+}
+
+void olTranslate(float x, float y)
+{
+       float trans[9] = {
+               1,0,0,
+               0,1,0,
+               x,y,1,
+       };
+       olMultMatrix(trans);
+}
+
+
+void olScale(float sx, float sy)
+{
+       float scale[9] = {
+               sx,0,0,
+               0,sy,0,
+               0,0,1,
+       };
+       olMultMatrix(scale);
+}
+
+void olMultMatrix(float m[9])
+{
+       float new[3][3];
+
+       new[0][0] = mtx2d[0][0]*m[0] + mtx2d[0][1]*m[1] + mtx2d[0][2]*m[2];
+       new[0][1] = mtx2d[0][0]*m[3] + mtx2d[0][1]*m[4] + mtx2d[0][2]*m[5];
+       new[0][2] = mtx2d[0][0]*m[6] + mtx2d[0][1]*m[7] + mtx2d[0][2]*m[8];
+       new[1][0] = mtx2d[1][0]*m[0] + mtx2d[1][1]*m[1] + mtx2d[1][2]*m[2];
+       new[1][1] = mtx2d[1][0]*m[3] + mtx2d[1][1]*m[4] + mtx2d[1][2]*m[5];
+       new[1][2] = mtx2d[1][0]*m[6] + mtx2d[1][1]*m[7] + mtx2d[1][2]*m[8];
+       new[2][0] = mtx2d[2][0]*m[0] + mtx2d[2][1]*m[1] + mtx2d[2][2]*m[2];
+       new[2][1] = mtx2d[2][0]*m[3] + mtx2d[2][1]*m[4] + mtx2d[2][2]*m[5];
+       new[2][2] = mtx2d[2][0]*m[6] + mtx2d[2][1]*m[7] + mtx2d[2][2]*m[8];
+
+       memcpy(&mtx2d[0][0], &new[0][0], sizeof(mtx2d));
+}
+
+void olPushMatrix(void)
+{
+       memcpy(&mtx2ds[mtx2dp][0][0], &mtx2d[0][0], sizeof(mtx2d));
+       mtx2dp++;
+}
+
+void olPopMatrix(void)
+{
+       mtx2dp--;
+       memcpy(&mtx2d[0][0], &mtx2ds[mtx2dp][0][0], sizeof(mtx2d));
+}
+
+void olLoadIdentity3(void)
+{
+       static const float identity[4][4] = {
+               {1,0,0,0},
+               {0,1,0,0},
+               {0,0,1,0},
+               {0,0,0,1},
+       };
+       memcpy(&mtx3d[0][0], &identity[0][0], sizeof(mtx3d));
+}
+
+void olRotate3X(float theta)
+{
+       float rot[16] = {
+               1,0,0,0,
+               0,cosf(theta),sinf(theta),0,
+               0,-sinf(theta),cosf(theta),0,
+               0,0,0,1
+       };
+       olMultMatrix3(rot);
+}
+
+void olRotate3Y(float theta)
+{
+       float rot[16] = {
+               cosf(theta),0,-sinf(theta),0,
+               0,1,0,0,
+               sinf(theta),0,cosf(theta),0,
+               0,0,0,1
+       };
+       olMultMatrix3(rot);
+}
+
+void olRotate3Z(float theta)
+{
+       float rot[16] = {
+               cosf(theta), sinf(theta), 0, 0,
+               -sinf(theta), cosf(theta), 0, 0,
+               0, 0, 1, 0,
+               0, 0, 0, 1
+       };
+       olMultMatrix3(rot);
+}
+
+void olTranslate3(float x, float y, float z)
+{
+       float trans[16] = {
+               1,0,0,0,
+               0,1,0,0,
+               0,0,1,0,
+               x,y,z,1,
+       };
+       olMultMatrix3(trans);
+}
+
+
+void olScale3(float sx, float sy, float sz)
+{
+       float trans[16] = {
+               sx,0,0,0,
+               0,sy,0,0,
+               0,0,sz,0,
+               0,0,0,1,
+       };
+       olMultMatrix3(trans);
+}
+
+void olMultMatrix3(float m[16])
+{
+       float new[4][4];
+
+       new[0][0] = mtx3d[0][0]*m[ 0] + mtx3d[0][1]*m[ 1] + mtx3d[0][2]*m[ 2] + mtx3d[0][3]*m[ 3];
+       new[0][1] = mtx3d[0][0]*m[ 4] + mtx3d[0][1]*m[ 5] + mtx3d[0][2]*m[ 6] + mtx3d[0][3]*m[ 7];
+       new[0][2] = mtx3d[0][0]*m[ 8] + mtx3d[0][1]*m[ 9] + mtx3d[0][2]*m[10] + mtx3d[0][3]*m[11];
+       new[0][3] = mtx3d[0][0]*m[12] + mtx3d[0][1]*m[13] + mtx3d[0][2]*m[14] + mtx3d[0][3]*m[15];
+       new[1][0] = mtx3d[1][0]*m[ 0] + mtx3d[1][1]*m[ 1] + mtx3d[1][2]*m[ 2] + mtx3d[1][3]*m[ 3];
+       new[1][1] = mtx3d[1][0]*m[ 4] + mtx3d[1][1]*m[ 5] + mtx3d[1][2]*m[ 6] + mtx3d[1][3]*m[ 7];
+       new[1][2] = mtx3d[1][0]*m[ 8] + mtx3d[1][1]*m[ 9] + mtx3d[1][2]*m[10] + mtx3d[1][3]*m[11];
+       new[1][3] = mtx3d[1][0]*m[12] + mtx3d[1][1]*m[13] + mtx3d[1][2]*m[14] + mtx3d[1][3]*m[15];
+       new[2][0] = mtx3d[2][0]*m[ 0] + mtx3d[2][1]*m[ 1] + mtx3d[2][2]*m[ 2] + mtx3d[2][3]*m[ 3];
+       new[2][1] = mtx3d[2][0]*m[ 4] + mtx3d[2][1]*m[ 5] + mtx3d[2][2]*m[ 6] + mtx3d[2][3]*m[ 7];
+       new[2][2] = mtx3d[2][0]*m[ 8] + mtx3d[2][1]*m[ 9] + mtx3d[2][2]*m[10] + mtx3d[2][3]*m[11];
+       new[2][3] = mtx3d[2][0]*m[12] + mtx3d[2][1]*m[13] + mtx3d[2][2]*m[14] + mtx3d[2][3]*m[15];
+       new[3][0] = mtx3d[3][0]*m[ 0] + mtx3d[3][1]*m[ 1] + mtx3d[3][2]*m[ 2] + mtx3d[3][3]*m[ 3];
+       new[3][1] = mtx3d[3][0]*m[ 4] + mtx3d[3][1]*m[ 5] + mtx3d[3][2]*m[ 6] + mtx3d[3][3]*m[ 7];
+       new[3][2] = mtx3d[3][0]*m[ 8] + mtx3d[3][1]*m[ 9] + mtx3d[3][2]*m[10] + mtx3d[3][3]*m[11];
+       new[3][3] = mtx3d[3][0]*m[12] + mtx3d[3][1]*m[13] + mtx3d[3][2]*m[14] + mtx3d[3][3]*m[15];
+
+       memcpy(&mtx3d[0][0], &new[0][0], sizeof(mtx3d));
+}
+
+void olPushMatrix3(void)
+{
+       memcpy(&mtx3ds[mtx3dp][0][0], &mtx3d[0][0], sizeof(mtx3d));
+       mtx3dp++;
+}
+
+void olPopMatrix3(void)
+{
+       mtx3dp--;
+       memcpy(&mtx3d[0][0], &mtx3ds[mtx3dp][0][0], sizeof(mtx3d));
+}
+
+void olTransformVertex3(float *x, float *y, float *z)
+{
+       float px;
+       float py;
+       float pz;
+       float pw;
+
+       px = mtx3d[0][0]**x + mtx3d[0][1]**y + mtx3d[0][2]**z + mtx3d[0][3];
+       py = mtx3d[1][0]**x + mtx3d[1][1]**y + mtx3d[1][2]**z + mtx3d[1][3];
+       pz = mtx3d[2][0]**x + mtx3d[2][1]**y + mtx3d[2][2]**z + mtx3d[2][3];
+       pw = mtx3d[3][0]**x + mtx3d[3][1]**y + mtx3d[3][2]**z + mtx3d[3][3];
+
+       px /= pw;
+       py /= pw;
+       pz /= pw;
+
+       *x = px;
+       *y = py;
+       *z = pz;
+}
+
+void olVertex3(float x, float y, float z, uint32_t color)
+{
+       if(v3shader)
+               v3shader(&x, &y, &z, &color);
+       olTransformVertex3(&x, &y, &z);
+       olVertex(x, y, color);
+}
+
+void olRect(float x1, float y1, float x2, float y2, uint32_t color)
+{
+       olBegin(OL_LINESTRIP);
+       olVertex(x1,y1,color);
+       olVertex(x1,y2,color);
+       olVertex(x2,y2,color);
+       olVertex(x2,y1,color);
+       olVertex(x1,y1,color);
+       olEnd();
+}
+
+void olLine(float x1, float y1, float x2, float y2, uint32_t color)
+{
+       olBegin(OL_LINESTRIP);
+       olVertex(x1,y1,color);
+       olVertex(x2,y2,color);
+       olEnd();
+}
+
+
+void olDot(float x, float y, int samples, uint32_t color)
+{
+       int i;
+       olBegin(OL_POINTS);
+       for (i = 0; i < samples; i++)
+               olVertex(x,y,color);
+       olEnd();
+}
+
+void olResetColor(void)
+{
+       curcol = C_WHITE;
+}
+
+void olMultColor(uint32_t color)
+{
+       curcol = colmul(curcol, color);
+}
+
+void olPushColor(void)
+{
+       cols[coldp] = curcol;
+       coldp++;
+}
+
+void olPopColor(void)
+{
+       coldp--;
+       curcol = cols[coldp];
+}
+
+
+void olSetVertexPreShader(ShaderFunc f)
+{
+       vpreshader = f;
+}
+void olSetVertexShader(ShaderFunc f)
+{
+       vshader = f;
+}
+void olSetVertex3Shader(Shader3Func f)
+{
+       v3shader = f;
+}
+
+void olSetPixelShader(ShaderFunc f)
+{
+       pshader = f;
+}
+
+void olSetAudioCallback(AudioCallbackFunc f)
+{
+       audiocb = f;
+}
+
+void olFrustum (float l, float r, float b, float t, float n, float f)
+{
+       float m[16] = {
+               (2*n)/(r-l),  0,            (r+l)/(r-l),     0,
+               0,            (2*n)/(t-b),  (t+b)/(t-b),     0,
+               0,            0,            -(f+n)/(f-n),    (-2*f*n)/(f-n),
+               0,            0,            -1,              0,
+       };
+
+       olMultMatrix3(m);
+}
+
+void olPerspective (float fovy, float aspect, float zNear, float zFar)
+{
+       float xmin, xmax, ymin, ymax;
+
+       ymax = zNear * tanf(fovy * M_PI / 360.0);
+       ymin = -ymax;
+
+       xmin = ymin * aspect;
+       xmax = ymax * aspect;
+
+       olFrustum( xmin, xmax, ymin, ymax, zNear, zFar );
+}
+
+void olSetScissor (float x0, float y0, float x1, float y1)
+{
+       bbox[0][0] = x0;
+       bbox[0][1] = y0;
+       bbox[1][0] = x1;
+       bbox[1][1] = y1;
+}
+
+void olGetFrameInfo(OLFrameInfo *info)
+{
+       *info = last_info;
+}
diff --git a/libol/text.c b/libol/text.c
new file mode 100644 (file)
index 0000000..db178fe
--- /dev/null
@@ -0,0 +1,101 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include <math.h>
+#include "libol.h"
+#include "text.h"
+
+extern Font default_font;
+
+Font *olGetDefaultFont(void)
+{
+       return &default_font;
+}
+
+float olGetCharWidth(Font *font, char c)
+{
+       if (!font)
+               return 0;
+       return font->chars[(uint8_t)c].width / font->height;
+}
+
+float olGetCharOverlap(Font *font, float height)
+{
+       if (!font)
+               return 0;
+       float ratio = height / font->height;
+       return font->overlap * ratio;
+}
+
+float olDrawChar(Font *font, float x, float y, float height, uint32_t color, char c)
+{
+       if (!font)
+               return 0;
+
+       const FontChar *chr = &font->chars[(uint8_t)c];
+       const FontPoint *p = chr->points;
+
+       float ratio = height / font->height;
+
+       if (p) {
+
+               olBegin(OL_BEZIERSTRIP);
+
+               do {
+                       olVertex(x + p->x * ratio, y - p->y * ratio, color);
+                       if (p->flag == 1) {
+                               olEnd();
+                               olBegin(OL_BEZIERSTRIP);
+                       }
+               } while ((p++)->flag != 2);
+
+               olEnd();
+       }
+
+       return chr->width * ratio;
+}
+
+float olDrawString(Font *font, float x, float y, float height, uint32_t color, const char *s)
+{
+       float w = 0;
+       float ratio = height / font->height;
+
+       while(*s) {
+               w += olDrawChar(font, x+w, y, height, color, *s) - font->overlap * ratio;
+               s++;
+       }
+
+       return w + font->overlap * ratio;
+}
+
+
+float olGetStringWidth(Font *font, float height, const char *s)
+{
+       float w = 0;
+       float ratio = height / font->height;
+
+       while(*s) {
+               w += font->chars[(uint8_t)*s].width * ratio - font->overlap * ratio;
+               s++;
+       }
+
+       return w + font->overlap * ratio;
+}
+
+
diff --git a/output/CMakeLists.txt b/output/CMakeLists.txt
new file mode 100644 (file)
index 0000000..a6a0754
--- /dev/null
@@ -0,0 +1,28 @@
+#         OpenLase - a realtime laser graphics toolkit
+#
+# Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 or version 3.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+
+include(${QT_USE_FILE})
+
+QT4_WRAP_UI(output_UIS_H output_settings.ui)
+QT4_AUTOMOC(output_settings.cpp output.cpp)
+
+include_directories(${CMAKE_CURRENT_BINARY_DIR})
+
+add_executable(output output.cpp output_settings.cpp ${output_UIS_H})
+target_link_libraries(output ${JACK_LIBRARIES} ${QT_LIBRARIES})
+
diff --git a/output/output.cpp b/output/output.cpp
new file mode 100644 (file)
index 0000000..3d0cc81
--- /dev/null
@@ -0,0 +1,265 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <math.h>
+
+#include <jack/jack.h>
+
+#include "output.h"
+
+#include <QApplication>
+#include "output_settings.h"
+
+typedef jack_default_audio_sample_t sample_t;
+typedef jack_nframes_t nframes_t;
+
+jack_port_t *in_x;
+jack_port_t *in_y;
+jack_port_t *in_g;
+
+jack_port_t *out_x;
+jack_port_t *out_y;
+jack_port_t *out_g;
+jack_port_t *out_e;
+
+nframes_t rate, enable_period, enable_ctr;
+nframes_t frames_dead;
+
+#define DEAD_TIME (rate/10)
+
+nframes_t ibuf_g = 0;
+sample_t buf_g[MAX_DELAY];
+
+output_config_t *cfg;
+
+static void generate_enable(sample_t *buf, nframes_t nframes)
+{
+       while (nframes--) {
+               if (enable_ctr < (enable_period / 2))
+                       *buf++ = -1.0;
+               else
+                       *buf++ = 1.0;
+               enable_ctr = (enable_ctr + 1) % enable_period;
+       }
+}
+
+static void transform(sample_t *ox, sample_t *oy)
+{
+       float x,y,w;
+       x = *ox;
+       y = *oy;
+       
+       *ox = cfg->transform[0][0]*x + cfg->transform[0][1]*y + cfg->transform[0][2];
+       *oy = cfg->transform[1][0]*x + cfg->transform[1][1]*y + cfg->transform[1][2];
+       w = cfg->transform[2][0]*x + cfg->transform[2][1]*y + cfg->transform[2][2];
+       
+       *ox /= w;
+       *oy /= w;
+}
+
+/* The following filters attempt to compensate for imperfections in my galvos.
+You'll probably want to turn them off for anything else */
+
+#define LIMIT 0.007
+#define RATIO 0.30
+
+static void cfilter(float *c, float *p)
+{
+       float delta = fabsf(*c - *p);
+       if (delta > LIMIT) {
+               if (*c > *p)
+                       *p = *c - LIMIT;
+               else
+                       *p = *c + LIMIT;
+       } else {
+               *p = (1-RATIO) * *p + RATIO * *c;
+       }
+
+       *c += *c - *p;
+}
+
+#define DPOWER 0.05
+#define DRATIO 0.05
+
+static void dfilter(float *c, float *p)
+{
+       float delta = *c - *p;
+       *c += DPOWER*delta;
+
+       *p = (1-DRATIO) * *p + DRATIO * *c;
+}
+
+static inline void filter(float *x, float *y)
+{
+       static float px=0, py=0;
+       static float dx=0, dy=0;
+       dfilter(x,&dx);
+       dfilter(y,&dy);
+       cfilter(x,&px);
+       cfilter(y,&py);
+}
+
+static int process (nframes_t nframes, void *arg)
+{
+       sample_t *o_x = (sample_t *) jack_port_get_buffer (out_x, nframes);
+       sample_t *o_y = (sample_t *) jack_port_get_buffer (out_y, nframes);
+       sample_t *o_g = (sample_t *) jack_port_get_buffer (out_g, nframes);
+       sample_t *o_e = (sample_t *) jack_port_get_buffer (out_e, nframes);
+       
+       sample_t *i_x = (sample_t *) jack_port_get_buffer (in_x, nframes);
+       sample_t *i_y = (sample_t *) jack_port_get_buffer (in_y, nframes);
+       sample_t *i_g = (sample_t *) jack_port_get_buffer (in_g, nframes);
+
+       nframes_t frm;
+
+       for (frm = 0; frm < nframes; frm++) {
+               sample_t x,y,g,orig_g;
+               x = *i_x++;
+               y = *i_y++;
+               g = orig_g = *i_g++;
+
+               y = -y;
+               transform(&x, &y);
+               y = -y;
+
+               if (cfg->scan_flags & SWAP_XY) {
+                       sample_t tmp;
+                       tmp = x;
+                       x = y;
+                       y = tmp;
+               }
+
+               if (cfg->scan_flags & INVERT_X)
+                       x = -x;
+               if (cfg->scan_flags & INVERT_Y)
+                       y = -y;
+               if (!(cfg->scan_flags & ENABLE_X) && !cfg->safe)
+                       x = 0.0f;
+               if (!(cfg->scan_flags & ENABLE_Y) && !cfg->safe)
+                       y = 0.0f;
+               if (cfg->safe && cfg->size < 0.10f) {
+                       x *= 0.10f;
+                       y *= 0.10f;
+               } else {
+                       x *= cfg->size;
+                       y *= cfg->size;
+               }
+               
+               if (cfg->blank_flags & BLANK_INVERT)
+                       g = 1.0f - g;
+               if (!(cfg->blank_flags & BLANK_ENABLE))
+                       g = 1.0f;
+               if (!(cfg->blank_flags & OUTPUT_ENABLE))
+                       g = 0.0f;
+               g *= cfg->power * (1.0f-cfg->offset);
+               g += cfg->offset;
+
+               if(orig_g == 0.0f) {
+                       if(frames_dead >= DEAD_TIME) {
+                               g = 0.0f;
+                       } else {
+                               frames_dead++;
+                       }
+               } else {
+                   frames_dead = 0;
+               }
+               
+               filter(&x, &y);
+               
+               *o_x++ = x;
+               *o_y++ = y;
+               buf_g[ibuf_g] = g;
+               *o_g++ = buf_g[(ibuf_g + MAX_DELAY - cfg->delay) % MAX_DELAY];
+               ibuf_g = (ibuf_g + 1) % MAX_DELAY;
+       }
+       generate_enable(o_e, nframes);
+
+       return 0;
+}
+
+static int bufsize (nframes_t nframes, void *arg)
+{
+       printf ("the maximum buffer size is now %u\n", nframes);
+       return 0;
+}
+
+static int srate (nframes_t nframes, void *arg)
+{
+       rate = nframes;
+       if(rate % 1000) {
+               printf("error: the sample rate should be a multiple of 1000\n");
+               exit(1);
+       }
+       enable_period = nframes / 1000;
+       enable_ctr = 0;
+       printf ("Sample rate: %u/sec\n", nframes);
+       return 0;
+}
+
+static void jack_shutdown (void *arg)
+{
+       exit (1);
+}
+
+int main (int argc, char *argv[])
+{
+       int retval;
+       jack_client_t *client;
+
+       QApplication app(argc, argv);
+       OutputSettings settings;
+       
+       cfg = &settings.cfg;
+
+       if ((client = jack_client_new ("output")) == 0) {
+               fprintf (stderr, "jack server not running?\n");
+               return 1;
+       }
+
+       jack_set_process_callback (client, process, 0);
+       jack_set_buffer_size_callback (client, bufsize, 0);
+       jack_set_sample_rate_callback (client, srate, 0);
+       jack_on_shutdown (client, jack_shutdown, 0);
+
+       in_x = jack_port_register (client, "in_x", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
+       in_y = jack_port_register (client, "in_y", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
+       in_g = jack_port_register (client, "in_g", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0);
+       out_x = jack_port_register (client, "out_x", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_y = jack_port_register (client, "out_y", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_g = jack_port_register (client, "out_g", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_e = jack_port_register (client, "out_e", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+
+       if (jack_activate (client)) {
+               fprintf (stderr, "cannot activate client");
+               return 1;
+       }
+
+       settings.show();
+
+       retval = app.exec();
+
+       jack_client_close (client);
+       return retval;
+}
+
diff --git a/output/output.h b/output/output.h
new file mode 100644 (file)
index 0000000..aa6e62b
--- /dev/null
@@ -0,0 +1,47 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef OUTPUT_H
+#define OUTPUT_H
+
+#define ENABLE_X      0x01
+#define ENABLE_Y      0x02
+#define INVERT_X      0x04
+#define INVERT_Y      0x08
+#define SWAP_XY       0x10
+
+#define OUTPUT_ENABLE 0x01
+#define BLANK_ENABLE  0x02
+#define BLANK_INVERT  0x04
+
+#define MAX_DELAY 25
+
+typedef struct {
+       float power;
+       float offset;
+       float size;
+       float transform[3][3];
+
+       int delay;
+       int scan_flags;
+       int blank_flags;
+       int safe;
+} output_config_t;
+
+#endif
diff --git a/output/output_settings.cpp b/output/output_settings.cpp
new file mode 100644 (file)
index 0000000..df1a791
--- /dev/null
@@ -0,0 +1,394 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#include "output_settings.moc"
+#include <QMessageBox>
+#include <QGraphicsLineItem>
+
+#include <stdio.h>
+
+#define ASPECT_1_1    0
+#define ASPECT_4_3    1
+#define ASPECT_16_9   2
+
+ControlPoint::ControlPoint()
+{
+       form = 0;
+}
+
+QVariant ControlPoint::itemChange(GraphicsItemChange change, const QVariant &value)
+{
+       if (change == ItemPositionChange && form) {
+               form->pointMoved(this);
+       }
+       return QGraphicsEllipseItem::itemChange(change, value);
+}
+
+OutputSettings::OutputSettings(QWidget *parent)
+{
+       setupUi(this);
+       
+       QPen grid_pen(Qt::blue);
+       QPen line_pen(Qt::green);
+       QBrush point_brush(Qt::red);
+       
+       scene.setSceneRect(-1.1,-1.1,2.2,2.2);
+       
+       for (int i=-5; i<=5; i++) {
+               scene.addLine(i/5.0f, -1.0f, i/5.0f, 1.0f, grid_pen);
+               scene.addLine(-1.0f, i/5.0f, 1.0f, i/5.0f, grid_pen);
+       }
+       
+       for (int i=0; i<4; i++) {
+               pt[i] = new ControlPoint();
+       }
+       
+       for (int i=0; i<4; i++) {
+               pt[i]->setBrush(point_brush);
+               pt[i]->setRect(-5, -5, 10, 10);
+               pt[i]->setFlag(QGraphicsItem::ItemIgnoresTransformations, true);
+               pt[i]->setFlag(QGraphicsItem::ItemIsMovable, true);
+               pt[i]->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
+               pt[i]->form = this;
+               scene.addItem(pt[i]);
+       }
+       
+       pl.setPen(line_pen);
+
+       scene.addItem(&pl);
+
+       projView->setBackgroundBrush(Qt::black);
+       projView->setScene(&scene);
+       projView->fitInView(scene.sceneRect(), Qt::KeepAspectRatio);
+       projView->setInteractive(true);
+       //projView->setRenderHints(QPainter::Antialiasing);
+
+       resetDefaults();
+}
+
+OutputSettings::~OutputSettings()
+{
+}
+
+qreal OutputSettings::getYRatio(int ratio)
+{
+       switch(ratio) {
+               case ASPECT_1_1:
+                       return 1.0;
+               case ASPECT_4_3:
+                       return 3.0/4.0;
+               case ASPECT_16_9:
+                       return 9.0/16.0;
+       }
+       return 1.0;
+}
+
+void OutputSettings::resetPoints()
+{
+       mtx.reset();
+       mtx.scale(1.0, getYRatio(aspectRatio->currentIndex()));
+       updateMatrix();
+       loadPoints();
+       updatePoly();
+}
+
+void OutputSettings::updateMatrix()
+{
+       QTransform smtx;
+       QTransform omtx;
+       qreal yratio = getYRatio(aspectRatio->currentIndex());
+       
+       if (!aspectScale->isChecked()) {
+               if (fitSquare->isChecked())
+                       smtx.scale(yratio, 1.0);
+               else
+                       smtx.scale(1.0, 1/yratio);
+       }
+       
+       omtx = smtx * mtx;
+
+       cfg.transform[0][0] = omtx.m11();
+       cfg.transform[0][1] = omtx.m21();
+       cfg.transform[0][2] = omtx.m31();
+       cfg.transform[1][0] = omtx.m12();
+       cfg.transform[1][1] = omtx.m22();
+       cfg.transform[1][2] = omtx.m32();
+       cfg.transform[2][0] = omtx.m13();
+       cfg.transform[2][1] = omtx.m23();
+       cfg.transform[2][2] = omtx.m33();
+}
+
+void OutputSettings::loadPoints()
+{
+       QPointF p0(-1,-1);
+       QPointF p1(1,-1);
+       QPointF p2(-1,1);
+       QPointF p3(1,1);
+
+       for (int i=0; i<4; i++)
+               pt[i]->setFlag(QGraphicsItem::ItemSendsGeometryChanges, false);
+
+       pt[0]->setPos(mtx.map(p0));
+       pt[1]->setPos(mtx.map(p1));
+       pt[2]->setPos(mtx.map(p2));
+       pt[3]->setPos(mtx.map(p3));
+
+       for (int i=0; i<4; i++)
+               pt[i]->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true);
+
+       updatePoly();
+}
+
+void OutputSettings::pointMoved(ControlPoint *point)
+{
+       QPolygonF src;
+       src << QPoint(-1,-1) << QPoint(1,-1) << QPoint(1,1) << QPoint(-1,1);
+       QPolygonF dst;
+       dst << pt[0]->pos() << pt[1]->pos() << pt[3]->pos() << pt[2]->pos();
+
+       QTransform::quadToQuad(src, dst, mtx);
+       loadPoints();
+       updatePoly();
+       updateMatrix();
+}
+
+void OutputSettings::updatePoly()
+{
+       QPolygonF poly;
+       
+       poly << pt[0]->pos() << pt[1]->pos() << pt[3]->pos() << pt[2]->pos();
+       pl.setPolygon(poly);
+}
+
+void OutputSettings::resizeEvent (QResizeEvent * event)
+{
+       projView->fitInView(scene.sceneRect(), Qt::KeepAspectRatio);
+}
+
+void OutputSettings::showEvent (QShowEvent * event)
+{
+       projView->fitInView(scene.sceneRect(), Qt::KeepAspectRatio);
+}
+
+void OutputSettings::updateAllSettings()
+{
+       cfg.power = powerSlider->value() / 100.0f;
+       cfg.offset = offsetSlider->value() / 100.0f;
+       cfg.size = sizeSlider->value() / 100.0f;
+       cfg.delay = delaySlider->value();
+       
+       cfg.scan_flags = 0;
+       cfg.blank_flags = 0;
+       cfg.safe = enforceSafety->isChecked();
+
+       if(cfg.safe) {
+               xEnable->setChecked(true);
+               yEnable->setChecked(true);
+               cfg.scan_flags |= ENABLE_X | ENABLE_Y;
+       }
+       
+       xEnable->setEnabled(!cfg.safe);
+       yEnable->setEnabled(!cfg.safe);
+       
+       currentAspect = aspectRatio->currentIndex();
+
+       if (xEnable->isChecked())
+               cfg.scan_flags |= ENABLE_X;
+       if (yEnable->isChecked())
+               cfg.scan_flags |= ENABLE_Y;
+       if (xInvert->isChecked())
+               cfg.scan_flags |= INVERT_X;
+       if (yInvert->isChecked())
+               cfg.scan_flags |= INVERT_Y;
+       if (xySwap->isChecked())
+               cfg.scan_flags |= SWAP_XY;
+
+       if (outputEnable->isChecked())
+               cfg.blank_flags |= OUTPUT_ENABLE;
+       if (blankingEnable->isChecked())
+               cfg.blank_flags |= BLANK_ENABLE;
+
+       outputTest->setEnabled(!outputEnable->isChecked());
+}
+
+void OutputSettings::resetDefaults()
+{
+       xEnable->setChecked(true);
+       yEnable->setChecked(true);
+       xInvert->setChecked(true);
+       yInvert->setChecked(false);
+       xySwap->setChecked(false);
+       aspectScale->setChecked(false);
+       
+       enforceSafety->setChecked(true);
+       
+       aspectRatio->setCurrentIndex(ASPECT_1_1);
+
+       outputEnable->setChecked(true);
+       blankingEnable->setChecked(true);
+       
+       powerSlider->setValue(100);
+       offsetSlider->setValue(20);
+       sizeSlider->setValue(100);
+       delaySlider->setValue(6);
+       
+       resetPoints();
+       updateAllSettings();
+}
+
+void OutputSettings::on_outputEnable_toggled(bool state)
+{
+       if (state) cfg.blank_flags |= OUTPUT_ENABLE;
+       else cfg.blank_flags &= ~OUTPUT_ENABLE;
+       outputTest->setEnabled(!state);
+}
+
+void OutputSettings::on_blankingEnable_toggled(bool state)
+{
+       if (state) cfg.blank_flags |= BLANK_ENABLE;
+       else cfg.blank_flags &= ~BLANK_ENABLE;
+}
+
+void OutputSettings::on_blankingInvert_toggled(bool state)
+{
+       if (state) cfg.blank_flags |= BLANK_INVERT;
+       else cfg.blank_flags &= ~BLANK_INVERT;
+}
+
+void OutputSettings::on_outputTest_pressed()
+{
+       cfg.blank_flags |= OUTPUT_ENABLE;
+}
+
+void OutputSettings::on_outputTest_released()
+{
+       if (!outputEnable->isChecked())
+               cfg.blank_flags &= ~OUTPUT_ENABLE;
+}
+
+void OutputSettings::on_xEnable_toggled(bool state)
+{
+       if (state) cfg.scan_flags |= ENABLE_X;
+       else cfg.scan_flags &= ~ENABLE_X;
+}
+
+void OutputSettings::on_yEnable_toggled(bool state)
+{
+       if (state) cfg.scan_flags |= ENABLE_Y;
+       else cfg.scan_flags &= ~ENABLE_Y;
+}
+
+void OutputSettings::on_xInvert_toggled(bool state)
+{
+       if (state) cfg.scan_flags |= INVERT_X;
+       else cfg.scan_flags &= ~INVERT_X;
+}
+
+void OutputSettings::on_yInvert_toggled(bool state)
+{
+       if (state) cfg.scan_flags |= INVERT_Y;
+       else cfg.scan_flags &= ~INVERT_Y;
+}
+
+void OutputSettings::on_xySwap_toggled(bool state)
+{
+       if (state) cfg.scan_flags |= SWAP_XY;
+       else cfg.scan_flags &= ~SWAP_XY;
+}
+
+void OutputSettings::on_aspectScale_toggled(bool state)
+{
+       fitSquare->setEnabled(!state);
+       updateMatrix();
+}
+
+void OutputSettings::on_fitSquare_toggled(bool state)
+{
+       updateMatrix();
+}
+
+void OutputSettings::on_aspectRatio_currentIndexChanged(int index)
+{
+       QTransform smtx;
+       if (index == currentAspect)
+               return;
+
+       qreal rold = getYRatio(currentAspect);
+       qreal rnew = getYRatio(index);
+       
+       smtx.scale(1.0, rnew/rold);
+       mtx = smtx * mtx;
+       
+       currentAspect = index;
+       loadPoints();
+       updateMatrix();
+}
+
+void OutputSettings::on_resetTransform_clicked()
+{
+       resetPoints();
+}
+
+
+void OutputSettings::on_enforceSafety_toggled(bool state)
+{
+       if (!state) {
+               QMessageBox msgBox;
+               msgBox.setText("Do not stare into laser with remaining eye");
+               msgBox.setInformativeText("Do you really want to disable safety enforcement?");
+               msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No);
+               msgBox.setDefaultButton(QMessageBox::No);
+               int ret = msgBox.exec();
+               if (ret != QMessageBox::Yes) {
+                       enforceSafety->setChecked(true);
+                       return;
+               }
+       }
+       
+       if (state) {
+               xEnable->setChecked(true);
+               yEnable->setChecked(true);
+               cfg.scan_flags |= ENABLE_X | ENABLE_Y;
+       }
+       
+       xEnable->setEnabled(!state);
+       yEnable->setEnabled(!state);
+       
+       cfg.safe = state;
+}
+
+void OutputSettings::on_powerSlider_valueChanged(int value)
+{
+       cfg.power = value / 100.0f;
+}
+
+void OutputSettings::on_offsetSlider_valueChanged(int value)
+{
+       cfg.offset = value / 100.0f;
+}
+
+void OutputSettings::on_delaySlider_valueChanged(int value)
+{
+       cfg.delay = value;
+}
+
+void OutputSettings::on_sizeSlider_valueChanged(int value)
+{
+       cfg.size = value / 100.0f;
+}
diff --git a/output/output_settings.h b/output/output_settings.h
new file mode 100644 (file)
index 0000000..4c2865a
--- /dev/null
@@ -0,0 +1,93 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef OUTPUT_SETTINGS_H
+#define OUTPUT_SETTINGS_H
+
+#include "ui_output_settings.h"
+#include "output.h"
+
+#include <QGraphicsScene>
+#include <QGraphicsEllipseItem>
+#include <QTransform>
+
+class ControlPoint;
+
+class OutputSettings : public QMainWindow, private Ui::OutputSettingsDLG
+{
+       Q_OBJECT
+       
+public:
+       output_config_t cfg;
+       OutputSettings(QWidget *parent = 0);
+       ~OutputSettings();
+
+public slots:
+       void on_outputEnable_toggled(bool state);
+       void on_blankingEnable_toggled(bool state);
+       void on_blankingInvert_toggled(bool state);
+       void on_xEnable_toggled(bool state);
+       void on_yEnable_toggled(bool state);
+       void on_xInvert_toggled(bool state);
+       void on_yInvert_toggled(bool state);
+       void on_xySwap_toggled(bool state);
+       void on_aspectRatio_currentIndexChanged(int index);
+       void on_aspectScale_toggled(bool state);
+       void on_fitSquare_toggled(bool state);
+       void on_enforceSafety_toggled(bool state);
+       void on_outputTest_pressed();
+       void on_outputTest_released();
+       void on_powerSlider_valueChanged(int value);
+       void on_offsetSlider_valueChanged(int value);
+       void on_delaySlider_valueChanged(int value);
+       void on_sizeSlider_valueChanged(int value);
+       void on_resetTransform_clicked();
+
+       void resizeEvent (QResizeEvent * event);
+       void showEvent (QShowEvent * event);
+       
+       void pointMoved(ControlPoint *pt);
+
+private:
+       QTransform mtx;
+       QGraphicsScene scene;
+       ControlPoint *pt[4];
+       QGraphicsPolygonItem pl;
+       int currentAspect;
+       void updatePoly();
+       void updateMatrix();
+       void updateAllSettings();
+       void resetDefaults();
+       void resetPoints();
+       void loadPoints();
+       qreal getYRatio(int ratio);
+};
+
+class ControlPoint : public QGraphicsEllipseItem
+{
+
+public:
+       OutputSettings *form;
+       ControlPoint();
+       
+private:
+       QVariant itemChange (GraphicsItemChange change, const QVariant & value);
+};
+
+#endif
\ No newline at end of file
diff --git a/output/output_settings.ui b/output/output_settings.ui
new file mode 100644 (file)
index 0000000..1762dbd
--- /dev/null
@@ -0,0 +1,888 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<ui version="4.0">
+ <class>OutputSettingsDLG</class>
+ <widget class="QMainWindow" name="OutputSettingsDLG">
+  <property name="geometry">
+   <rect>
+    <x>0</x>
+    <y>0</y>
+    <width>648</width>
+    <height>854</height>
+   </rect>
+  </property>
+  <property name="windowTitle">
+   <string>Laser output configuration</string>
+  </property>
+  <property name="documentMode">
+   <bool>false</bool>
+  </property>
+  <property name="unifiedTitleAndToolBarOnMac">
+   <bool>false</bool>
+  </property>
+  <widget class="QWidget" name="centralwidget">
+   <layout class="QVBoxLayout" name="verticalLayout_9">
+    <item>
+     <layout class="QHBoxLayout" name="horizontalLayout_14">
+      <item>
+       <widget class="QGraphicsView" name="projView">
+        <property name="sizePolicy">
+         <sizepolicy hsizetype="Expanding" vsizetype="Expanding">
+          <horstretch>0</horstretch>
+          <verstretch>0</verstretch>
+         </sizepolicy>
+        </property>
+       </widget>
+      </item>
+      <item>
+       <layout class="QVBoxLayout" name="verticalLayout_8">
+        <item>
+         <widget class="QPushButton" name="shutdown">
+          <property name="text">
+           <string>Shut down</string>
+          </property>
+         </widget>
+        </item>
+        <item>
+         <widget class="QGroupBox" name="groupBox">
+          <property name="title">
+           <string>Blanking</string>
+          </property>
+          <layout class="QVBoxLayout" name="verticalLayout_6">
+           <item>
+            <widget class="QPushButton" name="outputTest">
+             <property name="text">
+              <string>Test</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QCheckBox" name="outputEnable">
+             <property name="text">
+              <string>Output enable</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QCheckBox" name="blankingEnable">
+             <property name="text">
+              <string>Blanking enable</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QCheckBox" name="blankingInvert">
+             <property name="text">
+              <string>Blanking invert</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </item>
+        <item>
+         <widget class="QGroupBox" name="groupBox_2">
+          <property name="title">
+           <string>Scanning</string>
+          </property>
+          <layout class="QVBoxLayout" name="verticalLayout_7">
+           <item>
+            <widget class="QCheckBox" name="xEnable">
+             <property name="text">
+              <string>X enable</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QCheckBox" name="xInvert">
+             <property name="text">
+              <string>X invert</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QCheckBox" name="yEnable">
+             <property name="text">
+              <string>Y enable</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QCheckBox" name="yInvert">
+             <property name="text">
+              <string>Y invert</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QCheckBox" name="xySwap">
+             <property name="text">
+              <string>Swap X &amp;&amp; Y</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QComboBox" name="aspectRatio">
+             <item>
+              <property name="text">
+               <string>1:1</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>4:3</string>
+              </property>
+             </item>
+             <item>
+              <property name="text">
+               <string>16:9</string>
+              </property>
+             </item>
+            </widget>
+           </item>
+           <item>
+            <widget class="QCheckBox" name="aspectScale">
+             <property name="text">
+              <string>Anamorphic input</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QCheckBox" name="fitSquare">
+             <property name="text">
+              <string>Shrink square</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <widget class="QPushButton" name="resetTransform">
+             <property name="text">
+              <string>Reset transform</string>
+             </property>
+            </widget>
+           </item>
+          </layout>
+         </widget>
+        </item>
+        <item>
+         <widget class="QGroupBox" name="groupBox_3">
+          <property name="title">
+           <string>Safety</string>
+          </property>
+          <layout class="QVBoxLayout" name="verticalLayout_5">
+           <item>
+            <widget class="QCheckBox" name="enforceSafety">
+             <property name="text">
+              <string>Enforce safety</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="verticalSpacer">
+             <property name="orientation">
+              <enum>Qt::Vertical</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>20</width>
+               <height>0</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </widget>
+        </item>
+       </layout>
+      </item>
+     </layout>
+    </item>
+    <item>
+     <widget class="QGroupBox" name="groupBox_4">
+      <property name="title">
+       <string>Analog settings</string>
+      </property>
+      <layout class="QHBoxLayout" name="horizontalLayout_13">
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout">
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout">
+           <item>
+            <spacer name="horizontalSpacer_5">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item>
+            <widget class="QLabel" name="label">
+             <property name="text">
+              <string>Power</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_6">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_2">
+           <item>
+            <spacer name="horizontalSpacer_3">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item>
+            <widget class="QSpinBox" name="powerBox">
+             <property name="maximum">
+              <number>100</number>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_4">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_3">
+           <item>
+            <spacer name="horizontalSpacer">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item>
+            <widget class="QSlider" name="powerSlider">
+             <property name="maximum">
+              <number>100</number>
+             </property>
+             <property name="orientation">
+              <enum>Qt::Vertical</enum>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_2">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout_2">
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_4">
+           <item>
+            <spacer name="horizontalSpacer_7">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item>
+            <widget class="QLabel" name="label_2">
+             <property name="text">
+              <string>Offset</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_8">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_5">
+           <item>
+            <spacer name="horizontalSpacer_9">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item>
+            <widget class="QSpinBox" name="offsetBox">
+             <property name="maximum">
+              <number>100</number>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_10">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_6">
+           <item>
+            <spacer name="horizontalSpacer_11">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item>
+            <widget class="QSlider" name="offsetSlider">
+             <property name="maximum">
+              <number>100</number>
+             </property>
+             <property name="orientation">
+              <enum>Qt::Vertical</enum>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_12">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout_3">
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_7">
+           <item>
+            <spacer name="horizontalSpacer_13">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item>
+            <widget class="QLabel" name="label_3">
+             <property name="text">
+              <string>Delay</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_14">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_8">
+           <item>
+            <spacer name="horizontalSpacer_15">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item>
+            <widget class="QSpinBox" name="delayBox">
+             <property name="maximum">
+              <number>20</number>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_16">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_9">
+           <item>
+            <spacer name="horizontalSpacer_17">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item>
+            <widget class="QSlider" name="delaySlider">
+             <property name="maximum">
+              <number>20</number>
+             </property>
+             <property name="orientation">
+              <enum>Qt::Vertical</enum>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_18">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+        </layout>
+       </item>
+       <item>
+        <layout class="QVBoxLayout" name="verticalLayout_4">
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_10">
+           <item>
+            <spacer name="horizontalSpacer_19">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item>
+            <widget class="QLabel" name="label_4">
+             <property name="text">
+              <string>Size</string>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_20">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_11">
+           <item>
+            <spacer name="horizontalSpacer_21">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item>
+            <widget class="QSpinBox" name="sizeBox">
+             <property name="maximum">
+              <number>100</number>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_22">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+         <item>
+          <layout class="QHBoxLayout" name="horizontalLayout_12">
+           <item>
+            <spacer name="horizontalSpacer_23">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+           <item>
+            <widget class="QSlider" name="sizeSlider">
+             <property name="maximum">
+              <number>100</number>
+             </property>
+             <property name="orientation">
+              <enum>Qt::Vertical</enum>
+             </property>
+            </widget>
+           </item>
+           <item>
+            <spacer name="horizontalSpacer_24">
+             <property name="orientation">
+              <enum>Qt::Horizontal</enum>
+             </property>
+             <property name="sizeHint" stdset="0">
+              <size>
+               <width>40</width>
+               <height>20</height>
+              </size>
+             </property>
+            </spacer>
+           </item>
+          </layout>
+         </item>
+        </layout>
+       </item>
+      </layout>
+     </widget>
+    </item>
+   </layout>
+  </widget>
+  <widget class="QStatusBar" name="statusbar"/>
+  <widget class="QMenuBar" name="menuBar">
+   <property name="geometry">
+    <rect>
+     <x>0</x>
+     <y>0</y>
+     <width>648</width>
+     <height>22</height>
+    </rect>
+   </property>
+   <widget class="QMenu" name="menuFile">
+    <property name="title">
+     <string>File</string>
+    </property>
+    <addaction name="actionSaveSettings"/>
+    <addaction name="actionLoadSettings"/>
+    <addaction name="separator"/>
+    <addaction name="actionQuit"/>
+   </widget>
+   <addaction name="menuFile"/>
+  </widget>
+  <action name="actionSaveSettings">
+   <property name="text">
+    <string>&amp;Save settings...</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+S</string>
+   </property>
+  </action>
+  <action name="actionLoadSettings">
+   <property name="text">
+    <string>&amp;Load settings...</string>
+   </property>
+   <property name="shortcut">
+    <string>Ctrl+O</string>
+   </property>
+  </action>
+  <action name="actionQuit">
+   <property name="text">
+    <string>&amp;Quit</string>
+   </property>
+  </action>
+ </widget>
+ <resources/>
+ <connections>
+  <connection>
+   <sender>powerSlider</sender>
+   <signal>valueChanged(int)</signal>
+   <receiver>powerBox</receiver>
+   <slot>setValue(int)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>91</x>
+     <y>718</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>116</x>
+     <y>598</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>offsetSlider</sender>
+   <signal>valueChanged(int)</signal>
+   <receiver>offsetBox</receiver>
+   <slot>setValue(int)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>255</x>
+     <y>779</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>273</x>
+     <y>598</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>delaySlider</sender>
+   <signal>valueChanged(int)</signal>
+   <receiver>delayBox</receiver>
+   <slot>setValue(int)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>412</x>
+     <y>779</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>429</x>
+     <y>598</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>sizeSlider</sender>
+   <signal>valueChanged(int)</signal>
+   <receiver>sizeBox</receiver>
+   <slot>setValue(int)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>568</x>
+     <y>779</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>586</x>
+     <y>598</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>sizeBox</sender>
+   <signal>valueChanged(int)</signal>
+   <receiver>sizeSlider</receiver>
+   <slot>setValue(int)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>586</x>
+     <y>598</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>568</x>
+     <y>779</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>delayBox</sender>
+   <signal>valueChanged(int)</signal>
+   <receiver>delaySlider</receiver>
+   <slot>setValue(int)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>429</x>
+     <y>598</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>412</x>
+     <y>779</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>offsetBox</sender>
+   <signal>valueChanged(int)</signal>
+   <receiver>offsetSlider</receiver>
+   <slot>setValue(int)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>273</x>
+     <y>598</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>255</x>
+     <y>779</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>powerBox</sender>
+   <signal>valueChanged(int)</signal>
+   <receiver>powerSlider</receiver>
+   <slot>setValue(int)</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>115</x>
+     <y>598</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>91</x>
+     <y>718</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>shutdown</sender>
+   <signal>clicked()</signal>
+   <receiver>OutputSettingsDLG</receiver>
+   <slot>close()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>562</x>
+     <y>43</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>589</x>
+     <y>151</y>
+    </hint>
+   </hints>
+  </connection>
+  <connection>
+   <sender>actionQuit</sender>
+   <signal>activated()</signal>
+   <receiver>OutputSettingsDLG</receiver>
+   <slot>close()</slot>
+   <hints>
+    <hint type="sourcelabel">
+     <x>-1</x>
+     <y>-1</y>
+    </hint>
+    <hint type="destinationlabel">
+     <x>323</x>
+     <y>426</y>
+    </hint>
+   </hints>
+  </connection>
+ </connections>
+</ui>
diff --git a/test_patterns/ILDA12K.ild b/test_patterns/ILDA12K.ild
new file mode 100644 (file)
index 0000000..e7b8c1a
Binary files /dev/null and b/test_patterns/ILDA12K.ild differ
diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt
new file mode 100644 (file)
index 0000000..b7327dc
--- /dev/null
@@ -0,0 +1,26 @@
+#         OpenLase - a realtime laser graphics toolkit
+#
+# Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+#
+# This program is free software; you can redistribute it and/or modify
+# it under the terms of the GNU General Public License as published by
+# the Free Software Foundation, either version 2 or version 3.
+#
+# This program is distributed in the hope that it will be useful,
+# but WITHOUT ANY WARRANTY; without even the implied warranty of
+# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+# GNU General Public License for more details.
+#
+# You should have received a copy of the GNU General Public License
+# along with this program; if not, write to the Free Software
+# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+#
+
+include_directories (${CMAKE_SOURCE_DIR}/include)
+link_directories (${CMAKE_BINARY_DIR}/libol)
+
+add_executable(playilda playilda.c)
+target_link_libraries(playilda ${JACK_LIBRARIES})
+
+add_executable(playvid playvid.c trace.c)
+target_link_libraries(playvid openlase avformat avcodec)
diff --git a/tools/genfont.py b/tools/genfont.py
new file mode 100644 (file)
index 0000000..92557cd
--- /dev/null
@@ -0,0 +1,437 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import os, sys, math
+import xml.sax, xml.sax.handler
+import re
+
+def pc(c):
+       x,y = c
+       if isinstance(x,int) and isinstance(y,int):
+               return "(%d,%d)"%(x,y)
+       else:
+               return "(%.4f, %.4f)"%(x,y)
+
+class PathLine(object):
+       def __init__(self, start, end, on=True):
+               self.start = start
+               self.end = end
+               self.on = on
+       def copy(self):
+               return PathLine(self.start, self.end)
+       def transform(self, func):
+               self.start = func(self.start)
+               self.end = func(self.end)
+       def reverse(self):
+               return PathLine(self.end, self.start, self.on)
+       def scp(self):
+               return self.end
+       def ecp(self):
+               return self.start
+       def showinfo(self, tr=''):
+               print tr+'Line( %s %s )'%(pc(self.start),pc(self.end))
+
+class PathBezier4(object):
+       def __init__(self, start, cp1, cp2, end):
+               self.start = start
+               self.end = end
+               self.cp1 = cp1
+               self.cp2 = cp2
+       def copy(self):
+               return PathBezier4(self.start, self.cp1, self.cp2, self.end)
+       def transform(self, func):
+               self.start = func(self.start)
+               self.cp1 = func(self.cp1)
+               self.cp2 = func(self.cp2)
+               self.end = func(self.end)
+
+       def reverse(self):
+               return PathBezier4(self.end, self.cp2, self.cp1, self.start)
+       def scp(self):
+               if self.cp1 != self.start:
+                       return self.cp1
+               elif self.cp2 != self.start:
+                       return self.cp2
+               else:
+                       return self.end
+       def ecp(self):
+               if self.cp2 != self.end:
+                       return self.cp2
+               elif self.cp1 != self.end:
+                       return self.cp1
+               else:
+                       return self.start
+       def showinfo(self, tr=''):
+               print tr+'Bezier( %s %s %s %s )'%(pc(self.start),pc(self.cp1),pc(self.cp2),pc(self.end))
+
+class PathBezier3(PathBezier4):
+       def __init__(self, start, cp, end):
+               sx,sy = start
+               cx,cy = cp
+               ex,ey = end
+
+               c1x = (1.0/3) * cx + (2.0/3) * sx
+               c1y = (1.0/3) * cy + (2.0/3) * sy
+               c2x = (1.0/3) * cx + (2.0/3) * ex
+               c2y = (1.0/3) * cy + (2.0/3) * ey
+               PathBezier4.__init__(start, (c1x,c1y), (c2x,c2y), end)
+
+class LaserPath(object):
+       def __init__(self):
+               self.segments = []
+       def add(self, seg):
+               self.segments.append(seg)
+       def transform(self, func):
+               for i in self.segments:
+                       i.transform(func)
+       def startpos(self):
+               return self.segments[0].start
+       def endpos(self):
+               return self.segments[-1].end
+       def reverse(self):
+               lp = LaserPath()
+               lp.segments = [x.reverse() for x in self.segments[::-1]]
+               return lp
+       def showinfo(self, tr=''):
+               print tr+'LaserPath:'
+               for i in self.segments:
+                       i.showinfo(tr+' ')
+
+class LaserFrame(object):
+       def __init__(self):
+               self.objects = []
+       def add(self, obj):
+               self.objects.append(obj)
+       def transform(self, func):
+               for i in self.objects:
+                       i.transform(func)
+       def showinfo(self, tr=''):
+               print tr+'LaserFrame:'
+               for i in self.objects:
+                       i.showinfo(tr+' ')
+
+class SVGPath(object):
+       def __init__(self, data=None):
+               self.subpaths = []
+               if data:
+                       self.parse(data)
+
+       def poptok(self):
+               if not self.tokens:
+                       raise ValueError("Expected token but got end of path data")
+               return self.tokens.pop(0)
+       def peektok(self):
+               if not self.tokens:
+                       raise ValueError("Expected token but got end of path data")
+               return self.tokens[0]
+       def popnum(self):
+               tok = self.tokens.pop(0)
+               try:
+                       return float(tok)
+               except ValueError:
+                       raise ValueError("Invalid SVG path numerical token: %s"%tok)
+       def isnum(self):
+               if not self.tokens:
+                       return False # no more tokens is considered a non-number
+               try:
+                       float(self.peektok())
+               except ValueError:
+                       return False
+               return True
+       def popcoord(self,rel=False, cur=None):
+               x = self.popnum()
+               if self.peektok() == ',':
+                       self.poptok()
+               y = self.popnum()
+               if rel:
+                       x += cur[0]
+                       y += cur[1]
+               return x,y
+       def reflect(self, center, point):
+               cx,cy = center
+               px,py = point
+               return (2*cx-px, 2*cy-py)
+
+       def parse(self, data):
+               ds = re.split(r"[ \r\n\t]*([-+]?\d+\.\d+[eE][+-]?\d+|[-+]?\d+\.\d+|[-+]?\.\d+[eE][+-]?\d+|[-+]?\.\d+|[-+]?\d+\.?[eE][+-]?\d+|[-+]?\d+\.?|[MmZzLlHhVvCcSsQqTtAa])[, \r\n\t]*", data)
+               tokens = ds[1::2]
+               if any(ds[::2]) or not all(ds[1::2]):
+                       raise ValueError("Invalid SVG path expression: %r"%data)
+               self.tokens = tokens
+
+               cur = (0,0)
+               curcpc = (0,0)
+               curcpq = (0,0)
+               sp_start = (0,0)
+               in_path = False
+               cmd = None
+               subpath = LaserPath()
+               while tokens:
+                       if self.isnum() and cmd in "MmLlHhVvCcSsQqTtAa":
+                               pass
+                       elif self.peektok() in "MmZzLlHhVvCcSsQqTtAa":
+                               cmd = tokens.pop(0)
+                       else:
+                               raise ValueError("Invalid SVG path token %s"%tok)
+
+                       rel = cmd.upper() != cmd
+                       ucmd = cmd.upper()
+
+                       if not in_path and ucmd != 'M' :
+                               raise ValueErorr("SVG path segment must begin with 'm' command, not '%s'"%cmd)
+
+                       if ucmd == 'M':
+                               sp_start = self.popcoord(rel, cur)
+                               if subpath.segments:
+                                       self.subpaths.append(subpath)
+                               subpath = LaserPath()
+                               cmd = 'Ll'[rel]
+                               cur = curcpc = curcpq = sp_start
+                               in_path = True
+                       elif ucmd == 'Z':
+                               if sp_start != cur:
+                                       subpath.add(PathLine(cur, sp_start))
+                               cur = curcpc = curcpq = sp_start
+                               in_path = False
+                       elif ucmd == 'L':
+                               end = self.popcoord(rel, cur)
+                               subpath.add(PathLine(cur, end))
+                               cur = curcpc = curcpq = end
+                       elif ucmd == 'H':
+                               ex = self.popnum()
+                               if rel:
+                                       ex += cur[0]
+                               end = ex,cur[1]
+                               subpath.add(PathLine(cur, end))
+                               cur = curcpc = curcpq = end
+                       elif ucmd == 'V':
+                               ey = self.popnum()
+                               if rel:
+                                       ey += cur[1]
+                               end = cur[0],ey
+                               subpath.add(PathLine(cur, end))
+                               cur = curcpc = curcpq = end
+                       elif ucmd in 'CS':
+                               if ucmd == 'S':
+                                       c1 = self.reflect(cur,curcpc)
+                               else:
+                                       c1 = self.popcoord(rel, cur)
+                               c2 = curcpc = self.popcoord(rel, cur)
+                               end = self.popcoord(rel, cur)
+                               subpath.add(PathBezier4(cur, c1, c2, end))
+                               cur = curcpq = end
+                       elif ucmd in 'QT':
+                               if ucmd == 'T':
+                                       cp = curcpq = self.reflect(cur,curcpq)
+                               else:
+                                       cp = curcpq = self.popcoord(rel, cur)
+                               end = self.popcoord(rel, cur)
+                               subpath.add(PathBezier3(cur, cp, end))
+                               cur = curcpc = end
+                       elif ucmd in 'Aa':
+                               raise ValueError("Arcs not implemented, biatch")
+
+               if subpath.segments:
+                       self.subpaths.append(subpath)
+
+class SVGReader(xml.sax.handler.ContentHandler):
+       def doctype(self, name, pubid, system):
+               print name,pubid,system
+       def startDocument(self):
+               self.frame = LaserFrame()
+               self.rects = []
+               self.matrix_stack = [(1,0,0,1,0,0)]
+       def endDocument(self):
+               pass
+       def startElement(self, name, attrs):
+               if name == "svg":
+                       self.width = float(attrs['width'].replace("px",""))
+                       self.height = float(attrs['height'].replace("px",""))
+               elif name == "path":
+                       if 'transform' in attrs.keys():
+                               self.transform(attrs['transform'])
+                       self.addPath(attrs['d'])
+                       if 'transform' in attrs.keys():
+                               self.popmatrix()
+               elif name == "rect":
+                       self.rects.append((attrs['id'],float(attrs['x']),float(attrs['y']),float(attrs['width']),float(attrs['height'])))
+               elif name == 'g':
+                       if 'transform' in attrs.keys():
+                               self.transform(attrs['transform'])
+                       else:
+                               self.pushmatrix((1,0,0,1,0,0))
+       def endElement(self, name):
+               if name == 'g':
+                       self.popmatrix()
+       def mmul(self, m1, m2):
+               a1,b1,c1,d1,e1,f1 = m1
+               a2,b2,c2,d2,e2,f2 = m2
+               a3 = a1 * a2 + c1 * b2
+               b3 = b1 * a2 + d1 * b2
+               c3 = a1 * c2 + c1 * d2
+               d3 = b1 * c2 + d1 * d2
+               e3 = a1 * e2 + c1 * f2 + e1
+               f3 = b1 * e2 + d1 * f2 + f1
+               return (a3,b3,c3,d3,e3,f3)
+       def pushmatrix(self, m):
+               new_mat = self.mmul(self.matrix_stack[-1], m)
+               self.matrix_stack.append(new_mat)
+       def popmatrix(self):
+               self.matrix_stack.pop()
+       def tc(self,coord):
+               vw = vh = max(self.width, self.height) / 2.0
+               x,y = coord
+               x -= self.width / 2.0
+               y -= self.height / 2.0
+               x = x / vw
+               y = y / vh
+               return (x,y)
+       def ts(self,coord):
+               a,b,c,d,e,f = self.matrix_stack[-1]
+               x,y = coord
+               nx = a*x + c*y + e
+               ny = b*x + d*y + f
+               return (nx,ny)
+       def transform(self, data):
+               ds = re.split(r"[ \r\n\t]*([a-z]+\([^)]+\)|,)[ \r\n\t]*", data)
+               tokens = ds[1::2]
+               if any(ds[::2]) or not all(ds[1::2]):
+                       raise ValueError("Invalid SVG transform expression: %r"%data)
+               if not all([x == ',' for x in tokens[1::2]]):
+                       raise ValueError("Invalid SVG transform expression: %r"%data)
+               transforms = tokens[::2]
+
+               mat = (1,0,0,1,0,0)
+               for t in transforms:
+                       name,rest = t.split("(")
+                       if rest[-1] != ")":
+                               raise ValueError("Invalid SVG transform expression: %r"%data)
+                       args = map(float,rest[:-1].split(","))
+                       if name == 'matrix':
+                               mat = self.mmul(mat, args)
+                       elif name == 'translate':
+                               tx,ty = args
+                               mat = self.mmul(mat, (1,0,0,1,tx,ty))
+                       elif name == 'scale':
+                               sx,sy = args
+                               mat = self.mmul(mat, (sx,0,0,sy,0,0))
+                       elif name == 'rotate':
+                               a = args[0] / 180.0 * math.pi
+                               if len(args) == 3:
+                                       cx,cy = args[1:]
+                                       mat = self.mmul(mat, (1,0,0,1,cx,cy))
+                               cos = math.cos(a)
+                               sin = math.sin(a)
+                               mat = self.mmul(mat, (cos,sin,-sin,cos,0,0))
+                               if len(args) == 3:
+                                       mat = self.mmul(mat, (1,0,0,1,-cx,-cy))
+                       elif name == 'skewX':
+                               a = args[0] / 180.0 * math.pi
+                               mat = self.mmul(mat, (1,0,math.tan(a),1,0,0))
+                       elif name == 'skewY':
+                               a = args[0] / 180.0 * math.pi
+                               mat = self.mmul(mat, (1,math.tan(a),0,1,0,0))
+               self.pushmatrix(mat)
+       def addPath(self, data):
+               p = SVGPath(data)
+               for path in p.subpaths:
+                       path.transform(self.ts)
+                       self.frame.add(path)
+
+svg = sys.argv[1]
+cfile = sys.argv[2]
+if len(sys.argv) > 3:
+       font_name = sys.argv[3]
+else:
+       font_name = "font"
+
+handler = SVGReader()
+parser = xml.sax.make_parser()
+parser.setContentHandler(handler)
+parser.setFeature(xml.sax.handler.feature_external_ges, False)
+parser.parse(sys.argv[1])
+
+frame = handler.frame
+
+cdefs = []
+
+max_h = 0
+
+output = ""
+
+output += "#include \"text.h\"\n"
+
+overlap = 0
+
+for id, x, y, w, h in handler.rects:
+       if id == "overlap":
+               overlap = w
+               continue
+       if id[:5] != "char_":
+               continue
+       cv = id[5:]
+       if len(cv) == 1:
+               chrval = ord(id[5:])
+       else:
+               chrval = int(cv,0)
+
+       if h > max_h:
+               max_h = h
+
+       #print >>sys.stderr, "Got rect %s"%id
+       char_objs = []
+       for obj in frame.objects:
+               sx,sy = obj.segments[0].start
+               if sx >= x and sy >= y and sx <= (x+w) and sy <= (y+h):
+                       char_objs.append(obj)
+       #print >>sys.stderr,  " - %d objects"%len(char_objs)
+       
+       def transform(pt):
+               px,py = pt
+               px -= x
+               py -= y
+               return (px,py)
+       
+       if len(char_objs) != 0:
+               output += "static const FontPoint fc_%02x[] = {\n"%chrval
+
+               for i,obj in enumerate(char_objs):
+                       obj.transform(transform)
+                       last_obj = i == len(char_objs)-1
+                       output += "\t{0, %8.4f, %8.4f},\n"%obj.startpos()
+                       
+                       for j,seg in enumerate(obj.segments):
+                               last_seg = j == len(obj.segments)-1
+                               if last_obj and last_seg:
+                                       ctl = 2
+                               elif last_seg:
+                                       ctl = 1
+                               else:
+                                       ctl = 0
+                               if isinstance(seg, PathBezier4):
+                                       output += "\t{0, %8.4f, %8.4f},\n"%seg.cp1
+                                       output += "\t{0, %8.4f, %8.4f},\n"%seg.cp2
+                                       output += "\t{%d, %8.4f, %8.4f},\n"%(ctl, seg.end[0], seg.end[1])
+                               elif isinstance(seg, PathLine):
+                                       output += "\t{0, %8.4f, %8.4f},\n"%seg.start
+                                       output += "\t{0, %8.4f, %8.4f},\n"%seg.end
+                                       output += "\t{%d, %8.4f, %8.4f},\n"%(ctl, seg.end[0], seg.end[1])
+               
+               output += "};\n"
+       
+               cdefs.append((chrval, w, "fc_%02x"%chrval))
+       else:
+               cdefs.append((chrval, w, "NULL"))
+
+output += "\nstatic const FontChar font_chars[256] = {\n"
+
+for chrval, width, sym in cdefs:
+       output += "\t['%s'] = {%8.4f, %s},\n"%(chr(chrval),width,sym)
+
+output += "};\n\n"
+
+output += "const Font %s = {%f, %f, font_chars};\n\n"%(font_name,max_h,overlap)
+
+fd = open(cfile,"w")
+fd.write(output)
+fd.close()
\ No newline at end of file
diff --git a/tools/playilda.c b/tools/playilda.c
new file mode 100644 (file)
index 0000000..c23034a
--- /dev/null
@@ -0,0 +1,532 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+/*
+Simple bare-bones ILDA file player. Works directly on JACK. libol does (now)
+have ILDA loading and display, but that goes through quite a bit of processing.
+This bare-bones player should accurately display ILDA files at the target sample
+rate or, if slower, performs simple resampling. Thus, it works well for things
+like the ILDA test pattern.
+
+Oh, and it also tries to watch the input file and reload if it changes. Nice
+if you make a shell loop to convert SVG to ILD every time the SVG changes (but
+use a temp file and rename the ILD at the end, otherwise this breaks horribly
+on partial files). Then you can have Inkscape open and every time you save
+the laser image updates.
+*/
+
+#define _BSD_SOURCE
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <jack/jack.h>
+
+#include <sys/types.h>
+#include <sys/param.h>
+#include <sys/stat.h>
+
+#if BYTE_ORDER == LITTLE_ENDIAN
+static inline uint16_t swapshort(uint16_t v) {
+       return (v >> 8) | (v << 8);
+}
+# define MAGIC 0x41444C49
+#else
+static inline uint16_t swapshort(uint16_t v) {
+       return v;
+}
+# define MAGIC 0x494C4441
+#endif
+
+#include <stdint.h>
+
+typedef jack_default_audio_sample_t sample_t;
+typedef jack_nframes_t nframes_t;
+
+jack_port_t *out_x;
+jack_port_t *out_y;
+jack_port_t *out_z;
+jack_port_t *out_r;
+jack_port_t *out_g;
+jack_port_t *out_b;
+jack_port_t *out_w;
+
+nframes_t rate;
+nframes_t divider = 1;
+
+nframes_t pointrate = 30000;
+
+int scale = 0;
+
+sample_t size = 1.0;
+
+struct ilda_hdr {
+       uint32_t magic;
+       uint8_t pad1[3];
+       uint8_t format;
+       char name[8];
+       char company[8];
+       uint16_t count;
+       uint16_t frameno;
+       uint16_t framecount;
+       uint8_t scanner;
+       uint8_t pad2;
+} __attribute__((packed));
+
+struct color {
+       uint8_t r, g, b;
+};
+
+#define BLANK 0x40
+#define LAST 0x80
+
+struct icoord3d {
+       int16_t x;
+       int16_t y;
+       int16_t z;
+       uint8_t state;
+       uint8_t color;
+} __attribute__((packed));
+
+struct icoord2d {
+       int16_t x;
+       int16_t y;
+       uint8_t state;
+       uint8_t color;
+} __attribute__((packed));
+
+struct coord3d {
+       int16_t x;
+       int16_t y;
+       int16_t z;
+       uint8_t state;
+       struct color color;
+};
+
+struct frame {
+       struct coord3d *points;
+       int position;
+       int count;
+};
+
+struct frame frames[2];
+
+struct frame * volatile curframe;
+struct frame *curdframe;
+
+int subpos;
+
+struct color palette[256] = {
+       {  0,   0,   0}, {255, 255, 255}, {255,   0,   0}, {255, 255,   0},
+       {  0, 255,   0}, {  0, 255, 255}, {  0,   0, 255}, {255,   0, 255},
+       {255, 128, 128}, {255, 140, 128}, {255, 151, 128}, {255, 163, 128},
+       {255, 174, 128}, {255, 186, 128}, {255, 197, 128}, {255, 209, 128},
+       {255, 220, 128}, {255, 232, 128}, {255, 243, 128}, {255, 255, 128},
+       {243, 255, 128}, {232, 255, 128}, {220, 255, 128}, {209, 255, 128},
+       {197, 255, 128}, {186, 255, 128}, {174, 255, 128}, {163, 255, 128},
+       {151, 255, 128}, {140, 255, 128}, {128, 255, 128}, {128, 255, 140},
+       {128, 255, 151}, {128, 255, 163}, {128, 255, 174}, {128, 255, 186},
+       {128, 255, 197}, {128, 255, 209}, {128, 255, 220}, {128, 255, 232},
+       {128, 255, 243}, {128, 255, 255}, {128, 243, 255}, {128, 232, 255},
+       {128, 220, 255}, {128, 209, 255}, {128, 197, 255}, {128, 186, 255},
+       {128, 174, 255}, {128, 163, 255}, {128, 151, 255}, {128, 140, 255},
+       {128, 128, 255}, {140, 128, 255}, {151, 128, 255}, {163, 128, 255},
+       {174, 128, 255}, {186, 128, 255}, {197, 128, 255}, {209, 128, 255},
+       {220, 128, 255}, {232, 128, 255}, {243, 128, 255}, {255, 128, 255},
+       {255, 128, 243}, {255, 128, 232}, {255, 128, 220}, {255, 128, 209},
+       {255, 128, 197}, {255, 128, 186}, {255, 128, 174}, {255, 128, 163},
+       {255, 128, 151}, {255, 128, 140}, {255,   0,   0}, {255,  23,   0},
+       {255,  46,   0}, {255,  70,   0}, {255,  93,   0}, {255, 116,   0},
+       {255, 139,   0}, {255, 162,   0}, {255, 185,   0}, {255, 209,   0},
+       {255, 232,   0}, {255, 255,   0}, {232, 255,   0}, {209, 255,   0},
+       {185, 255,   0}, {162, 255,   0}, {139, 255,   0}, {116, 255,   0},
+       { 93, 255,   0}, { 70, 255,   0}, { 46, 255,   0}, { 23, 255,   0},
+       {  0, 255,   0}, {  0, 255,  23}, {  0, 255,  46}, {  0, 255,  70},
+       {  0, 255,  93}, {  0, 255, 116}, {  0, 255, 139}, {  0, 255, 162},
+       {  0, 255, 185}, {  0, 255, 209}, {  0, 255, 232}, {  0, 255, 255},
+       {  0, 232, 255}, {  0, 209, 255}, {  0, 185, 255}, {  0, 162, 255},
+       {  0, 139, 255}, {  0, 116, 255}, {  0,  93, 255}, {  0,  70, 255},
+       {  0,  46, 255}, {  0,  23, 255}, {  0,   0, 255}, { 23,   0, 255},
+       { 46,   0, 255}, { 70,   0, 255}, { 93,   0, 255}, {116,   0, 255},
+       {139,   0, 255}, {162,   0, 255}, {185,   0, 255}, {209,   0, 255},
+       {232,   0, 255}, {255,   0, 255}, {255,   0, 232}, {255,   0, 209},
+       {255,   0, 185}, {255,   0, 162}, {255,   0, 139}, {255,   0, 116},
+       {255,   0,  93}, {255,   0,  70}, {255,   0,  46}, {255,   0,  23},
+       {128,   0,   0}, {128,  12,   0}, {128,  23,   0}, {128,  35,   0},
+       {128,  47,   0}, {128,  58,   0}, {128,  70,   0}, {128,  81,   0},
+       {128,  93,   0}, {128, 105,   0}, {128, 116,   0}, {128, 128,   0},
+       {116, 128,   0}, {105, 128,   0}, { 93, 128,   0}, { 81, 128,   0},
+       { 70, 128,   0}, { 58, 128,   0}, { 47, 128,   0}, { 35, 128,   0},
+       { 23, 128,   0}, { 12, 128,   0}, {  0, 128,   0}, {  0, 128,  12},
+       {  0, 128,  23}, {  0, 128,  35}, {  0, 128,  47}, {  0, 128,  58},
+       {  0, 128,  70}, {  0, 128,  81}, {  0, 128,  93}, {  0, 128, 105},
+       {  0, 128, 116}, {  0, 128, 128}, {  0, 116, 128}, {  0, 105, 128},
+       {  0,  93, 128}, {  0,  81, 128}, {  0,  70, 128}, {  0,  58, 128},
+       {  0,  47, 128}, {  0,  35, 128}, {  0,  23, 128}, {  0,  12, 128},
+       {  0,   0, 128}, { 12,   0, 128}, { 23,   0, 128}, { 35,   0, 128},
+       { 47,   0, 128}, { 58,   0, 128}, { 70,   0, 128}, { 81,   0, 128},
+       { 93,   0, 128}, {105,   0, 128}, {116,   0, 128}, {128,   0, 128},
+       {128,   0, 116}, {128,   0, 105}, {128,   0,  93}, {128,   0,  81},
+       {128,   0,  70}, {128,   0,  58}, {128,   0,  47}, {128,   0,  35},
+       {128,   0,  23}, {128,   0,  12}, {255, 192, 192}, {255,  64,  64},
+       {192,   0,   0}, { 64,   0,   0}, {255, 255, 192}, {255, 255,  64},
+       {192, 192,   0}, { 64,  64,   0}, {192, 255, 192}, { 64, 255,  64},
+       {  0, 192,   0}, {  0,  64,   0}, {192, 255, 255}, { 64, 255, 255},
+       {  0, 192, 192}, {  0,  64,  64}, {192, 192, 255}, { 64,  64, 255},
+       {  0,   0, 192}, {  0,   0,  64}, {255, 192, 255}, {255,  64, 255},
+       {192,   0, 192}, { 64,   0,  64}, {255,  96,  96}, {255, 255, 255},
+       {245, 245, 245}, {235, 235, 235}, {224, 224, 224}, {213, 213, 213},
+       {203, 203, 203}, {192, 192, 192}, {181, 181, 181}, {171, 171, 171},
+       {160, 160, 160}, {149, 149, 149}, {139, 139, 139}, {128, 128, 128},
+       {117, 117, 117}, {107, 107, 107}, { 96,  96,  96}, { 85,  85,  85},
+       { 75,  75,  75}, { 64,  64,  64}, { 53,  53,  53}, { 43,  43,  43},
+       { 32,  32,  32}, { 21,  21,  21}, { 11,  11,  11}, {  0,   0,   0},
+};
+
+#define MONOCHROME
+
+int process (nframes_t nframes, void *arg)
+{
+       struct frame *frame = curdframe;
+
+       sample_t *o_x = (sample_t *) jack_port_get_buffer (out_x, nframes);
+       sample_t *o_y = (sample_t *) jack_port_get_buffer (out_y, nframes);
+       sample_t *o_z = (sample_t *) jack_port_get_buffer (out_z, nframes);
+       sample_t *o_r = (sample_t *) jack_port_get_buffer (out_r, nframes);
+       sample_t *o_g = (sample_t *) jack_port_get_buffer (out_g, nframes);
+       sample_t *o_b = (sample_t *) jack_port_get_buffer (out_b, nframes);
+       sample_t *o_w = (sample_t *) jack_port_get_buffer (out_w, nframes);
+
+       nframes_t frm;
+       for (frm = 0; frm < nframes; frm++) {
+               struct coord3d *c = &frame->points[frame->position];
+               *o_x++ = (c->x / 32768.0) * size;
+               *o_y++ = (c->y / 32768.0) * size;
+               *o_z++ = (c->z / 32768.0) * size;
+               if(c->state & BLANK) {
+                       *o_r++ = *o_g++ = *o_b++ = *o_w++ = 0.0;
+               } else {
+#ifdef MONOCHROME
+                       *o_r++ = *o_g++ = *o_b++ = *o_w++ = 1.0;
+#else
+                       *o_r = c->color.r / 255.0;
+                       *o_g = c->color.g / 255.0;
+                       *o_b = c->color.b / 255.0;
+                       *o_w++ = 0.333 * *o_r++ + 0.333 * *o_g++ + 0.333 * *o_b++;
+                       //*o_w++ = 0.2126 * *o_r++ + 0.7152 * *o_g++ + 0.0722 * *o_b++;
+#endif
+               }
+               subpos++;
+               if (subpos == divider) {
+                       subpos = 0;
+                       if (c->state & LAST)
+                               frame->position = 0;
+                       else
+                               frame->position = (frame->position + 1) % frame->count;
+               }
+               if (frame->position == 0) {
+                       if(curdframe != curframe) {
+                               printf("Frame update\n");
+                               curdframe = curframe;
+                       }
+               }
+       }
+
+       return 0;
+}
+
+int bufsize (nframes_t nframes, void *arg)
+{
+       printf ("the maximum buffer size is now %u\n", nframes);
+       return 0;
+}
+
+int srate (nframes_t nframes, void *arg)
+{
+       rate = nframes;
+       printf ("Playing back at %u pps\n", rate / divider);
+       return 0;
+}
+
+void jack_shutdown (void *arg)
+{
+       exit (1);
+}
+
+int clamp(int v)
+{
+       if(v > 32767) {
+               printf("warn: val is %d\n", v);
+               return 32767;
+       }
+       if(v < -32768) {
+               printf("warn: val is %d\n", v);
+               return -32768;
+       }
+       return v;
+}
+
+int loadild(const char *fname, struct frame *frame)
+{
+       int i;
+       FILE *ild = fopen(fname, "rb");
+
+       int minx = 65536, maxx = -65536;
+       int miny = 65536, maxy = -65536;
+       int minz = 65536, maxz = -65536;
+
+       if (!ild) {
+               fprintf(stderr, "cannot open %s\n", fname);
+               return -1;
+       }
+
+       frame->count = 0;
+       memset(frame, 0, sizeof(struct frame));
+
+       while(!frame->count) {
+
+               struct ilda_hdr hdr;
+
+               if (fread(&hdr, sizeof(hdr), 1, ild) != 1) {
+                       fprintf(stderr, "error while reading file\n");
+                       return -1;
+               }
+
+               if (hdr.magic != MAGIC) {
+                       fprintf(stderr, "Invalid magic 0x%08x\n", hdr.magic);
+                       return -1;
+               }
+
+               hdr.count = swapshort(hdr.count);
+               hdr.frameno = swapshort(hdr.frameno);
+               hdr.framecount = swapshort(hdr.framecount);
+
+               switch (hdr.format) {
+               case 0:
+                       printf("Got 3D frame, %d points\n", hdr.count);
+                       frame->points = malloc(sizeof(struct coord3d) * hdr.count);
+                       struct icoord3d *tmp3d = malloc(sizeof(struct icoord3d) * hdr.count);
+                       if (fread(tmp3d, sizeof(struct icoord3d), hdr.count, ild) != hdr.count) {
+                               fprintf(stderr, "error while reading frame\n");
+                               return -1;
+                       }
+                       for(i=0; i<hdr.count; i++) {
+                               frame->points[i].x = swapshort(tmp3d[i].x);
+                               frame->points[i].y = swapshort(tmp3d[i].y);
+                               frame->points[i].z = swapshort(tmp3d[i].z);
+                               frame->points[i].state = tmp3d[i].state;
+                               frame->points[i].color = palette[tmp3d[i].color];
+                       }
+                       free(tmp3d);
+                       frame->count = hdr.count;
+                       break;
+               case 1:
+                       printf("Got 2D frame, %d points\n", hdr.count);
+                       frame->points = malloc(sizeof(struct coord3d) * hdr.count);
+                       struct icoord2d *tmp2d = malloc(sizeof(struct icoord2d) * hdr.count);
+                       if (fread(tmp2d, sizeof(struct icoord2d), hdr.count, ild) != hdr.count) {
+                               fprintf(stderr, "error while reading frame\n");
+                               return -1;
+                       }
+                       for(i=0; i<hdr.count; i++) {
+                               frame->points[i].x = swapshort(tmp2d[i].x);
+                               frame->points[i].y = swapshort(tmp2d[i].y);
+                               frame->points[i].z = 0;
+                               frame->points[i].state = tmp2d[i].state;
+                               frame->points[i].color = palette[tmp2d[i].color];
+                       }
+                       free(tmp2d);
+                       frame->count = hdr.count;
+                       break;
+               case 2:
+                       printf("Got color palette section, %d entries\n", hdr.count);
+                       if (fread(palette, 3, hdr.count, ild) != hdr.count) {
+                               fprintf(stderr, "error while reading palette\n");
+                               return -1;
+                       }
+                       break;
+               }
+       }
+
+       fclose(ild);
+
+       if (scale) {
+               for(i=0; i<frame->count; i++) {
+                       if(frame->points[i].x > maxx)
+                               maxx = frame->points[i].x;
+                       if(frame->points[i].y > maxy)
+                               maxy = frame->points[i].y;
+                       if(frame->points[i].z > maxz)
+                               maxz = frame->points[i].z;
+                       if(frame->points[i].x < minx)
+                               minx = frame->points[i].x;
+                       if(frame->points[i].y < miny)
+                               miny = frame->points[i].y;
+                       if(frame->points[i].z < minz)
+                               minz = frame->points[i].z;
+               }
+
+               int dx, dy, dz, dmax;
+               int cx, cy, cz;
+               dx = maxx-minx;
+               dy = maxy-miny;
+               dz = maxz-minz;
+               cx = (minx+maxx)/2;
+               cy = (miny+maxy)/2;
+               cz = (minz+maxz)/2;
+
+               dmax = dx;
+               if (dy > dmax)
+                       dmax = dy;
+               if (dz > dmax)
+                       dmax = dz;
+
+               printf("X range: %d .. %d (%d) (c:%d)\n", minx, maxx, dx, cx);
+               printf("Y range: %d .. %d (%d) (c:%d)\n", miny, maxy, dy, cy);
+               printf("Z range: %d .. %d (%d) (c:%d)\n", minz, maxz, dz, cz);
+
+               printf("Scaling by %d\n", dmax);
+
+               for(i=0; i<frame->count; i++) {
+                       frame->points[i].x -= cx;
+                       frame->points[i].y -= cy;
+                       frame->points[i].z -= cz;
+
+                       frame->points[i].x = clamp((int)frame->points[i].x * 32767 / (dmax/2+1));
+                       frame->points[i].y = clamp((int)frame->points[i].y * 32767 / (dmax/2+1));
+                       frame->points[i].z = clamp((int)frame->points[i].z * 32767 / (dmax/2+1));
+               }
+
+       }
+
+       printf("Resampling %d -> %d\n", pointrate, rate);
+
+       if (pointrate > rate) {
+               printf("Downsampling not implemented!\n");
+               return -1;
+       }
+
+       int ocount = frame->count * rate / pointrate;
+       struct coord3d *opoints = malloc(sizeof(struct coord3d) * ocount);
+
+       float mul = (float)frame->count / (float)ocount;
+
+       for(i=0; i<ocount; i++) {
+               float pos = i*mul;
+               int lpos = pos;
+               int rpos = pos+1;
+               if (rpos >= frame->count)
+                       rpos = lpos;
+               float off = pos - lpos;
+               opoints[i].x = frame->points[lpos].x * (1-off) + frame->points[rpos].x * off;
+               opoints[i].y = frame->points[lpos].y * (1-off) + frame->points[rpos].y * off;
+               opoints[i].z = frame->points[lpos].z * (1-off) + frame->points[rpos].z * off;
+               opoints[i].color.r = frame->points[lpos].color.r * (1-off) + frame->points[rpos].color.r * off;
+               opoints[i].color.g = frame->points[lpos].color.g * (1-off) + frame->points[rpos].color.g * off;
+               opoints[i].color.b = frame->points[lpos].color.b * (1-off) + frame->points[rpos].color.b * off;
+               opoints[i].state = frame->points[lpos].state | frame->points[rpos].state;
+       }
+
+       free(frame->points);
+       frame->points = opoints;
+       frame->count = ocount;
+
+       return 0;
+}
+
+int main (int argc, char *argv[])
+{
+       int frameno = 0;
+       char **argvp = &argv[1];
+       char *fname;
+       jack_client_t *client;
+       struct stat st1, st2;
+
+       if (argc > 2 && !strcmp(argvp[0],"-s")) {
+               scale = 1;
+               argc--;
+               argvp++;
+       }
+
+       if (argc < 2) {
+               fprintf(stderr, "usage: %s [-s] filename.ild [rate]\n", argv[0]);
+               return 1;
+       }
+
+       fname = *argvp;
+
+       if (argc > 2) {
+               pointrate = atoi(argvp[1]);
+       }
+
+       if ((client = jack_client_new ("playilda")) == 0) {
+               fprintf (stderr, "jack server not running?\n");
+               return 1;
+       }
+
+       jack_set_process_callback (client, process, 0);
+       jack_set_buffer_size_callback (client, bufsize, 0);
+       jack_set_sample_rate_callback (client, srate, 0);
+       jack_on_shutdown (client, jack_shutdown, 0);
+
+       out_x = jack_port_register (client, "out_x", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_y = jack_port_register (client, "out_y", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_z = jack_port_register (client, "out_z", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_r = jack_port_register (client, "out_r", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_g = jack_port_register (client, "out_g", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_b = jack_port_register (client, "out_b", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+       out_w = jack_port_register (client, "out_w", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0);
+
+       memset(frames, 0, sizeof(frames));
+
+       curframe = curdframe = &frames[frameno];
+       if (loadild(fname, curframe) < 0)
+       {
+               return 1;
+       }
+
+       stat(fname, &st1);
+
+       subpos = 0;
+
+       if (jack_activate (client)) {
+               fprintf (stderr, "cannot activate client");
+               return 1;
+       }
+
+       while (1) {
+               stat(fname, &st2);
+               if(st1.st_mtime != st2.st_mtime) {
+                       frameno = !frameno;
+                       printf("Loading new frame to slot %d\n", frameno);
+                       if(frames[frameno].points)
+                               free(frames[frameno].points);
+                       loadild(fname, &frames[frameno]);
+                       printf("New frame loaded\n");
+                       curframe = &frames[frameno];
+                       memcpy(&st1, &st2, sizeof(st1));
+               }
+               usleep(100000);
+       }
+       jack_client_close (client);
+       exit (0);
+}
+
diff --git a/tools/playvid.c b/tools/playvid.c
new file mode 100644 (file)
index 0000000..75a033f
--- /dev/null
@@ -0,0 +1,486 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+/*
+This is a simple video player based on libavformat. It originated as a quick
+hack based on my metaball/fire tracer from LASE, but evolved to have way too
+many settings.
+
+The one ugly as heck hack here is we open the input file twice, once for audio,
+once for video. This is because laser display is on one level asynchronous to
+audio, and a single video frame may end up taking very long to display, while
+we have to keep streaming audio. In other words, doing it this way saves us from
+doing more buffering and syncing logic.
+
+On the other hand, audio is pretty much synced to video as long as the input
+audio samplerate and video framerate are accurate. This is because in the end
+both audio and video go through one synchronized output device, and we keep a
+running time value for video that is precisely synced to audio (even though it
+can lead or lag audio, we drop or duplicate frames to sync in the long term).
+This is unlike your average video player where video is usually explicitly
+synced to audio. Here, video is implicitly synced to audio and we only have to
+keep track of the variable mapping between laser frames and video frames.
+
+See trace.c for the horrible ad-hoc tracing algorithm. And note that we
+currently use the video luma alone and convert it to 1bpp monochrome. It really
+is a hack.
+*/
+
+#include "libol.h"
+#include "trace.h"
+
+#include <stdio.h>
+#include <errno.h>
+#include <unistd.h>
+#include <string.h>
+#include <stdlib.h>
+#include <jack/jack.h>
+#include <math.h>
+
+#include <libavcodec/avcodec.h>
+#include <libavformat/avformat.h>
+
+#define FRAMES_BUF 8
+
+#define AUDIO_BUF AVCODEC_MAX_AUDIO_FRAME_SIZE
+
+AVFormatContext *pFormatCtx;
+AVFormatContext *pAFormatCtx;
+AVCodecContext  *pCodecCtx;
+AVCodecContext  *pACodecCtx;
+AVCodec         *pCodec;
+AVCodec         *pACodec;
+AVFrame         *pFrame;
+ReSampleContext *resampler;
+
+int buffered_samples;
+float *poabuf;
+float oabuf[AUDIO_BUF];
+short iabuf[AUDIO_BUF];
+
+float volume = 0.8;
+
+int videoStream, audioStream;
+
+int GetNextFrame(AVFormatContext *pFormatCtx, AVCodecContext *pCodecCtx,
+    int videoStream, AVFrame **oFrame)
+{
+       static AVPacket packet;
+       int             bytesDecoded;
+       int             frameFinished = 0;
+
+       while (!frameFinished) {
+               do {
+                       if(av_read_frame(pFormatCtx, &packet)<0) {
+                               fprintf(stderr, "EOF!\n");
+                               return 0;
+                       }
+               } while(packet.stream_index!=videoStream);
+
+               bytesDecoded=avcodec_decode_video2(pCodecCtx, pFrame, &frameFinished, &packet);
+               if (bytesDecoded < 0)
+               {
+                       fprintf(stderr, "Error while decoding frame\n");
+                       return 0;
+               }
+               if (bytesDecoded != packet.size) {
+                       printf("Multiframe packets not supported (%d != %d)\n", bytesDecoded, packet.size);
+                       exit(1);
+               }
+       }
+       *oFrame = pFrame;
+       return 1;
+}
+
+void moreaudio(float *lb, float *rb, int samples)
+{
+       AVPacket packet;
+       int bytes, bytesDecoded;
+       int input_samples;
+       while (samples)
+       {
+               if (!buffered_samples) {
+                       do {
+                               if(av_read_frame(pAFormatCtx, &packet)<0) {
+                                       fprintf(stderr, "Audio EOF!\n");
+                                       memset(lb, 0, samples*sizeof(float));
+                                       memset(rb, 0, samples*sizeof(float));
+                                       return;
+                               }
+                       } while(packet.stream_index!=audioStream);
+
+                       bytes = AUDIO_BUF * sizeof(short);
+
+                       bytesDecoded = avcodec_decode_audio3(pACodecCtx, iabuf, &bytes, &packet);
+                       if(bytesDecoded < 0)
+                       {
+                               fprintf(stderr, "Error while decoding audio frame\n");
+                               return;
+                       }
+
+                       input_samples = bytes / (sizeof(short)*pACodecCtx->channels);
+
+                       buffered_samples = audio_resample(resampler, (void*)oabuf, iabuf, input_samples);
+                       poabuf = oabuf;
+               }
+
+               *lb++ = *poabuf++ * volume;
+               *rb++ = *poabuf++ * volume;
+               buffered_samples--;
+               samples--;
+       }
+}
+
+int     av_vid_init(char *file)
+{
+       int i;
+
+       if (av_open_input_file(&pFormatCtx, file, NULL, 0, NULL)!=0)
+               return -1;
+
+       if (av_find_stream_info(pFormatCtx)<0)
+               return -1;
+
+       dump_format(pFormatCtx, 0, file, 0);
+
+       videoStream=-1;
+       for (i=0; i<pFormatCtx->nb_streams; i++) {
+               if (pFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_VIDEO) {
+                       videoStream=i;
+                       break;
+               }
+       }
+       if (videoStream==-1)
+               return -1;
+
+       pCodecCtx=pFormatCtx->streams[videoStream]->codec;
+
+       pCodec=avcodec_find_decoder(pCodecCtx->codec_id);
+       if (pCodec==NULL)
+               return -1;
+
+       if (avcodec_open(pCodecCtx, pCodec)<0)
+               return -1;
+
+       pFrame=avcodec_alloc_frame();
+
+       return 0;
+}
+
+int av_aud_init(char *file)
+{
+       int i;
+
+       av_register_all();
+
+       if (av_open_input_file(&pAFormatCtx, file, NULL, 0, NULL)!=0)
+               return -1;
+
+       if (av_find_stream_info(pAFormatCtx)<0)
+               return -1;
+
+       audioStream=-1;
+       for (i=0; i<pAFormatCtx->nb_streams; i++)
+               if (pAFormatCtx->streams[i]->codec->codec_type==CODEC_TYPE_AUDIO)
+               {
+                       audioStream=i;
+                       break;
+               }
+       if (audioStream==-1)
+               return -1;
+
+       pACodecCtx=pAFormatCtx->streams[audioStream]->codec;
+
+       pACodec=avcodec_find_decoder(pACodecCtx->codec_id);
+       if (pACodec==NULL)
+               return -1;
+
+       if (avcodec_open(pACodecCtx, pACodec)<0)
+               return -1;
+
+       resampler = av_audio_resample_init(2, pACodecCtx->channels,
+                                                                          48000, pACodecCtx->sample_rate,
+                                                                          SAMPLE_FMT_FLT, pACodecCtx->sample_fmt,
+                                                                          16, 10, 0, 0.8);
+
+       if (!resampler)
+               return -1;
+
+       buffered_samples = 0;
+
+       return 0;
+}
+
+int av_deinit(void)
+{
+       av_free(pFrame);
+
+       // Close the codec
+       avcodec_close(pCodecCtx);
+       avcodec_close(pACodecCtx);
+
+       audio_resample_close(resampler);
+
+       // Close the video file
+       av_close_input_file(pFormatCtx);
+       av_close_input_file(pAFormatCtx);
+
+       return 0;
+}
+
+void usage(const char *argv0)
+{
+       printf("Usage: %s [options] inputfile\n\n", argv0);
+       printf("Options:\n");
+       printf("-t INT    Tracing threshold\n");
+       printf("-b INT    Tracing threshold for dark-background (black) scenes\n");
+       printf("-w INT    Tracing threshold for light-background (white) scenes\n");
+       printf("-B INT    Average edge value at which the scene is considered dark\n");
+       printf("-W INT    Average edge value at which the scene is considered light\n");
+       printf("-O INT    Edge offset\n");
+       printf("-d INT    Decimation factor\n");
+       printf("-m INT    Minimum object size in samples\n");
+       printf("-S INT    Start wait in samples\n");
+       printf("-E INT    End wait in samples\n");
+       printf("-s FLOAT  Inverse off (inter-object) scan speed (in samples per screen width)\n");
+       printf("-p FLOAT  Snap distance in video pixels\n");
+       printf("-a FLOAT  Force aspect ratio\n");
+       printf("-r FLOAT  Force framerate\n");
+       printf("-R FLOAT  Minimum framerate (resample slow frames to be faster)\n");
+       printf("-o FLOAT  Overscan factor (to get rid of borders etc.)\n");
+       printf("-v FLOAT  Audio volume\n");
+}
+
+int main (int argc, char *argv[])
+{
+       OLRenderParams params;
+       AVFrame *frame;
+       int i;
+
+       // Register all formats and codecs
+       av_register_all();
+
+       memset(&params, 0, sizeof params);
+       params.rate = 48000;
+       params.on_speed = 2.0/100.0;
+       params.off_speed = 2.0/15.0;
+       params.start_wait = 8;
+       params.end_wait = 3;
+       params.snap = 1/120.0;
+       params.render_flags = RENDER_GRAYSCALE;
+       params.min_length = 4;
+
+       float snap_pix = 3;
+       float aspect = 0;
+       float framerate = 0;
+       float overscan = 0;
+       int thresh_dark = 60;
+       int thresh_light = 160;
+       int sw_dark = 100;
+       int sw_light = 256;
+       int decimate = 2;
+       int edge_off = 0;
+
+       int optchar;
+
+       while ((optchar = getopt(argc, argv, "ht:b:w:B:W:O:d:m:S:E:s:p:a:r:R:o:v:")) != -1) {
+               switch (optchar) {
+                       case 'h':
+                       case '?':
+                               usage(argv[0]);
+                               return 0;
+                       case 't':
+                               thresh_dark = thresh_light = atoi(optarg);
+                               break;
+                       case 'b':
+                               thresh_dark = atoi(optarg);
+                               break;
+                       case 'w':
+                               thresh_light = atoi(optarg);
+                               break;
+                       case 'B':
+                               sw_dark = atoi(optarg);
+                               break;
+                       case 'W':
+                               sw_light = atoi(optarg);
+                               break;
+                       case 'O':
+                               edge_off = atoi(optarg);
+                               break;
+                       case 'd':
+                               decimate = atoi(optarg);
+                               break;
+                       case 'm':
+                               params.min_length = atoi(optarg);
+                               break;
+                       case 'S':
+                               params.start_wait = atoi(optarg);
+                               break;
+                       case 'E':
+                               params.end_wait = atoi(optarg);
+                               break;
+                       case 's':
+                               params.off_speed = 2.0f/atof(optarg);
+                               break;
+                       case 'p':
+                               snap_pix = atof(optarg);
+                               break;
+                       case 'a':
+                               aspect = atof(optarg);
+                               break;
+                       case 'r':
+                               framerate = atof(optarg);
+                               break;
+                       case 'R':
+                               params.max_framelen = params.rate/atof(optarg);
+                               break;
+                       case 'o':
+                               overscan = atof(optarg);
+                               break;
+                       case 'v':
+                               volume = atof(optarg);
+                               break;
+               }
+       }
+
+       if (optind == argc) {
+               usage(argv[0]);
+               return 1;
+       }
+
+       if (av_vid_init(argv[optind]) != 0) {
+               printf("Video open/init failed\n");
+               return 1;
+       }
+       if (av_aud_init(argv[optind]) != 0) {
+               printf("Audio open/init failed\n");
+               return 1;
+       }
+
+       if(olInit(FRAMES_BUF, 300000) < 0) {
+               printf("OpenLase init failed\n");
+               return 1;
+       }
+
+       if (aspect == 0)
+               aspect = pCodecCtx->width / (float)pCodecCtx->height;
+
+       if (framerate == 0)
+               framerate = (float)pFormatCtx->streams[videoStream]->r_frame_rate.num / (float)pFormatCtx->streams[videoStream]->r_frame_rate.den;
+
+       float iaspect = 1/aspect;
+
+       if (aspect > 1) {
+               olSetScissor(-1, -iaspect, 1, iaspect);
+               olScale(1, iaspect);
+       } else {
+               olSetScissor(-aspect, -1, aspect, 1);
+               olScale(aspect, 1);
+       }
+
+       printf("Aspect is %f %f\n", aspect, iaspect);
+       printf("Overscan is %f\n", overscan);
+
+       olScale(1+overscan, 1+overscan);
+       olTranslate(-1.0f, 1.0f);
+       olScale(2.0f/pCodecCtx->width, -2.0f/pCodecCtx->height);
+
+       int maxd = pCodecCtx->width > pCodecCtx->height ? pCodecCtx->width : pCodecCtx->height;
+       params.snap = (snap_pix*2.0)/(float)maxd;
+
+       float frametime = 1.0f/framerate;
+       printf("Framerate: %f (%fs per frame)\n", framerate, frametime);
+
+       olSetAudioCallback(moreaudio);
+       olSetRenderParams(&params);
+
+       float vidtime = 0;
+       int inf=0;
+       int bg_white = -1;
+       float time = 0;
+       float ftime;
+       int frames = 0;
+
+       OLFrameInfo info;
+
+       void *tmp = malloc(pCodecCtx->width * pCodecCtx->height*2);
+
+       while(GetNextFrame(pFormatCtx, pCodecCtx, videoStream, &frame)) {
+               if (inf == 0)
+                       printf("Frame stride: %d\n", frame->linesize[0]);
+               inf+=1;
+               if (vidtime < time) {
+                       vidtime += frametime;
+                       printf("Frame skip!\n");
+                       continue;
+               }
+               vidtime += frametime;
+
+               do {
+                       int thresh;
+                       int obj;
+                       int bsum = 0;
+                       int c;
+                       for (c=edge_off; c<(pCodecCtx->width-edge_off); c++) {
+                               bsum += frame->data[0][c+edge_off*frame->linesize[0]];
+                               bsum += frame->data[0][c+(pCodecCtx->height-edge_off-1)*frame->linesize[0]];
+                       }
+                       for (c=edge_off; c<(pCodecCtx->height-edge_off); c++) {
+                               bsum += frame->data[0][edge_off+c*frame->linesize[0]];
+                               bsum += frame->data[0][(c+1)*frame->linesize[0]-1-edge_off];
+                       }
+                       bsum /= (2*(pCodecCtx->width+pCodecCtx->height));
+                       if (bg_white == -1)
+                               bg_white = bsum > 128;
+                       if (bg_white && bsum < sw_dark)
+                               bg_white = 0;
+                       if (!bg_white && bsum > sw_light)
+                               bg_white = 1;
+
+                       if (bg_white)
+                               thresh = thresh_light;
+                       else
+                               thresh = thresh_dark;
+
+                       obj = trace(frame->data[0], tmp, thresh,
+                                               pCodecCtx->width, pCodecCtx->height, frame->linesize[0], decimate);
+
+                       ftime = olRenderFrame(100);
+                       olGetFrameInfo(&info);
+                       frames++;
+                       time += ftime;
+                       printf("Frame time: %.04f, Cur FPS:%6.02f, Avg FPS:%6.02f, Drift: %7.4f, "
+                                  "In %4d, Out %4d Thr %3d Bg %3d Pts %4d",
+                                  ftime, 1/ftime, frames/time, time-vidtime,
+                                  inf, frames, thresh, bsum, info.points);
+                       if (info.resampled_points)
+                               printf(" Rp %4d Bp %4d", info.resampled_points, info.resampled_blacks);
+                       if (info.padding_points)
+                               printf(" Pad %4d", info.padding_points);
+                       printf("\n");
+               } while ((time+frametime) < vidtime);
+       }
+
+       for(i=0;i<FRAMES_BUF;i++)
+               olRenderFrame(100);
+
+       olShutdown();
+       av_deinit();
+       exit (0);
+}
diff --git a/tools/svg2ild.py b/tools/svg2ild.py
new file mode 100644 (file)
index 0000000..75040b2
--- /dev/null
@@ -0,0 +1,666 @@
+#!/usr/bin/python
+# -*- coding: utf-8 -*-
+
+import os, sys, math
+import xml.sax, xml.sax.handler
+import re
+
+def pc(c):
+       x,y = c
+       if isinstance(x,int) and isinstance(y,int):
+               return "(%d,%d)"%(x,y)
+       else:
+               return "(%.4f, %.4f)"%(x,y)
+
+class RenderParameters(object):
+       def __init__(self):
+               # output sample rate
+               self.rate = 48000
+               # time to display (seconds)
+               # use 0.0 for a single frame
+               self.time = 0.0
+               # step size of galvo movement, in fractions of output size per sample (2=full width)
+               self.on_speed = 2/90.0
+               # same, but for "off" laser state
+               self.off_speed = 2/20.0
+               # bound for accurate bezier rendering (lower = better)
+               self.flatness = 0.000002
+               # output render size (max 32767)
+               self.width = 32000
+               self.height = 32000
+               # angle below which a node is considered smooth
+               self.curve_angle = 30.0
+               # dwell time at the start of a path (samples)
+               self.start_dwell = 3
+               # dwell time on internal curved nodes (samples)
+               self.curve_dwell = 0
+               # dwell time on internal corner nodes (samples)
+               self.corner_dwell = 4
+               # dwell time at the end of a path (samples)
+               self.end_dwell = 3
+               # dwell time before switching the beam on, and after switching it off
+               self.switch_dwell = 6
+               # how many pointer of overdraw for closed shapes
+               self.closed_overdraw = 0
+               self.closed_start_dwell = 3
+               self.closed_end_dwell = 3
+               # extra dwell for the first active pointer
+               self.extra_first_dwell = 0
+               # invert image (show inter-object trips)
+               self.invert = False
+               self.force = False
+
+               self.reset_stats()
+       def reset_stats(self):
+               self.rate_divs = 0
+               self.flatness_divs = 0
+               self.objects = 0
+               self.subpaths = 0
+               self.points = 0
+               self.points_line = 0
+               self.points_trip = 0
+               self.points_bezier = 0
+               self.points_dwell_start = 0
+               self.points_dwell_curve = 0
+               self.points_dwell_corner = 0
+               self.points_dwell_end = 0
+               self.points_dwell_switch = 0
+               self.points_on = 0
+
+       def load(self, f):
+               for line in open(f):
+                       line = line.replace("\n","").strip()
+                       if line=="":
+                               continue
+                       var, val = line.split("=",1)
+                       var = var.strip()
+                       val = val.strip()
+                       self.__setattr__(var, eval(val))
+
+
+class PathLine(object):
+       def __init__(self, start, end, on=True):
+               self.start = start
+               self.end = end
+               self.on = on
+       def copy(self):
+               return PathLine(self.start, self.end)
+       def transform(self, func):
+               self.start = func(self.start)
+               self.end = func(self.end)
+       def render(self, params):
+               dx = self.end[0] - self.start[0]
+               dy = self.end[1] - self.start[1]
+               length = math.sqrt(dx**2 + dy**2)
+               if self.on:
+                       steps = int(length / params.on_speed) + 1
+               else:
+                       steps = int(length / params.off_speed) + 1
+               dx /= steps
+               dy /= steps
+               out = []
+               for i in range(1, steps+1):
+                       x = self.start[0] + i * dx
+                       y = self.start[1] + i * dy
+                       out.append(LaserSample((int(x*params.width),int(y*params.height)), self.on))
+                       if self.on:
+                               params.points_line += 1
+                       else:
+                               params.points_trip += 1
+               return out
+       def reverse(self):
+               return PathLine(self.end, self.start, self.on)
+       def scp(self):
+               return self.end
+       def ecp(self):
+               return self.start
+       def showinfo(self, tr=''):
+               print tr+'Line( %s %s )'%(pc(self.start),pc(self.end))
+
+class PathBezier3(object):
+       def __init__(self, start, cp, end):
+               self.start = start
+               self.end = end
+               self.cp = cp
+       def copy(self):
+               return PathBezier3(self.start, self.cp, self.end)
+       def transform(self, func):
+               self.start = func(self.start)
+               self.cp = func(self.cp)
+               self.end = func(self.end)
+       def render(self, params):
+               # just use PathBezier4, meh
+               sx,sy = self.start
+               cx,cy = self.cp
+               ex,ey = self.end
+
+               c1x = (1.0/3) * cx + (2.0/3) * sx
+               c1y = (1.0/3) * cy + (2.0/3) * sy
+               c2x = (1.0/3) * cx + (2.0/3) * ex
+               c2y = (1.0/3) * cy + (2.0/3) * ey
+               return PathBezier4(self.start, (c1x,c1y), (c2x,c2y), self.end).render()
+       def reverse(self):
+               return PathBezier3(self.end, self.cp, self.start)
+       def scp(self):
+               return self.cp
+       def ecp(self):
+               return self.cp
+       def showinfo(self, tr=''):
+               print tr+'Bezier( %s %s %s )'%(pc(self.start),pc(self.cp),pc(self.end))
+
+class PathBezier4(object):
+       def __init__(self, start, cp1, cp2, end):
+               self.start = start
+               self.end = end
+               self.cp1 = cp1
+               self.cp2 = cp2
+       def copy(self):
+               return PathBezier4(self.start, self.cp1, self.cp2, self.end)
+       def transform(self, func):
+               self.start = func(self.start)
+               self.cp1 = func(self.cp1)
+               self.cp2 = func(self.cp2)
+               self.end = func(self.end)
+
+       def render(self, params):
+               x0,y0 = self.start
+               x1,y1 = self.cp1
+               x2,y2 = self.cp2
+               x3,y3 = self.end
+               dx = x3-x0
+               dy = y3-y0
+               length = math.sqrt((dx**2) + (dy**2))
+               subdivide = False
+               if length > params.on_speed:
+                       subdivide = True
+                       params.rate_divs += 1
+               else:
+                       ux = (3.0*x1 - 2.0*x0 - x3)**2
+                       uy = (3.0*y1 - 2.0*y0 - y3)**2
+                       vx = (3.0*x2 - 2.0*x3 - x0)**2
+                       vy = (3.0*y2 - 2.0*y3 - y0)**2
+                       if ux < vx:
+                               ux = vx
+                       if uy < vy:
+                               uy = vy
+                       if (ux+uy) > params.flatness:
+                               subdivide = True
+                               params.flatness_divs += 1
+               if subdivide:
+                       # de Casteljau at t=0.5
+                       mcx = (x1 + x2) * 0.5
+                       mcy = (y1 + y2) * 0.5
+                       ax1 = (x0 + x1) * 0.5
+                       ay1 = (y0 + y1) * 0.5
+                       ax2 = (ax1 + mcx) * 0.5
+                       ay2 = (ay1 + mcy) * 0.5
+                       bx2 = (x2 + x3) * 0.5
+                       by2 = (y2 + y3) * 0.5
+                       bx1 = (bx2 + mcx) * 0.5
+                       by1 = (by2 + mcy) * 0.5
+                       xm = (ax2 + bx1) * 0.5
+                       ym = (ay2 + by1) * 0.5
+                       curve1 = PathBezier4((x0,y0),(ax1,ay1),(ax2,ay2),(xm,ym))
+                       curve2 = PathBezier4((xm,ym),(bx1,by1),(bx2,by2),(x3,y3))
+                       return curve1.render(params) + curve2.render(params)
+               else:
+                       params.points_bezier += 1
+                       return [LaserSample((int(x3*params.width),int(y3*params.height)))]
+       def reverse(self):
+               return PathBezier4(self.end, self.cp2, self.cp1, self.start)
+       def scp(self):
+               if self.cp1 != self.start:
+                       return self.cp1
+               elif self.cp2 != self.start:
+                       return self.cp2
+               else:
+                       return self.end
+       def ecp(self):
+               if self.cp2 != self.end:
+                       return self.cp2
+               elif self.cp1 != self.end:
+                       return self.cp1
+               else:
+                       return self.start
+       def showinfo(self, tr=''):
+               print tr+'Bezier( %s %s %s %s )'%(pc(self.start),pc(self.cp1),pc(self.cp2),pc(self.end))
+
+class LaserPath(object):
+       def __init__(self):
+               self.segments = []
+       def add(self, seg):
+               self.segments.append(seg)
+       def transform(self, func):
+               for i in self.segments:
+                       i.transform(func)
+       def render(self, params):
+               is_closed = self.segments[0].start == self.segments[-1].end
+               sx,sy = self.segments[0].start
+               out = []
+               if is_closed:
+                       out += [LaserSample((int(sx*params.width),int(sy*params.height)))] * params.closed_start_dwell
+               else:
+                       out += [LaserSample((int(sx*params.width),int(sy*params.height)))] * params.start_dwell
+               params.points_dwell_start += params.start_dwell
+               for i,s in enumerate(self.segments):
+                       params.subpaths += 1
+                       out += s.render(params)
+                       ex,ey = s.end
+                       end_render = [LaserSample((int(ex*params.width),int(ey*params.height)))]
+                       if i != len(self.segments)-1:
+                               ecx,ecy = s.ecp()
+                               sx,sy = self.segments[i+1].start
+                               scx,scy = self.segments[i+1].scp()
+
+                               dex,dey = ecx-ex,ecy-ey
+                               dsx,dsy = sx-scx,sy-scy
+
+                               dot = dex*dsx + dey*dsy
+                               lens = math.sqrt(dex**2 + dey**2) * math.sqrt(dsx**2 + dsy**2)
+                               if lens == 0:
+                                       # bail
+                                       out += end_render * params.corner_dwell
+                                       params.points_dwell_corner += params.corner_dwell
+                               else:
+                                       dot /= lens
+                                       curve_angle = math.cos(params.curve_angle*(math.pi/180.0))
+                                       if dot > curve_angle:
+                                               out += end_render * params.curve_dwell
+                                               params.points_dwell_curve += params.curve_dwell
+                                       else:
+                                               out += end_render * params.corner_dwell
+                                               params.points_dwell_corner += params.corner_dwell
+                       elif is_closed:
+                               out += end_render * params.closed_end_dwell
+                               overdraw = out[:params.closed_overdraw]
+                               out += overdraw
+                       else:
+                               out += end_render * params.end_dwell
+                               params.points_dwell_end += params.end_dwell
+               return out
+       def startpos(self):
+               return self.segments[0].start
+       def endpos(self):
+               return self.segments[-1].end
+       def reverse(self):
+               lp = LaserPath()
+               lp.segments = [x.reverse() for x in self.segments[::-1]]
+               return lp
+       def showinfo(self, tr=''):
+               print tr+'LaserPath:'
+               for i in self.segments:
+                       i.showinfo(tr+' ')
+
+class LaserFrame(object):
+       def __init__(self):
+               self.objects = []
+       def add(self, obj):
+               self.objects.append(obj)
+       def transform(self, func):
+               for i in self.objects:
+                       i.transform(func)
+       def render(self, params):
+               if not self.objects:
+                       return []
+               out = []
+               cpos = self.objects[-1].endpos()
+               for i in self.objects:
+                       trip = [LaserSample((int(cpos[0]*params.width),int(cpos[1]*params.height)))] * params.switch_dwell
+                       trip += PathLine(cpos,i.startpos(),False).render(params)
+                       trip += [LaserSample((int(i.startpos()[0]*params.width),int(i.startpos()[1]*params.height)))] * params.switch_dwell
+                       params.points_dwell_switch += params.switch_dwell * 2
+                       for s in trip:
+                               s.on = False
+                       out += trip
+                       out += i.render(params)
+                       cpos = i.endpos()
+                       params.objects += 1
+               params.points = len(out)
+               params.points_on = sum([int(s.on) for s in out])
+               return out
+       def sort(self):
+               oobj = []
+               cx,cy = 0,0
+               while self.objects:
+                       best = 99999
+                       besti = 0
+                       bestr = False
+                       for i,o in enumerate(self.objects):
+                               x,y = o.startpos()
+                               d = (x-cx)**2 + (y-cy)**2
+                               if d < best:
+                                       best = d
+                                       besti = i
+                               x,y = o.endpos()
+                               d = (x-cx)**2 + (y-cy)**2
+                               if d < best:
+                                       best = d
+                                       besti = i
+                                       bestr = True
+                       obj = self.objects.pop(besti)
+                       if bestr:
+                               obj = obj.reverse()
+                       oobj.append(obj)
+                       cx,cy = obj.endpos()
+               self.objects = oobj
+       def showinfo(self, tr=''):
+               print tr+'LaserFrame:'
+               for i in self.objects:
+                       i.showinfo(tr+' ')
+
+class LaserSample(object):
+       def __init__(self, coord, on=True):
+               self.coord = coord
+               self.on = on
+       def __str__(self):
+               return "[%d] %d,%d"%(int(self.on),self.coord[0],self.coord[1])
+       def __repr__(self):
+               return "LaserSample((%d,%d),%r)"%(self.coord[0],self.coord[1],self.on)
+
+class SVGPath(object):
+       def __init__(self, data=None):
+               self.subpaths = []
+               if data:
+                       self.parse(data)
+
+       def poptok(self):
+               if not self.tokens:
+                       raise ValueError("Expected token but got end of path data")
+               return self.tokens.pop(0)
+       def peektok(self):
+               if not self.tokens:
+                       raise ValueError("Expected token but got end of path data")
+               return self.tokens[0]
+       def popnum(self):
+               tok = self.tokens.pop(0)
+               try:
+                       return float(tok)
+               except ValueError:
+                       raise ValueError("Invalid SVG path numerical token: %s"%tok)
+       def isnum(self):
+               if not self.tokens:
+                       return False # no more tokens is considered a non-number
+               try:
+                       float(self.peektok())
+               except ValueError:
+                       return False
+               return True
+       def popcoord(self,rel=False, cur=None):
+               x = self.popnum()
+               if self.peektok() == ',':
+                       self.poptok()
+               y = self.popnum()
+               if rel:
+                       x += cur[0]
+                       y += cur[1]
+               return x,y
+       def reflect(self, center, point):
+               cx,cy = center
+               px,py = point
+               return (2*cx-px, 2*cy-py)
+
+       def parse(self, data):
+               ds = re.split(r"[ \r\n\t]*([-+]?\d+\.\d+[eE][+-]?\d+|[-+]?\d+\.\d+|[-+]?\.\d+[eE][+-]?\d+|[-+]?\.\d+|[-+]?\d+\.?[eE][+-]?\d+|[-+]?\d+\.?|[MmZzLlHhVvCcSsQqTtAa])[, \r\n\t]*", data)
+               tokens = ds[1::2]
+               if any(ds[::2]) or not all(ds[1::2]):
+                       raise ValueError("Invalid SVG path expression: %r"%data)
+               self.tokens = tokens
+
+               cur = (0,0)
+               curcpc = (0,0)
+               curcpq = (0,0)
+               sp_start = (0,0)
+               in_path = False
+               cmd = None
+               subpath = LaserPath()
+               while tokens:
+                       if self.isnum() and cmd in "MmLlHhVvCcSsQqTtAa":
+                               pass
+                       elif self.peektok() in "MmZzLlHhVvCcSsQqTtAa":
+                               cmd = tokens.pop(0)
+                       else:
+                               raise ValueError("Invalid SVG path token %s"%tok)
+
+                       rel = cmd.upper() != cmd
+                       ucmd = cmd.upper()
+
+                       if not in_path and ucmd != 'M' :
+                               raise ValueErorr("SVG path segment must begin with 'm' command, not '%s'"%cmd)
+
+                       if ucmd == 'M':
+                               sp_start = self.popcoord(rel, cur)
+                               if subpath.segments:
+                                       self.subpaths.append(subpath)
+                               subpath = LaserPath()
+                               cmd = 'Ll'[rel]
+                               cur = curcpc = curcpq = sp_start
+                               in_path = True
+                       elif ucmd == 'Z':
+                               if sp_start != cur:
+                                       subpath.add(PathLine(cur, sp_start))
+                               cur = curcpc = curcpq = sp_start
+                               in_path = False
+                       elif ucmd == 'L':
+                               end = self.popcoord(rel, cur)
+                               subpath.add(PathLine(cur, end))
+                               cur = curcpc = curcpq = end
+                       elif ucmd == 'H':
+                               ex = self.popnum()
+                               if rel:
+                                       ex += cur[0]
+                               end = ex,cur[1]
+                               subpath.add(PathLine(cur, end))
+                               cur = curcpc = curcpq = end
+                       elif ucmd == 'V':
+                               ey = self.popnum()
+                               if rel:
+                                       ey += cur[1]
+                               end = cur[0],ey
+                               subpath.add(PathLine(cur, end))
+                               cur = curcpc = curcpq = end
+                       elif ucmd in 'CS':
+                               if ucmd == 'S':
+                                       c1 = self.reflect(cur,curcpc)
+                               else:
+                                       c1 = self.popcoord(rel, cur)
+                               c2 = curcpc = self.popcoord(rel, cur)
+                               end = self.popcoord(rel, cur)
+                               subpath.add(PathBezier4(cur, c1, c2, end))
+                               cur = curcpq = end
+                       elif ucmd in 'QT':
+                               if ucmd == 'T':
+                                       cp = curcpq = self.reflect(cur,curcpq)
+                               else:
+                                       cp = curcpq = self.popcoord(rel, cur)
+                               end = self.popcoord(rel, cur)
+                               subpath.add(PathBezier3(cur, cp, end))
+                               cur = curcpc = end
+                       elif ucmd in 'Aa':
+                               raise ValueError("Arcs not implemented, biatch")
+
+               if subpath.segments:
+                       self.subpaths.append(subpath)
+
+class SVGReader(xml.sax.handler.ContentHandler):
+       def doctype(self, name, pubid, system):
+               print name,pubid,system
+       def startDocument(self):
+               self.frame = LaserFrame()
+               self.matrix_stack = [(1,0,0,1,0,0)]
+       def endDocument(self):
+               self.frame.transform(self.tc)
+       def startElement(self, name, attrs):
+               if name == "svg":
+                       self.width = float(attrs['width'].replace("px",""))
+                       self.height = float(attrs['height'].replace("px",""))
+               elif name == "path":
+                       if 'transform' in attrs.keys():
+                               self.transform(attrs['transform'])
+                       self.addPath(attrs['d'])
+                       if 'transform' in attrs.keys():
+                               self.popmatrix()
+               elif name == 'g':
+                       if 'transform' in attrs.keys():
+                               self.transform(attrs['transform'])
+                       else:
+                               self.pushmatrix((1,0,0,1,0,0))
+       def endElement(self, name):
+               if name == 'g':
+                       self.popmatrix()
+       def mmul(self, m1, m2):
+               a1,b1,c1,d1,e1,f1 = m1
+               a2,b2,c2,d2,e2,f2 = m2
+               a3 = a1 * a2 + c1 * b2
+               b3 = b1 * a2 + d1 * b2
+               c3 = a1 * c2 + c1 * d2
+               d3 = b1 * c2 + d1 * d2
+               e3 = a1 * e2 + c1 * f2 + e1
+               f3 = b1 * e2 + d1 * f2 + f1
+               return (a3,b3,c3,d3,e3,f3)
+       def pushmatrix(self, m):
+               new_mat = self.mmul(self.matrix_stack[-1], m)
+               self.matrix_stack.append(new_mat)
+       def popmatrix(self):
+               self.matrix_stack.pop()
+       def tc(self,coord):
+               vw = vh = max(self.width, self.height) / 2.0
+               x,y = coord
+               x -= self.width / 2.0
+               y -= self.height / 2.0
+               x = x / vw
+               y = y / vh
+               return (x,y)
+       def ts(self,coord):
+               a,b,c,d,e,f = self.matrix_stack[-1]
+               x,y = coord
+               nx = a*x + c*y + e
+               ny = b*x + d*y + f
+               return (nx,ny)
+       def transform(self, data):
+               ds = re.split(r"[ \r\n\t]*([a-z]+\([^)]+\)|,)[ \r\n\t]*", data)
+               tokens = ds[1::2]
+               if any(ds[::2]) or not all(ds[1::2]):
+                       raise ValueError("Invalid SVG transform expression: %r"%data)
+               if not all([x == ',' for x in tokens[1::2]]):
+                       raise ValueError("Invalid SVG transform expression: %r"%data)
+               transforms = tokens[::2]
+
+               mat = (1,0,0,1,0,0)
+               for t in transforms:
+                       name,rest = t.split("(")
+                       if rest[-1] != ")":
+                               raise ValueError("Invalid SVG transform expression: %r"%data)
+                       args = map(float,rest[:-1].split(","))
+                       if name == 'matrix':
+                               mat = self.mmul(mat, args)
+                       elif name == 'translate':
+                               tx,ty = args
+                               mat = self.mmul(mat, (1,0,0,1,tx,ty))
+                       elif name == 'scale':
+                               sx,sy = args
+                               mat = self.mmul(mat, (sx,0,0,sy,0,0))
+                       elif name == 'rotate':
+                               a = args[0] / 180.0 * math.pi
+                               if len(args) == 3:
+                                       cx,cy = args[1:]
+                                       mat = self.mmul(mat, (1,0,0,1,cx,cy))
+                               cos = math.cos(a)
+                               sin = math.sin(a)
+                               mat = self.mmul(mat, (cos,sin,-sin,cos,0,0))
+                               if len(args) == 3:
+                                       mat = self.mmul(mat, (1,0,0,1,-cx,-cy))
+                       elif name == 'skewX':
+                               a = args[0] / 180.0 * math.pi
+                               mat = self.mmul(mat, (1,0,math.tan(a),1,0,0))
+                       elif name == 'skewY':
+                               a = args[0] / 180.0 * math.pi
+                               mat = self.mmul(mat, (1,math.tan(a),0,1,0,0))
+               self.pushmatrix(mat)
+       def addPath(self, data):
+               p = SVGPath(data)
+               for path in p.subpaths:
+                       path.transform(self.ts)
+                       self.frame.add(path)
+
+optimize = True
+params = RenderParameters()
+
+if sys.argv[1] == "-noopt":
+       optimize = False
+       sys.argv = [sys.argv[0]] + sys.argv[2:]
+
+if sys.argv[1] == "-cfg":
+       params.load(sys.argv[2])
+       sys.argv = [sys.argv[0]] + sys.argv[3:]
+
+handler = SVGReader()
+print "Parse"
+parser = xml.sax.make_parser()
+parser.setContentHandler(handler)
+parser.setFeature(xml.sax.handler.feature_external_ges, False)
+parser.parse(sys.argv[1])
+print "Parsed"
+
+frame = handler.frame
+#frame.showinfo()
+if optimize:
+       frame.sort()
+
+print "Render"
+rframe = frame.render(params)
+print "Done"
+
+import struct
+
+for i,sample in enumerate(rframe):
+       if sample.on:
+               rframe = rframe[:i] + [sample]*params.extra_first_dwell + rframe[i+1:]
+               break
+
+fout = open(sys.argv[2], "wb")
+
+dout = struct.pack(">4s3xB8s8sHHHBx", "ILDA", 1, "svg2ilda", "", len(rframe), 1, 1, 0)
+for i,sample in enumerate(rframe):
+       x,y = sample.coord
+       mode = 0
+       if i == len(rframe):
+           mode |= 0x80
+       if params.invert:
+               sample.on = not sample.on
+       if params.force:
+               sample.on = True
+       if not sample.on:
+           mode |= 0x40
+       if abs(x) > params.width :
+               raise ValueError("X out of bounds")
+       if abs(y) > params.height :
+               raise ValueError("Y out of bounds")
+       dout += struct.pack(">hhBB",x,-y,mode,0x00)
+
+frame_time = len(rframe) / float(params.rate)
+
+if (frame_time*2) < params.time:
+       count = int(params.time / frame_time)
+       dout = dout * count
+
+fout.write(dout)
+
+print "Statistics:"
+print " Objects: %d"%params.objects
+print " Subpaths: %d"%params.subpaths
+print " Bezier subdivisions:"
+print "  Due to rate: %d"%params.rate_divs
+print "  Due to flatness: %d"%params.flatness_divs
+print " Points: %d"%params.points
+print "  Trip: %d"%params.points_trip
+print "  Line: %d"%params.points_line
+print "  Bezier: %d"%params.points_bezier
+print "  Start dwell: %d"%params.points_dwell_start
+print "  Curve dwell: %d"%params.points_dwell_curve
+print "  Corner dwell: %d"%params.points_dwell_corner
+print "  End dwell: %d"%params.points_dwell_end
+print "  Switch dwell: %d"%params.points_dwell_switch
+print " Total on: %d"%params.points_on
+print " Total off: %d"%(params.points - params.points_on)
+print " Efficiency: %.3f"%(params.points_on/float(params.points))
+print " Framerate: %.3f"%(params.rate/float(params.points))
diff --git a/tools/trace.c b/tools/trace.c
new file mode 100644 (file)
index 0000000..a27de25
--- /dev/null
@@ -0,0 +1,282 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+/*
+This is a horrible ad-hoc tracing algorithm. Originally it was a bit simpler,
+and it was merely used to trace metaballs and fire for the LASE demo. However,
+I plugged it into a video stream and it worked too well, so I ended up adding
+lots of incremental hacks and improvements.
+
+It's still highly primitive, though. It works by turning the input image into
+a 1bpp black and white image (with a threshold), performing edge detection,
+then walking those edges and as we clear them. There are quite a few hacks along
+the way to try to improve particular situations, such as clearing neighboring
+pixels to deal with double-wide edges produced under some circumstances, and
+stuff like decimation and overdraw.
+
+If you're wondering about the switch() at the end: in order to find stuff to
+trace, it walks the image in a spiral from the outside in, to try to put the
+object start/end points near the edges of the screen (less visible). Yes, this
+is highly suboptimal for anything but closed objects since quite often it
+catches them in the middle and breaks them into two objects. I'm kind of hoping
+libol catches most of those and merges them, though. Maybe.
+*/
+
+#include "libol.h"
+#include <math.h>
+#include <string.h>
+#include <stdio.h>
+
+#include "trace.h"
+
+#define ABS(a) ((a)<0?(-(a)):(a))
+
+#define OVERDRAW 8
+
+//#define DEBUG
+
+#ifdef DEBUG
+uint8_t dbg[640*480*16][3];
+#endif
+
+int trace(uint8_t *field, uint8_t *tmp, uint8_t thresh, int w, int h, int s, int decimate)
+{
+       int x, y, cx, cy, px, py, lx, ly, i;
+       int iters = 0;
+       int objects = 0;
+
+       int sx[OVERDRAW], sy[OVERDRAW];
+
+       memset(tmp, 0, s*h);
+#ifdef DEBUG
+       memset(dbg, 0, 3*s*h);
+#endif
+
+       for (y=2; y<h-2; y++) {
+               for (x=2; x<w-2;x++) {
+                       int idx = y*s+x;
+                       if (field[idx] > thresh && (!(field[idx-s] > thresh)
+                                                || !(field[idx-1] > thresh))) {
+                               tmp[idx] = 255;
+#ifdef DEBUG
+                               dbg[idx][0] = 64;
+                               dbg[idx][1] = 64;
+                               dbg[idx][2] = 64;
+#endif
+                       }
+                       if (field[idx] <= thresh && (!(field[idx-s] <= thresh)
+                                                || !(field[idx-1] <= thresh))) {
+                               tmp[idx] = 255;
+#ifdef DEBUG
+                               dbg[idx][0] = 64;
+                               dbg[idx][1] = 64;
+                               dbg[idx][2] = 64;
+#endif
+                       }
+               }
+       }
+
+       int total = h*w;
+       int dir = 0;
+       int minx = 0, miny = 0;
+       int maxx = w-1, maxy = h-1;
+       int ldir = 0;
+       int lsdir = 0;
+
+       int div = 0;
+       int dpcnt = 0;
+
+       int start = 0;
+
+       int pc = 0;
+
+       px = 0;
+       py = 0;
+       while (total--)
+       {
+               if (tmp[py*s+px]) {
+                       x = lx = cx = px;
+                       y = ly = cy = py;
+                       iters = 0;
+                       olBegin(OL_POINTS);
+                       start = 1;
+                       pc = pc + 37;
+                       pc %= 128;
+                       int lidx = y*s+x;
+                       while (1)
+                       {
+                               int idx = y*s+x;
+#ifdef DEBUG
+                               dbg[idx][1] = 128+pc;
+#endif
+                               int ddir = (ldir - lsdir + 8) % 8;
+                               if (ddir > 4)
+                                       ddir = 8 - ddir;
+                               if(div==0) {
+                                       if (iters < OVERDRAW) {
+                                               sx[iters] = x;
+                                               sy[iters] = y;
+                                       }
+                                       olVertex(x, y, C_WHITE);
+                                       /*if (ddir>3) {
+                                               olVertex(x, y, C_WHITE);
+                                               dpcnt++;
+                                       }*/
+                                       iters++;
+                               }
+                               div = (div+1)%decimate;
+                               lsdir = ldir;
+                               tmp[idx] = 0;
+                               //printf("Dlt: %d\n", idx-lidx);
+                               //dbg[idx][2] = 0;
+                               if (tmp[2*idx-lidx]) {
+                                       x = 2*x-lx;
+                                       y = 2*y-ly;
+#ifdef DEBUG
+                                       dbg[2*idx-lidx][2] += 64;
+#endif
+                               } else if ((ldir == 4 || ldir == 6) && tmp[idx-s-1]) {
+                                       y--; x--;
+                                       ldir = 5;
+                               } else if ((ldir == 2 || ldir == 4) && tmp[idx-s+1]) {
+                                       y--; x++;
+                                       ldir = 3;
+                               } else if ((ldir == 6 || ldir == 0) && tmp[idx+s-1]) {
+                                       y++; x--;
+                                       ldir = 7;
+                               } else if ((ldir == 0 || ldir == 2) && tmp[idx+s+1]) {
+                                       y++; x++;
+                                       ldir = 1;
+                               } else if ((ldir == 5 || ldir == 7) && tmp[idx-1]) {
+                                       x--;
+                                       ldir = 6;
+                               } else if ((ldir == 1 || ldir == 3) && tmp[idx+1]) {
+                                       x++;
+                                       ldir = 2;
+                               } else if ((ldir == 3 || ldir == 5) && tmp[idx-s]) {
+                                       y--;
+                                       ldir = 4;
+                               } else if ((ldir == 1 || ldir == 7) && tmp[idx+s]) {
+                                       y++;
+                                       ldir = 0;
+                               } else if (tmp[idx-s-1]) {
+                                       y--; x--;
+                                       ldir = 5;
+                               } else if (tmp[idx-s+1]) {
+                                       y--; x++;
+                                       ldir = 3;
+                               } else if (tmp[idx+s-1]) {
+                                       y++; x--;
+                                       ldir = 7;
+                               } else if (tmp[idx+s+1]) {
+                                       y++; x++;
+                                       ldir = 1;
+                               } else if (tmp[idx-1]) {
+                                       x--;
+                                       ldir = 6;
+                               } else if (tmp[idx+1]) {
+                                       x++;
+                                       ldir = 2;
+                               } else if (tmp[idx-s]) {
+                                       y--;
+                                       ldir = 4;
+                               } else if (tmp[idx+s]) {
+                                       y++;
+                                       ldir = 0;
+                               } else {
+                                       break;
+                               }
+                               if (!start) {
+                                       if (ldir & 1) {
+                                               tmp[idx-1] = 0;
+                                               tmp[idx+1] = 0;
+                                               tmp[idx-s] = 0;
+                                               tmp[idx+s] = 0;
+                                       } else if (ldir == 0) {
+                                               tmp[idx-1-s] = 0;
+                                               tmp[idx+1-s] = 0;
+                                       } else if (ldir == 2) {
+                                               tmp[idx-1-s] = 0;
+                                               tmp[idx-1+s] = 0;
+                                       } else if (ldir == 4) {
+                                               tmp[idx-1+s] = 0;
+                                               tmp[idx+1+s] = 0;
+                                       } else if (ldir == 6) {
+                                               tmp[idx+1-s] = 0;
+                                               tmp[idx+1+s] = 0;
+                                       }
+                               }
+                               start = 0;
+                               tmp[y*s+x] = 255;
+                               lidx = idx;
+                               lx = x;
+                               ly = y;
+                       }
+#ifdef DEBUG
+                       dbg[py*s+px][0] = 255;
+                       dbg[py*s+px][1] = 255;
+                       dbg[py*s+px][2] = 255;
+#endif
+                       if (iters) {
+                               objects++;
+                               if (ABS(cx-x) <= 1 && ABS(cy-y) <= 1 && iters > 10) {
+                                       if (iters > OVERDRAW)
+                                               iters = OVERDRAW;
+                                       for (i=0; i<iters; i++)
+                                               olVertex(sx[i], sy[i], C_GREY((int)(255.0 * (OVERDRAW - 1 - i) / (float)OVERDRAW)));
+                               }
+                       }
+                       olEnd();
+               }
+               switch(dir) {
+                       case 0:
+                               px++;
+                               if (px > maxx) {
+                                       px--; py++; maxx--; dir++;
+                               }
+                               break;
+                       case 1:
+                               py++;
+                               if (py > maxy) {
+                                       py--; px--; maxy--; dir++;
+                               }
+                               break;
+                       case 2:
+                               px--;
+                               if (px < minx) {
+                                       px++; py--; minx++; dir++;
+                               }
+                               break;
+                       case 3:
+                               py--;
+                               if (py < miny) {
+                                       py++; px++; miny++; dir=0;
+                               }
+                               break;
+               }
+       }
+#ifdef DEBUG
+       FILE *f = fopen("dump.bin","wb");
+       fwrite(dbg, h, s*3, f);
+       fclose(f);
+#endif
+       //printf("Dup'd: %d\n", dpcnt);
+       return objects;
+}
+
diff --git a/tools/trace.h b/tools/trace.h
new file mode 100644 (file)
index 0000000..af86815
--- /dev/null
@@ -0,0 +1,25 @@
+/*
+        OpenLase - a realtime laser graphics toolkit
+
+Copyright (C) 2009-2010 Hector Martin "marcan" <hector@marcansoft.com>
+
+This program is free software; you can redistribute it and/or modify
+it under the terms of the GNU General Public License as published by
+the Free Software Foundation, either version 2 or version 3.
+
+This program is distributed in the hope that it will be useful,
+but WITHOUT ANY WARRANTY; without even the implied warranty of
+MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
+GNU General Public License for more details.
+
+You should have received a copy of the GNU General Public License
+along with this program; if not, write to the Free Software
+Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA  02110-1301  USA
+*/
+
+#ifndef TRACE_H
+#define TRACE_H
+
+int trace(uint8_t *field, uint8_t *tmp, uint8_t thresh, int width, int height, int stride, int decimate);
+
+#endif
\ No newline at end of file