commit 25f0907c7859e6ab2b9f5a610b1e011912576683 Author: Hector Martin Date: Wed Nov 24 02:10:10 2010 +0100 Initial commit diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..dee53c5 --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +build +*~ diff --git a/CMakeLists.txt b/CMakeLists.txt new file mode 100644 index 0000000..8a1f1f4 --- /dev/null +++ b/CMakeLists.txt @@ -0,0 +1,33 @@ +# OpenLase - a realtime laser graphics toolkit +# +# Copyright (C) 2009-2010 Hector Martin "marcan" +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 or version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + +PROJECT(openlase) + +cmake_minimum_required(VERSION 2.6) + +set(CMAKE_MODULE_PATH ${CMAKE_MODULE_PATH} "${CMAKE_SOURCE_DIR}/Modules/") + +find_package(Qt4 REQUIRED) +find_package(JACK REQUIRED) + +set(CMAKE_C_FLAGS "-Wall -O3 -g") + +add_subdirectory (libol) +add_subdirectory (output) +add_subdirectory (tools) +add_subdirectory (examples) diff --git a/Modules/FindJACK.cmake b/Modules/FindJACK.cmake new file mode 100644 index 0000000..d5970b6 --- /dev/null +++ b/Modules/FindJACK.cmake @@ -0,0 +1,32 @@ +# - Try to find JACK +# Once done, this will define +# +# JACK_FOUND - system has JACK +# JACK_INCLUDE_DIRS - the JACK include directories +# JACK_LIBRARIES - link these to use JACK + +include(LibFindMacros) + +# Dependencies + +set(JACK_PKGCONF_INCLUDE_DIRS /usr/include) +set(JACK_PKGCONF_LIBRARY_DIRS /usr/lib) +set(JACK_LIBRARY_NAME jack) + +# Include dir +find_path(JACK_INCLUDE_DIR + NAMES jack/jack.h + PATHS ${JACK_PKGCONF_INCLUDE_DIRS} +) + +# Finally the library itself +find_library(JACK_LIBRARY + NAMES ${JACK_LIBRARY_NAME} + PATHS ${JACK_PKGCONF_LIBRARY_DIRS} +) + +# Set the include dir variables and the libraries and let libfind_process do the rest. +# NOTE: Singular variables for this library, plural for libraries this this lib depends on. +set(JACK_PROCESS_INCLUDES JACK_INCLUDE_DIR) +set(JACK_PROCESS_LIBS JACK_LIBRARY) +libfind_process(JACK) diff --git a/Modules/LibFindMacros.cmake b/Modules/LibFindMacros.cmake new file mode 100644 index 0000000..795d6b7 --- /dev/null +++ b/Modules/LibFindMacros.cmake @@ -0,0 +1,99 @@ +# Works the same as find_package, but forwards the "REQUIRED" and "QUIET" arguments +# used for the current package. For this to work, the first parameter must be the +# prefix of the current package, then the prefix of the new package etc, which are +# passed to find_package. +macro (libfind_package PREFIX) + set (LIBFIND_PACKAGE_ARGS ${ARGN}) + if (${PREFIX}_FIND_QUIETLY) + set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} QUIET) + endif (${PREFIX}_FIND_QUIETLY) + if (${PREFIX}_FIND_REQUIRED) + set (LIBFIND_PACKAGE_ARGS ${LIBFIND_PACKAGE_ARGS} REQUIRED) + endif (${PREFIX}_FIND_REQUIRED) + find_package(${LIBFIND_PACKAGE_ARGS}) +endmacro (libfind_package) + +# Damn CMake developers made the UsePkgConfig system deprecated in the same release (2.6) +# where they added pkg_check_modules. Consequently I need to support both in my scripts +# to avoid those deprecated warnings. Here's a helper that does just that. +# Works identically to pkg_check_modules, except that no checks are needed prior to use. +macro (libfind_pkg_check_modules PREFIX PKGNAME) + if (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) + include(UsePkgConfig) + pkgconfig(${PKGNAME} ${PREFIX}_INCLUDE_DIRS ${PREFIX}_LIBRARY_DIRS ${PREFIX}_LDFLAGS ${PREFIX}_CFLAGS) + else (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) + find_package(PkgConfig) + if (PKG_CONFIG_FOUND) + pkg_check_modules(${PREFIX} ${PKGNAME}) + endif (PKG_CONFIG_FOUND) + endif (${CMAKE_MAJOR_VERSION} EQUAL 2 AND ${CMAKE_MINOR_VERSION} EQUAL 4) +endmacro (libfind_pkg_check_modules) + +# Do the final processing once the paths have been detected. +# If include dirs are needed, ${PREFIX}_PROCESS_INCLUDES should be set to contain +# all the variables, each of which contain one include directory. +# Ditto for ${PREFIX}_PROCESS_LIBS and library files. +# Will set ${PREFIX}_FOUND, ${PREFIX}_INCLUDE_DIRS and ${PREFIX}_LIBRARIES. +# Also handles errors in case library detection was required, etc. +macro (libfind_process PREFIX) + # Skip processing if already processed during this run + if (NOT ${PREFIX}_FOUND) + # Start with the assumption that the library was found + set (${PREFIX}_FOUND TRUE) + + # Process all includes and set _FOUND to false if any are missing + foreach (i ${${PREFIX}_PROCESS_INCLUDES}) + if (${i}) + set (${PREFIX}_INCLUDE_DIRS ${${PREFIX}_INCLUDE_DIRS} ${${i}}) + mark_as_advanced(${i}) + else (${i}) + set (${PREFIX}_FOUND FALSE) + endif (${i}) + endforeach (i) + + # Process all libraries and set _FOUND to false if any are missing + foreach (i ${${PREFIX}_PROCESS_LIBS}) + if (${i}) + set (${PREFIX}_LIBRARIES ${${PREFIX}_LIBRARIES} ${${i}}) + mark_as_advanced(${i}) + else (${i}) + set (${PREFIX}_FOUND FALSE) + endif (${i}) + endforeach (i) + + # Print message and/or exit on fatal error + if (${PREFIX}_FOUND) + if (NOT ${PREFIX}_FIND_QUIETLY) + message (STATUS "Found ${PREFIX} ${${PREFIX}_VERSION}") + endif (NOT ${PREFIX}_FIND_QUIETLY) + else (${PREFIX}_FOUND) + if (${PREFIX}_FIND_REQUIRED) + foreach (i ${${PREFIX}_PROCESS_INCLUDES} ${${PREFIX}_PROCESS_LIBS}) + message("${i}=${${i}}") + endforeach (i) + message (FATAL_ERROR "Required library ${PREFIX} NOT FOUND.\nInstall the library (dev version) and try again. If the library is already installed, use ccmake to set the missing variables manually.") + endif (${PREFIX}_FIND_REQUIRED) + endif (${PREFIX}_FOUND) + endif (NOT ${PREFIX}_FOUND) +endmacro (libfind_process) + +macro(libfind_library PREFIX basename) + set(TMP "") + if(MSVC80) + set(TMP -vc80) + endif(MSVC80) + if(MSVC90) + set(TMP -vc90) + endif(MSVC90) + set(${PREFIX}_LIBNAMES ${basename}${TMP}) + if(${ARGC} GREATER 2) + set(${PREFIX}_LIBNAMES ${basename}${TMP}-${ARGV2}) + string(REGEX REPLACE "\\." "_" TMP ${${PREFIX}_LIBNAMES}) + set(${PREFIX}_LIBNAMES ${${PREFIX}_LIBNAMES} ${TMP}) + endif(${ARGC} GREATER 2) + find_library(${PREFIX}_LIBRARY + NAMES ${${PREFIX}_LIBNAMES} + PATHS ${${PREFIX}_PKGCONF_LIBRARY_DIRS} + ) +endmacro(libfind_library) + diff --git a/examples/CMakeLists.txt b/examples/CMakeLists.txt new file mode 100644 index 0000000..60dd6c1 --- /dev/null +++ b/examples/CMakeLists.txt @@ -0,0 +1,29 @@ +# OpenLase - a realtime laser graphics toolkit +# +# Copyright (C) 2009-2010 Hector Martin "marcan" +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 or version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + +include_directories (${CMAKE_SOURCE_DIR}/include) +link_directories (${CMAKE_BINARY_DIR}/libol) + +add_executable(circlescope circlescope.c) +target_link_libraries(circlescope ${JACK_LIBRARIES} m) + +add_executable(simple simple.c) +target_link_libraries(simple openlase) + +add_executable(pong pong.c) +target_link_libraries(pong openlase) diff --git a/examples/circlescope.c b/examples/circlescope.c new file mode 100644 index 0000000..ce6b1a0 --- /dev/null +++ b/examples/circlescope.c @@ -0,0 +1,134 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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 +#include +#include +#include +#include + +#include + +#include +#include + +typedef jack_default_audio_sample_t sample_t; +typedef jack_nframes_t nframes_t; + +jack_port_t *in_l; +jack_port_t *in_r; +jack_port_t *out_x; +jack_port_t *out_y; +jack_port_t *out_w; + +nframes_t rate; + +sample_t max_size = 1.0f; +sample_t min_size = 0.2f; +sample_t boost = 8; + +//float w = 110 * (2*M_PI); +float w = 523.251131f / 4.0f * (2*M_PI) / 1; +float pos = 0.0f; + +#define MAX(a,b) (((a)<(b))?(b):(a)) +#define MIN(a,b) (((a)>(b))?(b):(a)) + +int process (nframes_t nframes, void *arg) +{ + sample_t *i_l = (sample_t *) jack_port_get_buffer (in_l, nframes); + sample_t *i_r = (sample_t *) jack_port_get_buffer (in_r, nframes); + sample_t *o_x = (sample_t *) jack_port_get_buffer (out_x, nframes); + sample_t *o_y = (sample_t *) jack_port_get_buffer (out_y, nframes); + sample_t *o_w = (sample_t *) jack_port_get_buffer (out_w, nframes); + + nframes_t frm; + for (frm = 0; frm < nframes; frm++) { + sample_t val = (*i_l++ + *i_r++) / 2; + + val *= boost; + val = MAX(MIN(val,1.0f),-1.0f); + val = val * 0.5f + 0.5f; + val *= (max_size - min_size); + val += min_size; + + *o_w++ = 1.0f; + *o_x++ = cosf(pos) * val; + *o_y++ = sinf(pos) * val; + + pos += w / rate; + while(pos >= (2*M_PI)) { + pos -= (2*M_PI); + } + } + + return 0; +} + +int bufsize (nframes_t nframes, void *arg) +{ + printf ("the maximum buffer size is now %u\n", nframes); + return 0; +} + +int srate (nframes_t nframes, void *arg) +{ + rate = nframes; + printf ("Sample rate: %u/sec\n", nframes); + return 0; +} + +void jack_shutdown (void *arg) +{ + exit (1); +} + +int main (int argc, char *argv[]) +{ + jack_client_t *client; + + if ((client = jack_client_new ("circlescope")) == 0) { + fprintf (stderr, "jack server not running?\n"); + return 1; + } + + jack_set_process_callback (client, process, 0); + jack_set_buffer_size_callback (client, bufsize, 0); + jack_set_sample_rate_callback (client, srate, 0); + jack_on_shutdown (client, jack_shutdown, 0); + + in_l = jack_port_register (client, "in_l", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + in_r = jack_port_register (client, "in_r", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + out_x = jack_port_register (client, "out_x", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + out_y = jack_port_register (client, "out_y", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + out_w = jack_port_register (client, "out_w", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + + if (jack_activate (client)) { + fprintf (stderr, "cannot activate client"); + return 1; + } + + while (1) + sleep(1); + jack_client_close (client); + exit (0); +} + diff --git a/examples/pong.c b/examples/pong.c new file mode 100644 index 0000000..d939d30 --- /dev/null +++ b/examples/pong.c @@ -0,0 +1,317 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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 +#include +#include +#include +#include +#include +#include + +#include +#include +#include +#include + +#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); +} + diff --git a/examples/simple.c b/examples/simple.c new file mode 100644 index 0000000..3cc7f3a --- /dev/null +++ b/examples/simple.c @@ -0,0 +1,115 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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 +#include +#include +#include +#include +#include +#include + +/* +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); +} + diff --git a/include/ilda.h b/include/ilda.h new file mode 100644 index 0000000..a6aff4e --- /dev/null +++ b/include/ilda.h @@ -0,0 +1,49 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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 + +typedef struct { + float x; + float y; + float z; + int is_blank; + uint8_t color; +} IldaPoint; + +typedef struct { + int count; + float min_x; + float max_x; + float min_y; + float max_y; + float min_z; + float max_z; + IldaPoint *points; +} IldaFile; + +IldaFile *olLoadIlda(const char *filename); +void olDrawIlda(IldaFile *ild); +void olDrawIlda3D(IldaFile *ild); +void olFreeIlda(IldaFile *ild); + +#endif \ No newline at end of file diff --git a/include/libol.h b/include/libol.h new file mode 100644 index 0000000..8c6e3a5 --- /dev/null +++ b/include/libol.h @@ -0,0 +1,133 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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 + +enum { + OL_LINESTRIP, + OL_BEZIERSTRIP, + OL_POINTS +}; + +#define C_RED 0xff0000 +#define C_GREEN 0x00ff00 +#define C_BLUE 0x0000ff +#define C_WHITE 0xffffff +#define C_BLACK 0x000000 + +#define C_GREY(x) (0x010101 * ((int)(x))) + +enum { + RENDER_GRAYSCALE = 1, + RENDER_NOREORDER = 2, +}; + +typedef struct { + int rate; + float on_speed; + float off_speed; + int start_wait; + int start_dwell; + int curve_dwell; + int corner_dwell; + int end_dwell; + int end_wait; + float curve_angle; + float flatness; + float snap; + int render_flags; + int min_length; + int max_framelen; +} OLRenderParams; + +typedef struct { + int points; + int resampled_points; + int resampled_blacks; + int padding_points; +} OLFrameInfo; + +int olInit(int buffer_count, int max_points); + +void olSetRenderParams(OLRenderParams *params); + +typedef void (*AudioCallbackFunc)(float *leftbuf, float *rightbuf, int samples); + +void olSetAudioCallback(AudioCallbackFunc f); + +void olLoadIdentity(void); +void olPushMatrix(void); +void olPopMatrix(void); + +void olMultMatrix(float m[9]); +void olRotate(float theta); +void olTranslate(float x, float y); +void olScale(float sx, float sy); + +void olLoadIdentity3(void); +void olPushMatrix3(void); +void olPopMatrix3(void); + +void olMultMatrix3(float m[16]); +void olRotate3X(float theta); +void olRotate3Y(float theta); +void olRotate3Z(float theta); +void olTranslate3(float x, float y, float z); +void olScale3(float sx, float sy, float sz); + +void olFrustum (float left, float right, float bot, float ttop, float near, float far); +void olPerspective(float fovy, float aspect, float zNear, float zFar); + +void olResetColor(void); +void olMultColor(uint32_t color); +void olPushColor(void); +void olPopColor(void); + +void olBegin(int prim); +void olVertex(float x, float y, uint32_t color); +void olVertex3(float x, float y, float z, uint32_t color); +void olEnd(void); + +void olTransformVertex3(float *x, float *y, float *z); + +typedef void (*ShaderFunc)(float *x, float *y, uint32_t *color); +typedef void (*Shader3Func)(float *x, float *y, float *z, uint32_t *color); + +void olSetVertexPreShader(ShaderFunc f); +void olSetVertexShader(ShaderFunc f); +void olSetVertex3Shader(Shader3Func f); + +void olSetPixelShader(ShaderFunc f); + +void olRect(float x1, float y1, float x2, float y2, uint32_t color); +void olLine(float x1, float y1, float x2, float y2, uint32_t color); +void olDot(float x, float y, int points, uint32_t color); + +float olRenderFrame(int max_fps); + +void olGetFrameInfo(OLFrameInfo *info); + +void olShutdown(void); + +void olSetScissor (float x0, float y0, float x1, float y1); + +#endif diff --git a/include/text.h b/include/text.h new file mode 100644 index 0000000..b47da83 --- /dev/null +++ b/include/text.h @@ -0,0 +1,50 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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 +#include + +typedef struct { + int flag; + float x; + float y; +} FontPoint; + +typedef struct { + float width; + const FontPoint *points; +} FontChar; + +typedef struct { + float height; + float overlap; + const FontChar *chars; +} Font; + +Font *olGetDefaultFont(void); +float olGetCharWidth(Font *fnt, char c); +float olGetStringWidth(Font *fnt, float height, const char *s); +float olGetCharOverlap(Font *font, float height); +float olDrawChar(Font *fnt, float x, float y, float height, uint32_t color, char c); +float olDrawString(Font *fnt, float x, float y, float height, uint32_t color, const char *s); + +#endif diff --git a/libol/CMakeLists.txt b/libol/CMakeLists.txt new file mode 100644 index 0000000..455cf9c --- /dev/null +++ b/libol/CMakeLists.txt @@ -0,0 +1,29 @@ +# OpenLase - a realtime laser graphics toolkit +# +# Copyright (C) 2009-2010 Hector Martin "marcan" +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 or version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + +include_directories (${CMAKE_SOURCE_DIR}/include) +find_package(Threads) + +add_library (openlase libol.c text.c ilda.c ${CMAKE_CURRENT_BINARY_DIR}/fontdef.c) +find_library (PTHREAD pthread) +target_link_libraries (openlase ${CMAKE_THREAD_LIBS_INIT} m jack) + +add_custom_command(OUTPUT ${CMAKE_CURRENT_BINARY_DIR}/fontdef.c + DEPENDS ${CMAKE_SOURCE_DIR}/tools/genfont.py + MAIN_DEPENDENCY laserfont.svg + COMMAND python ${CMAKE_SOURCE_DIR}/tools/genfont.py ${CMAKE_CURRENT_SOURCE_DIR}/laserfont.svg ${CMAKE_CURRENT_BINARY_DIR}/fontdef.c default_font) diff --git a/libol/ilda.c b/libol/ilda.c new file mode 100644 index 0000000..d6528e8 --- /dev/null +++ b/libol/ilda.c @@ -0,0 +1,221 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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 +#include +#include + +#include +#include +#include + +#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 + +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; ipoints[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; ipoints[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; icount; i++) { + if(ild->points[i].x > ild->max_x) + ild->max_x = ild->points[i].x; + if(ild->points[i].y > ild->max_y) + ild->max_y = ild->points[i].y; + if(ild->points[i].z > ild->max_z) + ild->max_z = ild->points[i].z; + if(ild->points[i].x < ild->min_x) + ild->min_x = ild->points[i].x; + if(ild->points[i].y < ild->min_y) + ild->min_y = ild->points[i].y; + if(ild->points[i].z < ild->min_z) + ild->min_z = ild->points[i].z; + } + + return ild; +} + +void olDrawIlda(IldaFile *ild) +{ + if (!ild) + return; + IldaPoint *p = ild->points; + int i; + olBegin(OL_POINTS); + for (i = 0; i < ild->count; i++) { + //printf("%f %f %f %d\n", p->x, p->y, p->z, p->is_blank); + if (p->is_blank) + olVertex(p->x, p->y, C_BLACK); + else + olVertex(p->x, p->y, C_WHITE); + p++; + } + olEnd(); +} + +void olDrawIlda3D(IldaFile *ild) +{ + if (!ild) + return; + IldaPoint *p = ild->points; + int i; + olBegin(OL_POINTS); + for (i = 0; i < ild->count; i++) { + if (p->is_blank) + olVertex3(p->x, p->y, p->z, C_BLACK); + else + olVertex3(p->x, p->y, p->z, C_WHITE); + } + olEnd(); +} + +void olFreeIlda(IldaFile *ild) +{ + if(ild->points) + free(ild->points); + free(ild); +} diff --git a/libol/laserfont.svg b/libol/laserfont.svg new file mode 100644 index 0000000..02dfb4f --- /dev/null +++ b/libol/laserfont.svg @@ -0,0 +1,1328 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + image/svg+xml + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/libol/libol.c b/libol/libol.c new file mode 100644 index 0000000..0b471f4 --- /dev/null +++ b/libol/libol.c @@ -0,0 +1,1138 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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 +#include +#include +#include +#include +#include + +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; ix; + *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; ipoints = 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 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; ipoints + dstate.curobj->pointcnt - 1; + for (i=0; ix,last->y,last->color); + + if(pshader) { + for (i=0; ipointcnt; 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; ipointcnt; 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; ix, start->y, C_BLACK); + } + } + Point *op = &frames[cwbuf].points[frames[cwbuf].pnext]; + ip = obj->points; + for (i=0; ipointcnt; 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; jcolor = 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; jx = 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; jcolor = C_BLACK; + op++; + frames[cwbuf].pnext++; + } + } + *op++ = *ip; + frames[cwbuf].pnext++; + out_start = NULL; + } + } + if(!out_start) { + for (i=0; ix, end->y, C_BLACK); + } + last_render_point = *end; + } else { + for (i=0; ix, 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; ipoints; + int cnt = closest->pointcnt; + for (i=0; ipointcnt); + 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 params.max_framelen) + { + int in_count = count; + int out_count = params.max_framelen; + chkpts(count); + + Point *pin = frames[cwbuf].points; + Point *pout = &pin[in_count]; + + float pos = 0; + float delta = count / (float)out_count; + + count = 0; + while (pos < (in_count - 1)) { + int ipos = pos; + float rest = pos - ipos; + + pout->x = pin[ipos].x * (1-rest) + pin[ipos+1].x * rest; + pout->y = pin[ipos].y * (1-rest) + pin[ipos+1].y * rest; + + if (pin[ipos].color == C_BLACK || pin[ipos+1].color == C_BLACK) { + pout->color = C_BLACK; + pos += 1; + last_info.resampled_blacks++; + } else { + pout->color = pin[ipos].color; + pos += delta; + } + + pout++; + count++; + } + + memcpy(pin, &pin[in_count], count * sizeof(*pin)); + frames[cwbuf].pnext = count; + chkpts(0); + last_info.resampled_points = count; + } + + float last_x = frames[cwbuf].points[count-1].x; + float last_y = frames[cwbuf].points[count-1].y; + while(count < min_points) { + frames[cwbuf].points[count].x = last_x; + frames[cwbuf].points[count].y = last_y; + frames[cwbuf].points[count].color = C_BLACK; + count++; + last_info.padding_points++; + } + frames[cwbuf].pnext = count; + + if (audiocb) { + audiocb(frames[cwbuf].audio_l, frames[cwbuf].audio_r, count); + } else { + memset(frames[cwbuf].audio_l, 0, sizeof(float)*count); + memset(frames[cwbuf].audio_r, 0, sizeof(float)*count); + } + + //printf("Rendered frame! %d\n", cwbuf); + cwbuf = (cwbuf + 1) % fbufs; + + return count / (float)params.rate; +} + +void olLoadIdentity(void) +{ + static const float identity[3][3] = { + {1,0,0}, + {0,1,0}, + {0,0,1} + }; + memcpy(&mtx2d[0][0], &identity[0][0], sizeof(mtx2d)); +} + +void olRotate(float theta) +{ + float rot[9] = { + cosf(theta),-sinf(theta),0, + sinf(theta),cosf(theta),0, + 0,0,1, + }; + olMultMatrix(rot); +} + +void olTranslate(float x, float y) +{ + float trans[9] = { + 1,0,0, + 0,1,0, + x,y,1, + }; + olMultMatrix(trans); +} + + +void olScale(float sx, float sy) +{ + float scale[9] = { + sx,0,0, + 0,sy,0, + 0,0,1, + }; + olMultMatrix(scale); +} + +void olMultMatrix(float m[9]) +{ + float new[3][3]; + + new[0][0] = mtx2d[0][0]*m[0] + mtx2d[0][1]*m[1] + mtx2d[0][2]*m[2]; + new[0][1] = mtx2d[0][0]*m[3] + mtx2d[0][1]*m[4] + mtx2d[0][2]*m[5]; + new[0][2] = mtx2d[0][0]*m[6] + mtx2d[0][1]*m[7] + mtx2d[0][2]*m[8]; + new[1][0] = mtx2d[1][0]*m[0] + mtx2d[1][1]*m[1] + mtx2d[1][2]*m[2]; + new[1][1] = mtx2d[1][0]*m[3] + mtx2d[1][1]*m[4] + mtx2d[1][2]*m[5]; + new[1][2] = mtx2d[1][0]*m[6] + mtx2d[1][1]*m[7] + mtx2d[1][2]*m[8]; + new[2][0] = mtx2d[2][0]*m[0] + mtx2d[2][1]*m[1] + mtx2d[2][2]*m[2]; + new[2][1] = mtx2d[2][0]*m[3] + mtx2d[2][1]*m[4] + mtx2d[2][2]*m[5]; + new[2][2] = mtx2d[2][0]*m[6] + mtx2d[2][1]*m[7] + mtx2d[2][2]*m[8]; + + memcpy(&mtx2d[0][0], &new[0][0], sizeof(mtx2d)); +} + +void olPushMatrix(void) +{ + memcpy(&mtx2ds[mtx2dp][0][0], &mtx2d[0][0], sizeof(mtx2d)); + mtx2dp++; +} + +void olPopMatrix(void) +{ + mtx2dp--; + memcpy(&mtx2d[0][0], &mtx2ds[mtx2dp][0][0], sizeof(mtx2d)); +} + +void olLoadIdentity3(void) +{ + static const float identity[4][4] = { + {1,0,0,0}, + {0,1,0,0}, + {0,0,1,0}, + {0,0,0,1}, + }; + memcpy(&mtx3d[0][0], &identity[0][0], sizeof(mtx3d)); +} + +void olRotate3X(float theta) +{ + float rot[16] = { + 1,0,0,0, + 0,cosf(theta),sinf(theta),0, + 0,-sinf(theta),cosf(theta),0, + 0,0,0,1 + }; + olMultMatrix3(rot); +} + +void olRotate3Y(float theta) +{ + float rot[16] = { + cosf(theta),0,-sinf(theta),0, + 0,1,0,0, + sinf(theta),0,cosf(theta),0, + 0,0,0,1 + }; + olMultMatrix3(rot); +} + +void olRotate3Z(float theta) +{ + float rot[16] = { + cosf(theta), sinf(theta), 0, 0, + -sinf(theta), cosf(theta), 0, 0, + 0, 0, 1, 0, + 0, 0, 0, 1 + }; + olMultMatrix3(rot); +} + +void olTranslate3(float x, float y, float z) +{ + float trans[16] = { + 1,0,0,0, + 0,1,0,0, + 0,0,1,0, + x,y,z,1, + }; + olMultMatrix3(trans); +} + + +void olScale3(float sx, float sy, float sz) +{ + float trans[16] = { + sx,0,0,0, + 0,sy,0,0, + 0,0,sz,0, + 0,0,0,1, + }; + olMultMatrix3(trans); +} + +void olMultMatrix3(float m[16]) +{ + float new[4][4]; + + new[0][0] = mtx3d[0][0]*m[ 0] + mtx3d[0][1]*m[ 1] + mtx3d[0][2]*m[ 2] + mtx3d[0][3]*m[ 3]; + new[0][1] = mtx3d[0][0]*m[ 4] + mtx3d[0][1]*m[ 5] + mtx3d[0][2]*m[ 6] + mtx3d[0][3]*m[ 7]; + new[0][2] = mtx3d[0][0]*m[ 8] + mtx3d[0][1]*m[ 9] + mtx3d[0][2]*m[10] + mtx3d[0][3]*m[11]; + new[0][3] = mtx3d[0][0]*m[12] + mtx3d[0][1]*m[13] + mtx3d[0][2]*m[14] + mtx3d[0][3]*m[15]; + new[1][0] = mtx3d[1][0]*m[ 0] + mtx3d[1][1]*m[ 1] + mtx3d[1][2]*m[ 2] + mtx3d[1][3]*m[ 3]; + new[1][1] = mtx3d[1][0]*m[ 4] + mtx3d[1][1]*m[ 5] + mtx3d[1][2]*m[ 6] + mtx3d[1][3]*m[ 7]; + new[1][2] = mtx3d[1][0]*m[ 8] + mtx3d[1][1]*m[ 9] + mtx3d[1][2]*m[10] + mtx3d[1][3]*m[11]; + new[1][3] = mtx3d[1][0]*m[12] + mtx3d[1][1]*m[13] + mtx3d[1][2]*m[14] + mtx3d[1][3]*m[15]; + new[2][0] = mtx3d[2][0]*m[ 0] + mtx3d[2][1]*m[ 1] + mtx3d[2][2]*m[ 2] + mtx3d[2][3]*m[ 3]; + new[2][1] = mtx3d[2][0]*m[ 4] + mtx3d[2][1]*m[ 5] + mtx3d[2][2]*m[ 6] + mtx3d[2][3]*m[ 7]; + new[2][2] = mtx3d[2][0]*m[ 8] + mtx3d[2][1]*m[ 9] + mtx3d[2][2]*m[10] + mtx3d[2][3]*m[11]; + new[2][3] = mtx3d[2][0]*m[12] + mtx3d[2][1]*m[13] + mtx3d[2][2]*m[14] + mtx3d[2][3]*m[15]; + new[3][0] = mtx3d[3][0]*m[ 0] + mtx3d[3][1]*m[ 1] + mtx3d[3][2]*m[ 2] + mtx3d[3][3]*m[ 3]; + new[3][1] = mtx3d[3][0]*m[ 4] + mtx3d[3][1]*m[ 5] + mtx3d[3][2]*m[ 6] + mtx3d[3][3]*m[ 7]; + new[3][2] = mtx3d[3][0]*m[ 8] + mtx3d[3][1]*m[ 9] + mtx3d[3][2]*m[10] + mtx3d[3][3]*m[11]; + new[3][3] = mtx3d[3][0]*m[12] + mtx3d[3][1]*m[13] + mtx3d[3][2]*m[14] + mtx3d[3][3]*m[15]; + + memcpy(&mtx3d[0][0], &new[0][0], sizeof(mtx3d)); +} + +void olPushMatrix3(void) +{ + memcpy(&mtx3ds[mtx3dp][0][0], &mtx3d[0][0], sizeof(mtx3d)); + mtx3dp++; +} + +void olPopMatrix3(void) +{ + mtx3dp--; + memcpy(&mtx3d[0][0], &mtx3ds[mtx3dp][0][0], sizeof(mtx3d)); +} + +void olTransformVertex3(float *x, float *y, float *z) +{ + float px; + float py; + float pz; + float pw; + + px = mtx3d[0][0]**x + mtx3d[0][1]**y + mtx3d[0][2]**z + mtx3d[0][3]; + py = mtx3d[1][0]**x + mtx3d[1][1]**y + mtx3d[1][2]**z + mtx3d[1][3]; + pz = mtx3d[2][0]**x + mtx3d[2][1]**y + mtx3d[2][2]**z + mtx3d[2][3]; + pw = mtx3d[3][0]**x + mtx3d[3][1]**y + mtx3d[3][2]**z + mtx3d[3][3]; + + px /= pw; + py /= pw; + pz /= pw; + + *x = px; + *y = py; + *z = pz; +} + +void olVertex3(float x, float y, float z, uint32_t color) +{ + if(v3shader) + v3shader(&x, &y, &z, &color); + olTransformVertex3(&x, &y, &z); + olVertex(x, y, color); +} + +void olRect(float x1, float y1, float x2, float y2, uint32_t color) +{ + olBegin(OL_LINESTRIP); + olVertex(x1,y1,color); + olVertex(x1,y2,color); + olVertex(x2,y2,color); + olVertex(x2,y1,color); + olVertex(x1,y1,color); + olEnd(); +} + +void olLine(float x1, float y1, float x2, float y2, uint32_t color) +{ + olBegin(OL_LINESTRIP); + olVertex(x1,y1,color); + olVertex(x2,y2,color); + olEnd(); +} + + +void olDot(float x, float y, int samples, uint32_t color) +{ + int i; + olBegin(OL_POINTS); + for (i = 0; i < samples; i++) + olVertex(x,y,color); + olEnd(); +} + +void olResetColor(void) +{ + curcol = C_WHITE; +} + +void olMultColor(uint32_t color) +{ + curcol = colmul(curcol, color); +} + +void olPushColor(void) +{ + cols[coldp] = curcol; + coldp++; +} + +void olPopColor(void) +{ + coldp--; + curcol = cols[coldp]; +} + + +void olSetVertexPreShader(ShaderFunc f) +{ + vpreshader = f; +} +void olSetVertexShader(ShaderFunc f) +{ + vshader = f; +} +void olSetVertex3Shader(Shader3Func f) +{ + v3shader = f; +} + +void olSetPixelShader(ShaderFunc f) +{ + pshader = f; +} + +void olSetAudioCallback(AudioCallbackFunc f) +{ + audiocb = f; +} + +void olFrustum (float l, float r, float b, float t, float n, float f) +{ + float m[16] = { + (2*n)/(r-l), 0, (r+l)/(r-l), 0, + 0, (2*n)/(t-b), (t+b)/(t-b), 0, + 0, 0, -(f+n)/(f-n), (-2*f*n)/(f-n), + 0, 0, -1, 0, + }; + + olMultMatrix3(m); +} + +void olPerspective (float fovy, float aspect, float zNear, float zFar) +{ + float xmin, xmax, ymin, ymax; + + ymax = zNear * tanf(fovy * M_PI / 360.0); + ymin = -ymax; + + xmin = ymin * aspect; + xmax = ymax * aspect; + + olFrustum( xmin, xmax, ymin, ymax, zNear, zFar ); +} + +void olSetScissor (float x0, float y0, float x1, float y1) +{ + bbox[0][0] = x0; + bbox[0][1] = y0; + bbox[1][0] = x1; + bbox[1][1] = y1; +} + +void olGetFrameInfo(OLFrameInfo *info) +{ + *info = last_info; +} diff --git a/libol/text.c b/libol/text.c new file mode 100644 index 0000000..db178fe --- /dev/null +++ b/libol/text.c @@ -0,0 +1,101 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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 +#include "libol.h" +#include "text.h" + +extern Font default_font; + +Font *olGetDefaultFont(void) +{ + return &default_font; +} + +float olGetCharWidth(Font *font, char c) +{ + if (!font) + return 0; + return font->chars[(uint8_t)c].width / font->height; +} + +float olGetCharOverlap(Font *font, float height) +{ + if (!font) + return 0; + float ratio = height / font->height; + return font->overlap * ratio; +} + +float olDrawChar(Font *font, float x, float y, float height, uint32_t color, char c) +{ + if (!font) + return 0; + + const FontChar *chr = &font->chars[(uint8_t)c]; + const FontPoint *p = chr->points; + + float ratio = height / font->height; + + if (p) { + + olBegin(OL_BEZIERSTRIP); + + do { + olVertex(x + p->x * ratio, y - p->y * ratio, color); + if (p->flag == 1) { + olEnd(); + olBegin(OL_BEZIERSTRIP); + } + } while ((p++)->flag != 2); + + olEnd(); + } + + return chr->width * ratio; +} + +float olDrawString(Font *font, float x, float y, float height, uint32_t color, const char *s) +{ + float w = 0; + float ratio = height / font->height; + + while(*s) { + w += olDrawChar(font, x+w, y, height, color, *s) - font->overlap * ratio; + s++; + } + + return w + font->overlap * ratio; +} + + +float olGetStringWidth(Font *font, float height, const char *s) +{ + float w = 0; + float ratio = height / font->height; + + while(*s) { + w += font->chars[(uint8_t)*s].width * ratio - font->overlap * ratio; + s++; + } + + return w + font->overlap * ratio; +} + + diff --git a/output/CMakeLists.txt b/output/CMakeLists.txt new file mode 100644 index 0000000..a6a0754 --- /dev/null +++ b/output/CMakeLists.txt @@ -0,0 +1,28 @@ +# OpenLase - a realtime laser graphics toolkit +# +# Copyright (C) 2009-2010 Hector Martin "marcan" +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 or version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + +include(${QT_USE_FILE}) + +QT4_WRAP_UI(output_UIS_H output_settings.ui) +QT4_AUTOMOC(output_settings.cpp output.cpp) + +include_directories(${CMAKE_CURRENT_BINARY_DIR}) + +add_executable(output output.cpp output_settings.cpp ${output_UIS_H}) +target_link_libraries(output ${JACK_LIBRARIES} ${QT_LIBRARIES}) + diff --git a/output/output.cpp b/output/output.cpp new file mode 100644 index 0000000..3d0cc81 --- /dev/null +++ b/output/output.cpp @@ -0,0 +1,265 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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 +#include +#include +#include +#include +#include + +#include + +#include "output.h" + +#include +#include "output_settings.h" + +typedef jack_default_audio_sample_t sample_t; +typedef jack_nframes_t nframes_t; + +jack_port_t *in_x; +jack_port_t *in_y; +jack_port_t *in_g; + +jack_port_t *out_x; +jack_port_t *out_y; +jack_port_t *out_g; +jack_port_t *out_e; + +nframes_t rate, enable_period, enable_ctr; +nframes_t frames_dead; + +#define DEAD_TIME (rate/10) + +nframes_t ibuf_g = 0; +sample_t buf_g[MAX_DELAY]; + +output_config_t *cfg; + +static void generate_enable(sample_t *buf, nframes_t nframes) +{ + while (nframes--) { + if (enable_ctr < (enable_period / 2)) + *buf++ = -1.0; + else + *buf++ = 1.0; + enable_ctr = (enable_ctr + 1) % enable_period; + } +} + +static void transform(sample_t *ox, sample_t *oy) +{ + float x,y,w; + x = *ox; + y = *oy; + + *ox = cfg->transform[0][0]*x + cfg->transform[0][1]*y + cfg->transform[0][2]; + *oy = cfg->transform[1][0]*x + cfg->transform[1][1]*y + cfg->transform[1][2]; + w = cfg->transform[2][0]*x + cfg->transform[2][1]*y + cfg->transform[2][2]; + + *ox /= w; + *oy /= w; +} + +/* The following filters attempt to compensate for imperfections in my galvos. +You'll probably want to turn them off for anything else */ + +#define LIMIT 0.007 +#define RATIO 0.30 + +static void cfilter(float *c, float *p) +{ + float delta = fabsf(*c - *p); + if (delta > LIMIT) { + if (*c > *p) + *p = *c - LIMIT; + else + *p = *c + LIMIT; + } else { + *p = (1-RATIO) * *p + RATIO * *c; + } + + *c += *c - *p; +} + +#define DPOWER 0.05 +#define DRATIO 0.05 + +static void dfilter(float *c, float *p) +{ + float delta = *c - *p; + *c += DPOWER*delta; + + *p = (1-DRATIO) * *p + DRATIO * *c; +} + +static inline void filter(float *x, float *y) +{ + static float px=0, py=0; + static float dx=0, dy=0; + dfilter(x,&dx); + dfilter(y,&dy); + cfilter(x,&px); + cfilter(y,&py); +} + +static int process (nframes_t nframes, void *arg) +{ + sample_t *o_x = (sample_t *) jack_port_get_buffer (out_x, nframes); + sample_t *o_y = (sample_t *) jack_port_get_buffer (out_y, nframes); + sample_t *o_g = (sample_t *) jack_port_get_buffer (out_g, nframes); + sample_t *o_e = (sample_t *) jack_port_get_buffer (out_e, nframes); + + sample_t *i_x = (sample_t *) jack_port_get_buffer (in_x, nframes); + sample_t *i_y = (sample_t *) jack_port_get_buffer (in_y, nframes); + sample_t *i_g = (sample_t *) jack_port_get_buffer (in_g, nframes); + + nframes_t frm; + + for (frm = 0; frm < nframes; frm++) { + sample_t x,y,g,orig_g; + x = *i_x++; + y = *i_y++; + g = orig_g = *i_g++; + + y = -y; + transform(&x, &y); + y = -y; + + if (cfg->scan_flags & SWAP_XY) { + sample_t tmp; + tmp = x; + x = y; + y = tmp; + } + + if (cfg->scan_flags & INVERT_X) + x = -x; + if (cfg->scan_flags & INVERT_Y) + y = -y; + if (!(cfg->scan_flags & ENABLE_X) && !cfg->safe) + x = 0.0f; + if (!(cfg->scan_flags & ENABLE_Y) && !cfg->safe) + y = 0.0f; + if (cfg->safe && cfg->size < 0.10f) { + x *= 0.10f; + y *= 0.10f; + } else { + x *= cfg->size; + y *= cfg->size; + } + + if (cfg->blank_flags & BLANK_INVERT) + g = 1.0f - g; + if (!(cfg->blank_flags & BLANK_ENABLE)) + g = 1.0f; + if (!(cfg->blank_flags & OUTPUT_ENABLE)) + g = 0.0f; + g *= cfg->power * (1.0f-cfg->offset); + g += cfg->offset; + + if(orig_g == 0.0f) { + if(frames_dead >= DEAD_TIME) { + g = 0.0f; + } else { + frames_dead++; + } + } else { + frames_dead = 0; + } + + filter(&x, &y); + + *o_x++ = x; + *o_y++ = y; + buf_g[ibuf_g] = g; + *o_g++ = buf_g[(ibuf_g + MAX_DELAY - cfg->delay) % MAX_DELAY]; + ibuf_g = (ibuf_g + 1) % MAX_DELAY; + } + generate_enable(o_e, nframes); + + return 0; +} + +static int bufsize (nframes_t nframes, void *arg) +{ + printf ("the maximum buffer size is now %u\n", nframes); + return 0; +} + +static int srate (nframes_t nframes, void *arg) +{ + rate = nframes; + if(rate % 1000) { + printf("error: the sample rate should be a multiple of 1000\n"); + exit(1); + } + enable_period = nframes / 1000; + enable_ctr = 0; + printf ("Sample rate: %u/sec\n", nframes); + return 0; +} + +static void jack_shutdown (void *arg) +{ + exit (1); +} + +int main (int argc, char *argv[]) +{ + int retval; + jack_client_t *client; + + QApplication app(argc, argv); + OutputSettings settings; + + cfg = &settings.cfg; + + if ((client = jack_client_new ("output")) == 0) { + fprintf (stderr, "jack server not running?\n"); + return 1; + } + + jack_set_process_callback (client, process, 0); + jack_set_buffer_size_callback (client, bufsize, 0); + jack_set_sample_rate_callback (client, srate, 0); + jack_on_shutdown (client, jack_shutdown, 0); + + in_x = jack_port_register (client, "in_x", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + in_y = jack_port_register (client, "in_y", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + in_g = jack_port_register (client, "in_g", JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0); + out_x = jack_port_register (client, "out_x", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + out_y = jack_port_register (client, "out_y", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + out_g = jack_port_register (client, "out_g", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + out_e = jack_port_register (client, "out_e", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + + if (jack_activate (client)) { + fprintf (stderr, "cannot activate client"); + return 1; + } + + settings.show(); + + retval = app.exec(); + + jack_client_close (client); + return retval; +} + diff --git a/output/output.h b/output/output.h new file mode 100644 index 0000000..aa6e62b --- /dev/null +++ b/output/output.h @@ -0,0 +1,47 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +This program is free software; you can redistribute it and/or modify +it under the terms of the GNU General Public License as published by +the Free Software Foundation, either version 2 or version 3. + +This program is distributed in the hope that it will be useful, +but WITHOUT ANY WARRANTY; without even the implied warranty of +MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +GNU General Public License for more details. + +You should have received a copy of the GNU General Public License +along with this program; if not, write to the Free Software +Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +*/ + +#ifndef OUTPUT_H +#define OUTPUT_H + +#define ENABLE_X 0x01 +#define ENABLE_Y 0x02 +#define INVERT_X 0x04 +#define INVERT_Y 0x08 +#define SWAP_XY 0x10 + +#define OUTPUT_ENABLE 0x01 +#define BLANK_ENABLE 0x02 +#define BLANK_INVERT 0x04 + +#define MAX_DELAY 25 + +typedef struct { + float power; + float offset; + float size; + float transform[3][3]; + + int delay; + int scan_flags; + int blank_flags; + int safe; +} output_config_t; + +#endif diff --git a/output/output_settings.cpp b/output/output_settings.cpp new file mode 100644 index 0000000..df1a791 --- /dev/null +++ b/output/output_settings.cpp @@ -0,0 +1,394 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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 +#include + +#include + +#define ASPECT_1_1 0 +#define ASPECT_4_3 1 +#define ASPECT_16_9 2 + +ControlPoint::ControlPoint() +{ + form = 0; +} + +QVariant ControlPoint::itemChange(GraphicsItemChange change, const QVariant &value) +{ + if (change == ItemPositionChange && form) { + form->pointMoved(this); + } + return QGraphicsEllipseItem::itemChange(change, value); +} + +OutputSettings::OutputSettings(QWidget *parent) +{ + setupUi(this); + + QPen grid_pen(Qt::blue); + QPen line_pen(Qt::green); + QBrush point_brush(Qt::red); + + scene.setSceneRect(-1.1,-1.1,2.2,2.2); + + for (int i=-5; i<=5; i++) { + scene.addLine(i/5.0f, -1.0f, i/5.0f, 1.0f, grid_pen); + scene.addLine(-1.0f, i/5.0f, 1.0f, i/5.0f, grid_pen); + } + + for (int i=0; i<4; i++) { + pt[i] = new ControlPoint(); + } + + for (int i=0; i<4; i++) { + pt[i]->setBrush(point_brush); + pt[i]->setRect(-5, -5, 10, 10); + pt[i]->setFlag(QGraphicsItem::ItemIgnoresTransformations, true); + pt[i]->setFlag(QGraphicsItem::ItemIsMovable, true); + pt[i]->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); + pt[i]->form = this; + scene.addItem(pt[i]); + } + + pl.setPen(line_pen); + + scene.addItem(&pl); + + projView->setBackgroundBrush(Qt::black); + projView->setScene(&scene); + projView->fitInView(scene.sceneRect(), Qt::KeepAspectRatio); + projView->setInteractive(true); + //projView->setRenderHints(QPainter::Antialiasing); + + resetDefaults(); +} + +OutputSettings::~OutputSettings() +{ +} + +qreal OutputSettings::getYRatio(int ratio) +{ + switch(ratio) { + case ASPECT_1_1: + return 1.0; + case ASPECT_4_3: + return 3.0/4.0; + case ASPECT_16_9: + return 9.0/16.0; + } + return 1.0; +} + +void OutputSettings::resetPoints() +{ + mtx.reset(); + mtx.scale(1.0, getYRatio(aspectRatio->currentIndex())); + updateMatrix(); + loadPoints(); + updatePoly(); +} + +void OutputSettings::updateMatrix() +{ + QTransform smtx; + QTransform omtx; + qreal yratio = getYRatio(aspectRatio->currentIndex()); + + if (!aspectScale->isChecked()) { + if (fitSquare->isChecked()) + smtx.scale(yratio, 1.0); + else + smtx.scale(1.0, 1/yratio); + } + + omtx = smtx * mtx; + + cfg.transform[0][0] = omtx.m11(); + cfg.transform[0][1] = omtx.m21(); + cfg.transform[0][2] = omtx.m31(); + cfg.transform[1][0] = omtx.m12(); + cfg.transform[1][1] = omtx.m22(); + cfg.transform[1][2] = omtx.m32(); + cfg.transform[2][0] = omtx.m13(); + cfg.transform[2][1] = omtx.m23(); + cfg.transform[2][2] = omtx.m33(); +} + +void OutputSettings::loadPoints() +{ + QPointF p0(-1,-1); + QPointF p1(1,-1); + QPointF p2(-1,1); + QPointF p3(1,1); + + for (int i=0; i<4; i++) + pt[i]->setFlag(QGraphicsItem::ItemSendsGeometryChanges, false); + + pt[0]->setPos(mtx.map(p0)); + pt[1]->setPos(mtx.map(p1)); + pt[2]->setPos(mtx.map(p2)); + pt[3]->setPos(mtx.map(p3)); + + for (int i=0; i<4; i++) + pt[i]->setFlag(QGraphicsItem::ItemSendsGeometryChanges, true); + + updatePoly(); +} + +void OutputSettings::pointMoved(ControlPoint *point) +{ + QPolygonF src; + src << QPoint(-1,-1) << QPoint(1,-1) << QPoint(1,1) << QPoint(-1,1); + QPolygonF dst; + dst << pt[0]->pos() << pt[1]->pos() << pt[3]->pos() << pt[2]->pos(); + + QTransform::quadToQuad(src, dst, mtx); + loadPoints(); + updatePoly(); + updateMatrix(); +} + +void OutputSettings::updatePoly() +{ + QPolygonF poly; + + poly << pt[0]->pos() << pt[1]->pos() << pt[3]->pos() << pt[2]->pos(); + pl.setPolygon(poly); +} + +void OutputSettings::resizeEvent (QResizeEvent * event) +{ + projView->fitInView(scene.sceneRect(), Qt::KeepAspectRatio); +} + +void OutputSettings::showEvent (QShowEvent * event) +{ + projView->fitInView(scene.sceneRect(), Qt::KeepAspectRatio); +} + +void OutputSettings::updateAllSettings() +{ + cfg.power = powerSlider->value() / 100.0f; + cfg.offset = offsetSlider->value() / 100.0f; + cfg.size = sizeSlider->value() / 100.0f; + cfg.delay = delaySlider->value(); + + cfg.scan_flags = 0; + cfg.blank_flags = 0; + cfg.safe = enforceSafety->isChecked(); + + if(cfg.safe) { + xEnable->setChecked(true); + yEnable->setChecked(true); + cfg.scan_flags |= ENABLE_X | ENABLE_Y; + } + + xEnable->setEnabled(!cfg.safe); + yEnable->setEnabled(!cfg.safe); + + currentAspect = aspectRatio->currentIndex(); + + if (xEnable->isChecked()) + cfg.scan_flags |= ENABLE_X; + if (yEnable->isChecked()) + cfg.scan_flags |= ENABLE_Y; + if (xInvert->isChecked()) + cfg.scan_flags |= INVERT_X; + if (yInvert->isChecked()) + cfg.scan_flags |= INVERT_Y; + if (xySwap->isChecked()) + cfg.scan_flags |= SWAP_XY; + + if (outputEnable->isChecked()) + cfg.blank_flags |= OUTPUT_ENABLE; + if (blankingEnable->isChecked()) + cfg.blank_flags |= BLANK_ENABLE; + + outputTest->setEnabled(!outputEnable->isChecked()); +} + +void OutputSettings::resetDefaults() +{ + xEnable->setChecked(true); + yEnable->setChecked(true); + xInvert->setChecked(true); + yInvert->setChecked(false); + xySwap->setChecked(false); + aspectScale->setChecked(false); + + enforceSafety->setChecked(true); + + aspectRatio->setCurrentIndex(ASPECT_1_1); + + outputEnable->setChecked(true); + blankingEnable->setChecked(true); + + powerSlider->setValue(100); + offsetSlider->setValue(20); + sizeSlider->setValue(100); + delaySlider->setValue(6); + + resetPoints(); + updateAllSettings(); +} + +void OutputSettings::on_outputEnable_toggled(bool state) +{ + if (state) cfg.blank_flags |= OUTPUT_ENABLE; + else cfg.blank_flags &= ~OUTPUT_ENABLE; + outputTest->setEnabled(!state); +} + +void OutputSettings::on_blankingEnable_toggled(bool state) +{ + if (state) cfg.blank_flags |= BLANK_ENABLE; + else cfg.blank_flags &= ~BLANK_ENABLE; +} + +void OutputSettings::on_blankingInvert_toggled(bool state) +{ + if (state) cfg.blank_flags |= BLANK_INVERT; + else cfg.blank_flags &= ~BLANK_INVERT; +} + +void OutputSettings::on_outputTest_pressed() +{ + cfg.blank_flags |= OUTPUT_ENABLE; +} + +void OutputSettings::on_outputTest_released() +{ + if (!outputEnable->isChecked()) + cfg.blank_flags &= ~OUTPUT_ENABLE; +} + +void OutputSettings::on_xEnable_toggled(bool state) +{ + if (state) cfg.scan_flags |= ENABLE_X; + else cfg.scan_flags &= ~ENABLE_X; +} + +void OutputSettings::on_yEnable_toggled(bool state) +{ + if (state) cfg.scan_flags |= ENABLE_Y; + else cfg.scan_flags &= ~ENABLE_Y; +} + +void OutputSettings::on_xInvert_toggled(bool state) +{ + if (state) cfg.scan_flags |= INVERT_X; + else cfg.scan_flags &= ~INVERT_X; +} + +void OutputSettings::on_yInvert_toggled(bool state) +{ + if (state) cfg.scan_flags |= INVERT_Y; + else cfg.scan_flags &= ~INVERT_Y; +} + +void OutputSettings::on_xySwap_toggled(bool state) +{ + if (state) cfg.scan_flags |= SWAP_XY; + else cfg.scan_flags &= ~SWAP_XY; +} + +void OutputSettings::on_aspectScale_toggled(bool state) +{ + fitSquare->setEnabled(!state); + updateMatrix(); +} + +void OutputSettings::on_fitSquare_toggled(bool state) +{ + updateMatrix(); +} + +void OutputSettings::on_aspectRatio_currentIndexChanged(int index) +{ + QTransform smtx; + if (index == currentAspect) + return; + + qreal rold = getYRatio(currentAspect); + qreal rnew = getYRatio(index); + + smtx.scale(1.0, rnew/rold); + mtx = smtx * mtx; + + currentAspect = index; + loadPoints(); + updateMatrix(); +} + +void OutputSettings::on_resetTransform_clicked() +{ + resetPoints(); +} + + +void OutputSettings::on_enforceSafety_toggled(bool state) +{ + if (!state) { + QMessageBox msgBox; + msgBox.setText("Do not stare into laser with remaining eye"); + msgBox.setInformativeText("Do you really want to disable safety enforcement?"); + msgBox.setStandardButtons(QMessageBox::Yes | QMessageBox::No); + msgBox.setDefaultButton(QMessageBox::No); + int ret = msgBox.exec(); + if (ret != QMessageBox::Yes) { + enforceSafety->setChecked(true); + return; + } + } + + if (state) { + xEnable->setChecked(true); + yEnable->setChecked(true); + cfg.scan_flags |= ENABLE_X | ENABLE_Y; + } + + xEnable->setEnabled(!state); + yEnable->setEnabled(!state); + + cfg.safe = state; +} + +void OutputSettings::on_powerSlider_valueChanged(int value) +{ + cfg.power = value / 100.0f; +} + +void OutputSettings::on_offsetSlider_valueChanged(int value) +{ + cfg.offset = value / 100.0f; +} + +void OutputSettings::on_delaySlider_valueChanged(int value) +{ + cfg.delay = value; +} + +void OutputSettings::on_sizeSlider_valueChanged(int value) +{ + cfg.size = value / 100.0f; +} diff --git a/output/output_settings.h b/output/output_settings.h new file mode 100644 index 0000000..4c2865a --- /dev/null +++ b/output/output_settings.h @@ -0,0 +1,93 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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 +#include +#include + +class ControlPoint; + +class OutputSettings : public QMainWindow, private Ui::OutputSettingsDLG +{ + Q_OBJECT + +public: + output_config_t cfg; + OutputSettings(QWidget *parent = 0); + ~OutputSettings(); + +public slots: + void on_outputEnable_toggled(bool state); + void on_blankingEnable_toggled(bool state); + void on_blankingInvert_toggled(bool state); + void on_xEnable_toggled(bool state); + void on_yEnable_toggled(bool state); + void on_xInvert_toggled(bool state); + void on_yInvert_toggled(bool state); + void on_xySwap_toggled(bool state); + void on_aspectRatio_currentIndexChanged(int index); + void on_aspectScale_toggled(bool state); + void on_fitSquare_toggled(bool state); + void on_enforceSafety_toggled(bool state); + void on_outputTest_pressed(); + void on_outputTest_released(); + void on_powerSlider_valueChanged(int value); + void on_offsetSlider_valueChanged(int value); + void on_delaySlider_valueChanged(int value); + void on_sizeSlider_valueChanged(int value); + void on_resetTransform_clicked(); + + void resizeEvent (QResizeEvent * event); + void showEvent (QShowEvent * event); + + void pointMoved(ControlPoint *pt); + +private: + QTransform mtx; + QGraphicsScene scene; + ControlPoint *pt[4]; + QGraphicsPolygonItem pl; + int currentAspect; + void updatePoly(); + void updateMatrix(); + void updateAllSettings(); + void resetDefaults(); + void resetPoints(); + void loadPoints(); + qreal getYRatio(int ratio); +}; + +class ControlPoint : public QGraphicsEllipseItem +{ + +public: + OutputSettings *form; + ControlPoint(); + +private: + QVariant itemChange (GraphicsItemChange change, const QVariant & value); +}; + +#endif \ No newline at end of file diff --git a/output/output_settings.ui b/output/output_settings.ui new file mode 100644 index 0000000..1762dbd --- /dev/null +++ b/output/output_settings.ui @@ -0,0 +1,888 @@ + + + OutputSettingsDLG + + + + 0 + 0 + 648 + 854 + + + + Laser output configuration + + + false + + + false + + + + + + + + + + 0 + 0 + + + + + + + + + + Shut down + + + + + + + Blanking + + + + + + Test + + + + + + + Output enable + + + + + + + Blanking enable + + + + + + + Blanking invert + + + + + + + + + + Scanning + + + + + + X enable + + + + + + + X invert + + + + + + + Y enable + + + + + + + Y invert + + + + + + + Swap X && Y + + + + + + + + 1:1 + + + + + 4:3 + + + + + 16:9 + + + + + + + + Anamorphic input + + + + + + + Shrink square + + + + + + + Reset transform + + + + + + + + + + Safety + + + + + + Enforce safety + + + + + + + Qt::Vertical + + + + 20 + 0 + + + + + + + + + + + + + + + Analog settings + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Power + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 100 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 100 + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Offset + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 100 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 100 + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Delay + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 20 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 20 + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + Size + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 100 + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + 100 + + + Qt::Vertical + + + + + + + Qt::Horizontal + + + + 40 + 20 + + + + + + + + + + + + + + + + + + 0 + 0 + 648 + 22 + + + + + File + + + + + + + + + + + &Save settings... + + + Ctrl+S + + + + + &Load settings... + + + Ctrl+O + + + + + &Quit + + + + + + + powerSlider + valueChanged(int) + powerBox + setValue(int) + + + 91 + 718 + + + 116 + 598 + + + + + offsetSlider + valueChanged(int) + offsetBox + setValue(int) + + + 255 + 779 + + + 273 + 598 + + + + + delaySlider + valueChanged(int) + delayBox + setValue(int) + + + 412 + 779 + + + 429 + 598 + + + + + sizeSlider + valueChanged(int) + sizeBox + setValue(int) + + + 568 + 779 + + + 586 + 598 + + + + + sizeBox + valueChanged(int) + sizeSlider + setValue(int) + + + 586 + 598 + + + 568 + 779 + + + + + delayBox + valueChanged(int) + delaySlider + setValue(int) + + + 429 + 598 + + + 412 + 779 + + + + + offsetBox + valueChanged(int) + offsetSlider + setValue(int) + + + 273 + 598 + + + 255 + 779 + + + + + powerBox + valueChanged(int) + powerSlider + setValue(int) + + + 115 + 598 + + + 91 + 718 + + + + + shutdown + clicked() + OutputSettingsDLG + close() + + + 562 + 43 + + + 589 + 151 + + + + + actionQuit + activated() + OutputSettingsDLG + close() + + + -1 + -1 + + + 323 + 426 + + + + + diff --git a/test_patterns/ILDA12K.ild b/test_patterns/ILDA12K.ild new file mode 100644 index 0000000..e7b8c1a Binary files /dev/null and b/test_patterns/ILDA12K.ild differ diff --git a/tools/CMakeLists.txt b/tools/CMakeLists.txt new file mode 100644 index 0000000..b7327dc --- /dev/null +++ b/tools/CMakeLists.txt @@ -0,0 +1,26 @@ +# OpenLase - a realtime laser graphics toolkit +# +# Copyright (C) 2009-2010 Hector Martin "marcan" +# +# This program is free software; you can redistribute it and/or modify +# it under the terms of the GNU General Public License as published by +# the Free Software Foundation, either version 2 or version 3. +# +# This program is distributed in the hope that it will be useful, +# but WITHOUT ANY WARRANTY; without even the implied warranty of +# MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the +# GNU General Public License for more details. +# +# You should have received a copy of the GNU General Public License +# along with this program; if not, write to the Free Software +# Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA +# + +include_directories (${CMAKE_SOURCE_DIR}/include) +link_directories (${CMAKE_BINARY_DIR}/libol) + +add_executable(playilda playilda.c) +target_link_libraries(playilda ${JACK_LIBRARIES}) + +add_executable(playvid playvid.c trace.c) +target_link_libraries(playvid openlase avformat avcodec) diff --git a/tools/genfont.py b/tools/genfont.py new file mode 100644 index 0000000..92557cd --- /dev/null +++ b/tools/genfont.py @@ -0,0 +1,437 @@ +#!/usr/bin/python +# -*- coding: utf-8 -*- + +import os, sys, math +import xml.sax, xml.sax.handler +import re + +def pc(c): + x,y = c + if isinstance(x,int) and isinstance(y,int): + return "(%d,%d)"%(x,y) + else: + return "(%.4f, %.4f)"%(x,y) + +class PathLine(object): + def __init__(self, start, end, on=True): + self.start = start + self.end = end + self.on = on + def copy(self): + return PathLine(self.start, self.end) + def transform(self, func): + self.start = func(self.start) + self.end = func(self.end) + def reverse(self): + return PathLine(self.end, self.start, self.on) + def scp(self): + return self.end + def ecp(self): + return self.start + def showinfo(self, tr=''): + print tr+'Line( %s %s )'%(pc(self.start),pc(self.end)) + +class PathBezier4(object): + def __init__(self, start, cp1, cp2, end): + self.start = start + self.end = end + self.cp1 = cp1 + self.cp2 = cp2 + def copy(self): + return PathBezier4(self.start, self.cp1, self.cp2, self.end) + def transform(self, func): + self.start = func(self.start) + self.cp1 = func(self.cp1) + self.cp2 = func(self.cp2) + self.end = func(self.end) + + def reverse(self): + return PathBezier4(self.end, self.cp2, self.cp1, self.start) + def scp(self): + if self.cp1 != self.start: + return self.cp1 + elif self.cp2 != self.start: + return self.cp2 + else: + return self.end + def ecp(self): + if self.cp2 != self.end: + return self.cp2 + elif self.cp1 != self.end: + return self.cp1 + else: + return self.start + def showinfo(self, tr=''): + print tr+'Bezier( %s %s %s %s )'%(pc(self.start),pc(self.cp1),pc(self.cp2),pc(self.end)) + +class PathBezier3(PathBezier4): + def __init__(self, start, cp, end): + sx,sy = start + cx,cy = cp + ex,ey = end + + c1x = (1.0/3) * cx + (2.0/3) * sx + c1y = (1.0/3) * cy + (2.0/3) * sy + c2x = (1.0/3) * cx + (2.0/3) * ex + c2y = (1.0/3) * cy + (2.0/3) * ey + PathBezier4.__init__(start, (c1x,c1y), (c2x,c2y), end) + +class LaserPath(object): + def __init__(self): + self.segments = [] + def add(self, seg): + self.segments.append(seg) + def transform(self, func): + for i in self.segments: + i.transform(func) + def startpos(self): + return self.segments[0].start + def endpos(self): + return self.segments[-1].end + def reverse(self): + lp = LaserPath() + lp.segments = [x.reverse() for x in self.segments[::-1]] + return lp + def showinfo(self, tr=''): + print tr+'LaserPath:' + for i in self.segments: + i.showinfo(tr+' ') + +class LaserFrame(object): + def __init__(self): + self.objects = [] + def add(self, obj): + self.objects.append(obj) + def transform(self, func): + for i in self.objects: + i.transform(func) + def showinfo(self, tr=''): + print tr+'LaserFrame:' + for i in self.objects: + i.showinfo(tr+' ') + +class SVGPath(object): + def __init__(self, data=None): + self.subpaths = [] + if data: + self.parse(data) + + def poptok(self): + if not self.tokens: + raise ValueError("Expected token but got end of path data") + return self.tokens.pop(0) + def peektok(self): + if not self.tokens: + raise ValueError("Expected token but got end of path data") + return self.tokens[0] + def popnum(self): + tok = self.tokens.pop(0) + try: + return float(tok) + except ValueError: + raise ValueError("Invalid SVG path numerical token: %s"%tok) + def isnum(self): + if not self.tokens: + return False # no more tokens is considered a non-number + try: + float(self.peektok()) + except ValueError: + return False + return True + def popcoord(self,rel=False, cur=None): + x = self.popnum() + if self.peektok() == ',': + self.poptok() + y = self.popnum() + if rel: + x += cur[0] + y += cur[1] + return x,y + def reflect(self, center, point): + cx,cy = center + px,py = point + return (2*cx-px, 2*cy-py) + + def parse(self, data): + ds = re.split(r"[ \r\n\t]*([-+]?\d+\.\d+[eE][+-]?\d+|[-+]?\d+\.\d+|[-+]?\.\d+[eE][+-]?\d+|[-+]?\.\d+|[-+]?\d+\.?[eE][+-]?\d+|[-+]?\d+\.?|[MmZzLlHhVvCcSsQqTtAa])[, \r\n\t]*", data) + tokens = ds[1::2] + if any(ds[::2]) or not all(ds[1::2]): + raise ValueError("Invalid SVG path expression: %r"%data) + self.tokens = tokens + + cur = (0,0) + curcpc = (0,0) + curcpq = (0,0) + sp_start = (0,0) + in_path = False + cmd = None + subpath = LaserPath() + while tokens: + if self.isnum() and cmd in "MmLlHhVvCcSsQqTtAa": + pass + elif self.peektok() in "MmZzLlHhVvCcSsQqTtAa": + cmd = tokens.pop(0) + else: + raise ValueError("Invalid SVG path token %s"%tok) + + rel = cmd.upper() != cmd + ucmd = cmd.upper() + + if not in_path and ucmd != 'M' : + raise ValueErorr("SVG path segment must begin with 'm' command, not '%s'"%cmd) + + if ucmd == 'M': + sp_start = self.popcoord(rel, cur) + if subpath.segments: + self.subpaths.append(subpath) + subpath = LaserPath() + cmd = 'Ll'[rel] + cur = curcpc = curcpq = sp_start + in_path = True + elif ucmd == 'Z': + if sp_start != cur: + subpath.add(PathLine(cur, sp_start)) + cur = curcpc = curcpq = sp_start + in_path = False + elif ucmd == 'L': + end = self.popcoord(rel, cur) + subpath.add(PathLine(cur, end)) + cur = curcpc = curcpq = end + elif ucmd == 'H': + ex = self.popnum() + if rel: + ex += cur[0] + end = ex,cur[1] + subpath.add(PathLine(cur, end)) + cur = curcpc = curcpq = end + elif ucmd == 'V': + ey = self.popnum() + if rel: + ey += cur[1] + end = cur[0],ey + subpath.add(PathLine(cur, end)) + cur = curcpc = curcpq = end + elif ucmd in 'CS': + if ucmd == 'S': + c1 = self.reflect(cur,curcpc) + else: + c1 = self.popcoord(rel, cur) + c2 = curcpc = self.popcoord(rel, cur) + end = self.popcoord(rel, cur) + subpath.add(PathBezier4(cur, c1, c2, end)) + cur = curcpq = end + elif ucmd in 'QT': + if ucmd == 'T': + cp = curcpq = self.reflect(cur,curcpq) + else: + cp = curcpq = self.popcoord(rel, cur) + end = self.popcoord(rel, cur) + subpath.add(PathBezier3(cur, cp, end)) + cur = curcpc = end + elif ucmd in 'Aa': + raise ValueError("Arcs not implemented, biatch") + + if subpath.segments: + self.subpaths.append(subpath) + +class SVGReader(xml.sax.handler.ContentHandler): + def doctype(self, name, pubid, system): + print name,pubid,system + def startDocument(self): + self.frame = LaserFrame() + self.rects = [] + self.matrix_stack = [(1,0,0,1,0,0)] + def endDocument(self): + pass + def startElement(self, name, attrs): + if name == "svg": + self.width = float(attrs['width'].replace("px","")) + self.height = float(attrs['height'].replace("px","")) + elif name == "path": + if 'transform' in attrs.keys(): + self.transform(attrs['transform']) + self.addPath(attrs['d']) + if 'transform' in attrs.keys(): + self.popmatrix() + elif name == "rect": + self.rects.append((attrs['id'],float(attrs['x']),float(attrs['y']),float(attrs['width']),float(attrs['height']))) + elif name == 'g': + if 'transform' in attrs.keys(): + self.transform(attrs['transform']) + else: + self.pushmatrix((1,0,0,1,0,0)) + def endElement(self, name): + if name == 'g': + self.popmatrix() + def mmul(self, m1, m2): + a1,b1,c1,d1,e1,f1 = m1 + a2,b2,c2,d2,e2,f2 = m2 + a3 = a1 * a2 + c1 * b2 + b3 = b1 * a2 + d1 * b2 + c3 = a1 * c2 + c1 * d2 + d3 = b1 * c2 + d1 * d2 + e3 = a1 * e2 + c1 * f2 + e1 + f3 = b1 * e2 + d1 * f2 + f1 + return (a3,b3,c3,d3,e3,f3) + def pushmatrix(self, m): + new_mat = self.mmul(self.matrix_stack[-1], m) + self.matrix_stack.append(new_mat) + def popmatrix(self): + self.matrix_stack.pop() + def tc(self,coord): + vw = vh = max(self.width, self.height) / 2.0 + x,y = coord + x -= self.width / 2.0 + y -= self.height / 2.0 + x = x / vw + y = y / vh + return (x,y) + def ts(self,coord): + a,b,c,d,e,f = self.matrix_stack[-1] + x,y = coord + nx = a*x + c*y + e + ny = b*x + d*y + f + return (nx,ny) + def transform(self, data): + ds = re.split(r"[ \r\n\t]*([a-z]+\([^)]+\)|,)[ \r\n\t]*", data) + tokens = ds[1::2] + if any(ds[::2]) or not all(ds[1::2]): + raise ValueError("Invalid SVG transform expression: %r"%data) + if not all([x == ',' for x in tokens[1::2]]): + raise ValueError("Invalid SVG transform expression: %r"%data) + transforms = tokens[::2] + + mat = (1,0,0,1,0,0) + for t in transforms: + name,rest = t.split("(") + if rest[-1] != ")": + raise ValueError("Invalid SVG transform expression: %r"%data) + args = map(float,rest[:-1].split(",")) + if name == 'matrix': + mat = self.mmul(mat, args) + elif name == 'translate': + tx,ty = args + mat = self.mmul(mat, (1,0,0,1,tx,ty)) + elif name == 'scale': + sx,sy = args + mat = self.mmul(mat, (sx,0,0,sy,0,0)) + elif name == 'rotate': + a = args[0] / 180.0 * math.pi + if len(args) == 3: + cx,cy = args[1:] + mat = self.mmul(mat, (1,0,0,1,cx,cy)) + cos = math.cos(a) + sin = math.sin(a) + mat = self.mmul(mat, (cos,sin,-sin,cos,0,0)) + if len(args) == 3: + mat = self.mmul(mat, (1,0,0,1,-cx,-cy)) + elif name == 'skewX': + a = args[0] / 180.0 * math.pi + mat = self.mmul(mat, (1,0,math.tan(a),1,0,0)) + elif name == 'skewY': + a = args[0] / 180.0 * math.pi + mat = self.mmul(mat, (1,math.tan(a),0,1,0,0)) + self.pushmatrix(mat) + def addPath(self, data): + p = SVGPath(data) + for path in p.subpaths: + path.transform(self.ts) + self.frame.add(path) + +svg = sys.argv[1] +cfile = sys.argv[2] +if len(sys.argv) > 3: + font_name = sys.argv[3] +else: + font_name = "font" + +handler = SVGReader() +parser = xml.sax.make_parser() +parser.setContentHandler(handler) +parser.setFeature(xml.sax.handler.feature_external_ges, False) +parser.parse(sys.argv[1]) + +frame = handler.frame + +cdefs = [] + +max_h = 0 + +output = "" + +output += "#include \"text.h\"\n" + +overlap = 0 + +for id, x, y, w, h in handler.rects: + if id == "overlap": + overlap = w + continue + if id[:5] != "char_": + continue + cv = id[5:] + if len(cv) == 1: + chrval = ord(id[5:]) + else: + chrval = int(cv,0) + + if h > max_h: + max_h = h + + #print >>sys.stderr, "Got rect %s"%id + char_objs = [] + for obj in frame.objects: + sx,sy = obj.segments[0].start + if sx >= x and sy >= y and sx <= (x+w) and sy <= (y+h): + char_objs.append(obj) + #print >>sys.stderr, " - %d objects"%len(char_objs) + + def transform(pt): + px,py = pt + px -= x + py -= y + return (px,py) + + if len(char_objs) != 0: + output += "static const FontPoint fc_%02x[] = {\n"%chrval + + for i,obj in enumerate(char_objs): + obj.transform(transform) + last_obj = i == len(char_objs)-1 + output += "\t{0, %8.4f, %8.4f},\n"%obj.startpos() + + for j,seg in enumerate(obj.segments): + last_seg = j == len(obj.segments)-1 + if last_obj and last_seg: + ctl = 2 + elif last_seg: + ctl = 1 + else: + ctl = 0 + if isinstance(seg, PathBezier4): + output += "\t{0, %8.4f, %8.4f},\n"%seg.cp1 + output += "\t{0, %8.4f, %8.4f},\n"%seg.cp2 + output += "\t{%d, %8.4f, %8.4f},\n"%(ctl, seg.end[0], seg.end[1]) + elif isinstance(seg, PathLine): + output += "\t{0, %8.4f, %8.4f},\n"%seg.start + output += "\t{0, %8.4f, %8.4f},\n"%seg.end + output += "\t{%d, %8.4f, %8.4f},\n"%(ctl, seg.end[0], seg.end[1]) + + output += "};\n" + + cdefs.append((chrval, w, "fc_%02x"%chrval)) + else: + cdefs.append((chrval, w, "NULL")) + +output += "\nstatic const FontChar font_chars[256] = {\n" + +for chrval, width, sym in cdefs: + output += "\t['%s'] = {%8.4f, %s},\n"%(chr(chrval),width,sym) + +output += "};\n\n" + +output += "const Font %s = {%f, %f, font_chars};\n\n"%(font_name,max_h,overlap) + +fd = open(cfile,"w") +fd.write(output) +fd.close() \ No newline at end of file diff --git a/tools/playilda.c b/tools/playilda.c new file mode 100644 index 0000000..c23034a --- /dev/null +++ b/tools/playilda.c @@ -0,0 +1,532 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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 +#include +#include +#include +#include +#include + +#include +#include +#include + +#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 + +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; ipoints[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; ipoints[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; icount; 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; icount; 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= frame->count) + rpos = lpos; + float off = pos - lpos; + opoints[i].x = frame->points[lpos].x * (1-off) + frame->points[rpos].x * off; + opoints[i].y = frame->points[lpos].y * (1-off) + frame->points[rpos].y * off; + opoints[i].z = frame->points[lpos].z * (1-off) + frame->points[rpos].z * off; + opoints[i].color.r = frame->points[lpos].color.r * (1-off) + frame->points[rpos].color.r * off; + opoints[i].color.g = frame->points[lpos].color.g * (1-off) + frame->points[rpos].color.g * off; + opoints[i].color.b = frame->points[lpos].color.b * (1-off) + frame->points[rpos].color.b * off; + opoints[i].state = frame->points[lpos].state | frame->points[rpos].state; + } + + free(frame->points); + frame->points = opoints; + frame->count = ocount; + + return 0; +} + +int main (int argc, char *argv[]) +{ + int frameno = 0; + char **argvp = &argv[1]; + char *fname; + jack_client_t *client; + struct stat st1, st2; + + if (argc > 2 && !strcmp(argvp[0],"-s")) { + scale = 1; + argc--; + argvp++; + } + + if (argc < 2) { + fprintf(stderr, "usage: %s [-s] filename.ild [rate]\n", argv[0]); + return 1; + } + + fname = *argvp; + + if (argc > 2) { + pointrate = atoi(argvp[1]); + } + + if ((client = jack_client_new ("playilda")) == 0) { + fprintf (stderr, "jack server not running?\n"); + return 1; + } + + jack_set_process_callback (client, process, 0); + jack_set_buffer_size_callback (client, bufsize, 0); + jack_set_sample_rate_callback (client, srate, 0); + jack_on_shutdown (client, jack_shutdown, 0); + + out_x = jack_port_register (client, "out_x", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + out_y = jack_port_register (client, "out_y", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + out_z = jack_port_register (client, "out_z", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + out_r = jack_port_register (client, "out_r", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + out_g = jack_port_register (client, "out_g", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + out_b = jack_port_register (client, "out_b", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + out_w = jack_port_register (client, "out_w", JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0); + + memset(frames, 0, sizeof(frames)); + + curframe = curdframe = &frames[frameno]; + if (loadild(fname, curframe) < 0) + { + return 1; + } + + stat(fname, &st1); + + subpos = 0; + + if (jack_activate (client)) { + fprintf (stderr, "cannot activate client"); + return 1; + } + + while (1) { + stat(fname, &st2); + if(st1.st_mtime != st2.st_mtime) { + frameno = !frameno; + printf("Loading new frame to slot %d\n", frameno); + if(frames[frameno].points) + free(frames[frameno].points); + loadild(fname, &frames[frameno]); + printf("New frame loaded\n"); + curframe = &frames[frameno]; + memcpy(&st1, &st2, sizeof(st1)); + } + usleep(100000); + } + jack_client_close (client); + exit (0); +} + diff --git a/tools/playvid.c b/tools/playvid.c new file mode 100644 index 0000000..75a033f --- /dev/null +++ b/tools/playvid.c @@ -0,0 +1,486 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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 +#include +#include +#include +#include +#include +#include + +#include +#include + +#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; inb_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; inb_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 params.on_speed: + subdivide = True + params.rate_divs += 1 + else: + ux = (3.0*x1 - 2.0*x0 - x3)**2 + uy = (3.0*y1 - 2.0*y0 - y3)**2 + vx = (3.0*x2 - 2.0*x3 - x0)**2 + vy = (3.0*y2 - 2.0*y3 - y0)**2 + if ux < vx: + ux = vx + if uy < vy: + uy = vy + if (ux+uy) > params.flatness: + subdivide = True + params.flatness_divs += 1 + if subdivide: + # de Casteljau at t=0.5 + mcx = (x1 + x2) * 0.5 + mcy = (y1 + y2) * 0.5 + ax1 = (x0 + x1) * 0.5 + ay1 = (y0 + y1) * 0.5 + ax2 = (ax1 + mcx) * 0.5 + ay2 = (ay1 + mcy) * 0.5 + bx2 = (x2 + x3) * 0.5 + by2 = (y2 + y3) * 0.5 + bx1 = (bx2 + mcx) * 0.5 + by1 = (by2 + mcy) * 0.5 + xm = (ax2 + bx1) * 0.5 + ym = (ay2 + by1) * 0.5 + curve1 = PathBezier4((x0,y0),(ax1,ay1),(ax2,ay2),(xm,ym)) + curve2 = PathBezier4((xm,ym),(bx1,by1),(bx2,by2),(x3,y3)) + return curve1.render(params) + curve2.render(params) + else: + params.points_bezier += 1 + return [LaserSample((int(x3*params.width),int(y3*params.height)))] + def reverse(self): + return PathBezier4(self.end, self.cp2, self.cp1, self.start) + def scp(self): + if self.cp1 != self.start: + return self.cp1 + elif self.cp2 != self.start: + return self.cp2 + else: + return self.end + def ecp(self): + if self.cp2 != self.end: + return self.cp2 + elif self.cp1 != self.end: + return self.cp1 + else: + return self.start + def showinfo(self, tr=''): + print tr+'Bezier( %s %s %s %s )'%(pc(self.start),pc(self.cp1),pc(self.cp2),pc(self.end)) + +class LaserPath(object): + def __init__(self): + self.segments = [] + def add(self, seg): + self.segments.append(seg) + def transform(self, func): + for i in self.segments: + i.transform(func) + def render(self, params): + is_closed = self.segments[0].start == self.segments[-1].end + sx,sy = self.segments[0].start + out = [] + if is_closed: + out += [LaserSample((int(sx*params.width),int(sy*params.height)))] * params.closed_start_dwell + else: + out += [LaserSample((int(sx*params.width),int(sy*params.height)))] * params.start_dwell + params.points_dwell_start += params.start_dwell + for i,s in enumerate(self.segments): + params.subpaths += 1 + out += s.render(params) + ex,ey = s.end + end_render = [LaserSample((int(ex*params.width),int(ey*params.height)))] + if i != len(self.segments)-1: + ecx,ecy = s.ecp() + sx,sy = self.segments[i+1].start + scx,scy = self.segments[i+1].scp() + + dex,dey = ecx-ex,ecy-ey + dsx,dsy = sx-scx,sy-scy + + dot = dex*dsx + dey*dsy + lens = math.sqrt(dex**2 + dey**2) * math.sqrt(dsx**2 + dsy**2) + if lens == 0: + # bail + out += end_render * params.corner_dwell + params.points_dwell_corner += params.corner_dwell + else: + dot /= lens + curve_angle = math.cos(params.curve_angle*(math.pi/180.0)) + if dot > curve_angle: + out += end_render * params.curve_dwell + params.points_dwell_curve += params.curve_dwell + else: + out += end_render * params.corner_dwell + params.points_dwell_corner += params.corner_dwell + elif is_closed: + out += end_render * params.closed_end_dwell + overdraw = out[:params.closed_overdraw] + out += overdraw + else: + out += end_render * params.end_dwell + params.points_dwell_end += params.end_dwell + return out + def startpos(self): + return self.segments[0].start + def endpos(self): + return self.segments[-1].end + def reverse(self): + lp = LaserPath() + lp.segments = [x.reverse() for x in self.segments[::-1]] + return lp + def showinfo(self, tr=''): + print tr+'LaserPath:' + for i in self.segments: + i.showinfo(tr+' ') + +class LaserFrame(object): + def __init__(self): + self.objects = [] + def add(self, obj): + self.objects.append(obj) + def transform(self, func): + for i in self.objects: + i.transform(func) + def render(self, params): + if not self.objects: + return [] + out = [] + cpos = self.objects[-1].endpos() + for i in self.objects: + trip = [LaserSample((int(cpos[0]*params.width),int(cpos[1]*params.height)))] * params.switch_dwell + trip += PathLine(cpos,i.startpos(),False).render(params) + trip += [LaserSample((int(i.startpos()[0]*params.width),int(i.startpos()[1]*params.height)))] * params.switch_dwell + params.points_dwell_switch += params.switch_dwell * 2 + for s in trip: + s.on = False + out += trip + out += i.render(params) + cpos = i.endpos() + params.objects += 1 + params.points = len(out) + params.points_on = sum([int(s.on) for s in out]) + return out + def sort(self): + oobj = [] + cx,cy = 0,0 + while self.objects: + best = 99999 + besti = 0 + bestr = False + for i,o in enumerate(self.objects): + x,y = o.startpos() + d = (x-cx)**2 + (y-cy)**2 + if d < best: + best = d + besti = i + x,y = o.endpos() + d = (x-cx)**2 + (y-cy)**2 + if d < best: + best = d + besti = i + bestr = True + obj = self.objects.pop(besti) + if bestr: + obj = obj.reverse() + oobj.append(obj) + cx,cy = obj.endpos() + self.objects = oobj + def showinfo(self, tr=''): + print tr+'LaserFrame:' + for i in self.objects: + i.showinfo(tr+' ') + +class LaserSample(object): + def __init__(self, coord, on=True): + self.coord = coord + self.on = on + def __str__(self): + return "[%d] %d,%d"%(int(self.on),self.coord[0],self.coord[1]) + def __repr__(self): + return "LaserSample((%d,%d),%r)"%(self.coord[0],self.coord[1],self.on) + +class SVGPath(object): + def __init__(self, data=None): + self.subpaths = [] + if data: + self.parse(data) + + def poptok(self): + if not self.tokens: + raise ValueError("Expected token but got end of path data") + return self.tokens.pop(0) + def peektok(self): + if not self.tokens: + raise ValueError("Expected token but got end of path data") + return self.tokens[0] + def popnum(self): + tok = self.tokens.pop(0) + try: + return float(tok) + except ValueError: + raise ValueError("Invalid SVG path numerical token: %s"%tok) + def isnum(self): + if not self.tokens: + return False # no more tokens is considered a non-number + try: + float(self.peektok()) + except ValueError: + return False + return True + def popcoord(self,rel=False, cur=None): + x = self.popnum() + if self.peektok() == ',': + self.poptok() + y = self.popnum() + if rel: + x += cur[0] + y += cur[1] + return x,y + def reflect(self, center, point): + cx,cy = center + px,py = point + return (2*cx-px, 2*cy-py) + + def parse(self, data): + ds = re.split(r"[ \r\n\t]*([-+]?\d+\.\d+[eE][+-]?\d+|[-+]?\d+\.\d+|[-+]?\.\d+[eE][+-]?\d+|[-+]?\.\d+|[-+]?\d+\.?[eE][+-]?\d+|[-+]?\d+\.?|[MmZzLlHhVvCcSsQqTtAa])[, \r\n\t]*", data) + tokens = ds[1::2] + if any(ds[::2]) or not all(ds[1::2]): + raise ValueError("Invalid SVG path expression: %r"%data) + self.tokens = tokens + + cur = (0,0) + curcpc = (0,0) + curcpq = (0,0) + sp_start = (0,0) + in_path = False + cmd = None + subpath = LaserPath() + while tokens: + if self.isnum() and cmd in "MmLlHhVvCcSsQqTtAa": + pass + elif self.peektok() in "MmZzLlHhVvCcSsQqTtAa": + cmd = tokens.pop(0) + else: + raise ValueError("Invalid SVG path token %s"%tok) + + rel = cmd.upper() != cmd + ucmd = cmd.upper() + + if not in_path and ucmd != 'M' : + raise ValueErorr("SVG path segment must begin with 'm' command, not '%s'"%cmd) + + if ucmd == 'M': + sp_start = self.popcoord(rel, cur) + if subpath.segments: + self.subpaths.append(subpath) + subpath = LaserPath() + cmd = 'Ll'[rel] + cur = curcpc = curcpq = sp_start + in_path = True + elif ucmd == 'Z': + if sp_start != cur: + subpath.add(PathLine(cur, sp_start)) + cur = curcpc = curcpq = sp_start + in_path = False + elif ucmd == 'L': + end = self.popcoord(rel, cur) + subpath.add(PathLine(cur, end)) + cur = curcpc = curcpq = end + elif ucmd == 'H': + ex = self.popnum() + if rel: + ex += cur[0] + end = ex,cur[1] + subpath.add(PathLine(cur, end)) + cur = curcpc = curcpq = end + elif ucmd == 'V': + ey = self.popnum() + if rel: + ey += cur[1] + end = cur[0],ey + subpath.add(PathLine(cur, end)) + cur = curcpc = curcpq = end + elif ucmd in 'CS': + if ucmd == 'S': + c1 = self.reflect(cur,curcpc) + else: + c1 = self.popcoord(rel, cur) + c2 = curcpc = self.popcoord(rel, cur) + end = self.popcoord(rel, cur) + subpath.add(PathBezier4(cur, c1, c2, end)) + cur = curcpq = end + elif ucmd in 'QT': + if ucmd == 'T': + cp = curcpq = self.reflect(cur,curcpq) + else: + cp = curcpq = self.popcoord(rel, cur) + end = self.popcoord(rel, cur) + subpath.add(PathBezier3(cur, cp, end)) + cur = curcpc = end + elif ucmd in 'Aa': + raise ValueError("Arcs not implemented, biatch") + + if subpath.segments: + self.subpaths.append(subpath) + +class SVGReader(xml.sax.handler.ContentHandler): + def doctype(self, name, pubid, system): + print name,pubid,system + def startDocument(self): + self.frame = LaserFrame() + self.matrix_stack = [(1,0,0,1,0,0)] + def endDocument(self): + self.frame.transform(self.tc) + def startElement(self, name, attrs): + if name == "svg": + self.width = float(attrs['width'].replace("px","")) + self.height = float(attrs['height'].replace("px","")) + elif name == "path": + if 'transform' in attrs.keys(): + self.transform(attrs['transform']) + self.addPath(attrs['d']) + if 'transform' in attrs.keys(): + self.popmatrix() + elif name == 'g': + if 'transform' in attrs.keys(): + self.transform(attrs['transform']) + else: + self.pushmatrix((1,0,0,1,0,0)) + def endElement(self, name): + if name == 'g': + self.popmatrix() + def mmul(self, m1, m2): + a1,b1,c1,d1,e1,f1 = m1 + a2,b2,c2,d2,e2,f2 = m2 + a3 = a1 * a2 + c1 * b2 + b3 = b1 * a2 + d1 * b2 + c3 = a1 * c2 + c1 * d2 + d3 = b1 * c2 + d1 * d2 + e3 = a1 * e2 + c1 * f2 + e1 + f3 = b1 * e2 + d1 * f2 + f1 + return (a3,b3,c3,d3,e3,f3) + def pushmatrix(self, m): + new_mat = self.mmul(self.matrix_stack[-1], m) + self.matrix_stack.append(new_mat) + def popmatrix(self): + self.matrix_stack.pop() + def tc(self,coord): + vw = vh = max(self.width, self.height) / 2.0 + x,y = coord + x -= self.width / 2.0 + y -= self.height / 2.0 + x = x / vw + y = y / vh + return (x,y) + def ts(self,coord): + a,b,c,d,e,f = self.matrix_stack[-1] + x,y = coord + nx = a*x + c*y + e + ny = b*x + d*y + f + return (nx,ny) + def transform(self, data): + ds = re.split(r"[ \r\n\t]*([a-z]+\([^)]+\)|,)[ \r\n\t]*", data) + tokens = ds[1::2] + if any(ds[::2]) or not all(ds[1::2]): + raise ValueError("Invalid SVG transform expression: %r"%data) + if not all([x == ',' for x in tokens[1::2]]): + raise ValueError("Invalid SVG transform expression: %r"%data) + transforms = tokens[::2] + + mat = (1,0,0,1,0,0) + for t in transforms: + name,rest = t.split("(") + if rest[-1] != ")": + raise ValueError("Invalid SVG transform expression: %r"%data) + args = map(float,rest[:-1].split(",")) + if name == 'matrix': + mat = self.mmul(mat, args) + elif name == 'translate': + tx,ty = args + mat = self.mmul(mat, (1,0,0,1,tx,ty)) + elif name == 'scale': + sx,sy = args + mat = self.mmul(mat, (sx,0,0,sy,0,0)) + elif name == 'rotate': + a = args[0] / 180.0 * math.pi + if len(args) == 3: + cx,cy = args[1:] + mat = self.mmul(mat, (1,0,0,1,cx,cy)) + cos = math.cos(a) + sin = math.sin(a) + mat = self.mmul(mat, (cos,sin,-sin,cos,0,0)) + if len(args) == 3: + mat = self.mmul(mat, (1,0,0,1,-cx,-cy)) + elif name == 'skewX': + a = args[0] / 180.0 * math.pi + mat = self.mmul(mat, (1,0,math.tan(a),1,0,0)) + elif name == 'skewY': + a = args[0] / 180.0 * math.pi + mat = self.mmul(mat, (1,math.tan(a),0,1,0,0)) + self.pushmatrix(mat) + def addPath(self, data): + p = SVGPath(data) + for path in p.subpaths: + path.transform(self.ts) + self.frame.add(path) + +optimize = True +params = RenderParameters() + +if sys.argv[1] == "-noopt": + optimize = False + sys.argv = [sys.argv[0]] + sys.argv[2:] + +if sys.argv[1] == "-cfg": + params.load(sys.argv[2]) + sys.argv = [sys.argv[0]] + sys.argv[3:] + +handler = SVGReader() +print "Parse" +parser = xml.sax.make_parser() +parser.setContentHandler(handler) +parser.setFeature(xml.sax.handler.feature_external_ges, False) +parser.parse(sys.argv[1]) +print "Parsed" + +frame = handler.frame +#frame.showinfo() +if optimize: + frame.sort() + +print "Render" +rframe = frame.render(params) +print "Done" + +import struct + +for i,sample in enumerate(rframe): + if sample.on: + rframe = rframe[:i] + [sample]*params.extra_first_dwell + rframe[i+1:] + break + +fout = open(sys.argv[2], "wb") + +dout = struct.pack(">4s3xB8s8sHHHBx", "ILDA", 1, "svg2ilda", "", len(rframe), 1, 1, 0) +for i,sample in enumerate(rframe): + x,y = sample.coord + mode = 0 + if i == len(rframe): + mode |= 0x80 + if params.invert: + sample.on = not sample.on + if params.force: + sample.on = True + if not sample.on: + mode |= 0x40 + if abs(x) > params.width : + raise ValueError("X out of bounds") + if abs(y) > params.height : + raise ValueError("Y out of bounds") + dout += struct.pack(">hhBB",x,-y,mode,0x00) + +frame_time = len(rframe) / float(params.rate) + +if (frame_time*2) < params.time: + count = int(params.time / frame_time) + dout = dout * count + +fout.write(dout) + +print "Statistics:" +print " Objects: %d"%params.objects +print " Subpaths: %d"%params.subpaths +print " Bezier subdivisions:" +print " Due to rate: %d"%params.rate_divs +print " Due to flatness: %d"%params.flatness_divs +print " Points: %d"%params.points +print " Trip: %d"%params.points_trip +print " Line: %d"%params.points_line +print " Bezier: %d"%params.points_bezier +print " Start dwell: %d"%params.points_dwell_start +print " Curve dwell: %d"%params.points_dwell_curve +print " Corner dwell: %d"%params.points_dwell_corner +print " End dwell: %d"%params.points_dwell_end +print " Switch dwell: %d"%params.points_dwell_switch +print " Total on: %d"%params.points_on +print " Total off: %d"%(params.points - params.points_on) +print " Efficiency: %.3f"%(params.points_on/float(params.points)) +print " Framerate: %.3f"%(params.rate/float(params.points)) diff --git a/tools/trace.c b/tools/trace.c new file mode 100644 index 0000000..a27de25 --- /dev/null +++ b/tools/trace.c @@ -0,0 +1,282 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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 +#include +#include + +#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 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 maxx) { + px--; py++; maxx--; dir++; + } + break; + case 1: + py++; + if (py > maxy) { + py--; px--; maxy--; dir++; + } + break; + case 2: + px--; + if (px < minx) { + px++; py--; minx++; dir++; + } + break; + case 3: + py--; + if (py < miny) { + py++; px++; miny++; dir=0; + } + break; + } + } +#ifdef DEBUG + FILE *f = fopen("dump.bin","wb"); + fwrite(dbg, h, s*3, f); + fclose(f); +#endif + //printf("Dup'd: %d\n", dpcnt); + return objects; +} + diff --git a/tools/trace.h b/tools/trace.h new file mode 100644 index 0000000..af86815 --- /dev/null +++ b/tools/trace.h @@ -0,0 +1,25 @@ +/* + OpenLase - a realtime laser graphics toolkit + +Copyright (C) 2009-2010 Hector Martin "marcan" + +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