--- /dev/null
+# 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)
--- /dev/null
+# - 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)
--- /dev/null
+# 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)
+
--- /dev/null
+# 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)
--- /dev/null
+/*
+ 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);
+}
+
--- /dev/null
+/*
+ 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(¶ms, 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(¶ms);
+
+ 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);
+}
+
--- /dev/null
+/*
+ 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(¶ms, 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(¶ms);
+
+ 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);
+}
+
--- /dev/null
+/*
+ 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
--- /dev/null
+/*
+ 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
--- /dev/null
+/*
+ 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
--- /dev/null
+# 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)
--- /dev/null
+/*
+ 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);
+}
--- /dev/null
+<?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>
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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;
+}
+
+
--- /dev/null
+# 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})
+
--- /dev/null
+/*
+ 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;
+}
+
--- /dev/null
+/*
+ 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
--- /dev/null
+/*
+ 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;
+}
--- /dev/null
+/*
+ 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
--- /dev/null
+<?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 && 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>&Save settings...</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+S</string>
+ </property>
+ </action>
+ <action name="actionLoadSettings">
+ <property name="text">
+ <string>&Load settings...</string>
+ </property>
+ <property name="shortcut">
+ <string>Ctrl+O</string>
+ </property>
+ </action>
+ <action name="actionQuit">
+ <property name="text">
+ <string>&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>
--- /dev/null
+# 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)
--- /dev/null
+#!/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
--- /dev/null
+/*
+ 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);
+}
+
--- /dev/null
+/*
+ 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(¶ms, 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(¶ms);
+
+ 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);
+}
--- /dev/null
+#!/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))
--- /dev/null
+/*
+ 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;
+}
+
--- /dev/null
+/*
+ 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