gsl-shell.git - gsl-shell

index : gsl-shell.git
gsl-shell
summary refs log tree commit diff
diff options
context:
space:
mode:
Diffstat
-rw-r--r--agg-plot/Makefile 5
-rw-r--r--agg-plot/agg-parse-trans.cpp 201
-rw-r--r--agg-plot/agg-parse-trans.h 16
-rw-r--r--agg-plot/agg_platform_support_win32.cpp 4
-rw-r--r--agg-plot/agg_platform_support_x11.cpp 2522
-rw-r--r--agg-plot/colors.cpp 24
-rw-r--r--agg-plot/colors.h 5
-rw-r--r--agg-plot/lua-draw.cpp 54
-rw-r--r--agg-plot/lua-plot.cpp 232
-rw-r--r--agg-plot/plot-window.cpp 345
-rw-r--r--agg-plot/plot-window.h 16
-rw-r--r--agg-plot/trans.h 22
-rw-r--r--agg-plot/xwin-show.cpp 4
-rw-r--r--examples/anim.lua 34
-rw-r--r--gs-types.c 2
-rw-r--r--gs-types.h 1
-rw-r--r--lua-gsl.c 2
-rw-r--r--lua-utils.c 14
-rw-r--r--pre3d/pre3d.js 1095
-rw-r--r--pre3d/pre3d.lua 949
-rw-r--r--pre3d/pre3d_shape_utils.lua 836
-rw-r--r--pre3d/test.lua 106
-rw-r--r--scripts/project-dir-compare.py 75
23 files changed, 5038 insertions, 1526 deletions
diff --git a/agg-plot/Makefile b/agg-plot/Makefile
index e1945ae2..de471055 100644
--- a/agg-plot/Makefile
+++ b/agg-plot/Makefile
@@ -44,7 +44,7 @@ endif
INCLUDES += $(AGG_INCLUDES) -I$(GSH_BASE_DIR) -I$(LUADIR)/src
-AGGPLOT_SRC_FILES = $(PLATSUP_SRC_FILES) utils.cpp units.cpp colors.cpp lua-draw.cpp lua-plot.cpp xwin-show.cpp
+AGGPLOT_SRC_FILES = $(PLATSUP_SRC_FILES) utils.cpp units.cpp colors.cpp lua-draw.cpp lua-plot.cpp agg-parse-trans.cpp xwin-show.cpp plot-window.cpp
AGGPLOT_OBJ_FILES := $(AGGPLOT_SRC_FILES:%.cpp=%.o)
@@ -58,9 +58,6 @@ TARGETS = libaggplot.a
all: $(TARGETS)
-#test.exe: $(AGGPLOT_OBJ_FILES)
-# gcc -o test.exe $(AGGPLOT_OBJ_FILES) $(AGG_LIBS)
-
libaggplot.a: $(AGGPLOT_OBJ_FILES)
$(AR) $@ $?
$(RANLIB) $@
diff --git a/agg-plot/agg-parse-trans.cpp b/agg-plot/agg-parse-trans.cpp
new file mode 100644
index 00000000..30657622
--- /dev/null
+++ b/agg-plot/agg-parse-trans.cpp
@@ -0,0 +1,201 @@
+
+extern "C" {
+#include "lua.h"
+#include "lauxlib.h"
+}
+
+#include "agg-parse-trans.h"
+#include "lua-cpp-utils.h"
+#include "lua-utils.h"
+#include "gs-types.h"
+#include "trans.h"
+
+struct property_reg {
+ int id;
+ const char *name;
+};
+
+struct builder_reg {
+ const char *name;
+ vertex_source *(*func)(lua_State *, int, vertex_source *);
+};
+
+static vertex_source * build_stroke (lua_State *L, int i, vertex_source *s);
+static vertex_source * build_curve (lua_State *L, int i, vertex_source *s);
+static vertex_source * build_marker (lua_State *L, int i, vertex_source *s);
+static vertex_source * build_dash (lua_State *L, int i, vertex_source *s);
+static vertex_source * build_extend (lua_State *L, int i, vertex_source *s);
+static vertex_source * build_translate (lua_State *L, int i, vertex_source *s);
+static vertex_source * build_rotate (lua_State *L, int i, vertex_source *s);
+
+struct property_reg line_cap_properties[] = {
+ {(int) agg::butt_cap, "butt" },
+ {(int) agg::square_cap, "square"},
+ {(int) agg::round_cap, "round" },
+ {0, NULL}
+};
+
+struct property_reg line_join_properties[] = {
+ {(int) agg::miter_join, "miter" },
+ {(int) agg::miter_join_revert, "miter.rev" },
+ {(int) agg::round_join, "round" },
+ {(int) agg::bevel_join, "bevel" },
+ {(int) agg::miter_join_round, "miter.round"},
+ {0, NULL}
+};
+
+const builder_reg builder_table[] = {
+ {"stroke", build_stroke },
+ {"dash", build_dash},
+ {"curve", build_curve},
+ {"marker", build_marker},
+ {"extend", build_extend},
+ {"translate", build_translate},
+ {"rotate", build_rotate},
+ {NULL, NULL}
+};
+
+static int
+property_lookup (struct property_reg *prop, const char *key)
+{
+ int default_value = prop->id;
+
+ if (key == NULL)
+ return default_value;
+
+ for ( ; prop->name; prop++)
+ {
+ if (strcmp (prop->name, key) == 0)
+ return prop->id;
+ }
+
+ return default_value;
+}
+
+vertex_source *
+build_stroke (lua_State *L, int specindex, vertex_source *obj)
+{
+ double width = mlua_named_optnumber (L, specindex, "width", 1.0);
+ const char *cap_str = mlua_named_optstring (L, specindex, "cap", NULL);
+ const char *join_str = mlua_named_optstring (L, specindex, "join", NULL);
+
+ trans::stroke *stroke = new trans::stroke(obj, width);
+
+ if (cap_str)
+ {
+ int cap = property_lookup (line_cap_properties, cap_str);
+ stroke->line_cap((agg::line_cap_e) cap);
+ }
+
+ if (join_str)
+ {
+ int join = property_lookup (line_join_properties, join_str);
+ stroke->line_join((agg::line_join_e) join);
+ }
+
+ return (vertex_source *) stroke;
+}
+
+vertex_source *
+build_curve (lua_State *L, int specindex, vertex_source *obj)
+{
+ trans::curve *c = new trans::curve(obj);
+ return (vertex_source *) c;
+}
+
+vertex_source *
+build_marker (lua_State *L, int specindex, vertex_source *obj)
+{
+ double size = mlua_named_optnumber (L, specindex, "size", 3.0);
+ return (vertex_source *) new trans::marker(obj, size);
+}
+
+vertex_source *
+build_dash (lua_State *L, int specindex, vertex_source *obj)
+{
+ double a = mlua_named_optnumber (L, specindex, "a", 10.0);
+ double b = mlua_named_optnumber (L, specindex, "b", a);
+
+ trans::dash *dash = new trans::dash(obj);
+ dash->add_dash(a, b);
+
+ return (vertex_source *) dash;
+}
+
+vertex_source *
+build_extend (lua_State *L, int specindex, vertex_source *obj)
+{
+ double width = mlua_named_optnumber (L, specindex, "width", 1.0);
+ return (vertex_source *) new trans::extend(obj, width);
+}
+
+vertex_source *
+build_translate (lua_State *L, int specindex, vertex_source *obj)
+{
+ double x = mlua_named_number (L, specindex, "x");
+ double y = mlua_named_number (L, specindex, "y");
+
+ trans::affine *t = new trans::affine(obj);
+ t->translate(x, y);
+
+ return (vertex_source *) t;
+}
+
+vertex_source *
+build_rotate (lua_State *L, int specindex, vertex_source *obj)
+{
+ double a = mlua_named_number (L, specindex, "angle");
+
+ trans::affine *t = new trans::affine(obj);
+ t->rotate(a);
+
+ return (vertex_source *) t;
+}
+
+vertex_source *
+parse_spec (lua_State *L, int specindex, vertex_source *obj)
+{
+ const char *tag;
+
+ lua_rawgeti (L, specindex, 1);
+ if (! lua_isstring (L, -1))
+ {
+ luaL_error (L, "the tag of the transformation is invalid");
+ return NULL;
+ }
+
+ tag = lua_tostring (L, -1);
+ lua_pop (L, 1);
+
+ for (const builder_reg *b = builder_table; b->name != NULL; b++)
+ {
+ if (strcmp (b->name, tag) == 0)
+ return b->func (L, specindex, obj);
+ }
+
+ luaL_error (L, "invalid trasformation tag");
+ return NULL;
+}
+
+vertex_source *
+parse_spec_pipeline (lua_State *L, int index, vertex_source *obj)
+{
+ size_t k, nb;
+
+ if (lua_type (L, index) == LUA_TTABLE)
+ nb = lua_objlen (L, index);
+ else
+ {
+ luaL_error (L, "post transform argument should be a table");
+ return NULL;
+ }
+
+ for (k = nb; k > 0; k--)
+ {
+ lua_rawgeti (L, index, k);
+ obj = parse_spec (L, index+1, obj);
+ lua_pop (L, 1);
+ }
+
+ return obj;
+}
diff --git a/agg-plot/agg-parse-trans.h b/agg-plot/agg-parse-trans.h
new file mode 100644
index 00000000..87973582
--- /dev/null
+++ b/agg-plot/agg-parse-trans.h
@@ -0,0 +1,16 @@
+#ifndef AGG_PARSE_TRANS_H
+#define AGG_PARSE_TRANS_H
+
+extern "C" {
+#include "lua.h"
+}
+
+#include "vertex-source.h"
+
+extern vertex_source * parse_spec (lua_State *L, int specindex,
+ vertex_source *obj);
+
+extern vertex_source * parse_spec_pipeline (lua_State *L, int index,
+ vertex_source *obj);
+
+#endif
diff --git a/agg-plot/agg_platform_support_win32.cpp b/agg-plot/agg_platform_support_win32.cpp
index 71589048..3a8f52a9 100644
--- a/agg-plot/agg_platform_support_win32.cpp
+++ b/agg-plot/agg_platform_support_win32.cpp
@@ -1056,8 +1056,6 @@ namespace agg
{
MSG msg;
- pthread_mutex_lock (m_specific->m_mutex);
-
for(;;)
{
if(m_wait_mode)
@@ -1093,8 +1091,6 @@ namespace agg
}
}
- pthread_mutex_unlock (m_specific->m_mutex);
-
return (int)msg.wParam;
}
diff --git a/agg-plot/agg_platform_support_x11.cpp b/agg-plot/agg_platform_support_x11.cpp
index 567f8e44..8e9152a7 100644
--- a/agg-plot/agg_platform_support_x11.cpp
+++ b/agg-plot/agg_platform_support_x11.cpp
@@ -1,1263 +1,1259 @@
-//----------------------------------------------------------------------------
-// Anti-Grain Geometry (AGG) - Version 2.5
-// A high quality rendering engine for C++
-// Copyright (C) 2002-2006 Maxim Shemanarev
-// Contact: mcseem@antigrain.com
-// mcseemagg@yahoo.com
-// http://antigrain.com
-//
-// AGG 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
-// of the License, or (at your option) any later version.
-//
-// AGG 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 AGG; if not, write to the Free Software
-// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
-// MA 02110-1301, USA.
-//----------------------------------------------------------------------------
-
-#include <stdio.h>
-#include <string.h>
-#include <stdlib.h>
-#include <ctype.h>
-#include <time.h>
-#include <X11/Xlib.h>
-#include <X11/Xutil.h>
-#include <X11/Xatom.h>
-#include <X11/keysym.h>
-#include <pthread.h>
-#include "agg_basics.h"
-#include "util/agg_color_conv_rgb8.h"
-#include "platform/agg_platform_support.h"
-
-
-namespace agg
-{
- //------------------------------------------------------------------------
- class platform_specific
- {
- public:
- platform_specific(pix_format_e format, bool flip_y);
- ~platform_specific();
-
- void caption(const char* capt);
- void put_image(const rendering_buffer* src);
-
- pix_format_e m_format;
- pix_format_e m_sys_format;
- int m_byte_order;
- bool m_flip_y;
- unsigned m_bpp;
- unsigned m_sys_bpp;
- Display* m_display;
- int m_screen;
- int m_depth;
- Visual* m_visual;
- Window m_window;
- GC m_gc;
- XImage* m_ximg_window;
- XSetWindowAttributes m_window_attributes;
- Atom m_close_atom;
- unsigned char* m_buf_window;
- unsigned char* m_buf_img[platform_support::max_images];
- unsigned m_keymap[256];
-
- bool m_update_flag;
- bool m_resize_flag;
- bool m_initialized;
- bool m_is_mapped;
- clock_t m_sw_start;
-
- pthread_mutex_t m_mutex[1];
-
- };
-
-
-
- //------------------------------------------------------------------------
- platform_specific::platform_specific(pix_format_e format, bool flip_y) :
- m_format(format),
- m_sys_format(pix_format_undefined),
- m_byte_order(LSBFirst),
- m_flip_y(flip_y),
- m_bpp(0),
- m_sys_bpp(0),
- m_display(0),
- m_screen(0),
- m_depth(0),
- m_visual(0),
- m_window(0),
- m_gc(0),
- m_ximg_window(0),
- m_close_atom(0),
-
- m_buf_window(0),
-
- m_update_flag(true),
- m_resize_flag(true),
- m_initialized(false),
- m_is_mapped(false)
- {
- memset(m_buf_img, 0, sizeof(m_buf_img));
-
- unsigned i;
- for(i = 0; i < 256; i++)
- {
- m_keymap[i] = i;
- }
-
- m_keymap[XK_Pause&0xFF] = key_pause;
- m_keymap[XK_Clear&0xFF] = key_clear;
-
- m_keymap[XK_KP_0&0xFF] = key_kp0;
- m_keymap[XK_KP_1&0xFF] = key_kp1;
- m_keymap[XK_KP_2&0xFF] = key_kp2;
- m_keymap[XK_KP_3&0xFF] = key_kp3;
- m_keymap[XK_KP_4&0xFF] = key_kp4;
- m_keymap[XK_KP_5&0xFF] = key_kp5;
- m_keymap[XK_KP_6&0xFF] = key_kp6;
- m_keymap[XK_KP_7&0xFF] = key_kp7;
- m_keymap[XK_KP_8&0xFF] = key_kp8;
- m_keymap[XK_KP_9&0xFF] = key_kp9;
-
- m_keymap[XK_KP_Insert&0xFF] = key_kp0;
- m_keymap[XK_KP_End&0xFF] = key_kp1;
- m_keymap[XK_KP_Down&0xFF] = key_kp2;
- m_keymap[XK_KP_Page_Down&0xFF] = key_kp3;
- m_keymap[XK_KP_Left&0xFF] = key_kp4;
- m_keymap[XK_KP_Begin&0xFF] = key_kp5;
- m_keymap[XK_KP_Right&0xFF] = key_kp6;
- m_keymap[XK_KP_Home&0xFF] = key_kp7;
- m_keymap[XK_KP_Up&0xFF] = key_kp8;
- m_keymap[XK_KP_Page_Up&0xFF] = key_kp9;
- m_keymap[XK_KP_Delete&0xFF] = key_kp_period;
- m_keymap[XK_KP_Decimal&0xFF] = key_kp_period;
- m_keymap[XK_KP_Divide&0xFF] = key_kp_divide;
- m_keymap[XK_KP_Multiply&0xFF] = key_kp_multiply;
- m_keymap[XK_KP_Subtract&0xFF] = key_kp_minus;
- m_keymap[XK_KP_Add&0xFF] = key_kp_plus;
- m_keymap[XK_KP_Enter&0xFF] = key_kp_enter;
- m_keymap[XK_KP_Equal&0xFF] = key_kp_equals;
-
- m_keymap[XK_Up&0xFF] = key_up;
- m_keymap[XK_Down&0xFF] = key_down;
- m_keymap[XK_Right&0xFF] = key_right;
- m_keymap[XK_Left&0xFF] = key_left;
- m_keymap[XK_Insert&0xFF] = key_insert;
- m_keymap[XK_Home&0xFF] = key_delete;
- m_keymap[XK_End&0xFF] = key_end;
- m_keymap[XK_Page_Up&0xFF] = key_page_up;
- m_keymap[XK_Page_Down&0xFF] = key_page_down;
-
- m_keymap[XK_F1&0xFF] = key_f1;
- m_keymap[XK_F2&0xFF] = key_f2;
- m_keymap[XK_F3&0xFF] = key_f3;
- m_keymap[XK_F4&0xFF] = key_f4;
- m_keymap[XK_F5&0xFF] = key_f5;
- m_keymap[XK_F6&0xFF] = key_f6;
- m_keymap[XK_F7&0xFF] = key_f7;
- m_keymap[XK_F8&0xFF] = key_f8;
- m_keymap[XK_F9&0xFF] = key_f9;
- m_keymap[XK_F10&0xFF] = key_f10;
- m_keymap[XK_F11&0xFF] = key_f11;
- m_keymap[XK_F12&0xFF] = key_f12;
- m_keymap[XK_F13&0xFF] = key_f13;
- m_keymap[XK_F14&0xFF] = key_f14;
- m_keymap[XK_F15&0xFF] = key_f15;
-
- m_keymap[XK_Num_Lock&0xFF] = key_numlock;
- m_keymap[XK_Caps_Lock&0xFF] = key_capslock;
- m_keymap[XK_Scroll_Lock&0xFF] = key_scrollock;
-
- switch(m_format)
- {
- default: break;
- case pix_format_gray8:
- m_bpp = 8;
- break;
-
- case pix_format_rgb565:
- case pix_format_rgb555:
- m_bpp = 16;
- break;
-
- case pix_format_rgb24:
- case pix_format_bgr24:
- m_bpp = 24;
- break;
-
- case pix_format_bgra32:
- case pix_format_abgr32:
- case pix_format_argb32:
- case pix_format_rgba32:
- m_bpp = 32;
- break;
- }
- m_sw_start = clock();
-
- pthread_mutex_init (m_mutex, NULL);
- }
-
- //------------------------------------------------------------------------
- platform_specific::~platform_specific()
- {
- pthread_mutex_destroy (m_mutex);
- }
-
- //------------------------------------------------------------------------
- void platform_specific::caption(const char* capt)
- {
- // Fixed by Enno Fennema (in original AGG library)
- XStoreName(m_display, m_window, capt);
- XSetIconName(m_display, m_window, capt);
- }
-
-
- //------------------------------------------------------------------------
- void platform_specific::put_image(const rendering_buffer* src)
- {
- if(m_ximg_window == 0) return;
- m_ximg_window->data = (char*)m_buf_window;
-
- if(m_format == m_sys_format)
- {
- XPutImage(m_display,
- m_window,
- m_gc,
- m_ximg_window,
- 0, 0, 0, 0,
- src->width(),
- src->height());
- }
- else
- {
- int row_len = src->width() * m_sys_bpp / 8;
- unsigned char* buf_tmp =
- new unsigned char[row_len * src->height()];
-
- rendering_buffer rbuf_tmp;
- rbuf_tmp.attach(buf_tmp,
- src->width(),
- src->height(),
- m_flip_y ? -row_len : row_len);
-
- switch(m_sys_format)
- {
- default: break;
- case pix_format_rgb555:
- switch(m_format)
- {
- default: break;
- case pix_format_rgb555: color_conv(&rbuf_tmp, src, color_conv_rgb555_to_rgb555()); break;
- case pix_format_rgb565: color_conv(&rbuf_tmp, src, color_conv_rgb565_to_rgb555()); break;
- case pix_format_rgb24: color_conv(&rbuf_tmp, src, color_conv_rgb24_to_rgb555()); break;
- case pix_format_bgr24: color_conv(&rbuf_tmp, src, color_conv_bgr24_to_rgb555()); break;
- case pix_format_rgba32: color_conv(&rbuf_tmp, src, color_conv_rgba32_to_rgb555()); break;
- case pix_format_argb32: color_conv(&rbuf_tmp, src, color_conv_argb32_to_rgb555()); break;
- case pix_format_bgra32: color_conv(&rbuf_tmp, src, color_conv_bgra32_to_rgb555()); break;
- case pix_format_abgr32: color_conv(&rbuf_tmp, src, color_conv_abgr32_to_rgb555()); break;
- }
- break;
-
- case pix_format_rgb565:
- switch(m_format)
- {
- default: break;
- case pix_format_rgb555: color_conv(&rbuf_tmp, src, color_conv_rgb555_to_rgb565()); break;
- case pix_format_rgb565: color_conv(&rbuf_tmp, src, color_conv_rgb565_to_rgb565()); break;
- case pix_format_rgb24: color_conv(&rbuf_tmp, src, color_conv_rgb24_to_rgb565()); break;
- case pix_format_bgr24: color_conv(&rbuf_tmp, src, color_conv_bgr24_to_rgb565()); break;
- case pix_format_rgba32: color_conv(&rbuf_tmp, src, color_conv_rgba32_to_rgb565()); break;
- case pix_format_argb32: color_conv(&rbuf_tmp, src, color_conv_argb32_to_rgb565()); break;
- case pix_format_bgra32: color_conv(&rbuf_tmp, src, color_conv_bgra32_to_rgb565()); break;
- case pix_format_abgr32: color_conv(&rbuf_tmp, src, color_conv_abgr32_to_rgb565()); break;
- }
- break;
-
- case pix_format_rgba32:
- switch(m_format)
- {
- default: break;
- case pix_format_rgb555: color_conv(&rbuf_tmp, src, color_conv_rgb555_to_rgba32()); break;
- case pix_format_rgb565: color_conv(&rbuf_tmp, src, color_conv_rgb565_to_rgba32()); break;
- case pix_format_rgb24: color_conv(&rbuf_tmp, src, color_conv_rgb24_to_rgba32()); break;
- case pix_format_bgr24: color_conv(&rbuf_tmp, src, color_conv_bgr24_to_rgba32()); break;
- case pix_format_rgba32: color_conv(&rbuf_tmp, src, color_conv_rgba32_to_rgba32()); break;
- case pix_format_argb32: color_conv(&rbuf_tmp, src, color_conv_argb32_to_rgba32()); break;
- case pix_format_bgra32: color_conv(&rbuf_tmp, src, color_conv_bgra32_to_rgba32()); break;
- case pix_format_abgr32: color_conv(&rbuf_tmp, src, color_conv_abgr32_to_rgba32()); break;
- }
- break;
-
- case pix_format_abgr32:
- switch(m_format)
- {
- default: break;
- case pix_format_rgb555: color_conv(&rbuf_tmp, src, color_conv_rgb555_to_abgr32()); break;
- case pix_format_rgb565: color_conv(&rbuf_tmp, src, color_conv_rgb565_to_abgr32()); break;
- case pix_format_rgb24: color_conv(&rbuf_tmp, src, color_conv_rgb24_to_abgr32()); break;
- case pix_format_bgr24: color_conv(&rbuf_tmp, src, color_conv_bgr24_to_abgr32()); break;
- case pix_format_abgr32: color_conv(&rbuf_tmp, src, color_conv_abgr32_to_abgr32()); break;
- case pix_format_rgba32: color_conv(&rbuf_tmp, src, color_conv_rgba32_to_abgr32()); break;
- case pix_format_argb32: color_conv(&rbuf_tmp, src, color_conv_argb32_to_abgr32()); break;
- case pix_format_bgra32: color_conv(&rbuf_tmp, src, color_conv_bgra32_to_abgr32()); break;
- }
- break;
-
- case pix_format_argb32:
- switch(m_format)
- {
- default: break;
- case pix_format_rgb555: color_conv(&rbuf_tmp, src, color_conv_rgb555_to_argb32()); break;
- case pix_format_rgb565: color_conv(&rbuf_tmp, src, color_conv_rgb565_to_argb32()); break;
- case pix_format_rgb24: color_conv(&rbuf_tmp, src, color_conv_rgb24_to_argb32()); break;
- case pix_format_bgr24: color_conv(&rbuf_tmp, src, color_conv_bgr24_to_argb32()); break;
- case pix_format_rgba32: color_conv(&rbuf_tmp, src, color_conv_rgba32_to_argb32()); break;
- case pix_format_argb32: color_conv(&rbuf_tmp, src, color_conv_argb32_to_argb32()); break;
- case pix_format_abgr32: color_conv(&rbuf_tmp, src, color_conv_abgr32_to_argb32()); break;
- case pix_format_bgra32: color_conv(&rbuf_tmp, src, color_conv_bgra32_to_argb32()); break;
- }
- break;
-
- case pix_format_bgra32:
- switch(m_format)
- {
- default: break;
- case pix_format_rgb555: color_conv(&rbuf_tmp, src, color_conv_rgb555_to_bgra32()); break;
- case pix_format_rgb565: color_conv(&rbuf_tmp, src, color_conv_rgb565_to_bgra32()); break;
- case pix_format_rgb24: color_conv(&rbuf_tmp, src, color_conv_rgb24_to_bgra32()); break;
- case pix_format_bgr24: color_conv(&rbuf_tmp, src, color_conv_bgr24_to_bgra32()); break;
- case pix_format_rgba32: color_conv(&rbuf_tmp, src, color_conv_rgba32_to_bgra32()); break;
- case pix_format_argb32: color_conv(&rbuf_tmp, src, color_conv_argb32_to_bgra32()); break;
- case pix_format_abgr32: color_conv(&rbuf_tmp, src, color_conv_abgr32_to_bgra32()); break;
- case pix_format_bgra32: color_conv(&rbuf_tmp, src, color_conv_bgra32_to_bgra32()); break;
- }
- break;
- }
-
- m_ximg_window->data = (char*)buf_tmp;
- XPutImage(m_display,
- m_window,
- m_gc,
- m_ximg_window,
- 0, 0, 0, 0,
- src->width(),
- src->height());
-
- delete [] buf_tmp;
- }
- }
-
- //------------------------------------------------------------------------
- platform_support::platform_support(pix_format_e format, bool flip_y) :
- m_specific(new platform_specific(format, flip_y)),
- m_format(format),
- m_bpp(m_specific->m_bpp),
- m_window_flags(0),
- m_wait_mode(true),
- m_flip_y(flip_y),
- m_initial_width(10),
- m_initial_height(10)
- {
- strcpy(m_caption, "AGG Application");
- }
-
- //------------------------------------------------------------------------
- platform_support::~platform_support()
- {
- delete m_specific;
- }
-
-
-
- //------------------------------------------------------------------------
- void platform_support::caption(const char* cap)
- {
- strcpy(m_caption, cap);
- if(m_specific->m_initialized)
- {
- m_specific->caption(cap);
- }
- }
-
-
- //------------------------------------------------------------------------
- enum xevent_mask_e
- {
- xevent_mask =
- PointerMotionMask|
- ButtonPressMask|
- ButtonReleaseMask|
- ExposureMask|
- KeyPressMask|
- StructureNotifyMask
- };
-
-
- //------------------------------------------------------------------------
- bool platform_support::init(unsigned width, unsigned height, unsigned flags)
- {
- m_window_flags = flags;
-
- m_specific->m_display = XOpenDisplay(NULL);
- if(m_specific->m_display == 0)
- {
- fprintf(stderr, "Unable to open DISPLAY!\n");
- return false;
- }
-
- m_specific->m_screen = XDefaultScreen(m_specific->m_display);
- m_specific->m_depth = XDefaultDepth(m_specific->m_display,
- m_specific->m_screen);
- m_specific->m_visual = XDefaultVisual(m_specific->m_display,
- m_specific->m_screen);
- unsigned long r_mask = m_specific->m_visual->red_mask;
- unsigned long g_mask = m_specific->m_visual->green_mask;
- unsigned long b_mask = m_specific->m_visual->blue_mask;
-
- if(m_specific->m_depth < 15 ||
- r_mask == 0 || g_mask == 0 || b_mask == 0)
- {
- fprintf(stderr,
- "There's no Visual compatible with minimal AGG requirements:\n"
- "At least 15-bit color depth and True- or DirectColor class.\n\n");
- XCloseDisplay(m_specific->m_display);
- return false;
- }
-
- int t = 1;
- int hw_byte_order = LSBFirst;
- if(*(char*)&t == 0) hw_byte_order = MSBFirst;
-
- // Perceive SYS-format by mask
- switch(m_specific->m_depth)
- {
- case 15:
- m_specific->m_sys_bpp = 16;
- if(r_mask == 0x7C00 && g_mask == 0x3E0 && b_mask == 0x1F)
- {
- m_specific->m_sys_format = pix_format_rgb555;
- m_specific->m_byte_order = hw_byte_order;
- }
- break;
-
- case 16:
- m_specific->m_sys_bpp = 16;
- if(r_mask == 0xF800 && g_mask == 0x7E0 && b_mask == 0x1F)
- {
- m_specific->m_sys_format = pix_format_rgb565;
- m_specific->m_byte_order = hw_byte_order;
- }
- break;
-
- case 24:
- case 32:
- m_specific->m_sys_bpp = 32;
- if(g_mask == 0xFF00)
- {
- if(r_mask == 0xFF && b_mask == 0xFF0000)
- {
- switch(m_specific->m_format)
- {
- case pix_format_rgba32:
- m_specific->m_sys_format = pix_format_rgba32;
- m_specific->m_byte_order = LSBFirst;
- break;
-
- case pix_format_abgr32:
- m_specific->m_sys_format = pix_format_abgr32;
- m_specific->m_byte_order = MSBFirst;
- break;
-
- default:
- m_specific->m_byte_order = hw_byte_order;
- m_specific->m_sys_format =
- (hw_byte_order == LSBFirst) ?
- pix_format_rgba32 :
- pix_format_abgr32;
- break;
- }
- }
-
- if(r_mask == 0xFF0000 && b_mask == 0xFF)
- {
- switch(m_specific->m_format)
- {
- case pix_format_argb32:
- m_specific->m_sys_format = pix_format_argb32;
- m_specific->m_byte_order = MSBFirst;
- break;
-
- case pix_format_bgra32:
- m_specific->m_sys_format = pix_format_bgra32;
- m_specific->m_byte_order = LSBFirst;
- break;
-
- default:
- m_specific->m_byte_order = hw_byte_order;
- m_specific->m_sys_format =
- (hw_byte_order == MSBFirst) ?
- pix_format_argb32 :
- pix_format_bgra32;
- break;
- }
- }
- }
- break;
- }
-
- if(m_specific->m_sys_format == pix_format_undefined)
- {
- fprintf(stderr,
- "RGB masks are not compatible with AGG pixel formats:\n"
- "R=%08x, R=%08x, B=%08x\n",
- (unsigned)r_mask, (unsigned)g_mask, (unsigned)b_mask);
- XCloseDisplay(m_specific->m_display);
- return false;
- }
-
-
-
- memset(&m_specific->m_window_attributes,
- 0,
- sizeof(m_specific->m_window_attributes));
-
- m_specific->m_window_attributes.border_pixel =
- XBlackPixel(m_specific->m_display, m_specific->m_screen);
-
- m_specific->m_window_attributes.background_pixel =
- XWhitePixel(m_specific->m_display, m_specific->m_screen);
-
- m_specific->m_window_attributes.override_redirect = 0;
-
- unsigned long window_mask = CWBackPixel | CWBorderPixel;
-
- m_specific->m_window =
- XCreateWindow(m_specific->m_display,
- XDefaultRootWindow(m_specific->m_display),
- 0, 0,
- width,
- height,
- 0,
- m_specific->m_depth,
- InputOutput,
- CopyFromParent,
- window_mask,
- &m_specific->m_window_attributes);
-
-
- m_specific->m_gc = XCreateGC(m_specific->m_display,
- m_specific->m_window,
- 0, 0);
- m_specific->m_buf_window =
- new unsigned char[width * height * (m_bpp / 8)];
-
- memset(m_specific->m_buf_window, 255, width * height * (m_bpp / 8));
-
- m_rbuf_window.attach(m_specific->m_buf_window,
- width,
- height,
- m_flip_y ? -width * (m_bpp / 8) : width * (m_bpp / 8));
-
- m_specific->m_ximg_window =
- XCreateImage(m_specific->m_display,
- m_specific->m_visual, //CopyFromParent,
- m_specific->m_depth,
- ZPixmap,
- 0,
- (char*)m_specific->m_buf_window,
- width,
- height,
- m_specific->m_sys_bpp,
- width * (m_specific->m_sys_bpp / 8));
- m_specific->m_ximg_window->byte_order = m_specific->m_byte_order;
-
- m_specific->caption(m_caption);
- m_initial_width = width;
- m_initial_height = height;
-
- if(!m_specific->m_initialized)
- {
- on_init();
- m_specific->m_initialized = true;
- }
-
- trans_affine_resizing(width, height);
- on_resize(width, height);
- m_specific->m_update_flag = true;
-
- XSizeHints *hints = XAllocSizeHints();
- if(hints)
- {
- if(flags & window_resize)
- {
- hints->min_width = 32;
- hints->min_height = 32;
- hints->max_width = 4096;
- hints->max_height = 4096;
- }
- else
- {
- hints->min_width = width;
- hints->min_height = height;
- hints->max_width = width;
- hints->max_height = height;
- }
- hints->flags = PMaxSize | PMinSize;
-
- XSetWMNormalHints(m_specific->m_display,
- m_specific->m_window,
- hints);
-
- XFree(hints);
- }
-
-
- XMapWindow(m_specific->m_display,
- m_specific->m_window);
-
- XSelectInput(m_specific->m_display,
- m_specific->m_window,
- xevent_mask);
-
-
- m_specific->m_close_atom = XInternAtom(m_specific->m_display,
- "WM_DELETE_WINDOW",
- false);
-
- XSetWMProtocols(m_specific->m_display,
- m_specific->m_window,
- &m_specific->m_close_atom,
- 1);
-
- return true;
- }
-
-
-
- //------------------------------------------------------------------------
- void platform_support::update_window()
- {
- if (! m_specific->m_is_mapped)
- return;
-
- m_specific->put_image(&m_rbuf_window);
-
- // When m_wait_mode is true we can discard all the events
- // came while the image is being drawn. In this case
- // the X server does not accumulate mouse motion events.
- // When m_wait_mode is false, i.e. we have some idle drawing
- // we cannot afford to miss any events
- XSync(m_specific->m_display, m_wait_mode);
- }
-
-
- //------------------------------------------------------------------------
- int platform_support::run()
- {
- pthread_mutex_lock (m_specific->m_mutex);
-
- XFlush(m_specific->m_display);
-
- bool quit = false;
- unsigned flags;
- int cur_x;
- int cur_y;
-
- while(!quit)
- {
- if(m_specific->m_update_flag && m_specific->m_is_mapped)
- {
- on_draw();
- update_window();
- m_specific->m_update_flag = false;
- }
-
- if(!m_wait_mode)
- {
- if(XPending(m_specific->m_display) == 0)
- {
- on_idle();
- continue;
- }
- }
-
- XEvent x_event;
- pthread_mutex_unlock (m_specific->m_mutex);
- XNextEvent(m_specific->m_display, &x_event);
- pthread_mutex_lock (m_specific->m_mutex);
-
- // In the Idle mode discard all intermediate MotionNotify events
- if(!m_wait_mode && x_event.type == MotionNotify)
- {
- XEvent te = x_event;
- for(;;)
- {
- if(XPending(m_specific->m_display) == 0) break;
- XNextEvent(m_specific->m_display, &te);
- if(te.type != MotionNotify) break;
- }
- x_event = te;
- }
-
- switch(x_event.type)
- {
- case MapNotify:
- {
- on_draw();
- update_window();
- m_specific->m_is_mapped = true;
- m_specific->m_update_flag = false;
- }
- break;
-
- case ConfigureNotify:
- {
- if(x_event.xconfigure.width != int(m_rbuf_window.width()) ||
- x_event.xconfigure.height != int(m_rbuf_window.height()))
- {
- int width = x_event.xconfigure.width;
- int height = x_event.xconfigure.height;
-
- delete [] m_specific->m_buf_window;
- m_specific->m_ximg_window->data = 0;
- XDestroyImage(m_specific->m_ximg_window);
-
- m_specific->m_buf_window =
- new unsigned char[width * height * (m_bpp / 8)];
-
- m_rbuf_window.attach(m_specific->m_buf_window,
- width,
- height,
- m_flip_y ?
- -width * (m_bpp / 8) :
- width * (m_bpp / 8));
-
- m_specific->m_ximg_window =
- XCreateImage(m_specific->m_display,
- m_specific->m_visual, //CopyFromParent,
- m_specific->m_depth,
- ZPixmap,
- 0,
- (char*)m_specific->m_buf_window,
- width,
- height,
- m_specific->m_sys_bpp,
- width * (m_specific->m_sys_bpp / 8));
- m_specific->m_ximg_window->byte_order = m_specific->m_byte_order;
-
- trans_affine_resizing(width, height);
- on_resize(width, height);
- on_draw();
- update_window();
- }
- }
- break;
-
- case Expose:
- m_specific->put_image(&m_rbuf_window);
- XFlush(m_specific->m_display);
- XSync(m_specific->m_display, false);
- break;
-
- case KeyPress:
- {
- KeySym key = XLookupKeysym(&x_event.xkey, 0);
- flags = 0;
- if(x_event.xkey.state & Button1Mask) flags |= mouse_left;
- if(x_event.xkey.state & Button3Mask) flags |= mouse_right;
- if(x_event.xkey.state & ShiftMask) flags |= kbd_shift;
- if(x_event.xkey.state & ControlMask) flags |= kbd_ctrl;
-
- bool left = false;
- bool up = false;
- bool right = false;
- bool down = false;
-
- switch(m_specific->m_keymap[key & 0xFF])
- {
- case key_left:
- left = true;
- break;
-
- case key_up:
- up = true;
- break;
-
- case key_right:
- right = true;
- break;
-
- case key_down:
- down = true;
- break;
-
- case key_f2:
- copy_window_to_img(max_images - 1);
- save_img(max_images - 1, "screenshot");
- break;
- }
-
- if(m_ctrls.on_arrow_keys(left, right, down, up))
- {
- on_ctrl_change();
- force_redraw();
- }
- else
- {
- on_key(x_event.xkey.x,
- m_flip_y ?
- m_rbuf_window.height() - x_event.xkey.y :
- x_event.xkey.y,
- m_specific->m_keymap[key & 0xFF],
- flags);
- }
- }
- break;
-
-
- case ButtonPress:
- {
- flags = 0;
- if(x_event.xbutton.state & ShiftMask) flags |= kbd_shift;
- if(x_event.xbutton.state & ControlMask) flags |= kbd_ctrl;
- if(x_event.xbutton.button == Button1) flags |= mouse_left;
- if(x_event.xbutton.button == Button3) flags |= mouse_right;
-
- cur_x = x_event.xbutton.x;
- cur_y = m_flip_y ? m_rbuf_window.height() - x_event.xbutton.y :
- x_event.xbutton.y;
-
- if(flags & mouse_left)
- {
- if(m_ctrls.on_mouse_button_down(cur_x, cur_y))
- {
- m_ctrls.set_cur(cur_x, cur_y);
- on_ctrl_change();
- force_redraw();
- }
- else
- {
- if(m_ctrls.in_rect(cur_x, cur_y))
- {
- if(m_ctrls.set_cur(cur_x, cur_y))
- {
- on_ctrl_change();
- force_redraw();
- }
- }
- else
- {
- on_mouse_button_down(cur_x, cur_y, flags);
- }
- }
- }
- if(flags & mouse_right)
- {
- on_mouse_button_down(cur_x, cur_y, flags);
- }
- //m_specific->m_wait_mode = m_wait_mode;
- //m_wait_mode = true;
- }
- break;
-
-
- case MotionNotify:
- {
- flags = 0;
- if(x_event.xmotion.state & Button1Mask) flags |= mouse_left;
- if(x_event.xmotion.state & Button3Mask) flags |= mouse_right;
- if(x_event.xmotion.state & ShiftMask) flags |= kbd_shift;
- if(x_event.xmotion.state & ControlMask) flags |= kbd_ctrl;
-
- cur_x = x_event.xbutton.x;
- cur_y = m_flip_y ? m_rbuf_window.height() - x_event.xbutton.y :
- x_event.xbutton.y;
-
- if(m_ctrls.on_mouse_move(cur_x, cur_y, (flags & mouse_left) != 0))
- {
- on_ctrl_change();
- force_redraw();
- }
- else
- {
- if(!m_ctrls.in_rect(cur_x, cur_y))
- {
- on_mouse_move(cur_x, cur_y, flags);
- }
- }
- }
- break;
-
- case ButtonRelease:
- {
- flags = 0;
- if(x_event.xbutton.state & ShiftMask) flags |= kbd_shift;
- if(x_event.xbutton.state & ControlMask) flags |= kbd_ctrl;
- if(x_event.xbutton.button == Button1) flags |= mouse_left;
- if(x_event.xbutton.button == Button3) flags |= mouse_right;
-
- cur_x = x_event.xbutton.x;
- cur_y = m_flip_y ? m_rbuf_window.height() - x_event.xbutton.y :
- x_event.xbutton.y;
-
- if(flags & mouse_left)
- {
- if(m_ctrls.on_mouse_button_up(cur_x, cur_y))
- {
- on_ctrl_change();
- force_redraw();
- }
- }
- if(flags & (mouse_left | mouse_right))
- {
- on_mouse_button_up(cur_x, cur_y, flags);
- }
- }
- //m_wait_mode = m_specific->m_wait_mode;
- break;
-
- case ClientMessage:
- if((x_event.xclient.format == 32) &&
- (x_event.xclient.data.l[0] == int(m_specific->m_close_atom)))
- {
- quit = true;
- }
- break;
- }
- }
-
-
- unsigned i = platform_support::max_images;
- while(i--)
- {
- if(m_specific->m_buf_img[i])
- {
- delete [] m_specific->m_buf_img[i];
- }
- }
-
- delete [] m_specific->m_buf_window;
- m_specific->m_ximg_window->data = 0;
- XDestroyImage(m_specific->m_ximg_window);
- XFreeGC(m_specific->m_display, m_specific->m_gc);
- XDestroyWindow(m_specific->m_display, m_specific->m_window);
- XCloseDisplay(m_specific->m_display);
-
- pthread_mutex_unlock (m_specific->m_mutex);
-
- return 0;
- }
-
-
-
- //------------------------------------------------------------------------
- const char* platform_support::img_ext() const { return ".ppm"; }
-
- //------------------------------------------------------------------------
- const char* platform_support::full_file_name(const char* file_name)
- {
- return file_name;
- }
-
- //------------------------------------------------------------------------
- bool platform_support::load_img(unsigned idx, const char* file)
- {
- if(idx < max_images)
- {
- char buf[1024];
- strcpy(buf, file);
- int len = strlen(buf);
- if(len < 4 || strcasecmp(buf + len - 4, ".ppm") != 0)
- {
- strcat(buf, ".ppm");
- }
-
- FILE* fd = fopen(buf, "rb");
- if(fd == 0) return false;
-
- if((len = fread(buf, 1, 1022, fd)) == 0)
- {
- fclose(fd);
- return false;
- }
- buf[len] = 0;
-
- if(buf[0] != 'P' && buf[1] != '6')
- {
- fclose(fd);
- return false;
- }
-
- char* ptr = buf + 2;
-
- while(*ptr && !isdigit(*ptr)) ptr++;
- if(*ptr == 0)
- {
- fclose(fd);
- return false;
- }
-
- unsigned width = atoi(ptr);
- if(width == 0 || width > 4096)
- {
- fclose(fd);
- return false;
- }
- while(*ptr && isdigit(*ptr)) ptr++;
- while(*ptr && !isdigit(*ptr)) ptr++;
- if(*ptr == 0)
- {
- fclose(fd);
- return false;
- }
- unsigned height = atoi(ptr);
- if(height == 0 || height > 4096)
- {
- fclose(fd);
- return false;
- }
- while(*ptr && isdigit(*ptr)) ptr++;
- while(*ptr && !isdigit(*ptr)) ptr++;
- if(atoi(ptr) != 255)
- {
- fclose(fd);
- return false;
- }
- while(*ptr && isdigit(*ptr)) ptr++;
- if(*ptr == 0)
- {
- fclose(fd);
- return false;
- }
- ptr++;
- fseek(fd, long(ptr - buf), SEEK_SET);
-
- create_img(idx, width, height);
- bool ret = true;
-
- if(m_format == pix_format_rgb24)
- {
- fread(m_specific->m_buf_img[idx], 1, width * height * 3, fd);
- }
- else
- {
- unsigned char* buf_img = new unsigned char [width * height * 3];
- rendering_buffer rbuf_img;
- rbuf_img.attach(buf_img,
- width,
- height,
- m_flip_y ?
- -width * 3 :
- width * 3);
-
- fread(buf_img, 1, width * height * 3, fd);
-
- switch(m_format)
- {
- case pix_format_rgb555:
- color_conv(m_rbuf_img+idx, &rbuf_img, color_conv_rgb24_to_rgb555());
- break;
-
- case pix_format_rgb565:
- color_conv(m_rbuf_img+idx, &rbuf_img, color_conv_rgb24_to_rgb565());
- break;
-
- case pix_format_bgr24:
- color_conv(m_rbuf_img+idx, &rbuf_img, color_conv_rgb24_to_bgr24());
- break;
-
- case pix_format_rgba32:
- color_conv(m_rbuf_img+idx, &rbuf_img, color_conv_rgb24_to_rgba32());
- break;
-
- case pix_format_argb32:
- color_conv(m_rbuf_img+idx, &rbuf_img, color_conv_rgb24_to_argb32());
- break;
-
- case pix_format_bgra32:
- color_conv(m_rbuf_img+idx, &rbuf_img, color_conv_rgb24_to_bgra32());
- break;
-
- case pix_format_abgr32:
- color_conv(m_rbuf_img+idx, &rbuf_img, color_conv_rgb24_to_abgr32());
- break;
-
- default:
- ret = false;
- }
- delete [] buf_img;
- }
-
- fclose(fd);
- return ret;
- }
- return false;
- }
-
-
-
-
- //------------------------------------------------------------------------
- bool platform_support::save_img(unsigned idx, const char* file)
- {
- if(idx < max_images && rbuf_img(idx).buf())
- {
- char buf[1024];
- strcpy(buf, file);
- int len = strlen(buf);
- if(len < 4 || strcasecmp(buf + len - 4, ".ppm") != 0)
- {
- strcat(buf, ".ppm");
- }
-
- FILE* fd = fopen(buf, "wb");
- if(fd == 0) return false;
-
- unsigned w = rbuf_img(idx).width();
- unsigned h = rbuf_img(idx).height();
-
- fprintf(fd, "P6\n%d %d\n255\n", w, h);
-
- unsigned y;
- unsigned char* tmp_buf = new unsigned char [w * 3];
- for(y = 0; y < rbuf_img(idx).height(); y++)
- {
- const unsigned char* src = rbuf_img(idx).row_ptr(m_flip_y ? h - 1 - y : y);
- switch(m_format)
- {
- default: break;
- case pix_format_rgb555:
- color_conv_row(tmp_buf, src, w, color_conv_rgb555_to_rgb24());
- break;
-
- case pix_format_rgb565:
- color_conv_row(tmp_buf, src, w, color_conv_rgb565_to_rgb24());
- break;
-
- case pix_format_bgr24:
- color_conv_row(tmp_buf, src, w, color_conv_bgr24_to_rgb24());
- break;
-
- case pix_format_rgb24:
- color_conv_row(tmp_buf, src, w, color_conv_rgb24_to_rgb24());
- break;
-
- case pix_format_rgba32:
- color_conv_row(tmp_buf, src, w, color_conv_rgba32_to_rgb24());
- break;
-
- case pix_format_argb32:
- color_conv_row(tmp_buf, src, w, color_conv_argb32_to_rgb24());
- break;
-
- case pix_format_bgra32:
- color_conv_row(tmp_buf, src, w, color_conv_bgra32_to_rgb24());
- break;
-
- case pix_format_abgr32:
- color_conv_row(tmp_buf, src, w, color_conv_abgr32_to_rgb24());
- break;
- }
- fwrite(tmp_buf, 1, w * 3, fd);
- }
- delete [] tmp_buf;
- fclose(fd);
- return true;
- }
- return false;
- }
-
-
-
- //------------------------------------------------------------------------
- bool platform_support::create_img(unsigned idx, unsigned width, unsigned height)
- {
- if(idx < max_images)
- {
- if(width == 0) width = rbuf_window().width();
- if(height == 0) height = rbuf_window().height();
- delete [] m_specific->m_buf_img[idx];
- m_specific->m_buf_img[idx] =
- new unsigned char[width * height * (m_bpp / 8)];
-
- m_rbuf_img[idx].attach(m_specific->m_buf_img[idx],
- width,
- height,
- m_flip_y ?
- -width * (m_bpp / 8) :
- width * (m_bpp / 8));
- return true;
- }
- return false;
- }
-
-
- //------------------------------------------------------------------------
- void platform_support::force_redraw()
- {
- m_specific->m_update_flag = true;
- }
-
-
- //------------------------------------------------------------------------
- void platform_support::message(const char* msg)
- {
- fprintf(stderr, "%s\n", msg);
- }
-
- //------------------------------------------------------------------------
- void platform_support::start_timer()
- {
- m_specific->m_sw_start = clock();
- }
-
- //------------------------------------------------------------------------
- double platform_support::elapsed_time() const
- {
- clock_t stop = clock();
- return double(stop - m_specific->m_sw_start) * 1000.0 / CLOCKS_PER_SEC;
- }
-
-
- //------------------------------------------------------------------------
- void platform_support::on_init() {}
- void platform_support::on_resize(int sx, int sy) {}
- void platform_support::on_idle() {}
- void platform_support::on_mouse_move(int x, int y, unsigned flags) {}
- void platform_support::on_mouse_button_down(int x, int y, unsigned flags) {}
- void platform_support::on_mouse_button_up(int x, int y, unsigned flags) {}
- void platform_support::on_key(int x, int y, unsigned key, unsigned flags) {}
- void platform_support::on_ctrl_change() {}
- void platform_support::on_draw() {}
- void platform_support::on_post_draw(void* raw_handler) {}
-
-
-
-}
-
-
-void platform_support_prepare()
-{
-}
-
-void platform_support_lock(agg::platform_support *app)
-{
- pthread_mutex_lock (app->m_specific->m_mutex);
-}
-
-void platform_support_unlock(agg::platform_support *app)
-{
- pthread_mutex_unlock (app->m_specific->m_mutex);
-}
-
-bool platform_support_is_mapped(agg::platform_support *app)
-{
- return app->m_specific->m_is_mapped;
-}
+//----------------------------------------------------------------------------
+// Anti-Grain Geometry (AGG) - Version 2.5
+// A high quality rendering engine for C++
+// Copyright (C) 2002-2006 Maxim Shemanarev
+// Contact: mcseem@antigrain.com
+// mcseemagg@yahoo.com
+// http://antigrain.com
+//
+// AGG 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
+// of the License, or (at your option) any later version.
+//
+// AGG 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 AGG; if not, write to the Free Software
+// Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston,
+// MA 02110-1301, USA.
+//----------------------------------------------------------------------------
+
+#include <stdio.h>
+#include <string.h>
+#include <stdlib.h>
+#include <ctype.h>
+#include <time.h>
+#include <X11/Xlib.h>
+#include <X11/Xutil.h>
+#include <X11/Xatom.h>
+#include <X11/keysym.h>
+#include <pthread.h>
+#include "agg_basics.h"
+#include "util/agg_color_conv_rgb8.h"
+#include "platform/agg_platform_support.h"
+
+
+namespace agg
+{
+ //------------------------------------------------------------------------
+ class platform_specific
+ {
+ public:
+ platform_specific(pix_format_e format, bool flip_y);
+ ~platform_specific();
+
+ void caption(const char* capt);
+ void put_image(const rendering_buffer* src);
+
+ pix_format_e m_format;
+ pix_format_e m_sys_format;
+ int m_byte_order;
+ bool m_flip_y;
+ unsigned m_bpp;
+ unsigned m_sys_bpp;
+ Display* m_display;
+ int m_screen;
+ int m_depth;
+ Visual* m_visual;
+ Window m_window;
+ GC m_gc;
+ XImage* m_ximg_window;
+ XSetWindowAttributes m_window_attributes;
+ Atom m_close_atom;
+ unsigned char* m_buf_window;
+ unsigned char* m_buf_img[platform_support::max_images];
+ unsigned m_keymap[256];
+
+ bool m_update_flag;
+ bool m_resize_flag;
+ bool m_initialized;
+ bool m_is_mapped;
+ clock_t m_sw_start;
+
+ pthread_mutex_t m_mutex[1];
+
+ };
+
+
+
+ //------------------------------------------------------------------------
+ platform_specific::platform_specific(pix_format_e format, bool flip_y) :
+ m_format(format),
+ m_sys_format(pix_format_undefined),
+ m_byte_order(LSBFirst),
+ m_flip_y(flip_y),
+ m_bpp(0),
+ m_sys_bpp(0),
+ m_display(0),
+ m_screen(0),
+ m_depth(0),
+ m_visual(0),
+ m_window(0),
+ m_gc(0),
+ m_ximg_window(0),
+ m_close_atom(0),
+
+ m_buf_window(0),
+
+ m_update_flag(true),
+ m_resize_flag(true),
+ m_initialized(false),
+ m_is_mapped(false)
+ {
+ memset(m_buf_img, 0, sizeof(m_buf_img));
+
+ unsigned i;
+ for(i = 0; i < 256; i++)
+ {
+ m_keymap[i] = i;
+ }
+
+ m_keymap[XK_Pause&0xFF] = key_pause;
+ m_keymap[XK_Clear&0xFF] = key_clear;
+
+ m_keymap[XK_KP_0&0xFF] = key_kp0;
+ m_keymap[XK_KP_1&0xFF] = key_kp1;
+ m_keymap[XK_KP_2&0xFF] = key_kp2;
+ m_keymap[XK_KP_3&0xFF] = key_kp3;
+ m_keymap[XK_KP_4&0xFF] = key_kp4;
+ m_keymap[XK_KP_5&0xFF] = key_kp5;
+ m_keymap[XK_KP_6&0xFF] = key_kp6;
+ m_keymap[XK_KP_7&0xFF] = key_kp7;
+ m_keymap[XK_KP_8&0xFF] = key_kp8;
+ m_keymap[XK_KP_9&0xFF] = key_kp9;
+
+ m_keymap[XK_KP_Insert&0xFF] = key_kp0;
+ m_keymap[XK_KP_End&0xFF] = key_kp1;
+ m_keymap[XK_KP_Down&0xFF] = key_kp2;
+ m_keymap[XK_KP_Page_Down&0xFF] = key_kp3;
+ m_keymap[XK_KP_Left&0xFF] = key_kp4;
+ m_keymap[XK_KP_Begin&0xFF] = key_kp5;
+ m_keymap[XK_KP_Right&0xFF] = key_kp6;
+ m_keymap[XK_KP_Home&0xFF] = key_kp7;
+ m_keymap[XK_KP_Up&0xFF] = key_kp8;
+ m_keymap[XK_KP_Page_Up&0xFF] = key_kp9;
+ m_keymap[XK_KP_Delete&0xFF] = key_kp_period;
+ m_keymap[XK_KP_Decimal&0xFF] = key_kp_period;
+ m_keymap[XK_KP_Divide&0xFF] = key_kp_divide;
+ m_keymap[XK_KP_Multiply&0xFF] = key_kp_multiply;
+ m_keymap[XK_KP_Subtract&0xFF] = key_kp_minus;
+ m_keymap[XK_KP_Add&0xFF] = key_kp_plus;
+ m_keymap[XK_KP_Enter&0xFF] = key_kp_enter;
+ m_keymap[XK_KP_Equal&0xFF] = key_kp_equals;
+
+ m_keymap[XK_Up&0xFF] = key_up;
+ m_keymap[XK_Down&0xFF] = key_down;
+ m_keymap[XK_Right&0xFF] = key_right;
+ m_keymap[XK_Left&0xFF] = key_left;
+ m_keymap[XK_Insert&0xFF] = key_insert;
+ m_keymap[XK_Home&0xFF] = key_delete;
+ m_keymap[XK_End&0xFF] = key_end;
+ m_keymap[XK_Page_Up&0xFF] = key_page_up;
+ m_keymap[XK_Page_Down&0xFF] = key_page_down;
+
+ m_keymap[XK_F1&0xFF] = key_f1;
+ m_keymap[XK_F2&0xFF] = key_f2;
+ m_keymap[XK_F3&0xFF] = key_f3;
+ m_keymap[XK_F4&0xFF] = key_f4;
+ m_keymap[XK_F5&0xFF] = key_f5;
+ m_keymap[XK_F6&0xFF] = key_f6;
+ m_keymap[XK_F7&0xFF] = key_f7;
+ m_keymap[XK_F8&0xFF] = key_f8;
+ m_keymap[XK_F9&0xFF] = key_f9;
+ m_keymap[XK_F10&0xFF] = key_f10;
+ m_keymap[XK_F11&0xFF] = key_f11;
+ m_keymap[XK_F12&0xFF] = key_f12;
+ m_keymap[XK_F13&0xFF] = key_f13;
+ m_keymap[XK_F14&0xFF] = key_f14;
+ m_keymap[XK_F15&0xFF] = key_f15;
+
+ m_keymap[XK_Num_Lock&0xFF] = key_numlock;
+ m_keymap[XK_Caps_Lock&0xFF] = key_capslock;
+ m_keymap[XK_Scroll_Lock&0xFF] = key_scrollock;
+
+ switch(m_format)
+ {
+ default: break;
+ case pix_format_gray8:
+ m_bpp = 8;
+ break;
+
+ case pix_format_rgb565:
+ case pix_format_rgb555:
+ m_bpp = 16;
+ break;
+
+ case pix_format_rgb24:
+ case pix_format_bgr24:
+ m_bpp = 24;
+ break;
+
+ case pix_format_bgra32:
+ case pix_format_abgr32:
+ case pix_format_argb32:
+ case pix_format_rgba32:
+ m_bpp = 32;
+ break;
+ }
+ m_sw_start = clock();
+
+ pthread_mutex_init (m_mutex, NULL);
+ }
+
+ //------------------------------------------------------------------------
+ platform_specific::~platform_specific()
+ {
+ pthread_mutex_destroy (m_mutex);
+ }
+
+ //------------------------------------------------------------------------
+ void platform_specific::caption(const char* capt)
+ {
+ // Fixed by Enno Fennema (in original AGG library)
+ XStoreName(m_display, m_window, capt);
+ XSetIconName(m_display, m_window, capt);
+ }
+
+
+ //------------------------------------------------------------------------
+ void platform_specific::put_image(const rendering_buffer* src)
+ {
+ if(m_ximg_window == 0) return;
+ m_ximg_window->data = (char*)m_buf_window;
+
+ if(m_format == m_sys_format)
+ {
+ XPutImage(m_display,
+ m_window,
+ m_gc,
+ m_ximg_window,
+ 0, 0, 0, 0,
+ src->width(),
+ src->height());
+ }
+ else
+ {
+ int row_len = src->width() * m_sys_bpp / 8;
+ unsigned char* buf_tmp =
+ new unsigned char[row_len * src->height()];
+
+ rendering_buffer rbuf_tmp;
+ rbuf_tmp.attach(buf_tmp,
+ src->width(),
+ src->height(),
+ m_flip_y ? -row_len : row_len);
+
+ switch(m_sys_format)
+ {
+ default: break;
+ case pix_format_rgb555:
+ switch(m_format)
+ {
+ default: break;
+ case pix_format_rgb555: color_conv(&rbuf_tmp, src, color_conv_rgb555_to_rgb555()); break;
+ case pix_format_rgb565: color_conv(&rbuf_tmp, src, color_conv_rgb565_to_rgb555()); break;
+ case pix_format_rgb24: color_conv(&rbuf_tmp, src, color_conv_rgb24_to_rgb555()); break;
+ case pix_format_bgr24: color_conv(&rbuf_tmp, src, color_conv_bgr24_to_rgb555()); break;
+ case pix_format_rgba32: color_conv(&rbuf_tmp, src, color_conv_rgba32_to_rgb555()); break;
+ case pix_format_argb32: color_conv(&rbuf_tmp, src, color_conv_argb32_to_rgb555()); break;
+ case pix_format_bgra32: color_conv(&rbuf_tmp, src, color_conv_bgra32_to_rgb555()); break;
+ case pix_format_abgr32: color_conv(&rbuf_tmp, src, color_conv_abgr32_to_rgb555()); break;
+ }
+ break;
+
+ case pix_format_rgb565:
+ switch(m_format)
+ {
+ default: break;
+ case pix_format_rgb555: color_conv(&rbuf_tmp, src, color_conv_rgb555_to_rgb565()); break;
+ case pix_format_rgb565: color_conv(&rbuf_tmp, src, color_conv_rgb565_to_rgb565()); break;
+ case pix_format_rgb24: color_conv(&rbuf_tmp, src, color_conv_rgb24_to_rgb565()); break;
+ case pix_format_bgr24: color_conv(&rbuf_tmp, src, color_conv_bgr24_to_rgb565()); break;
+ case pix_format_rgba32: color_conv(&rbuf_tmp, src, color_conv_rgba32_to_rgb565()); break;
+ case pix_format_argb32: color_conv(&rbuf_tmp, src, color_conv_argb32_to_rgb565()); break;
+ case pix_format_bgra32: color_conv(&rbuf_tmp, src, color_conv_bgra32_to_rgb565()); break;
+ case pix_format_abgr32: color_conv(&rbuf_tmp, src, color_conv_abgr32_to_rgb565()); break;
+ }
+ break;
+
+ case pix_format_rgba32:
+ switch(m_format)
+ {
+ default: break;
+ case pix_format_rgb555: color_conv(&rbuf_tmp, src, color_conv_rgb555_to_rgba32()); break;
+ case pix_format_rgb565: color_conv(&rbuf_tmp, src, color_conv_rgb565_to_rgba32()); break;
+ case pix_format_rgb24: color_conv(&rbuf_tmp, src, color_conv_rgb24_to_rgba32()); break;
+ case pix_format_bgr24: color_conv(&rbuf_tmp, src, color_conv_bgr24_to_rgba32()); break;
+ case pix_format_rgba32: color_conv(&rbuf_tmp, src, color_conv_rgba32_to_rgba32()); break;
+ case pix_format_argb32: color_conv(&rbuf_tmp, src, color_conv_argb32_to_rgba32()); break;
+ case pix_format_bgra32: color_conv(&rbuf_tmp, src, color_conv_bgra32_to_rgba32()); break;
+ case pix_format_abgr32: color_conv(&rbuf_tmp, src, color_conv_abgr32_to_rgba32()); break;
+ }
+ break;
+
+ case pix_format_abgr32:
+ switch(m_format)
+ {
+ default: break;
+ case pix_format_rgb555: color_conv(&rbuf_tmp, src, color_conv_rgb555_to_abgr32()); break;
+ case pix_format_rgb565: color_conv(&rbuf_tmp, src, color_conv_rgb565_to_abgr32()); break;
+ case pix_format_rgb24: color_conv(&rbuf_tmp, src, color_conv_rgb24_to_abgr32()); break;
+ case pix_format_bgr24: color_conv(&rbuf_tmp, src, color_conv_bgr24_to_abgr32()); break;
+ case pix_format_abgr32: color_conv(&rbuf_tmp, src, color_conv_abgr32_to_abgr32()); break;
+ case pix_format_rgba32: color_conv(&rbuf_tmp, src, color_conv_rgba32_to_abgr32()); break;
+ case pix_format_argb32: color_conv(&rbuf_tmp, src, color_conv_argb32_to_abgr32()); break;
+ case pix_format_bgra32: color_conv(&rbuf_tmp, src, color_conv_bgra32_to_abgr32()); break;
+ }
+ break;
+
+ case pix_format_argb32:
+ switch(m_format)
+ {
+ default: break;
+ case pix_format_rgb555: color_conv(&rbuf_tmp, src, color_conv_rgb555_to_argb32()); break;
+ case pix_format_rgb565: color_conv(&rbuf_tmp, src, color_conv_rgb565_to_argb32()); break;
+ case pix_format_rgb24: color_conv(&rbuf_tmp, src, color_conv_rgb24_to_argb32()); break;
+ case pix_format_bgr24: color_conv(&rbuf_tmp, src, color_conv_bgr24_to_argb32()); break;
+ case pix_format_rgba32: color_conv(&rbuf_tmp, src, color_conv_rgba32_to_argb32()); break;
+ case pix_format_argb32: color_conv(&rbuf_tmp, src, color_conv_argb32_to_argb32()); break;
+ case pix_format_abgr32: color_conv(&rbuf_tmp, src, color_conv_abgr32_to_argb32()); break;
+ case pix_format_bgra32: color_conv(&rbuf_tmp, src, color_conv_bgra32_to_argb32()); break;
+ }
+ break;
+
+ case pix_format_bgra32:
+ switch(m_format)
+ {
+ default: break;
+ case pix_format_rgb555: color_conv(&rbuf_tmp, src, color_conv_rgb555_to_bgra32()); break;
+ case pix_format_rgb565: color_conv(&rbuf_tmp, src, color_conv_rgb565_to_bgra32()); break;
+ case pix_format_rgb24: color_conv(&rbuf_tmp, src, color_conv_rgb24_to_bgra32()); break;
+ case pix_format_bgr24: color_conv(&rbuf_tmp, src, color_conv_bgr24_to_bgra32()); break;
+ case pix_format_rgba32: color_conv(&rbuf_tmp, src, color_conv_rgba32_to_bgra32()); break;
+ case pix_format_argb32: color_conv(&rbuf_tmp, src, color_conv_argb32_to_bgra32()); break;
+ case pix_format_abgr32: color_conv(&rbuf_tmp, src, color_conv_abgr32_to_bgra32()); break;
+ case pix_format_bgra32: color_conv(&rbuf_tmp, src, color_conv_bgra32_to_bgra32()); break;
+ }
+ break;
+ }
+
+ m_ximg_window->data = (char*)buf_tmp;
+ XPutImage(m_display,
+ m_window,
+ m_gc,
+ m_ximg_window,
+ 0, 0, 0, 0,
+ src->width(),
+ src->height());
+
+ delete [] buf_tmp;
+ }
+ }
+
+ //------------------------------------------------------------------------
+ platform_support::platform_support(pix_format_e format, bool flip_y) :
+ m_specific(new platform_specific(format, flip_y)),
+ m_format(format),
+ m_bpp(m_specific->m_bpp),
+ m_window_flags(0),
+ m_wait_mode(true),
+ m_flip_y(flip_y),
+ m_initial_width(10),
+ m_initial_height(10)
+ {
+ strcpy(m_caption, "AGG Application");
+ }
+
+ //------------------------------------------------------------------------
+ platform_support::~platform_support()
+ {
+ delete m_specific;
+ }
+
+
+
+ //------------------------------------------------------------------------
+ void platform_support::caption(const char* cap)
+ {
+ strcpy(m_caption, cap);
+ if(m_specific->m_initialized)
+ {
+ m_specific->caption(cap);
+ }
+ }
+
+
+ //------------------------------------------------------------------------
+ enum xevent_mask_e
+ {
+ xevent_mask =
+ PointerMotionMask|
+ ButtonPressMask|
+ ButtonReleaseMask|
+ ExposureMask|
+ KeyPressMask|
+ StructureNotifyMask
+ };
+
+
+ //------------------------------------------------------------------------
+ bool platform_support::init(unsigned width, unsigned height, unsigned flags)
+ {
+ m_window_flags = flags;
+
+ m_specific->m_display = XOpenDisplay(NULL);
+ if(m_specific->m_display == 0)
+ {
+ fprintf(stderr, "Unable to open DISPLAY!\n");
+ return false;
+ }
+
+ m_specific->m_screen = XDefaultScreen(m_specific->m_display);
+ m_specific->m_depth = XDefaultDepth(m_specific->m_display,
+ m_specific->m_screen);
+ m_specific->m_visual = XDefaultVisual(m_specific->m_display,
+ m_specific->m_screen);
+ unsigned long r_mask = m_specific->m_visual->red_mask;
+ unsigned long g_mask = m_specific->m_visual->green_mask;
+ unsigned long b_mask = m_specific->m_visual->blue_mask;
+
+ if(m_specific->m_depth < 15 ||
+ r_mask == 0 || g_mask == 0 || b_mask == 0)
+ {
+ fprintf(stderr,
+ "There's no Visual compatible with minimal AGG requirements:\n"
+ "At least 15-bit color depth and True- or DirectColor class.\n\n");
+ XCloseDisplay(m_specific->m_display);
+ return false;
+ }
+
+ int t = 1;
+ int hw_byte_order = LSBFirst;
+ if(*(char*)&t == 0) hw_byte_order = MSBFirst;
+
+ // Perceive SYS-format by mask
+ switch(m_specific->m_depth)
+ {
+ case 15:
+ m_specific->m_sys_bpp = 16;
+ if(r_mask == 0x7C00 && g_mask == 0x3E0 && b_mask == 0x1F)
+ {
+ m_specific->m_sys_format = pix_format_rgb555;
+ m_specific->m_byte_order = hw_byte_order;
+ }
+ break;
+
+ case 16:
+ m_specific->m_sys_bpp = 16;
+ if(r_mask == 0xF800 && g_mask == 0x7E0 && b_mask == 0x1F)
+ {
+ m_specific->m_sys_format = pix_format_rgb565;
+ m_specific->m_byte_order = hw_byte_order;
+ }
+ break;
+
+ case 24:
+ case 32:
+ m_specific->m_sys_bpp = 32;
+ if(g_mask == 0xFF00)
+ {
+ if(r_mask == 0xFF && b_mask == 0xFF0000)
+ {
+ switch(m_specific->m_format)
+ {
+ case pix_format_rgba32:
+ m_specific->m_sys_format = pix_format_rgba32;
+ m_specific->m_byte_order = LSBFirst;
+ break;
+
+ case pix_format_abgr32:
+ m_specific->m_sys_format = pix_format_abgr32;
+ m_specific->m_byte_order = MSBFirst;
+ break;
+
+ default:
+ m_specific->m_byte_order = hw_byte_order;
+ m_specific->m_sys_format =
+ (hw_byte_order == LSBFirst) ?
+ pix_format_rgba32 :
+ pix_format_abgr32;
+ break;
+ }
+ }
+
+ if(r_mask == 0xFF0000 && b_mask == 0xFF)
+ {
+ switch(m_specific->m_format)
+ {
+ case pix_format_argb32:
+ m_specific->m_sys_format = pix_format_argb32;
+ m_specific->m_byte_order = MSBFirst;
+ break;
+
+ case pix_format_bgra32:
+ m_specific->m_sys_format = pix_format_bgra32;
+ m_specific->m_byte_order = LSBFirst;
+ break;
+
+ default:
+ m_specific->m_byte_order = hw_byte_order;
+ m_specific->m_sys_format =
+ (hw_byte_order == MSBFirst) ?
+ pix_format_argb32 :
+ pix_format_bgra32;
+ break;
+ }
+ }
+ }
+ break;
+ }
+
+ if(m_specific->m_sys_format == pix_format_undefined)
+ {
+ fprintf(stderr,
+ "RGB masks are not compatible with AGG pixel formats:\n"
+ "R=%08x, R=%08x, B=%08x\n",
+ (unsigned)r_mask, (unsigned)g_mask, (unsigned)b_mask);
+ XCloseDisplay(m_specific->m_display);
+ return false;
+ }
+
+
+
+ memset(&m_specific->m_window_attributes,
+ 0,
+ sizeof(m_specific->m_window_attributes));
+
+ m_specific->m_window_attributes.border_pixel =
+ XBlackPixel(m_specific->m_display, m_specific->m_screen);
+
+ m_specific->m_window_attributes.background_pixel =
+ XWhitePixel(m_specific->m_display, m_specific->m_screen);
+
+ m_specific->m_window_attributes.override_redirect = 0;
+
+ unsigned long window_mask = CWBackPixel | CWBorderPixel;
+
+ m_specific->m_window =
+ XCreateWindow(m_specific->m_display,
+ XDefaultRootWindow(m_specific->m_display),
+ 0, 0,
+ width,
+ height,
+ 0,
+ m_specific->m_depth,
+ InputOutput,
+ CopyFromParent,
+ window_mask,
+ &m_specific->m_window_attributes);
+
+
+ m_specific->m_gc = XCreateGC(m_specific->m_display,
+ m_specific->m_window,
+ 0, 0);
+ m_specific->m_buf_window =
+ new unsigned char[width * height * (m_bpp / 8)];
+
+ memset(m_specific->m_buf_window, 255, width * height * (m_bpp / 8));
+
+ m_rbuf_window.attach(m_specific->m_buf_window,
+ width,
+ height,
+ m_flip_y ? -width * (m_bpp / 8) : width * (m_bpp / 8));
+
+ m_specific->m_ximg_window =
+ XCreateImage(m_specific->m_display,
+ m_specific->m_visual, //CopyFromParent,
+ m_specific->m_depth,
+ ZPixmap,
+ 0,
+ (char*)m_specific->m_buf_window,
+ width,
+ height,
+ m_specific->m_sys_bpp,
+ width * (m_specific->m_sys_bpp / 8));
+ m_specific->m_ximg_window->byte_order = m_specific->m_byte_order;
+
+ m_specific->caption(m_caption);
+ m_initial_width = width;
+ m_initial_height = height;
+
+ if(!m_specific->m_initialized)
+ {
+ on_init();
+ m_specific->m_initialized = true;
+ }
+
+ trans_affine_resizing(width, height);
+ on_resize(width, height);
+ m_specific->m_update_flag = true;
+
+ XSizeHints *hints = XAllocSizeHints();
+ if(hints)
+ {
+ if(flags & window_resize)
+ {
+ hints->min_width = 32;
+ hints->min_height = 32;
+ hints->max_width = 4096;
+ hints->max_height = 4096;
+ }
+ else
+ {
+ hints->min_width = width;
+ hints->min_height = height;
+ hints->max_width = width;
+ hints->max_height = height;
+ }
+ hints->flags = PMaxSize | PMinSize;
+
+ XSetWMNormalHints(m_specific->m_display,
+ m_specific->m_window,
+ hints);
+
+ XFree(hints);
+ }
+
+
+ XMapWindow(m_specific->m_display,
+ m_specific->m_window);
+
+ XSelectInput(m_specific->m_display,
+ m_specific->m_window,
+ xevent_mask);
+
+
+ m_specific->m_close_atom = XInternAtom(m_specific->m_display,
+ "WM_DELETE_WINDOW",
+ false);
+
+ XSetWMProtocols(m_specific->m_display,
+ m_specific->m_window,
+ &m_specific->m_close_atom,
+ 1);
+
+ return true;
+ }
+
+
+
+ //------------------------------------------------------------------------
+ void platform_support::update_window()
+ {
+ if (! m_specific->m_is_mapped)
+ return;
+
+ m_specific->put_image(&m_rbuf_window);
+
+ // When m_wait_mode is true we can discard all the events
+ // came while the image is being drawn. In this case
+ // the X server does not accumulate mouse motion events.
+ // When m_wait_mode is false, i.e. we have some idle drawing
+ // we cannot afford to miss any events
+ XSync(m_specific->m_display, m_wait_mode);
+ }
+
+
+ //------------------------------------------------------------------------
+ int platform_support::run()
+ {
+ XFlush(m_specific->m_display);
+
+ bool quit = false;
+ unsigned flags;
+ int cur_x;
+ int cur_y;
+
+ while(!quit)
+ {
+ if(m_specific->m_update_flag && m_specific->m_is_mapped)
+ {
+ on_draw();
+ update_window();
+ m_specific->m_update_flag = false;
+ }
+
+ if(!m_wait_mode)
+ {
+ if(XPending(m_specific->m_display) == 0)
+ {
+ on_idle();
+ continue;
+ }
+ }
+
+ XEvent x_event;
+ pthread_mutex_unlock (m_specific->m_mutex);
+ XNextEvent(m_specific->m_display, &x_event);
+ pthread_mutex_lock (m_specific->m_mutex);
+
+ // In the Idle mode discard all intermediate MotionNotify events
+ if(!m_wait_mode && x_event.type == MotionNotify)
+ {
+ XEvent te = x_event;
+ for(;;)
+ {
+ if(XPending(m_specific->m_display) == 0) break;
+ XNextEvent(m_specific->m_display, &te);
+ if(te.type != MotionNotify) break;
+ }
+ x_event = te;
+ }
+
+ switch(x_event.type)
+ {
+ case MapNotify:
+ {
+ on_draw();
+ update_window();
+ m_specific->m_is_mapped = true;
+ m_specific->m_update_flag = false;
+ }
+ break;
+
+ case ConfigureNotify:
+ {
+ if(x_event.xconfigure.width != int(m_rbuf_window.width()) ||
+ x_event.xconfigure.height != int(m_rbuf_window.height()))
+ {
+ int width = x_event.xconfigure.width;
+ int height = x_event.xconfigure.height;
+
+ delete [] m_specific->m_buf_window;
+ m_specific->m_ximg_window->data = 0;
+ XDestroyImage(m_specific->m_ximg_window);
+
+ m_specific->m_buf_window =
+ new unsigned char[width * height * (m_bpp / 8)];
+
+ m_rbuf_window.attach(m_specific->m_buf_window,
+ width,
+ height,
+ m_flip_y ?
+ -width * (m_bpp / 8) :
+ width * (m_bpp / 8));
+
+ m_specific->m_ximg_window =
+ XCreateImage(m_specific->m_display,
+ m_specific->m_visual, //CopyFromParent,
+ m_specific->m_depth,
+ ZPixmap,
+ 0,
+ (char*)m_specific->m_buf_window,
+ width,
+ height,
+ m_specific->m_sys_bpp,
+ width * (m_specific->m_sys_bpp / 8));
+ m_specific->m_ximg_window->byte_order = m_specific->m_byte_order;
+
+ trans_affine_resizing(width, height);
+ on_resize(width, height);
+ on_draw();
+ update_window();
+ }
+ }
+ break;
+
+ case Expose:
+ m_specific->put_image(&m_rbuf_window);
+ XFlush(m_specific->m_display);
+ XSync(m_specific->m_display, false);
+ break;
+
+ case KeyPress:
+ {
+ KeySym key = XLookupKeysym(&x_event.xkey, 0);
+ flags = 0;
+ if(x_event.xkey.state & Button1Mask) flags |= mouse_left;
+ if(x_event.xkey.state & Button3Mask) flags |= mouse_right;
+ if(x_event.xkey.state & ShiftMask) flags |= kbd_shift;
+ if(x_event.xkey.state & ControlMask) flags |= kbd_ctrl;
+
+ bool left = false;
+ bool up = false;
+ bool right = false;
+ bool down = false;
+
+ switch(m_specific->m_keymap[key & 0xFF])
+ {
+ case key_left:
+ left = true;
+ break;
+
+ case key_up:
+ up = true;
+ break;
+
+ case key_right:
+ right = true;
+ break;
+
+ case key_down:
+ down = true;
+ break;
+
+ case key_f2:
+ copy_window_to_img(max_images - 1);
+ save_img(max_images - 1, "screenshot");
+ break;
+ }
+
+ if(m_ctrls.on_arrow_keys(left, right, down, up))
+ {
+ on_ctrl_change();
+ force_redraw();
+ }
+ else
+ {
+ on_key(x_event.xkey.x,
+ m_flip_y ?
+ m_rbuf_window.height() - x_event.xkey.y :
+ x_event.xkey.y,
+ m_specific->m_keymap[key & 0xFF],
+ flags);
+ }
+ }
+ break;
+
+
+ case ButtonPress:
+ {
+ flags = 0;
+ if(x_event.xbutton.state & ShiftMask) flags |= kbd_shift;
+ if(x_event.xbutton.state & ControlMask) flags |= kbd_ctrl;
+ if(x_event.xbutton.button == Button1) flags |= mouse_left;
+ if(x_event.xbutton.button == Button3) flags |= mouse_right;
+
+ cur_x = x_event.xbutton.x;
+ cur_y = m_flip_y ? m_rbuf_window.height() - x_event.xbutton.y :
+ x_event.xbutton.y;
+
+ if(flags & mouse_left)
+ {
+ if(m_ctrls.on_mouse_button_down(cur_x, cur_y))
+ {
+ m_ctrls.set_cur(cur_x, cur_y);
+ on_ctrl_change();
+ force_redraw();
+ }
+ else
+ {
+ if(m_ctrls.in_rect(cur_x, cur_y))
+ {
+ if(m_ctrls.set_cur(cur_x, cur_y))
+ {
+ on_ctrl_change();
+ force_redraw();
+ }
+ }
+ else
+ {
+ on_mouse_button_down(cur_x, cur_y, flags);
+ }
+ }
+ }
+ if(flags & mouse_right)
+ {
+ on_mouse_button_down(cur_x, cur_y, flags);
+ }
+ //m_specific->m_wait_mode = m_wait_mode;
+ //m_wait_mode = true;
+ }
+ break;
+
+
+ case MotionNotify:
+ {
+ flags = 0;
+ if(x_event.xmotion.state & Button1Mask) flags |= mouse_left;
+ if(x_event.xmotion.state & Button3Mask) flags |= mouse_right;
+ if(x_event.xmotion.state & ShiftMask) flags |= kbd_shift;
+ if(x_event.xmotion.state & ControlMask) flags |= kbd_ctrl;
+
+ cur_x = x_event.xbutton.x;
+ cur_y = m_flip_y ? m_rbuf_window.height() - x_event.xbutton.y :
+ x_event.xbutton.y;
+
+ if(m_ctrls.on_mouse_move(cur_x, cur_y, (flags & mouse_left) != 0))
+ {
+ on_ctrl_change();
+ force_redraw();
+ }
+ else
+ {
+ if(!m_ctrls.in_rect(cur_x, cur_y))
+ {
+ on_mouse_move(cur_x, cur_y, flags);
+ }
+ }
+ }
+ break;
+
+ case ButtonRelease:
+ {
+ flags = 0;
+ if(x_event.xbutton.state & ShiftMask) flags |= kbd_shift;
+ if(x_event.xbutton.state & ControlMask) flags |= kbd_ctrl;
+ if(x_event.xbutton.button == Button1) flags |= mouse_left;
+ if(x_event.xbutton.button == Button3) flags |= mouse_right;
+
+ cur_x = x_event.xbutton.x;
+ cur_y = m_flip_y ? m_rbuf_window.height() - x_event.xbutton.y :
+ x_event.xbutton.y;
+
+ if(flags & mouse_left)
+ {
+ if(m_ctrls.on_mouse_button_up(cur_x, cur_y))
+ {
+ on_ctrl_change();
+ force_redraw();
+ }
+ }
+ if(flags & (mouse_left | mouse_right))
+ {
+ on_mouse_button_up(cur_x, cur_y, flags);
+ }
+ }
+ //m_wait_mode = m_specific->m_wait_mode;
+ break;
+
+ case ClientMessage:
+ if((x_event.xclient.format == 32) &&
+ (x_event.xclient.data.l[0] == int(m_specific->m_close_atom)))
+ {
+ quit = true;
+ }
+ break;
+ }
+ }
+
+
+ unsigned i = platform_support::max_images;
+ while(i--)
+ {
+ if(m_specific->m_buf_img[i])
+ {
+ delete [] m_specific->m_buf_img[i];
+ }
+ }
+
+ delete [] m_specific->m_buf_window;
+ m_specific->m_ximg_window->data = 0;
+ XDestroyImage(m_specific->m_ximg_window);
+ XFreeGC(m_specific->m_display, m_specific->m_gc);
+ XDestroyWindow(m_specific->m_display, m_specific->m_window);
+ XCloseDisplay(m_specific->m_display);
+
+ return 0;
+ }
+
+
+
+ //------------------------------------------------------------------------
+ const char* platform_support::img_ext() const { return ".ppm"; }
+
+ //------------------------------------------------------------------------
+ const char* platform_support::full_file_name(const char* file_name)
+ {
+ return file_name;
+ }
+
+ //------------------------------------------------------------------------
+ bool platform_support::load_img(unsigned idx, const char* file)
+ {
+ if(idx < max_images)
+ {
+ char buf[1024];
+ strcpy(buf, file);
+ int len = strlen(buf);
+ if(len < 4 || strcasecmp(buf + len - 4, ".ppm") != 0)
+ {
+ strcat(buf, ".ppm");
+ }
+
+ FILE* fd = fopen(buf, "rb");
+ if(fd == 0) return false;
+
+ if((len = fread(buf, 1, 1022, fd)) == 0)
+ {
+ fclose(fd);
+ return false;
+ }
+ buf[len] = 0;
+
+ if(buf[0] != 'P' && buf[1] != '6')
+ {
+ fclose(fd);
+ return false;
+ }
+
+ char* ptr = buf + 2;
+
+ while(*ptr && !isdigit(*ptr)) ptr++;
+ if(*ptr == 0)
+ {
+ fclose(fd);
+ return false;
+ }
+
+ unsigned width = atoi(ptr);
+ if(width == 0 || width > 4096)
+ {
+ fclose(fd);
+ return false;
+ }
+ while(*ptr && isdigit(*ptr)) ptr++;
+ while(*ptr && !isdigit(*ptr)) ptr++;
+ if(*ptr == 0)
+ {
+ fclose(fd);
+ return false;
+ }
+ unsigned height = atoi(ptr);
+ if(height == 0 || height > 4096)
+ {
+ fclose(fd);
+ return false;
+ }
+ while(*ptr && isdigit(*ptr)) ptr++;
+ while(*ptr && !isdigit(*ptr)) ptr++;
+ if(atoi(ptr) != 255)
+ {
+ fclose(fd);
+ return false;
+ }
+ while(*ptr && isdigit(*ptr)) ptr++;
+ if(*ptr == 0)
+ {
+ fclose(fd);
+ return false;
+ }
+ ptr++;
+ fseek(fd, long(ptr - buf), SEEK_SET);
+
+ create_img(idx, width, height);
+ bool ret = true;
+
+ if(m_format == pix_format_rgb24)
+ {
+ fread(m_specific->m_buf_img[idx], 1, width * height * 3, fd);
+ }
+ else
+ {
+ unsigned char* buf_img = new unsigned char [width * height * 3];
+ rendering_buffer rbuf_img;
+ rbuf_img.attach(buf_img,
+ width,
+ height,
+ m_flip_y ?
+ -width * 3 :
+ width * 3);
+
+ fread(buf_img, 1, width * height * 3, fd);
+
+ switch(m_format)
+ {
+ case pix_format_rgb555:
+ color_conv(m_rbuf_img+idx, &rbuf_img, color_conv_rgb24_to_rgb555());
+ break;
+
+ case pix_format_rgb565:
+ color_conv(m_rbuf_img+idx, &rbuf_img, color_conv_rgb24_to_rgb565());
+ break;
+
+ case pix_format_bgr24:
+ color_conv(m_rbuf_img+idx, &rbuf_img, color_conv_rgb24_to_bgr24());
+ break;
+
+ case pix_format_rgba32:
+ color_conv(m_rbuf_img+idx, &rbuf_img, color_conv_rgb24_to_rgba32());
+ break;
+
+ case pix_format_argb32:
+ color_conv(m_rbuf_img+idx, &rbuf_img, color_conv_rgb24_to_argb32());
+ break;
+
+ case pix_format_bgra32:
+ color_conv(m_rbuf_img+idx, &rbuf_img, color_conv_rgb24_to_bgra32());
+ break;
+
+ case pix_format_abgr32:
+ color_conv(m_rbuf_img+idx, &rbuf_img, color_conv_rgb24_to_abgr32());
+ break;
+
+ default:
+ ret = false;
+ }
+ delete [] buf_img;
+ }
+
+ fclose(fd);
+ return ret;
+ }
+ return false;
+ }
+
+
+
+
+ //------------------------------------------------------------------------
+ bool platform_support::save_img(unsigned idx, const char* file)
+ {
+ if(idx < max_images && rbuf_img(idx).buf())
+ {
+ char buf[1024];
+ strcpy(buf, file);
+ int len = strlen(buf);
+ if(len < 4 || strcasecmp(buf + len - 4, ".ppm") != 0)
+ {
+ strcat(buf, ".ppm");
+ }
+
+ FILE* fd = fopen(buf, "wb");
+ if(fd == 0) return false;
+
+ unsigned w = rbuf_img(idx).width();
+ unsigned h = rbuf_img(idx).height();
+
+ fprintf(fd, "P6\n%d %d\n255\n", w, h);
+
+ unsigned y;
+ unsigned char* tmp_buf = new unsigned char [w * 3];
+ for(y = 0; y < rbuf_img(idx).height(); y++)
+ {
+ const unsigned char* src = rbuf_img(idx).row_ptr(m_flip_y ? h - 1 - y : y);
+ switch(m_format)
+ {
+ default: break;
+ case pix_format_rgb555:
+ color_conv_row(tmp_buf, src, w, color_conv_rgb555_to_rgb24());
+ break;
+
+ case pix_format_rgb565:
+ color_conv_row(tmp_buf, src, w, color_conv_rgb565_to_rgb24());
+ break;
+
+ case pix_format_bgr24:
+ color_conv_row(tmp_buf, src, w, color_conv_bgr24_to_rgb24());
+ break;
+
+ case pix_format_rgb24:
+ color_conv_row(tmp_buf, src, w, color_conv_rgb24_to_rgb24());
+ break;
+
+ case pix_format_rgba32:
+ color_conv_row(tmp_buf, src, w, color_conv_rgba32_to_rgb24());
+ break;
+
+ case pix_format_argb32:
+ color_conv_row(tmp_buf, src, w, color_conv_argb32_to_rgb24());
+ break;
+
+ case pix_format_bgra32:
+ color_conv_row(tmp_buf, src, w, color_conv_bgra32_to_rgb24());
+ break;
+
+ case pix_format_abgr32:
+ color_conv_row(tmp_buf, src, w, color_conv_abgr32_to_rgb24());
+ break;
+ }
+ fwrite(tmp_buf, 1, w * 3, fd);
+ }
+ delete [] tmp_buf;
+ fclose(fd);
+ return true;
+ }
+ return false;
+ }
+
+
+
+ //------------------------------------------------------------------------
+ bool platform_support::create_img(unsigned idx, unsigned width, unsigned height)
+ {
+ if(idx < max_images)
+ {
+ if(width == 0) width = rbuf_window().width();
+ if(height == 0) height = rbuf_window().height();
+ delete [] m_specific->m_buf_img[idx];
+ m_specific->m_buf_img[idx] =
+ new unsigned char[width * height * (m_bpp / 8)];
+
+ m_rbuf_img[idx].attach(m_specific->m_buf_img[idx],
+ width,
+ height,
+ m_flip_y ?
+ -width * (m_bpp / 8) :
+ width * (m_bpp / 8));
+ return true;
+ }
+ return false;
+ }
+
+
+ //------------------------------------------------------------------------
+ void platform_support::force_redraw()
+ {
+ m_specific->m_update_flag = true;
+ }
+
+
+ //------------------------------------------------------------------------
+ void platform_support::message(const char* msg)
+ {
+ fprintf(stderr, "%s\n", msg);
+ }
+
+ //------------------------------------------------------------------------
+ void platform_support::start_timer()
+ {
+ m_specific->m_sw_start = clock();
+ }
+
+ //------------------------------------------------------------------------
+ double platform_support::elapsed_time() const
+ {
+ clock_t stop = clock();
+ return double(stop - m_specific->m_sw_start) * 1000.0 / CLOCKS_PER_SEC;
+ }
+
+
+ //------------------------------------------------------------------------
+ void platform_support::on_init() {}
+ void platform_support::on_resize(int sx, int sy) {}
+ void platform_support::on_idle() {}
+ void platform_support::on_mouse_move(int x, int y, unsigned flags) {}
+ void platform_support::on_mouse_button_down(int x, int y, unsigned flags) {}
+ void platform_support::on_mouse_button_up(int x, int y, unsigned flags) {}
+ void platform_support::on_key(int x, int y, unsigned key, unsigned flags) {}
+ void platform_support::on_ctrl_change() {}
+ void platform_support::on_draw() {}
+ void platform_support::on_post_draw(void* raw_handler) {}
+
+
+
+}
+
+
+void platform_support_prepare()
+{
+}
+
+void platform_support_lock(agg::platform_support *app)
+{
+ pthread_mutex_lock (app->m_specific->m_mutex);
+}
+
+void platform_support_unlock(agg::platform_support *app)
+{
+ pthread_mutex_unlock (app->m_specific->m_mutex);
+}
+
+bool platform_support_is_mapped(agg::platform_support *app)
+{
+ return app->m_specific->m_is_mapped;
+}
diff --git a/agg-plot/colors.cpp b/agg-plot/colors.cpp
index e4ea0807..1b16be47 100644
--- a/agg-plot/colors.cpp
+++ b/agg-plot/colors.cpp
@@ -50,3 +50,27 @@ rgba8_push_lookup (lua_State *L, const char *color_str)
return new(L, GS_RGBA_COLOR) agg::rgba8(r, g, b, a);
}
+
+agg::rgba8 *
+color_arg_lookup (lua_State *L, int index)
+{
+ agg::rgba8 *c;
+
+ if (lua_isnil (L, index))
+ {
+ c = rgba8_push_default (L);
+ lua_replace (L, index);
+ }
+ else if (lua_isstring (L, index))
+ {
+ const char *cstr = lua_tostring (L, index);
+ c = rgba8_push_lookup (L, cstr);
+ lua_replace (L, index);
+ }
+ else
+ {
+ c = (agg::rgba8 *) gs_check_userdata (L, index, GS_RGBA_COLOR);
+ }
+
+ return c;
+}
diff --git a/agg-plot/colors.h b/agg-plot/colors.h
index 6add76f2..6e9263d6 100644
--- a/agg-plot/colors.h
+++ b/agg-plot/colors.h
@@ -8,7 +8,8 @@ extern "C" {
#include "defs.h"
#include "agg_color_rgba.h"
-extern agg::rgba8 * rgba8_push_lookup(lua_State *L, const char *color_str);
-extern agg::rgba8 * rgba8_push_default(lua_State *L);
+extern agg::rgba8 * rgba8_push_lookup (lua_State *L, const char *color_str);
+extern agg::rgba8 * rgba8_push_default (lua_State *L);
+extern agg::rgba8 * color_arg_lookup (lua_State *L, int index);
#endif
diff --git a/agg-plot/lua-draw.cpp b/agg-plot/lua-draw.cpp
index d812d7ea..48e9580f 100644
--- a/agg-plot/lua-draw.cpp
+++ b/agg-plot/lua-draw.cpp
@@ -45,6 +45,9 @@ static int agg_text_set_point (lua_State *L);
static int agg_text_rotate (lua_State *L);
static int agg_rgba_free (lua_State *L);
+static int agg_rgba_add (lua_State *L);
+static int agg_rgba_mul (lua_State *L);
+static int agg_rgba_set_alpha (lua_State *L);
static void path_cmd (my::path *p, int cmd, struct cmd_call_stack *stack);
@@ -74,6 +77,9 @@ static const struct luaL_Reg agg_path_methods[] = {
static const struct luaL_Reg rgba_methods[] = {
{"__gc", agg_rgba_free},
+ {"__add", agg_rgba_add },
+ {"__mul", agg_rgba_mul },
+ {"alpha", agg_rgba_set_alpha },
{NULL, NULL}
};
@@ -240,7 +246,7 @@ agg_text_new (lua_State *L)
{
double size = luaL_optnumber (L, 1, 10.0);
double width = luaL_optnumber (L, 2, 1.0);
- my::text *txt = new(L, GS_DRAW_TEXT) my::text(size, width);
+ new(L, GS_DRAW_TEXT) my::text(size, width);
return 1;
}
@@ -321,6 +327,50 @@ agg_rgb_new (lua_State *L)
}
int
+agg_rgba_set_alpha (lua_State *L)
+{
+ agg::rgba8 *c = (agg::rgba8 *) gs_check_userdata (L, 1, GS_RGBA_COLOR);
+ double a = luaL_checknumber (L, 2);
+ c->a = agg::rgba8::base_mask * a;
+ return 0;
+}
+
+int
+agg_rgba_add (lua_State *L)
+{
+ agg::rgba8 *c1 = (agg::rgba8 *) gs_check_userdata (L, 1, GS_RGBA_COLOR);
+ agg::rgba8 *c2 = (agg::rgba8 *) gs_check_userdata (L, 2, GS_RGBA_COLOR);
+
+ unsigned int r = c1->r + c2->r;
+ unsigned int g = c1->g + c2->g;
+ unsigned int b = c1->b + c2->b;
+
+ new(L, GS_RGBA_COLOR) agg::rgba8(r, g, b);
+
+ return 1;
+}
+
+int
+agg_rgba_mul (lua_State *L)
+{
+ int is = 1, ic = 2;
+
+ if (gs_is_userdata (L, 1, GS_RGBA_COLOR))
+ {
+ ic = 1;
+ is = 2;
+ }
+
+ double f = luaL_checknumber (L, is);
+ agg::rgba8 *c = (agg::rgba8 *) gs_check_userdata (L, ic, GS_RGBA_COLOR);
+
+ unsigned int r = f * c->r, g = f * c->g, b = f * c->b;
+
+ new(L, GS_RGBA_COLOR) agg::rgba8(r, g, b);
+ return 1;
+}
+
+int
agg_rgba_free (lua_State *L)
{
typedef agg::rgba8 rgba_t;
@@ -345,6 +395,8 @@ draw_register (lua_State *L)
lua_pop (L, 1);
luaL_newmetatable (L, GS_METATABLE(GS_RGBA_COLOR));
+ lua_pushvalue (L, -1);
+ lua_setfield (L, -2, "__index");
luaL_register (L, NULL, rgba_methods);
lua_pop (L, 1);
}
diff --git a/agg-plot/lua-plot.cpp b/agg-plot/lua-plot.cpp
index f0993db9..0d59a5e7 100644
--- a/agg-plot/lua-plot.cpp
+++ b/agg-plot/lua-plot.cpp
@@ -17,6 +17,7 @@ extern "C" {
#include "trans.h"
#include "colors.h"
#include "xwin-show.h"
+#include "agg-parse-trans.h"
__BEGIN_DECLS
@@ -33,25 +34,12 @@ static int agg_plot_title_get (lua_State *L);
static int agg_plot_units_set (lua_State *L);
static int agg_plot_units_get (lua_State *L);
-static vertex_source * build_stroke (lua_State *L, int index, vertex_source *obj);
-static vertex_source * build_dash (lua_State *L, int index, vertex_source *obj);
-static vertex_source * build_curve (lua_State *L, int index, vertex_source *obj);
-static vertex_source * build_marker (lua_State *L, int index, vertex_source *obj);
-static vertex_source * build_rotate (lua_State *L, int index, vertex_source *obj);
-static vertex_source * build_translate (lua_State *L, int index, vertex_source *obj);
-
-/* DEBUG DEBUG DEBUG */
-static int window_debug_list (lua_State *L);
-static int obj_getfenv (lua_State *L);
-
static const struct luaL_Reg plot_functions[] = {
{"path", agg_path_new},
{"text", agg_text_new},
{"rgba", agg_rgba_new},
{"rgb", agg_rgb_new},
{"plot", agg_plot_new},
- {"windows", window_debug_list},
- {"objgetfenv", obj_getfenv},
{NULL, NULL}
};
@@ -78,61 +66,7 @@ static const struct luaL_Reg agg_plot_properties_set[] = {
{NULL, NULL}
};
-struct property_reg {
- int id;
- const char *name;
-};
-
-struct builder_reg {
- const char *name;
- vertex_source *(*func)(lua_State *, int, vertex_source *);
-};
-
__END_DECLS
-
-
-struct property_reg line_cap_properties[] = {
- {(int) agg::butt_cap, "butt" },
- {(int) agg::square_cap, "square"},
- {(int) agg::round_cap, "round" },
- {0, NULL}
-};
-
-struct property_reg line_join_properties[] = {
- {(int) agg::miter_join, "miter" },
- {(int) agg::miter_join_revert, "miter.rev" },
- {(int) agg::round_join, "round" },
- {(int) agg::bevel_join, "bevel" },
- {(int) agg::miter_join_round, "miter.round"},
- {0, NULL}
-};
-
-const builder_reg builder_table[] = {
- {"stroke", build_stroke },
- {"dash", build_dash},
- {"curve", build_curve},
- {"marker", build_marker},
- {"translate", build_translate},
- {"rotate", build_rotate},
- {NULL, NULL}
-};
-
-static int
-property_lookup (struct property_reg *prop, const char *key)
-{
- int default_value = prop->id;
-
- if (key == NULL)
- return default_value;
-
- for ( ; prop->name; prop++)
- {
- if (strcmp (prop->name, key) == 0)
- return prop->id;
- }
-
- return default_value;
-}
void agg_plot::wait_update()
{
@@ -237,151 +171,6 @@ agg_plot_newindex (lua_State *L)
return mlua_newindex_with_properties (L, agg_plot_properties_set);
}
-vertex_source *
-build_stroke (lua_State *L, int specindex, vertex_source *obj)
-{
- double width = mlua_named_optnumber (L, specindex, "width", 1.0);
- const char *cap_str = mlua_named_optstring (L, specindex, "cap", NULL);
- const char *join_str = mlua_named_optstring (L, specindex, "join", NULL);
-
- trans::stroke *stroke = new trans::stroke(obj, width);
-
- if (cap_str)
- {
- int cap = property_lookup (line_cap_properties, cap_str);
- stroke->line_cap((agg::line_cap_e) cap);
- }
-
- if (join_str)
- {
- int join = property_lookup (line_join_properties, join_str);
- stroke->line_join((agg::line_join_e) join);
- }
-
- return (vertex_source *) stroke;
-}
-
-vertex_source *
-build_curve (lua_State *L, int specindex, vertex_source *obj)
-{
- trans::curve *c = new trans::curve(obj);
- return (vertex_source *) c;
-}
-
-vertex_source *
-build_marker (lua_State *L, int specindex, vertex_source *obj)
-{
- double size = mlua_named_optnumber (L, specindex, "size", 3.0);
- return (vertex_source *) new trans::marker(obj, size);
-}
-
-vertex_source *
-build_dash (lua_State *L, int specindex, vertex_source *obj)
-{
- double a = mlua_named_optnumber (L, specindex, "a", 10.0);
- double b = mlua_named_optnumber (L, specindex, "b", a);
-
- trans::dash *dash = new trans::dash(obj);
- dash->add_dash(a, b);
-
- return (vertex_source *) dash;
-}
-
-vertex_source *
-build_translate (lua_State *L, int specindex, vertex_source *obj)
-{
- double x = mlua_named_number (L, specindex, "x");
- double y = mlua_named_number (L, specindex, "y");
-
- trans::affine *t = new trans::affine(obj);
- t->translate(x, y);
-
- return (vertex_source *) t;
-}
-
-vertex_source *
-build_rotate (lua_State *L, int specindex, vertex_source *obj)
-{
- double a = mlua_named_number (L, specindex, "angle");
-
- trans::affine *t = new trans::affine(obj);
- t->rotate(a);
-
- return (vertex_source *) t;
-}
-
-vertex_source *
-parse_spec (lua_State *L, int specindex, vertex_source *obj)
-{
- const char *tag;
-
- lua_rawgeti (L, specindex, 1);
- if (! lua_isstring (L, -1))
- {
- luaL_error (L, "the tag of the transformation is invalid");
- return NULL;
- }
-
- tag = lua_tostring (L, -1);
- lua_pop (L, 1);
-
- for (const builder_reg *b = builder_table; b->name != NULL; b++)
- {
- if (strcmp (b->name, tag) == 0)
- return b->func (L, specindex, obj);
- }
-
- luaL_error (L, "invalid trasformation tag");
- return NULL;
-}
-
-vertex_source *
-lparse_spec_pipeline (lua_State *L, int index, vertex_source *obj)
-{
- size_t k, nb;
-
- if (lua_type (L, index) == LUA_TTABLE)
- nb = lua_objlen (L, index);
- else
- {
- luaL_error (L, "post transform argument should be a table");
- return NULL;
- }
-
- for (k = nb; k > 0; k--)
- {
- lua_rawgeti (L, index, k);
- obj = parse_spec (L, index+1, obj);
- lua_pop (L, 1);
- }
-
- return obj;
-}
-
-static agg::rgba8 *
-color_arg_lookup (lua_State *L, int index)
-{
- agg::rgba8 *c;
-
- if (lua_isnil (L, index))
- {
- c = rgba8_push_default (L);
- lua_replace (L, index);
- }
- else if (lua_isstring (L, index))
- {
- const char *cstr = lua_tostring (L, index);
- c = rgba8_push_lookup (L, cstr);
- lua_replace (L, index);
- }
- else
- {
- c = check_agg_rgba8 (L, index);
- }
-
- return c;
-}
-
static int
agg_plot_add_gener (lua_State *L, bool as_line)
{
@@ -401,7 +190,7 @@ agg_plot_add_gener (lua_State *L, bool as_line)
if (narg > 4)
{
- curr = lparse_spec_pipeline (L, 5, curr);
+ curr = parse_spec_pipeline (L, 5, curr);
lua_pop (L, 1);
}
@@ -412,7 +201,7 @@ agg_plot_add_gener (lua_State *L, bool as_line)
if (narg > 3)
{
- curr = lparse_spec_pipeline (L, 4, curr);
+ curr = parse_spec_pipeline (L, 4, curr);
lua_pop (L, 1);
}
@@ -469,6 +258,7 @@ agg_plot_show (lua_State *L)
pthread_attr_destroy (attr);
mlua_window_unref(L, p->id);
p->id = -1;
+ AGG_UNLOCK();
return luaL_error(L, "error creating thread.");
}
@@ -481,20 +271,6 @@ agg_plot_show (lua_State *L)
return 0;
}
-int
-window_debug_list(lua_State *L)
-{
- lua_getfield (L, LUA_REGISTRYINDEX, "GSL.windows");
- return 1;
-}
-
-int
-obj_getfenv(lua_State *L)
-{
- lua_getfenv (L, 1);
- return 1;
-}
-
void
plot_register (lua_State *L)
{
diff --git a/agg-plot/plot-window.cpp b/agg-plot/plot-window.cpp
new file mode 100644
index 00000000..d16688b3
--- /dev/null
+++ b/agg-plot/plot-window.cpp
@@ -0,0 +1,345 @@
+
+extern "C" {
+#include "lua.h"
+#include "lauxlib.h"
+}
+
+#include "platform/agg_platform_support.h"
+
+#include "defs.h"
+#include "resource-manager.h"
+#include "gsl-shell.h"
+#include "plot-window.h"
+#include "agg-parse-trans.h"
+#include "lua-cpp-utils.h"
+#include "lua-utils.h"
+#include "lua-draw.h"
+#include "gs-types.h"
+#include "colors.h"
+#include "canvas.h"
+#include "trans.h"
+
+extern void platform_support_prepare ();
+extern void platform_support_lock (agg::platform_support *app);
+extern void platform_support_unlock (agg::platform_support *app);
+extern bool platform_support_is_mapped (agg::platform_support *app);
+
+
+__BEGIN_DECLS
+
+static void * win_thread_function (void *_win);
+
+static int plot_window_new (lua_State *L);
+static int plot_window_free (lua_State *L);
+static int plot_window_index (lua_State *L);
+static int plot_window_draw (lua_State *L);
+static int plot_window_clear (lua_State *L);
+static int plot_window_update (lua_State *L);
+static int plot_window_size (lua_State *L);
+static int plot_window_set_transform (lua_State *L);
+
+static const struct luaL_Reg plotwin_functions[] = {
+ {"window", plot_window_new},
+ {NULL, NULL}
+};
+
+static const struct luaL_Reg plot_window_methods[] = {
+ {"__gc", plot_window_free},
+ {"__index", plot_window_index},
+ {NULL, NULL}
+};
+
+static const struct luaL_Reg plot_window_methods_protected[] = {
+ {"draw", plot_window_draw},
+ {"clear", plot_window_clear},
+ {"update", plot_window_update},
+ {"size", plot_window_size},
+ {"transform", plot_window_set_transform},
+ {NULL, NULL}
+};
+
+__END_DECLS
+
+class plot_window : public agg::platform_support {
+private:
+ canvas *m_canvas;
+ agg::rgba m_bgcolor;
+
+public:
+ agg::trans_affine m_trans;
+
+ enum win_status_e { not_ready, starting, running, error, closed };
+
+ int id;
+ enum win_status_e status;
+
+ plot_window(agg::rgba& bgcol) :
+ agg::platform_support(agg::pix_format_bgr24, true),
+ m_canvas(NULL), m_bgcolor(bgcol), m_trans(), id(-1), status(not_ready)
+ { };
+
+ virtual ~plot_window()
+ {
+ if (m_canvas)
+ delete m_canvas;
+ };
+
+ virtual void on_init();
+ virtual void on_resize(int sx, int sy);
+
+ void start();
+ void clear() { if (m_canvas) m_canvas->clear(); };
+
+ bool draw(vertex_source *obj, agg::rgba8 *color)
+ {
+ if (! m_canvas)
+ return false;
+
+ m_canvas->draw(*obj, *color);
+ return true;
+ };
+
+ void set_transform(double sx, double sy, double x0, double y0)
+ {
+ m_trans = agg::trans_affine(sx, 0.0, 0.0, sy, x0, y0);
+ };
+
+ static plot_window *check (lua_State *L, int index);
+};
+
+void
+plot_window::on_init()
+{
+ if (m_canvas)
+ delete m_canvas;
+
+ m_canvas = new canvas(rbuf_window(), width(), height(), m_bgcolor);
+}
+
+void
+plot_window::on_resize(int sx, int sy)
+{
+ if (m_canvas)
+ delete m_canvas;
+
+ m_canvas = new canvas(rbuf_window(), sx, sy, m_bgcolor);
+}
+
+void
+plot_window::start()
+{
+ this->caption("GSL shell plot");
+ if (this->init(480, 480, agg::window_resize))
+ {
+ this->status = plot_window::running;
+
+ this->run();
+
+ this->status = plot_window::closed;
+
+ GSL_SHELL_LOCK();
+ gsl_shell_unref_plot (this->id);
+ GSL_SHELL_UNLOCK();
+ }
+
+ platform_support_unlock (this);
+}
+
+void *
+win_thread_function (void *_win)
+{
+ platform_support_prepare();
+
+ plot_window *win = (plot_window *) _win;
+ win->start();
+ return NULL;
+}
+
+plot_window *
+plot_window::check (lua_State *L, int index)
+{
+ return (plot_window *) gs_check_userdata (L, index, GS_AGG_WINDOW);
+}
+
+int
+plot_window_new (lua_State *L)
+{
+ agg::rgba8 *c8;
+
+ if (lua_gettop (L) == 0)
+ c8 = rgba8_push_default (L);
+ else
+ c8 = color_arg_lookup (L, 1);
+
+ const double bs = (double) agg::rgba8::base_mask;
+ agg::rgba color(c8->r / bs, c8->g / bs, c8->b / bs, c8->a / bs);
+
+ plot_window *win = new(L, GS_AGG_WINDOW) plot_window(color);
+
+ win->id = mlua_window_ref(L, lua_gettop (L));
+
+ pthread_attr_t attr[1];
+ pthread_t win_thread[1];
+
+ pthread_attr_init (attr);
+ pthread_attr_setdetachstate (attr, PTHREAD_CREATE_DETACHED);
+
+ platform_support_lock (win);
+
+ if (pthread_create(win_thread, attr, win_thread_function, (void*) win))
+ {
+ mlua_window_unref(L, win->id);
+
+ pthread_attr_destroy (attr);
+ win->status = plot_window::error;
+
+ luaL_error(L, "error creating thread");
+ }
+
+ pthread_attr_destroy (attr);
+ win->status = plot_window::starting;
+
+ return 1;
+}
+
+int
+plot_window_free (lua_State *L)
+{
+ plot_window *win = plot_window::check (L, 1);
+ win->~plot_window();
+ return 0;
+}
+
+int
+plot_window_draw (lua_State *L)
+{
+ plot_window *win = plot_window::check (L, 1);
+ int narg = lua_gettop (L);
+ agg::rgba8 *color;
+
+ if (narg <= 2)
+ color = rgba8_push_default (L);
+ else
+ color = color_arg_lookup (L, 3);
+
+ vertex_source *curr = check_agg_obj (L, 2);
+
+ trans::affine *to = new trans::affine(curr);
+ to->set_matrix(win->m_trans);
+ curr = to;
+
+ if (narg > 3)
+ {
+ curr = parse_spec_pipeline (L, 4, curr);
+ lua_pop (L, 1);
+ }
+
+ bool success = win->draw(curr, color);
+
+ lua_management::dispose(curr);
+
+ if (! success)
+ return luaL_error (L, "canvas not ready");
+
+ return 0;
+}
+
+int
+plot_window_clear (lua_State *L)
+{
+ plot_window *win = plot_window::check (L, 1);
+ win->clear();
+ return 0;
+}
+
+int
+plot_window_update (lua_State *L)
+{
+ plot_window *win = plot_window::check (L, 1);
+ win->update_window();
+ return 0;
+}
+
+static int
+plot_window_index_protected (lua_State *L)
+{
+ plot_window *win = plot_window::check(L, lua_upvalueindex(2));
+
+ platform_support_lock (win);
+
+ if (win->status != plot_window::running)
+ {
+ platform_support_unlock (win);
+ return luaL_error (L, "window is not active");
+ }
+
+ int narg = lua_gettop (L);
+
+ lua_pushvalue (L, lua_upvalueindex(1));
+ lua_insert (L, 1);
+
+ if (lua_pcall (L, narg, LUA_MULTRET, 0) != 0)
+ {
+ platform_support_unlock (win);
+ return lua_error (L);
+ }
+
+ platform_support_unlock (win);
+ return lua_gettop (L);
+}
+
+int
+plot_window_index (lua_State *L)
+{
+ const char *key = luaL_checkstring (L, 2);
+
+ const struct luaL_Reg *r = mlua_find_method (plot_window_methods, key);
+ if (r)
+ {
+ lua_pushcfunction (L, r->func);
+ return 1;
+ }
+
+ r = mlua_find_method (plot_window_methods_protected, key);
+ if (r)
+ {
+ lua_pushcfunction (L, r->func);
+ lua_pushvalue (L, 1);
+ lua_pushcclosure (L, plot_window_index_protected, 2);
+ return 1;
+ }
+
+ return 0;
+}
+
+int
+plot_window_size (lua_State *L)
+{
+ plot_window *win = plot_window::check(L, 1);
+ lua_pushinteger (L, win->width());
+ lua_pushinteger (L, win->height());
+ return 2;
+}
+
+int
+plot_window_set_transform (lua_State *L)
+{
+ plot_window *win = plot_window::check(L, 1);
+ double sx = luaL_checknumber (L, 2);
+ double sy = luaL_checknumber (L, 3);
+ double x0 = luaL_optnumber (L, 4, 0.0);
+ double y0 = luaL_optnumber (L, 5, 0.0);
+ win->set_transform(sx, sy, x0, y0);
+ return 0;
+}
+
+void
+plot_window_register (lua_State *L)
+{
+ luaL_newmetatable (L, GS_METATABLE(GS_AGG_WINDOW));
+ luaL_register (L, NULL, plot_window_methods);
+ lua_pop (L, 1);
+
+ /* gsl module registration */
+ luaL_register (L, NULL, plotwin_functions);
+}
diff --git a/agg-plot/plot-window.h b/agg-plot/plot-window.h
new file mode 100644
index 00000000..18a21a0a
--- /dev/null
+++ b/agg-plot/plot-window.h
@@ -0,0 +1,16 @@
+#ifndef PLOT_WINDOW_H
+#define PLOT_WINDOW_H
+
+__BEGIN_DECLS
+#include "lua.h"
+__END_DECLS
+
+#include "defs.h"
+
+__BEGIN_DECLS
+
+extern void plot_window_register (lua_State *L);
+
+__END_DECLS
+
+#endif
diff --git a/agg-plot/trans.h b/agg-plot/trans.h
index 6110263d..35dec2a7 100644
--- a/agg-plot/trans.h
+++ b/agg-plot/trans.h
@@ -6,6 +6,7 @@
#include "agg_conv_curve.h"
#include "agg_conv_dash.h"
#include "agg_conv_transform.h"
+#include "agg_conv_contour.h"
#include "agg_vcgen_markers_term.h"
#include "agg_arrowhead.h"
#include "agg_bounding_rect.h"
@@ -59,6 +60,7 @@ typedef vs_trans_proxy<agg::conv_stroke<vertex_source> > vs_stroke;
typedef vs_trans_proxy<agg::conv_curve<vertex_source> > vs_curve;
typedef vs_trans_proxy<agg::conv_dash<vertex_source> > vs_dash;
typedef vs_trans_proxy<agg::conv_transform<vertex_source> > vs_transform;
+typedef vs_trans_proxy<agg::conv_contour<vertex_source> > vs_contour;
namespace trans {
@@ -127,6 +129,24 @@ namespace trans {
m_source->apply_transform(m, as);
};
};
+
+ class extend : public vs_contour {
+ public:
+ typedef agg::conv_contour<vertex_source> base_type;
+
+ extend(vertex_source* src, double width): vs_contour(src)
+ {
+ base_type& v = self();
+ v.width(width);
+ v.auto_detect_orientation(true);
+ };
+
+ virtual void apply_transform(agg::trans_affine& m, double as)
+ {
+ m_output.approximation_scale(as);
+ m_source->apply_transform(m, as);
+ };
+ };
class resize : public vs_transform {
agg::trans_affine m_matrix;
@@ -161,6 +181,8 @@ namespace trans {
const trans_affine& rotate(double a) { return m_matrix.rotate(a); };
const trans_affine& translate(double x, double y) { return m_matrix.translate(x, y); };
+
+ void set_matrix(const agg::trans_affine& m) { m_matrix = m; };
};
}
diff --git a/agg-plot/xwin-show.cpp b/agg-plot/xwin-show.cpp
index 25cd6bee..f96c401c 100644
--- a/agg-plot/xwin-show.cpp
+++ b/agg-plot/xwin-show.cpp
@@ -77,6 +77,8 @@ xwin_thread_function (void *_plot)
the_application app(agg::pix_format_bgr24, flip_y, p);
app.caption("GSL shell plot");
+ platform_support_lock (&app);
+
if(app.init(780, 400, agg::window_resize))
{
p->window = (void *) &app;
@@ -95,5 +97,7 @@ xwin_thread_function (void *_plot)
GSL_SHELL_UNLOCK();
}
+ platform_support_unlock (&app);
+
return NULL;
}
diff --git a/examples/anim.lua b/examples/anim.lua
new file mode 100644
index 00000000..731d4a6d
--- /dev/null
+++ b/examples/anim.lua
@@ -0,0 +1,34 @@
+
+function circle(x0, y0, R, N)
+ local f = function(j) return x0+R*cos(2*pi*j/N), y0+R*sin(2*pi*j/N) end
+ local ln = ipath(sequence(f, 0, N))
+ ln:close()
+ return ln
+end
+
+function rotate()
+ local c = window('white')
+ local fig = circle(0, 0, 0.3, 5)
+ c:transform(480, 480, 240, 240)
+ local N= 1000
+ for j=0, N do
+ c:clear()
+ c:draw(fig, 'yellow', {{'rotate', angle= 2*pi*j/N}})
+ c:draw(fig, 'black', {{'stroke'}, {'rotate', angle= 2*pi*j/N}})
+ c:update()
+ end
+end
+
+function rotate_OLD()
+ local c = window('white')
+ local fig = circle(0, 0, 100, 5)
+ local N= 1000
+ for j=0, N do
+ c:clear()
+ c:draw(fig, 'yellow', {{'translate', x= 240, y=240},
+ {'rotate', angle= 2*pi*j/N}})
+ c:draw(fig, 'black', {{'stroke'}, {'translate', x= 240, y=240},
+ {'rotate', angle= 2*pi*j/N}})
+ c:update()
+ end
+end
diff --git a/gs-types.c b/gs-types.c
index 375e3871..9e0b9b6c 100644
--- a/gs-types.c
+++ b/gs-types.c
@@ -26,6 +26,7 @@ static int gs_type_string (lua_State *L);
#define GS_DRAW_PATH_NAME_DEF "GSL.path"
#define GS_DRAW_TEXT_NAME_DEF "GSL.text"
#define GS_RGBA_COLOR_NAME_DEF "GSL.rgba"
+#define GS_AGG_WINDOW_NAME_DEF "GSL.window"
#endif
const struct gs_type gs_type_table[] = {
@@ -46,6 +47,7 @@ const struct gs_type gs_type_table[] = {
{GS_DRAW_PATH, GS_DRAW_PATH_NAME_DEF, "geometric line"},
{GS_DRAW_TEXT, GS_DRAW_TEXT_NAME_DEF, "graphical text"},
{GS_RGBA_COLOR, GS_RGBA_COLOR_NAME_DEF, "color"},
+ {GS_AGG_WINDOW, GS_AGG_WINDOW_NAME_DEF, "graphical window"},
#endif
};
diff --git a/gs-types.h b/gs-types.h
index ba9fe580..a4a713db 100644
--- a/gs-types.h
+++ b/gs-types.h
@@ -26,6 +26,7 @@ enum gs_type_e {
GS_DRAW_PATH,
GS_DRAW_TEXT,
GS_RGBA_COLOR,
+ GS_AGG_WINDOW,
#endif
GS_INVALID_TYPE,
};
diff --git a/lua-gsl.c b/lua-gsl.c
index 14fab569..11465b96 100644
--- a/lua-gsl.c
+++ b/lua-gsl.c
@@ -47,6 +47,7 @@
#ifdef AGG_PLOT_ENABLED
#include "lua-plot.h"
#include "lua-draw.h"
+#include "plot-window.h"
#endif
static const struct luaL_Reg gsl_methods_dummy[] = {{NULL, NULL}};
@@ -83,6 +84,7 @@ luaopen_gsl (lua_State *L)
#ifdef AGG_PLOT_ENABLED
plot_register (L);
draw_register (L);
+ plot_window_register (L);
#endif
#ifdef LNUM_COMPLEX
diff --git a/lua-utils.c b/lua-utils.c
index 01565177..c40065fd 100644
--- a/lua-utils.c
+++ b/lua-utils.c
@@ -233,9 +233,9 @@ void
prepare_window_ref_table (lua_State *L)
{
lua_newtable (L);
- lua_pushinteger (L, 0);
- lua_setfield (L, -2, "N");
lua_setfield (L, LUA_REGISTRYINDEX, "GSL.windows");
+ lua_pushinteger (L, 0);
+ lua_setfield (L, LUA_REGISTRYINDEX, "GSL.windows.n");
}
int
@@ -243,13 +243,13 @@ mlua_window_ref(lua_State *L, int index)
{
int n;
- lua_getfield (L, LUA_REGISTRYINDEX, "GSL.windows");
-
- lua_getfield (L, -1, "N");
+ lua_getfield (L, LUA_REGISTRYINDEX, "GSL.windows.n");
n = lua_tointeger (L, -1);
+ lua_pop (L, 1);
lua_pushinteger (L, n+1);
- lua_remove (L, -2);
- lua_setfield (L, -2, "N");
+ lua_setfield (L, LUA_REGISTRYINDEX, "GSL.windows.n");
+
+ lua_getfield (L, LUA_REGISTRYINDEX, "GSL.windows");
lua_pushvalue (L, index);
lua_rawseti (L, -2, n+1);
diff --git a/pre3d/pre3d.js b/pre3d/pre3d.js
new file mode 100644
index 00000000..1996e295
--- /dev/null
+++ b/pre3d/pre3d.js
@@ -0,0 +1,1095 @@
+// Pre3d, a JavaScript software 3d renderer.
+// (c) Dean McNamee <dean@gmail.com>, Dec 2008.
+//
+// Permission is hereby granted, free of charge, to any person obtaining a copy
+// of this software and associated documentation files (the "Software"), to
+// deal in the Software without restriction, including without limitation the
+// rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+// sell copies of the Software, and to permit persons to whom the Software is
+// furnished to do so, subject to the following conditions:
+//
+// The above copyright notice and this permission notice shall be included in
+// all copies or substantial portions of the Software.
+//
+// THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+// IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+// FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+// AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+// LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+// IN THE SOFTWARE.
+//
+// Here are a few notes about what was involved in making this code fast.
+//
+// - Being careful about painting The engine works in quads, 4 vertices per
+// face, no restriction on being coplanar, or on triangles. If we were to
+// work only in triangles, we would have to do twice as many paints and
+// longer sorts, since we would double the polygon count.
+//
+// Depending on the underlying rasterization system, strokes can be pretty
+// slow, slower than fills. This is why overdraw is not a stroke.
+//
+// - Objects over Arrays
+// Because Arrays always go through the key lookup path (a[0] is a['0']), and
+// there is no way to do a named lookup (like a.0), it is faster to use
+// objects than arrays for fixed size storage. You can think of this like
+// the difference between a List and Tuple in languages like python. Modern
+// engines can do a better job accessing named properties, so we represented
+// our data as objects. Profiling showed a huge difference, keyed lookup
+// used to be the most expensive operation in profiling, taking around ~5%.
+//
+// There is also a performance (and convenience) balance betweening object
+// literals and constructor functions. Small and obvious structures like
+// points have no constructor, and are expected to be created as object
+// literals. Objects with many properties are created through a constructor.
+//
+// - Object creation / GC pressure
+// One of the trickiest things about a language like JavaScript is avoiding
+// long GC pauses and object churn. You can do things like cache and reuse
+// objects, avoid creating extra intermediate objects, etc. Right now there
+// has been a little bit of work done here, but there is more to be done.
+//
+// - Flattening
+// It is very tempting as a programmer to write generic routines, for example
+// math functions that could work on either 2d or 3d. This is convenient,
+// but the caller already knows which they should be using, and the extra
+// overhead for generic routines turned out to be substantial. Unrolling
+// specialized code makes a big difference, for example an early profile:
+// before: 2.5% 2.5% Function: subPoints // old general 2d and 3d
+// after: 0.3% 0.3% Function: subPoints2d // fast case 2d
+// after: 0.2% 0.2% Function: subPoints3d // fast case 3d
+//
+// - Don't use new if you don't have to
+// Some profiles showed that new (JSConstructCall) at about ~1%. These were
+// for code like new Array(size); Specifically for the Array constructor, it
+// ignores the object created and passed in via new, and returns a different
+// object anyway. This means 'new Array()' and 'Array()' should be
+// interchangable, and this allows you to avoid the overhead for new.
+//
+// - Local variable caching
+// In most cases it should be faster to look something up in the local frame
+// than to evaluate the expression / lookup more than once. In these cases
+// I generally try to cache the variable in a local var.
+//
+// You might notice that in a few places there is code like:
+// Blah.protype.someMethod = function someMethod() { }
+// someMethod is duplicated on the function so that the name of the function
+// is not anonymous, and it can be easier to debug and profile.
+
+var Pre3d = (function() {
+
+ // 2D and 3D point / vector / matrix math. Points and vectors are expected
+ // to have an x, y and z (if 3d) property. It is important to be consistent
+ // when creating these objects to allow the JavaScript engine to properly
+ // optimize the property access. Create this as object literals, ex:
+ // var my_2d_point_or_vector = {x: 0, y: 0};
+ // var my_3d_point_or_vector = {x: 0, y: 0, z: 0};
+ //
+ // There is one convention that might be confusing. In order to avoid extra
+ // object creations, there are some "IP" versions of these functions. This
+ // stands for "in place", and they write the result to one of the arguments.
+
+ function crossProduct(a, b) {
+ // a1b2 - a2b1, a2b0 - a0b2, a0b1 - a1b0
+ return {
+ x: a.y * b.z - a.z * b.y,
+ y: a.z * b.x - a.x * b.z,
+ z: a.x * b.y - a.y * b.x
+ };
+ }
+
+ function dotProduct2d(a, b) {
+ return a.x * b.x + a.y * b.y;
+ }
+ function dotProduct3d(a, b) {
+ return a.x * b.x + a.y * b.y + a.z * b.z;
+ }
+
+ // a - b
+ function subPoints2d(a, b) {
+ return {x: a.x - b.x, y: a.y - b.y};
+ }
+ function subPoints3d(a, b) {
+ return {x: a.x - b.x, y: a.y - b.y, z: a.z - b.z};
+ }
+
+ // c = a - b
+ function subPoints2dIP(c, a, b) {
+ c.x = a.x - b.x;
+ c.y = a.y - b.y;
+ return c;
+ }
+ function subPoints3dIP(c, a, b) {
+ c.x = a.x - b.x;
+ c.y = a.y - b.y;
+ c.z = a.z - b.z;
+ return c;
+ }
+
+ // a + b
+ function addPoints2d(a, b) {
+ return {x: a.x + b.x, y: a.y + b.y};
+ }
+ function addPoints3d(a, b) {
+ return {x: a.x + b.x, y: a.y + b.y, z: a.z + b.z};
+ }
+
+ // c = a + b
+ function addPoints2dIP(c, a, b) {
+ c.x = a.x + b.x;
+ c.y = a.y + b.y;
+ return c;
+ }
+ function addPoints3dIP(c, a, b) {
+ c.x = a.x + b.x;
+ c.y = a.y + b.y;
+ c.z = a.z + b.z;
+ return c;
+ }
+
+ // a * s
+ function mulPoint2d(a, s) {
+ return {x: a.x * s, y: a.y * s};
+ }
+ function mulPoint3d(a, s) {
+ return {x: a.x * s, y: a.y * s, z: a.z * s};
+ }
+
+ // |a|
+ function vecMag2d(a) {
+ var ax = a.x, ay = a.y;
+ return Math.sqrt(ax * ax + ay * ay);
+ }
+ function vecMag3d(a) {
+ var ax = a.x, ay = a.y, az = a.z;
+ return Math.sqrt(ax * ax + ay * ay + az * az);
+ }
+
+ // a / |a|
+ function unitVector2d(a) {
+ return mulPoint2d(a, 1 / vecMag2d(a));
+ }
+ function unitVector3d(a) {
+ return mulPoint3d(a, 1 / vecMag3d(a));
+ }
+
+ // Linear interpolation on the line along points (0, |a|) and (1, |b|). The
+ // position |d| is the x coordinate, where 0 is |a| and 1 is |b|.
+ function linearInterpolate(a, b, d) {
+ return (b-a)*d + a;
+ }
+
+ // Linear interpolation on the line along points |a| and |b|. |d| is the
+ // position, where 0 is |a| and 1 is |b|.
+ function linearInterpolatePoints3d(a, b, d) {
+ return {
+ x: (b.x-a.x)*d + a.x,
+ y: (b.y-a.y)*d + a.y,
+ z: (b.z-a.z)*d + a.z
+ }
+ }
+
+ // This represents an affine 4x4 matrix, stored as a 3x4 matrix with the last
+ // row implied as [0, 0, 0, 1]. This is to avoid generally unneeded work,
+ // skipping part of the homogeneous coordinates calculations and the
+ // homogeneous divide. Unlike points, we use a constructor function instead
+ // of object literals to ensure map sharing. The matrix looks like:
+ // e0 e1 e2 e3
+ // e4 e5 e6 e7
+ // e8 e9 e10 e11
+ // 0 0 0 1
+ function AffineMatrix(e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11) {
+ this.e0 = e0;
+ this.e1 = e1;
+ this.e2 = e2;
+ this.e3 = e3;
+ this.e4 = e4;
+ this.e5 = e5;
+ this.e6 = e6;
+ this.e7 = e7;
+ this.e8 = e8;
+ this.e9 = e9;
+ this.e10 = e10;
+ this.e11 = e11;
+ };
+
+ // Matrix multiplication of AffineMatrix |a| x |b|. This is unrolled,
+ // and includes the calculations with the implied last row.
+ function multiplyAffine(a, b) {
+ // Avoid repeated property lookups by accessing into the local frame.
+ var a0 = a.e0, a1 = a.e1, a2 = a.e2, a3 = a.e3, a4 = a.e4, a5 = a.e5;
+ var a6 = a.e6, a7 = a.e7, a8 = a.e8, a9 = a.e9, a10 = a.e10, a11 = a.e11;
+ var b0 = b.e0, b1 = b.e1, b2 = b.e2, b3 = b.e3, b4 = b.e4, b5 = b.e5;
+ var b6 = b.e6, b7 = b.e7, b8 = b.e8, b9 = b.e9, b10 = b.e10, b11 = b.e11;
+
+ return new AffineMatrix(
+ a0 * b0 + a1 * b4 + a2 * b8,
+ a0 * b1 + a1 * b5 + a2 * b9,
+ a0 * b2 + a1 * b6 + a2 * b10,
+ a0 * b3 + a1 * b7 + a2 * b11 + a3,
+ a4 * b0 + a5 * b4 + a6 * b8,
+ a4 * b1 + a5 * b5 + a6 * b9,
+ a4 * b2 + a5 * b6 + a6 * b10,
+ a4 * b3 + a5 * b7 + a6 * b11 + a7,
+ a8 * b0 + a9 * b4 + a10 * b8,
+ a8 * b1 + a9 * b5 + a10 * b9,
+ a8 * b2 + a9 * b6 + a10 * b10,
+ a8 * b3 + a9 * b7 + a10 * b11 + a11
+ );
+ }
+
+ function makeIdentityAffine() {
+ return new AffineMatrix(
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0
+ );
+ }
+
+ // http://en.wikipedia.org/wiki/Rotation_matrix
+ function makeRotateAffineX(theta) {
+ var s = Math.sin(theta);
+ var c = Math.cos(theta);
+ return new AffineMatrix(
+ 1, 0, 0, 0,
+ 0, c, -s, 0,
+ 0, s, c, 0
+ );
+ }
+
+ function makeRotateAffineY(theta) {
+ var s = Math.sin(theta);
+ var c = Math.cos(theta);
+ return new AffineMatrix(
+ c, 0, s, 0,
+ 0, 1, 0, 0,
+ -s, 0, c, 0
+ );
+ }
+
+ function makeRotateAffineZ(theta) {
+ var s = Math.sin(theta);
+ var c = Math.cos(theta);
+ return new AffineMatrix(
+ c, -s, 0, 0,
+ s, c, 0, 0,
+ 0, 0, 1, 0
+ );
+ }
+
+ function makeTranslateAffine(dx, dy, dz) {
+ return new AffineMatrix(
+ 1, 0, 0, dx,
+ 0, 1, 0, dy,
+ 0, 0, 1, dz
+ );
+ }
+
+ function makeScaleAffine(sx, sy, sz) {
+ return new AffineMatrix(
+ sx, 0, 0, 0,
+ 0, sy, 0, 0,
+ 0, 0, sz, 0
+ );
+ }
+
+ // Return a copy of the affine matrix |m|.
+ function dupAffine(m) {
+ return new AffineMatrix(
+ m.e0, m.e1, m.e2, m.e3,
+ m.e4, m.e5, m.e6, m.e7,
+ m.e8, m.e9, m.e10, m.e11);
+ }
+
+ // Return the transpose of the inverse done via the classical adjoint. This
+ // skips division by the determinant, so vectors transformed by the resulting
+ // transform will not retain their original length.
+ // Reference: "Transformations of Surface Normal Vectors" by Ken Turkowski.
+ function transAdjoint(a) {
+ var a0 = a.e0, a1 = a.e1, a2 = a.e2, a4 = a.e4, a5 = a.e5;
+ var a6 = a.e6, a8 = a.e8, a9 = a.e9, a10 = a.e10;
+ return new AffineMatrix(
+ a10 * a5 - a6 * a9,
+ a6 * a8 - a4 * a10,
+ a4 * a9 - a8 * a5,
+ 0,
+ a2 * a9 - a10 * a1,
+ a10 * a0 - a2 * a8,
+ a8 * a1 - a0 * a9,
+ 0,
+ a6 * a1 - a2 * a5,
+ a4 * a2 - a6 * a0,
+ a0 * a5 - a4 * a1,
+ 0
+ );
+ }
+
+ // Transform the point |p| by the AffineMatrix |t|.
+ function transformPoint(t, p) {
+ return {
+ x: t.e0 * p.x + t.e1 * p.y + t.e2 * p.z + t.e3,
+ y: t.e4 * p.x + t.e5 * p.y + t.e6 * p.z + t.e7,
+ z: t.e8 * p.x + t.e9 * p.y + t.e10 * p.z + t.e11
+ };
+ }
+
+ // A Transform is a convenient wrapper around a AffineMatrix, and it is what
+ // will be exposed for most transforms (camera, etc).
+ function Transform() {
+ this.reset();
+ }
+
+ // Reset the transform to the identity matrix.
+ Transform.prototype.reset = function() {
+ this.m = makeIdentityAffine();
+ };
+
+ // TODO(deanm): We are creating two extra objects here. What would be most
+ // effecient is something like multiplyAffineByRotateXIP(this.m), etc.
+ Transform.prototype.rotateX = function(theta) {
+ this.m =
+ multiplyAffine(makeRotateAffineX(theta), this.m);
+ };
+ Transform.prototype.rotateXPre = function(theta) {
+ this.m =
+ multiplyAffine(this.m, makeRotateAffineX(theta));
+ };
+
+ Transform.prototype.rotateY = function(theta) {
+ this.m =
+ multiplyAffine(makeRotateAffineY(theta), this.m);
+ };
+ Transform.prototype.rotateYPre = function(theta) {
+ this.m =
+ multiplyAffine(this.m, makeRotateAffineY(theta));
+ };
+
+ Transform.prototype.rotateZ = function(theta) {
+ this.m =
+ multiplyAffine(makeRotateAffineZ(theta), this.m);
+ };
+ Transform.prototype.rotateZPre = function(theta) {
+ this.m =
+ multiplyAffine(this.m, makeRotateAffineZ(theta));
+ };
+
+ Transform.prototype.translate = function(dx, dy, dz) {
+ this.m =
+ multiplyAffine(makeTranslateAffine(dx, dy, dz), this.m);
+ };
+ Transform.prototype.translatePre = function(dx, dy, dz) {
+ this.m =
+ multiplyAffine(this.m, makeTranslateAffine(dx, dy, dz));
+ };
+
+ Transform.prototype.scale = function(sx, sy, sz) {
+ this.m =
+ multiplyAffine(makeScaleAffine(sx, sy, sz), this.m);
+ };
+
+ Transform.prototype.scalePre = function(sx, sy, sz) {
+ this.m =
+ multiplyAffine(this.m, makeScaleAffine(sx, sy, sz));
+ };
+
+ Transform.prototype.transformPoint = function(p) {
+ return transformPoint(this.m, p);
+ };
+
+ Transform.prototype.multTransform = function(t) {
+ this.m = multiplyAffine(this.m, t.m);
+ };
+
+ Transform.prototype.setDCM = function(u, v, w) {
+ var m = this.m;
+ m.e0 = u.x; m.e4 = u.y; m.e8 = u.z;
+ m.e1 = v.x; m.e5 = v.y; m.e9 = v.z;
+ m.e2 = w.x; m.e6 = w.y; m.e10 = w.z;
+ };
+
+ Transform.prototype.dup = function() {
+ // TODO(deanm): This should be better.
+ var tm = new Transform();
+ tm.m = dupAffine(this.m);
+ return tm;
+ };
+
+ // Transform and return a new array of points with transform matrix |t|.
+ function transformPoints(t, ps) {
+ var il = ps.length;
+ var out = Array(il);
+ for (var i = 0; i < il; ++i) {
+ out[i] = transformPoint(t, ps[i]);
+ }
+ return out;
+ }
+
+ // Average a list of points, returning a new "centroid" point.
+ function averagePoints(ps) {
+ var avg = {x: 0, y: 0, z: 0};
+ for (var i = 0, il = ps.length; i < il; ++i) {
+ var p = ps[i];
+ avg.x += p.x;
+ avg.y += p.y;
+ avg.z += p.z;
+ }
+
+ // TODO(deanm): 1 divide and 3 multiplies cheaper than 3 divides?
+ var f = 1 / il;
+
+ avg.x *= f;
+ avg.y *= f;
+ avg.z *= f;
+
+ return avg;
+ }
+
+ // Push a and b away from each other. This means that the distance between
+ // a and be should be greater, by 2 units, 1 in each direction.
+ function pushPoints2dIP(a, b) {
+ var vec = unitVector2d(subPoints2d(b, a));
+ addPoints2dIP(b, b, vec);
+ subPoints2dIP(a, a, vec);
+ }
+
+ // RGBA is our simple representation for colors.
+ function RGBA(r, g, b, a) {
+ this.setRGBA(r, g, b, a);
+ };
+
+ RGBA.prototype.setRGBA = function(r, g, b, a) {
+ this.r = r;
+ this.g = g;
+ this.b = b;
+ this.a = a;
+ };
+
+ RGBA.prototype.setRGB = function(r, g, b) {
+ this.setRGBA(r, g, b, 1);
+ };
+
+ RGBA.prototype.invert = function() {
+ this.r = 1 - this.r;
+ this.g = 1 - this.g;
+ this.b = 1 - this.b;
+ };
+
+ RGBA.prototype.dup = function() {
+ return new RGBA(this.r, this.g, this.b, this.a);
+ };
+
+ // A QuadFace represents a polygon, either a four sided quad, or sort of a
+ // degenerated quad triangle. Passing null as i3 indicates a triangle. The
+ // QuadFace stores indices, which will generally point into some vertex list
+ // that the QuadFace has nothing to do with. At the annoyance of keeping
+ // the data up to date, QuadFace stores a pre-calculated centroid and two
+ // normals (two triangles in a quad). This is an optimization for rendering
+ // and procedural operations, and you must set them correctly.
+ // NOTE: The front of a QuadFace has vertices in counter-clockwise order.
+ function QuadFace(i0, i1, i2, i3) {
+ this.i0 = i0;
+ this.i1 = i1;
+ this.i2 = i2;
+ this.i3 = i3;
+
+ this.centroid = null;
+ this.normal1 = null;
+ this.normal2 = null;
+ }
+
+ QuadFace.prototype.isTriangle = function() {
+ return (this.i3 === null);
+ };
+
+ QuadFace.prototype.setQuad = function(i0, i1, i2, i3) {
+ this.i0 = i0;
+ this.i1 = i1;
+ this.i2 = i2;
+ this.i3 = i3;
+ };
+
+ QuadFace.prototype.setTriangle = function(i0, i1, i2) {
+ this.i0 = i0;
+ this.i1 = i1;
+ this.i2 = i2;
+ this.i3 = null;
+ };
+
+ // A Shape represents a mesh, a collection of QuadFaces. The Shape stores
+ // a list of all vertices (so they can be shared across QuadFaces), and the
+ // QuadFaces store indices into this list.
+ //
+ // All properties of shapes are meant to be public, so access them directly.
+ function Shape() {
+ // Array of 3d points, our vertices.
+ this.vertices = [ ];
+ // Array of QuadFaces, the indices will point into |vertices|.
+ this.quads = [ ];
+ }
+
+ // A curve represents a bezier curve, either quadratic or cubic. It is
+ // the QuadFace equivalent for 3d paths. Like QuadFace, the points are
+ // indices into a Path.
+ function Curve(ep, c0, c1) {
+ this.ep = ep; // End point.
+ this.c0 = c0; // Control point.
+ this.c1 = c1; // Control point.
+ }
+
+ Curve.prototype.isQuadratic = function() {
+ return (this.c1 === null);
+ };
+
+ Curve.prototype.setQuadratic = function(ep, c0) {
+ this.ep = ep;
+ this.c0 = c0;
+ this.c1 = null;
+ };
+
+ Curve.prototype.setCubic = function(ep, c0, c1) {
+ this.ep = ep;
+ this.c0 = c0;
+ this.c1 = c1;
+ };
+
+ // A path is a collection of Curves. The path starts implicitly at
+ // (0, 0, 0), and then continues along each curve, each piece of curve
+ // continuing where the last left off, forming a continuous path.
+ function Path() {
+ // An array of points.
+ this.points = [ ];
+ // The Curves index into points.
+ this.curves = [ ];
+ // Optional starting point. If this is null, the path will start at the
+ // origin (0, 0, 0). Otherwise this is an index into points.
+ this.starting_point = null;
+ }
+
+ // A camera is represented by a transform, and a focal length.
+ function Camera() {
+ this.transform = new Transform();
+ this.focal_length = 1;
+ }
+
+ // TextureInfo is used to describe when and how a QuadFace should be
+ // textured. |image| should be something drawable by <canvas>, like a <img>
+ // or another <canvas> element. This also stores the 2d uv coordinates.
+ function TextureInfo() {
+ this.image = null;
+ this.u0 = null;
+ this.v0 = null;
+ this.u1 = null;
+ this.v1 = null;
+ this.u2 = null;
+ this.v2 = null;
+ this.u3 = null;
+ this.v3 = null;
+ };
+
+ // This is the guts, drawing 3d onto a <canvas> element. This class does a
+ // few things:
+ // - Manage the render state, things like colors, transforms, camera, etc.
+ // - Manage a buffer of quads to be drawn. When you add something to be
+ // drawn, it will use the render state at the time it was added. The
+ // pattern is generally to add some things, modify the render state, add
+ // some more things, change some colors, add some more, than draw.
+ // NOTE: The reason for buffering is having to z-sort. We do not perform
+ // the rasterization, so something like a z-buffer isn't applicable.
+ // - Draw the buffer of things to be drawn. This will do a background
+ // color paint, render all of the buffered quads to the screen, etc.
+ //
+ // NOTE: Drawing does not clear the buffered quads, so you can keep drawing
+ // and adding more things and drawing, etc. You must explicitly empty the
+ // things to be drawn when you want to start fresh.
+ //
+ // NOTE: Some things, such as colors, as copied into the buffered state as
+ // a reference. If you want to update the color on the render state, you
+ // should replace it with a new color. Modifying the original will modify
+ // it for objects that have already been buffered. Same holds for textures.
+ function Renderer(canvas_element) {
+ // Should we z-sort for painters back to front.
+ this.perform_z_sorting = true;
+ // Should we inflate quads to visually cover up antialiasing gaps.
+ this.draw_overdraw = true;
+ // Should we skip backface culling.
+ this.draw_backfaces = false;
+
+ this.texture = null;
+ this.fill_rgba = new RGBA(1, 0, 0, 1);
+
+ this.stroke_rgba = null;
+
+ this.normal1_rgba = null;
+ this.normal2_rgba = null;
+
+ this.canvas = canvas_element;
+ this.ctx = canvas_element.getContext('2d');
+
+ // The camera.
+ this.camera = new Camera();
+
+ // Object to world coordinates transformation.
+ this.transform = new Transform();
+
+ // Used for pushTransform and popTransform. The current transform is
+ // always r.transform, and the stack holds anything else. Internal.
+ this.transform_stack_ = [ ];
+
+ // A callback before a QuadFace is processed during bufferShape. This
+ // allows you to change the render state per-quad, and also to skip a quad
+ // by returning true from the callback. For example:
+ // renderer.quad_callback = function(quad_face, quad_index, shape) {
+ // renderer.fill_rgba.r = quad_index * 40;
+ // return false; // Don't skip this quad.
+ // };
+ this.quad_callback = null;
+
+ // Internals, don't access me.
+ this.width_ = canvas_element.width;
+ this.height_ = canvas_element.height;
+ this.scale_ = this.height_ / 2;
+ this.xoff_ = this.width_ / 2;
+
+ this.buffered_quads_ = null;
+ this.emptyBuffer();
+
+ // We prefer these functions as they avoid the CSS color parsing path, but
+ // if they're not available (Firefox), then augment the ctx to fall back.
+ if (this.ctx.setStrokeColor == null) {
+ this.ctx.setStrokeColor = function setStrokeColor(r, g, b, a) {
+ var rgba = [
+ Math.floor(r * 255),
+ Math.floor(g * 255),
+ Math.floor(b * 255),
+ a
+ ];
+ this.strokeStyle = 'rgba(' + rgba.join(',') + ')';
+ }
+ }
+ if (this.ctx.setFillColor == null) {
+ this.ctx.setFillColor = function setFillColor(r, g, b, a) {
+ var rgba = [
+ Math.floor(r * 255),
+ Math.floor(g * 255),
+ Math.floor(b * 255),
+ a
+ ];
+ this.fillStyle = 'rgba(' + rgba.join(',') + ')';
+ }
+ }
+ }
+
+ Renderer.prototype.pushTransform = function() {
+ this.transform_stack_.push(this.transform.dup());
+ };
+
+ Renderer.prototype.popTransform = function() {
+ // If the stack is empty we'll end up with undefined as the transform.
+ this.transform = this.transform_stack_.pop();
+ };
+
+ Renderer.prototype.emptyBuffer = function() {
+ this.buffered_quads_ = [ ];
+ };
+
+ // TODO(deanm): Pull the project stuff off the class if possible.
+
+ // http://en.wikipedia.org/wiki/Pinhole_camera_model
+ //
+ // Project the 3d point |p| to a point in 2d.
+ // Takes the current focal_length_ in account.
+ Renderer.prototype.projectPointToCanvas = function projectPointToCanvas(p) {
+ // We're looking down the z-axis in the negative direction...
+ var v = this.camera.focal_length / -p.z;
+ var scale = this.scale_;
+ // Map the height to -1 .. 1, and the width to maintain aspect.
+ return {x: p.x * v * scale + this.xoff_,
+ y: p.y * v * -scale + scale};
+ };
+
+ // Project a 3d point onto the 2d canvas surface (pixel coordinates).
+ // Takes the current focal_length in account.
+ // TODO: flatten this calculation so we don't need make a method call.
+ Renderer.prototype.projectPointsToCanvas =
+ function projectPointsToCanvas(ps) {
+ var il = ps.length;
+ var out = Array(il);
+ for (var i = 0; i < il; ++i) {
+ out[i] = this.projectPointToCanvas(ps[i]);
+ }
+ return out;
+ };
+
+ Renderer.prototype.projectQuadFaceToCanvasIP = function(qf) {
+ qf.i0 = this.projectPointToCanvas(qf.i0);
+ qf.i1 = this.projectPointToCanvas(qf.i1);
+ qf.i2 = this.projectPointToCanvas(qf.i2);
+ if (!qf.isTriangle())
+ qf.i3 = this.projectPointToCanvas(qf.i3);
+ return qf;
+ };
+
+ // Textured triangle drawing by Thatcher Ulrich. Draw a triangle portion of
+ // an image, with the source (uv coordinates) mapped to screen x/y
+ // coordinates. A transformation matrix for this mapping is calculated, so
+ // that the image |im| is rotated / scaled / etc to map to the x/y dest. A
+ // clipping mask is applied when drawing |im|, so only the triangle is drawn.
+ function drawCanvasTexturedTriangle(ctx, im,
+ x0, y0, x1, y1, x2, y2,
+ sx0, sy0, sx1, sy1, sx2, sy2) {
+ ctx.save();
+
+ // Clip the output to the on-screen triangle boundaries.
+ ctx.beginPath();
+ ctx.moveTo(x0, y0);
+ ctx.lineTo(x1, y1);
+ ctx.lineTo(x2, y2);
+ ctx.closePath();
+ ctx.clip();
+
+ var denom =
+ sx0 * (sy2 - sy1) -
+ sx1 * sy2 +
+ sx2 * sy1 +
+ (sx1 - sx2) * sy0;
+
+ var m11 = - (
+ sy0 * (x2 - x1) -
+ sy1 * x2 +
+ sy2 * x1 +
+ (sy1 - sy2) * x0) / denom;
+ var m12 = (
+ sy1 * y2 +
+ sy0 * (y1 - y2) -
+ sy2 * y1 +
+ (sy2 - sy1) * y0) / denom;
+ var m21 = (
+ sx0 * (x2 - x1) -
+ sx1 * x2 +
+ sx2 * x1 +
+ (sx1 - sx2) * x0) / denom;
+ var m22 = - (
+ sx1 * y2 +
+ sx0 * (y1 - y2) -
+ sx2 * y1 +
+ (sx2 - sx1) * y0) / denom;
+ var dx = (
+ sx0 * (sy2 * x1 - sy1 * x2) +
+ sy0 * (sx1 * x2 - sx2 * x1) +
+ (sx2 * sy1 - sx1 * sy2) * x0) / denom;
+ var dy = (
+ sx0 * (sy2 * y1 - sy1 * y2) +
+ sy0 * (sx1 * y2 - sx2 * y1) +
+ (sx2 * sy1 - sx1 * sy2) * y0) / denom;
+
+ ctx.transform(m11, m12, m21, m22, dx, dy);
+
+ // Draw the whole image. Transform and clip will map it onto the
+ // correct output triangle.
+ //
+ // TODO(tulrich): figure out if drawImage goes faster if we specify the
+ // rectangle that bounds the source coords.
+ ctx.drawImage(im, 0, 0);
+ ctx.restore();
+ }
+
+ // A unit vector down the z-axis.
+ var g_z_axis_vector = {x: 0, y: 0, z: 1};
+
+ // Put a shape into the draw buffer, transforming it by the current camera,
+ // applying any current render state, etc.
+ Renderer.prototype.bufferShape = function bufferShape(shape) {
+ var draw_backfaces = this.draw_backfaces;
+ var quad_callback = this.quad_callback;
+
+ // Our vertex transformation matrix.
+ var t = multiplyAffine(this.camera.transform.m,
+ this.transform.m);
+ // Our normal transformation matrix.
+ var tn = transAdjoint(t);
+
+ // We are transforming the points even if we decide it's back facing.
+ // We could just transform the normal, and then only transform the
+ // points if we needed it. But then you need to check to see if the
+ // point was already translated to avoid duplicating work, or just
+ // always calculate it and duplicate the work. Not sure what's best...
+ var world_vertices = transformPoints(t, shape.vertices);
+ var quads = shape.quads;
+
+ for (var j = 0, jl = shape.quads.length; j < jl; ++j) {
+ var qf = quads[j];
+
+ // Call the optional quad callback. This gives a chance to update the
+ // render state per-quad, before we emit into the buffered quads. It
+ // also gives the earliest chance to skip a quad.
+ if (quad_callback !== null && quad_callback(qf, j, shape) === true)
+ continue;
+
+ var centroid = transformPoint(t, qf.centroid);
+
+ // Cull quads that are behind the camera.
+ // TODO(deanm): this should probably involve the focal point?
+ if (centroid.z >= -1)
+ continue;
+
+ // NOTE: The transform tn isn't going to always keep the vectors unit
+ // length, so n1 and n2 should be normalized if needed.
+ // We unit vector n1 (for lighting, etc).
+ var n1 = unitVector3d(transformPoint(tn, qf.normal1));
+ var n2 = transformPoint(tn, qf.normal2);
+
+ // Backface culling. I'm not sure the exact right way to do this, but
+ // this seems to look ok, following the eye from the origin. We look
+ // at the normals of the triangulated quad, and make sure at least one
+ // is point towards the camera...
+ if (draw_backfaces !== true &&
+ dotProduct3d(centroid, n1) > 0 &&
+ dotProduct3d(centroid, n2) > 0) {
+ continue;
+ }
+
+ // Lighting intensity is just based on just one of the normals pointing
+ // towards the camera. Should do something better here someday...
+ var intensity = dotProduct3d(g_z_axis_vector, n1);
+ if (intensity < 0)
+ intensity = 0;
+
+ // We map the quad into world coordinates, and also replace the indices
+ // with the actual points.
+ var world_qf;
+
+ if (qf.isTriangle() === true) {
+ world_qf = new QuadFace(
+ world_vertices[qf.i0],
+ world_vertices[qf.i1],
+ world_vertices[qf.i2],
+ null
+ );
+ } else {
+ world_qf = new QuadFace(
+ world_vertices[qf.i0],
+ world_vertices[qf.i1],
+ world_vertices[qf.i2],
+ world_vertices[qf.i3]
+ );
+ }
+
+ world_qf.centroid = centroid;
+ world_qf.normal1 = n1;
+ world_qf.normal2 = n2;
+
+ var obj = {
+ qf: world_qf,
+ intensity: intensity,
+ draw_overdraw: this.draw_overdraw,
+ texture: this.texture,
+ fill_rgba: this.fill_rgba,
+ stroke_rgba: this.stroke_rgba,
+ normal1_rgba: this.normal1_rgba,
+ normal2_rgba: this.normal2_rgba
+ };
+
+ this.buffered_quads_.push(obj);
+ }
+ };
+
+ // Sort an array of points by z axis.
+ function zSorter(x, y) {
+ return x.qf.centroid.z - y.qf.centroid.z;
+ }
+
+ // Paint the background. You should setup the fill color on ctx.
+ Renderer.prototype.drawBackground = function() {
+ this.ctx.fillRect(0, 0, this.width_, this.height_);
+ };
+
+ // Clear the background so the canvas is transparent.
+ Renderer.prototype.clearBackground = function() {
+ this.ctx.clearRect(0, 0, this.width_, this.height_);
+ };
+
+ Renderer.prototype.drawBuffer = function drawBuffer() {
+ var ctx = this.ctx;
+
+ var all_quads = this.buffered_quads_;
+ var num_quads = all_quads.length;
+
+ // Sort the quads by z-index for painters algorithm :(
+ // We're looking down the z-axis in the negative direction, so we want
+ // to paint the most negative z quads first.
+ if (this.perform_z_sorting === true)
+ all_quads.sort(zSorter);
+
+ for (var j = 0; j < num_quads; ++j) {
+ var obj = all_quads[j];
+ var qf = obj.qf;
+
+ this.projectQuadFaceToCanvasIP(qf);
+
+ var is_triangle = qf.isTriangle();
+
+ if (obj.draw_overdraw === true) {
+ // Unfortunately when we fill with canvas, we can get some gap looking
+ // things on the edges between quads. One possible solution is to
+ // stroke the path, but this turns out to be really expensive. Instead
+ // we try to increase the area of the quad. Each edge pushes its
+ // vertices away from each other. This is sort of similar in concept
+ // to the builtin canvas shadow support (shadowOffsetX, etc). However,
+ // Chrome doesn't support shadows correctly now. It does in trunk, but
+ // using shadows to fill the gaps looks awful, and also seems slower.
+
+ pushPoints2dIP(qf.i0, qf.i1);
+ pushPoints2dIP(qf.i1, qf.i2);
+ if (is_triangle === true) {
+ pushPoints2dIP(qf.i2, qf.i0);
+ } else { // Quad.
+ pushPoints2dIP(qf.i2, qf.i3);
+ pushPoints2dIP(qf.i3, qf.i0);
+ }
+ }
+
+ // Create our quad as a <canvas> path.
+ ctx.beginPath();
+ ctx.moveTo(qf.i0.x, qf.i0.y);
+ ctx.lineTo(qf.i1.x, qf.i1.y);
+ ctx.lineTo(qf.i2.x, qf.i2.y);
+ if (is_triangle !== true)
+ ctx.lineTo(qf.i3.x, qf.i3.y);
+ // Don't bother closing it unless we need to.
+
+ // Fill...
+ var frgba = obj.fill_rgba;
+ if (frgba !== null) {
+ var iy = obj.intensity;
+ ctx.setFillColor(frgba.r * iy, frgba.g * iy, frgba.b * iy, frgba.a);
+ ctx.fill();
+ }
+
+ // Texturing...
+ var texture = obj.texture;
+ if (texture !== null) {
+ drawCanvasTexturedTriangle(ctx, texture.image,
+ qf.i0.x, qf.i0.y, qf.i1.x, qf.i1.y, qf.i2.x, qf.i2.y,
+ texture.u0, texture.v0, texture.u1, texture.v1,
+ texture.u2, texture.v2);
+ if (!is_triangle) {
+ drawCanvasTexturedTriangle(ctx, texture.image,
+ qf.i0.x, qf.i0.y, qf.i2.x, qf.i2.y, qf.i3.x, qf.i3.y,
+ texture.u0, texture.v0, texture.u2, texture.v2,
+ texture.u3, texture.v3);
+ }
+ }
+
+ // Stroke...
+ var srgba = obj.stroke_rgba;
+ if (srgba !== null) {
+ ctx.closePath();
+ ctx.setStrokeColor(srgba.r, srgba.g, srgba.b, srgba.a);
+ ctx.stroke();
+ }
+
+ // Normal lines (stroke)...
+ var n1r = obj.normal1_rgba;
+ var n2r = obj.normal2_rgba;
+ if (n1r !== null) {
+ ctx.setStrokeColor(n1r.r, n1r.g, n1r.b, n1r.a);
+ var screen_centroid = this.projectPointToCanvas(qf.centroid);
+ var screen_point = this.projectPointToCanvas(
+ addPoints3d(qf.centroid, unitVector3d(qf.normal1)));
+ ctx.beginPath();
+ ctx.moveTo(screen_centroid.x, screen_centroid.y);
+ ctx.lineTo(screen_point.x, screen_point.y);
+ ctx.stroke();
+ }
+ if (n2r !== null) {
+ ctx.setStrokeColor(n2r.r, n2r.g, n2r.b, n2r.a);
+ var screen_centroid = this.projectPointToCanvas(qf.centroid);
+ var screen_point = this.projectPointToCanvas(
+ addPoints3d(qf.centroid, unitVector3d(qf.normal2)));
+ ctx.beginPath();
+ ctx.moveTo(screen_centroid.x, screen_centroid.y);
+ ctx.lineTo(screen_point.x, screen_point.y);
+ ctx.stroke();
+ }
+ }
+
+ return num_quads;
+ }
+
+ // Draw a Path. There is no buffering, because there is no culling or
+ // z-sorting. There is currently no filling, paths are only stroked. To
+ // control the render state, you should modify ctx directly, and set whatever
+ // properties you want (stroke color, etc). The drawing happens immediately.
+ Renderer.prototype.drawPath = function drawPath(path, opts) {
+ var ctx = this.ctx;
+ opts = opts || { };
+
+ var t = multiplyAffine(this.camera.transform.m,
+ this.transform.m);
+
+ var screen_points = this.projectPointsToCanvas(
+ transformPoints(t, path.points));
+
+ // Start the path at (0, 0, 0) unless there is an explicit starting point.
+ var start_point = (path.starting_point === null ?
+ this.projectPointToCanvas(transformPoint(t, {x: 0, y: 0, z: 0})) :
+ screen_points[path.starting_point]);
+
+ ctx.beginPath();
+ ctx.moveTo(start_point.x, start_point.y);
+
+ var curves = path.curves;
+ for (var j = 0, jl = curves.length; j < jl; ++j) {
+ var curve = curves[j];
+
+ if (curve.isQuadratic() === true) {
+ var c0 = screen_points[curve.c0];
+ var ep = screen_points[curve.ep];
+ ctx.quadraticCurveTo(c0.x, c0.y, ep.x, ep.y);
+ } else {
+ var c0 = screen_points[curve.c0];
+ var c1 = screen_points[curve.c1];
+ var ep = screen_points[curve.ep];
+ ctx.bezierCurveTo(c0.x, c0.y, c1.x, c1.y, ep.x, ep.y);
+ }
+ }
+
+ // We've connected all our Curves into a <canvas> path, now draw it.
+ if (opts.fill === true) {
+ ctx.fill();
+ } else {
+ ctx.stroke();
+ }
+ };
+
+ return {
+ RGBA: RGBA,
+ AffineMatrix: AffineMatrix,
+ Transform: Transform,
+ QuadFace: QuadFace,
+ Shape: Shape,
+ Curve: Curve,
+ Path: Path,
+ Camera: Camera,
+ TextureInfo: TextureInfo,
+ Renderer: Renderer,
+ Math: {
+ crossProduct: crossProduct,
+ dotProduct2d: dotProduct2d,
+ dotProduct3d: dotProduct3d,
+ subPoints2d: subPoints2d,
+ subPoints3d: subPoints3d,
+ addPoints2d: addPoints2d,
+ addPoints3d: addPoints3d,
+ mulPoint2d: mulPoint2d,
+ mulPoint3d: mulPoint3d,
+ vecMag2d: vecMag2d,
+ vecMag3d: vecMag3d,
+ unitVector2d: unitVector2d,
+ unitVector3d: unitVector3d,
+ linearInterpolate: linearInterpolate,
+ linearInterpolatePoints3d: linearInterpolatePoints3d,
+ averagePoints: averagePoints
+ }
+ };
+})();
diff --git a/pre3d/pre3d.lua b/pre3d/pre3d.lua
new file mode 100644
index 00000000..2c379f44
--- /dev/null
+++ b/pre3d/pre3d.lua
@@ -0,0 +1,949 @@
+-- Pre3d, a JavaScript software 3d renderer.
+-- (c) Dean McNamee <dean@gmail.com>, Dec 2008.
+--
+-- Code adapted for Lua/GSL shell by Francesco Abbate
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the "Software"), to
+-- deal in the Software without restriction, including without limitation the
+-- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+-- sell copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+-- IN THE SOFTWARE.
+--
+-- Here are a few notes about what was involved in making this code fast.
+--
+-- - Being careful about painting The engine works in quads, 4 vertices per
+-- face, no restriction on being coplanar, or on triangles. If we were to
+-- work only in triangles, we would have to do twice as many paints and
+-- longer sorts, since we would double the polygon count.
+--
+-- Depending on the underlying rasterization system, strokes can be pretty
+-- slow, slower than fills. This is why overdraw is not a stroke.
+--
+-- - Objects over Arrays
+-- Because Arrays always go through the key lookup path (a[0] is a['0']), and
+-- there is no way to do a named lookup (like a.0), it is faster to use
+-- objects than arrays for fixed size storage. You can think of this like
+-- the difference between a List and Tuple in languages like python. Modern
+-- engines can do a better job accessing named properties, so we represented
+-- our data as objects. Profiling showed a huge difference, keyed lookup
+-- used to be the most expensive operation in profiling, taking around ~5%.
+--
+-- There is also a performance (and convenience) balance betweening object
+-- literals and constructor functions. Small and obvious structures like
+-- points have no constructor, and are expected to be created as object
+-- literals. Objects with many properties are created through a constructor.
+--
+-- - Object creation / GC pressure
+-- One of the trickiest things about a language like JavaScript is avoiding
+-- long GC pauses and object churn. You can do things like cache and reuse
+-- objects, avoid creating extra intermediate objects, etc. Right now there
+-- has been a little bit of work done here, but there is more to be done.
+--
+-- - Flattening
+-- It is very tempting as a programmer to write generic routines, for example
+-- math functions that could work on either 2d or 3d. This is convenient,
+-- but the caller already knows which they should be using, and the extra
+-- overhead for generic routines turned out to be substantial. Unrolling
+-- specialized code makes a big difference, for example an early profile:
+-- before: 2.5% 2.5% Function: subPoints // old general 2d and 3d
+-- after: 0.3% 0.3% Function: subPoints2d // fast case 2d
+-- after: 0.2% 0.2% Function: subPoints3d // fast case 3d
+--
+-- - Don't use new if you don't have to
+-- Some profiles showed that new (JSConstructCall) at about ~1%. These were
+-- for code like new Array(size); Specifically for the Array constructor, it
+-- ignores the object created and passed in via new, and returns a different
+-- object anyway. This means 'new Array()' and 'Array()' should be
+-- interchangable, and this allows you to avoid the overhead for new.
+--
+-- - Local variable caching
+-- In most cases it should be faster to look something up in the local frame
+-- than to evaluate the expression / lookup more than once. In these cases
+-- I generally try to cache the variable in a local var.
+--
+-- You might notice that in a few places there is code like:
+-- Blah.protype.someMethod = function someMethod() { }
+-- someMethod is duplicated on the function so that the name of the function
+-- is not anonymous, and it can be easier to debug and profile.
+
+-- 2D and 3D point / vector / matrix math. Points and vectors are expected
+-- to have an x, y and z (if 3d) property. It is important to be consistent
+-- when creating these objects to allow the JavaScript engine to properly
+-- optimize the property access. Create this as object literals, ex:
+-- var my_2d_point_or_vector = {x: 0, y: 0};
+-- var my_3d_point_or_vector = {x: 0, y: 0, z: 0};
+--
+-- There is one convention that might be confusing. In order to avoid extra
+-- object creations, there are some "IP" versions of these functions. This
+-- stands for "in place", and they write the result to one of the arguments.
+
+local function crossProduct(a, b)
+ -- a1b2 - a2b1, a2b0 - a0b2, a0b1 - a1b0
+ return {
+ x= a.y * b.z - a.z * b.y,
+ y= a.z * b.x - a.x * b.z,
+ z= a.x * b.y - a.y * b.x
+ }
+end
+
+local function dotProduct2d(a, b)
+ return a.x * b.x + a.y * b.y
+end
+
+local function dotProduct3d(a, b)
+ return a.x * b.x + a.y * b.y + a.z * b.z
+end
+
+ -- a - b
+local function subPoints2d(a, b)
+ return {x= a.x - b.x, y= a.y - b.y}
+end
+
+local function subPoints3d(a, b)
+ return {x= a.x - b.x, y= a.y - b.y, z= a.z - b.z}
+end
+
+-- c = a - b
+local function subPoints2dIP(c, a, b)
+ c.x = a.x - b.x
+ c.y = a.y - b.y
+ return c
+end
+
+local function subPoints3dIP(c, a, b)
+ c.x = a.x - b.x
+ c.y = a.y - b.y
+ c.z = a.z - b.z
+ return c
+end
+
+-- a + b
+local function addPoints2d(a, b)
+ return {x= a.x + b.x, y= a.y + b.y}
+end
+
+local function addPoints3d(a, b)
+ return {x= a.x + b.x, y= a.y + b.y, z= a.z + b.z}
+end
+
+ -- c = a + b
+local function addPoints2dIP(c, a, b)
+ c.x = a.x + b.x
+ c.y = a.y + b.y
+ return c
+end
+
+local function addPoints3dIP(c, a, b)
+ c.x = a.x + b.x
+ c.y = a.y + b.y
+ c.z = a.z + b.z
+ return c
+end
+
+-- a * s
+local function mulPoint2d(a, s)
+ return {x= a.x * s, y= a.y * s}
+end
+
+local function mulPoint3d(a, s)
+ return {x= a.x * s, y= a.y * s, z= a.z * s}
+end
+
+ -- |a|
+local function vecMag2d(a)
+ local ax, ay = a.x, a.y
+ return sqrt(ax * ax + ay * ay)
+end
+
+local function vecMag3d(a)
+ local ax, ay, az = a.x, a.y, a.z
+ return sqrt(ax * ax + ay * ay + az * az)
+end
+
+ -- a / |a|
+local function unitVector2d(a)
+ return mulPoint2d(a, 1 / vecMag2d(a))
+end
+
+local function unitVector3d(a)
+ return mulPoint3d(a, 1 / vecMag3d(a))
+end
+
+ -- Linear interpolation on the line along points (0, |a|) and (1, |b|). The
+ -- position |d| is the x coordinate, where 0 is |a| and 1 is |b|.
+local function linearInterpolate(a, b, d)
+ return (b-a)*d + a
+end
+
+-- Linear interpolation on the line along points |a| and |b|. |d| is the
+-- position, where 0 is |a| and 1 is |b|.
+local function linearInterpolatePoints3d(a, b, d)
+ return {
+ x= (b.x-a.x)*d + a.x,
+ y= (b.y-a.y)*d + a.y,
+ z= (b.z-a.z)*d + a.z
+ }
+end
+
+-- This represents an affine 4x4 matrix, stored as a 3x4 matrix with the last
+-- row implied as [0, 0, 0, 1]. This is to avoid generally unneeded work,
+-- skipping part of the homogeneous coordinates calculations and the
+-- homogeneous divide. Unlike points, we use a constructor function instead
+-- of object literals to ensure map sharing. The matrix looks like:
+-- e0 e1 e2 e3
+-- e4 e5 e6 e7
+-- e8 e9 e10 e11
+-- 0 0 0 1
+local function AffineMatrix(e0, e1, e2, e3, e4, e5, e6, e7, e8, e9, e10, e11)
+ return {
+ e0 = e0,
+ e1 = e1,
+ e2 = e2,
+ e3 = e3,
+ e4 = e4,
+ e5 = e5,
+ e6 = e6,
+ e7 = e7,
+ e8 = e8,
+ e9 = e9,
+ e10 = e10,
+ e11 = e11
+ }
+end
+
+-- Matrix multiplication of AffineMatrix |a| x |b|. This is unrolled,
+-- and includes the calculations with the implied last row.
+local function multiplyAffine(a, b)
+ -- Avoid repeated property lookups by accessing into the local frame.
+ local a0, a1, a2, a3, a4, a5 = a.e0, a.e1, a.e2, a.e3, a.e4, a.e5
+ local a6, a7, a8, a9, a10, a11 = a.e6, a.e7, a.e8, a.e9, a.e10, a.e11
+ local b0, b1, b2, b3, b4, b5 = b.e0, b.e1, b.e2, b.e3, b.e4, b.e5
+ local b6, b7, b8, b9, b10, b11 = b.e6, b.e7, b.e8, b.e9, b.e10, b.e11
+
+ return AffineMatrix(
+ a0 * b0 + a1 * b4 + a2 * b8,
+ a0 * b1 + a1 * b5 + a2 * b9,
+ a0 * b2 + a1 * b6 + a2 * b10,
+ a0 * b3 + a1 * b7 + a2 * b11 + a3,
+ a4 * b0 + a5 * b4 + a6 * b8,
+ a4 * b1 + a5 * b5 + a6 * b9,
+ a4 * b2 + a5 * b6 + a6 * b10,
+ a4 * b3 + a5 * b7 + a6 * b11 + a7,
+ a8 * b0 + a9 * b4 + a10 * b8,
+ a8 * b1 + a9 * b5 + a10 * b9,
+ a8 * b2 + a9 * b6 + a10 * b10,
+ a8 * b3 + a9 * b7 + a10 * b11 + a11
+ )
+end
+
+local function makeIdentityAffine()
+ return AffineMatrix(
+ 1, 0, 0, 0,
+ 0, 1, 0, 0,
+ 0, 0, 1, 0
+ )
+end
+
+-- http://en.wikipedia.org/wiki/Rotation_matrix
+local function makeRotateAffineX(theta)
+ local s = sin(theta)
+ local c = cos(theta)
+ return AffineMatrix(
+ 1, 0, 0, 0,
+ 0, c, -s, 0,
+ 0, s, c, 0
+ )
+end
+
+local function makeRotateAffineY(theta)
+ local s = sin(theta)
+ local c = cos(theta)
+ return AffineMatrix(
+ c, 0, s, 0,
+ 0, 1, 0, 0,
+ -s, 0, c, 0
+ )
+end
+
+local function makeRotateAffineZ(theta)
+ local s = sin(theta)
+ local c = cos(theta)
+ return AffineMatrix(
+ c, -s, 0, 0,
+ s, c, 0, 0,
+ 0, 0, 1, 0
+ )
+ end
+
+local function makeTranslateAffine(dx, dy, dz)
+ return AffineMatrix(
+ 1, 0, 0, dx,
+ 0, 1, 0, dy,
+ 0, 0, 1, dz
+ )
+end
+
+local function makeScaleAffine(sx, sy, sz)
+ return AffineMatrix(
+ sx, 0, 0, 0,
+ 0, sy, 0, 0,
+ 0, 0, sz, 0
+ )
+end
+
+ -- Return a copy of the affine matrix |m|.
+local function dupAffine(m)
+ return AffineMatrix(
+ m.e0, m.e1, m.e2, m.e3,
+ m.e4, m.e5, m.e6, m.e7,
+ m.e8, m.e9, m.e10, m.e11)
+end
+
+-- Return the transpose of the inverse done via the classical adjoint. This
+-- skips division by the determinant, so vectors transformed by the resulting
+-- transform will not retain their original length.
+-- Reference: "Transformations of Surface Normal Vectors" by Ken Turkowski.
+local function transAdjoint(a)
+ local a0, a1, a2, a4, a5 = a.e0, a.e1, a.e2, a.e4, a.e5
+ local a6, a8, a9, a10 = a.e6, a.e8, a.e9, a.e10
+ return AffineMatrix(
+ a10 * a5 - a6 * a9,
+ a6 * a8 - a4 * a10,
+ a4 * a9 - a8 * a5,
+ 0,
+ a2 * a9 - a10 * a1,
+ a10 * a0 - a2 * a8,
+ a8 * a1 - a0 * a9,
+ 0,
+ a6 * a1 - a2 * a5,
+ a4 * a2 - a6 * a0,
+ a0 * a5 - a4 * a1,
+ 0
+ )
+end
+
+-- Transform the point |p| by the AffineMatrix |t|.
+local function transformPoint(t, p)
+ return {
+ x= t.e0 * p.x + t.e1 * p.y + t.e2 * p.z + t.e3,
+ y= t.e4 * p.x + t.e5 * p.y + t.e6 * p.z + t.e7,
+ z= t.e8 * p.x + t.e9 * p.y + t.e10 * p.z + t.e11
+ }
+end
+
+local TransformMT = {}
+TransformMT.__index = TransformMT
+
+ -- A Transform is a convenient wrapper around a AffineMatrix, and it is what
+ -- will be exposed for most transforms (camera, etc).
+local function Transform()
+ local t = {}
+ setmetatable(t, TransformMT)
+ t:reset()
+ return t
+end
+
+-- Reset the transform to the identity matrix.
+function TransformMT.reset(this)
+ this.m = makeIdentityAffine()
+end
+
+-- TODO(deanm): We are creating two extra objects here. What would be most
+-- effecient is something like multiplyAffineByRotateXIP(this.m), etc.
+function TransformMT.rotateX(this, theta)
+ this.m = multiplyAffine(makeRotateAffineX(theta), this.m)
+end
+
+function TransformMT.rotateXPre(this, theta)
+ this.m = multiplyAffine(this.m, makeRotateAffineX(theta))
+ end
+
+function TransformMT.rotateY(this, theta)
+ this.m = multiplyAffine(makeRotateAffineY(theta), this.m)
+ end
+
+function TransformMT.rotateYPre(this, theta)
+ this.m =
+ multiplyAffine(this.m, makeRotateAffineY(theta))
+end
+
+function TransformMT.rotateZ(this, theta)
+ this.m = multiplyAffine(makeRotateAffineZ(theta), this.m)
+end
+
+function TransformMT.rotateZPre(this, theta)
+ this.m =
+ multiplyAffine(this.m, makeRotateAffineZ(theta))
+end
+
+function TransformMT.translate(this, dx, dy, dz)
+ this.m =
+ multiplyAffine(makeTranslateAffine(dx, dy, dz), this.m)
+end
+function TransformMT.translatePre(this, dx, dy, dz)
+ this.m =
+ multiplyAffine(this.m, makeTranslateAffine(dx, dy, dz))
+end
+
+function TransformMT.scale(this, sx, sy, sz)
+ this.m =
+ multiplyAffine(makeScaleAffine(sx, sy, sz), this.m)
+end
+
+function TransformMT.scalePre(this, sx, sy, sz)
+ this.m =
+ multiplyAffine(this.m, makeScaleAffine(sx, sy, sz))
+end
+
+function TransformMT.transformPoint(this, p)
+ return transformPoint(this.m, p)
+end
+
+function TransformMT.multTransform(this, t)
+ this.m = multiplyAffine(this.m, t.m)
+end
+
+function TransformMT.setDCM(this, u, v, w)
+ local m = this.m
+ m.e0, m.e4, m.e8 = u.x, u.y, u.z
+ m.e1, m.e5, m.e9 = v.x, v.y, v.z
+ m.e2, m.e6, m.e10 = w.x, w.y, w.z
+end
+
+function TransformMT.dup(this)
+ -- TODO(deanm): This should be better.
+ local tm = Transform()
+ tm.m = dupAffine(this.m)
+ return tm
+end
+
+-- Transform and return a new array of points with transform matrix |t|.
+local function transformPoints(t, ps)
+ local il = #ps
+ local out = {}
+ for i=1, il do
+ out[i] = transformPoint(t, ps[i])
+ end
+ return out
+end
+
+ -- Average a list of points, returning a new "centroid" point.
+local function averagePoints(ps)
+ local avg = {x= 0, y= 0, z= 0}
+ for i, p in ipairs(ps) do
+ avg.x = avg.x + p.x
+ avg.y = avg.y + p.y
+ avg.z = avg.z + p.z
+ end
+ -- TODO(deanm): 1 divide and 3 multiplies cheaper than 3 divides?
+ local f = 1 / #ps
+
+ avg.x = avg.x * f
+ avg.y = avg.y * f
+ avg.z = avg.z * f
+
+ return avg
+end
+
+ -- Push a and b away from each other. This means that the distance between
+ -- a and be should be greater, by 2 units, 1 in each direction.
+local function pushPoints2dIP(a, b)
+ local vec = unitVector2d(subPoints2d(b, a))
+ addPoints2dIP(b, b, vec)
+ subPoints2dIP(a, a, vec)
+end
+
+-- A QuadFace represents a polygon, either a four sided quad, or sort of a
+-- degenerated quad triangle. Passing null as i3 indicates a triangle. The
+-- QuadFace stores indices, which will generally point into some vertex list
+-- that the QuadFace has nothing to do with. At the annoyance of keeping
+-- the data up to date, QuadFace stores a pre-calculated centroid and two
+-- normals (two triangles in a quad). This is an optimization for rendering
+-- and procedural operations, and you must set them correctly.
+-- NOTE: The front of a QuadFace has vertices in counter-clockwise order.
+
+local QuadFaceMT = {}
+QuadFaceMT.__index = QuadFaceMT
+
+local function QuadFace(i0, i1, i2, i3)
+ local this = {i0= i0, i1= i1, i2= i2, i3= i3}
+ this.centroid = nil
+ this.normal1 = nil
+ this.normal2 = nil
+ setmetatable(this, QuadFaceMT)
+ return this
+end
+
+function QuadFaceMT.isTriangle(this)
+ return this.i3 == nil
+end
+
+function QuadFaceMT.setQuad(this, i0, i1, i2, i3)
+ this.i0 = i0
+ this.i1 = i1
+ this.i2 = i2
+ this.i3 = i3
+end
+
+function QuadFaceMT.setTriangle(i0, i1, i2)
+ this.i0 = i0
+ this.i1 = i1
+ this.i2 = i2
+ this.i3 = nil
+end
+
+-- A Shape represents a mesh, a collection of QuadFaces. The Shape stores
+-- a list of all vertices (so they can be shared across QuadFaces), and the
+-- QuadFaces store indices into this list.
+--
+-- All properties of shapes are meant to be public, so access them directly.
+local function Shape()
+ return {vertices= {}, quads= {}}
+end
+
+local CurveMT = {}
+CurveMT.__index = CurveMT
+
+-- A curve represents a bezier curve, either quadratic or cubic. It is
+-- the QuadFace equivalent for 3d paths. Like QuadFace, the points are
+-- indices into a Path.
+local function Curve(ep, c0, c1)
+ return {ep= ep, c0= c0, c1= c1}
+end
+
+function CurveMT.isQuadratic(this)
+ return this.c1 == nil
+end
+
+function CurveMT.setQuadratic(this, ep, c0)
+ this.ep = ep
+ this.c0 = c0
+ this.c1 = nil
+end
+
+function CurveMT.setCubic(this, ep, c0, c1)
+ this.ep = ep
+ this.c0 = c0
+ this.c1 = c1
+end
+
+-- A path is a collection of Curves. The path starts implicitly at
+-- (0, 0, 0), and then continues along each curve, each piece of curve
+-- continuing where the last left off, forming a continuous path.
+local function Path()
+ -- An array of points.
+ -- The Curves index into points.
+ -- Optional starting point. If this is null, the path will start at the
+ -- origin (0, 0, 0). Otherwise this is an index into points.
+ local this = {points= {}, curves= {}, starting_point = nil}
+ return this
+end
+
+
+-- A camera is represented by a transform, and a focal length.
+local function Camera()
+ local this = {}
+ this.transform = Transform()
+ this.focal_length = 1
+ return this
+end
+
+-- This is the guts, drawing 3d onto a <canvas> element. This class does a
+-- few things:
+-- - Manage the render state, things like colors, transforms, camera, etc.
+-- - Manage a buffer of quads to be drawn. When you add something to be
+-- drawn, it will use the render state at the time it was added. The
+-- pattern is generally to add some things, modify the render state, add
+-- some more things, change some colors, add some more, than draw.
+-- NOTE: The reason for buffering is having to z-sort. We do not perform
+-- the rasterization, so something like a z-buffer isn't applicable.
+-- - Draw the buffer of things to be drawn. This will do a background
+-- color paint, render all of the buffered quads to the screen, etc.
+--
+-- NOTE: Drawing does not clear the buffered quads, so you can keep drawing
+-- and adding more things and drawing, etc. You must explicitly empty the
+-- things to be drawn when you want to start fresh.
+--
+-- NOTE: Some things, such as colors, as copied into the buffered state as
+-- a reference. If you want to update the color on the render state, you
+-- should replace it with a new color. Modifying the original will modify
+-- it for objects that have already been buffered. Same holds for textures.
+local RendererMT = {}
+RendererMT.__index = RendererMT
+
+function Renderer(win)
+ local this = {}
+ setmetatable(this, RendererMT)
+
+ -- Should we z-sort for painters back to front.
+ this.perform_z_sorting = true
+ -- Should we inflate quads to visually cover up antialiasing gaps.
+ this.draw_overdraw = true
+ -- Should we skip backface culling.
+ this.draw_backfaces = false
+
+ -- this.texture = nil
+ this.fill_rgba = rgba(1, 0, 0, 1)
+
+ -- this.stroke_rgba = nil
+
+ -- this.normal1_rgba = nil
+ -- this.normal2_rgba = nil
+
+ this.window = win
+
+ -- The camera.
+ this.camera = Camera()
+
+ -- Object to world coordinates transformation.
+ this.transform = Transform()
+
+ -- Used for pushTransform and popTransform. The current transform is
+ -- always r.transform, and the stack holds anything else. Internal.
+ this.transform_stack_ = {}
+
+ -- A callback before a QuadFace is processed during bufferShape. This
+ -- allows you to change the render state per-quad, and also to skip a quad
+ -- by returning true from the callback. For example:
+ -- renderer.quad_callback = function(quad_face, quad_index, shape) {
+ -- renderer.fill_rgba.r = quad_index * 40;
+ -- return false; // Don't skip this quad.
+ -- };
+ -- this.quad_callback = nil
+
+ -- this.buffered_quads_ = nil
+ this:emptyBuffer()
+
+ return this
+end
+
+function RendererMT.pushTransform(this)
+ table.insert(this.transform_stack_, this.transform.dup())
+end
+
+function RendererMT.popTransform(this)
+ -- If the stack is empty we'll end up with undefined as the transform.
+ local n = #this.transform_stack_
+ if n > 0 then
+ this.transform_stack_[n] = nil
+ else
+ error 'empty transform stack'
+ end
+end
+
+function RendererMT.emptyBuffer(this)
+ this.buffered_quads_ = {}
+end
+
+-- TODO(deanm): Pull the project stuff off the class if possible.
+
+-- http://en.wikipedia.org/wiki/Pinhole_camera_model
+--
+-- Project the 3d point |p| to a point in 2d.
+-- Takes the current focal_length_ in account.
+function RendererMT.projectPointToCanvas(this, p)
+ -- We're looking down the z-axis in the negative direction...
+ local v = this.camera.focal_length / -p.z
+ -- Map the height to -1 .. 1, and the width to maintain aspect.
+ return {x= p.x * v, y= p.y * v}
+-- return {x= p.x + 1/(2*sqrt(2)) * p.z, y= p.y + 1/(2*sqrt(2)) * p.z}
+end
+
+-- Project a 3d point onto the 2d canvas surface (pixel coordinates).
+-- Takes the current focal_length in account.
+-- TODO: flatten this calculation so we don't need make a method call.
+function RendererMT.projectPointsToCanvas(this, ps)
+ local il = #ps
+ local out = {}
+ for i, p in ipairs(ps) do
+ out[i] = this:projectPointToCanvas(p)
+ end
+ return out
+end
+
+function RendererMT.projectQuadFaceToCanvasIP(this, qf)
+ qf.i0 = this:projectPointToCanvas(qf.i0)
+ qf.i1 = this:projectPointToCanvas(qf.i1)
+ qf.i2 = this:projectPointToCanvas(qf.i2)
+ if not qf:isTriangle() then
+ qf.i3 = this:projectPointToCanvas(qf.i3)
+ end
+ return qf
+end
+
+ -- A unit vector down the z-axis.
+local g_z_axis_vector = {x= 0, y= 0, z= 1}
+
+-- Put a shape into the draw buffer, transforming it by the current camera,
+-- applying any current render state, etc.
+function RendererMT.bufferShape(this, shape)
+ local draw_backfaces = this.draw_backfaces
+ local quad_callback = this.quad_callback
+
+ -- Our vertex transformation matrix.
+ local t = multiplyAffine(this.camera.transform.m,
+ this.transform.m)
+ -- Our normal transformation matrix.
+ local tn = transAdjoint(t)
+
+ -- We are transforming the points even if we decide it's back facing.
+ -- We could just transform the normal, and then only transform the
+ -- points if we needed it. But then you need to check to see if the
+ -- point was already translated to avoid duplicating work, or just
+ -- always calculate it and duplicate the work. Not sure what's best...
+ local world_vertices = transformPoints(t, shape.vertices)
+ local quads = shape.quads
+
+ for j, qf in ipairs(quads) do
+ -- Call the optional quad callback. This gives a chance to update the
+ -- render state per-quad, before we emit into the buffered quads. It
+ -- also gives the earliest chance to skip a quad.
+ if not (quad_callback and quad_callback(qf, j, shape)) then
+ local centroid = transformPoint(t, qf.centroid)
+
+ -- Cull quads that are not behind the camera.
+ -- TODO(deanm): this should probably involve the focal point?
+ if centroid.z < -1 then
+ -- NOTE: The transform tn isn't going to always keep the vectors unit
+ -- length, so n1 and n2 should be normalized if needed.
+ -- We unit vector n1 (for lighting, etc).
+ local n1 = unitVector3d(transformPoint(tn, qf.normal1))
+ local n2 = transformPoint(tn, qf.normal2)
+
+ -- Backface culling. I'm not sure the exact right way to do this, but
+ -- this seems to look ok, following the eye from the origin. We look
+ -- at the normals of the triangulated quad, and make sure at least one
+ -- is point towards the camera...
+ if draw_backfaces or dotProduct3d(centroid, n1) <= 0 or
+ dotProduct3d(centroid, n2) <= 0
+ then
+
+ -- Lighting intensity is just based on just one of the normals pointing
+ -- towards the camera. Should do something better here someday...
+ local fill_rgba = this.fill_rgba
+
+ local intensity = dotProduct3d(g_z_axis_vector, n1)
+ if intensity < 0 then
+ intensity = -intensity
+ if this.fill_rgba_backside then
+ fill_rgba = this.fill_rgba_backside
+ end
+ end
+
+ -- We map the quad into world coordinates, and also replace the indices
+ -- with the actual points.
+ local world_qf
+
+ if qf:isTriangle() then
+ world_qf = QuadFace(
+ world_vertices[qf.i0],
+ world_vertices[qf.i1],
+ world_vertices[qf.i2],
+ nil)
+ else
+ world_qf = QuadFace(
+ world_vertices[qf.i0],
+ world_vertices[qf.i1],
+ world_vertices[qf.i2],
+ world_vertices[qf.i3]
+ )
+ end
+
+ world_qf.centroid = centroid
+ world_qf.normal1 = n1
+ world_qf.normal2 = n2
+
+-- local cr, cg, cb = 0x42/255, 0x82/255, 0xAA/255
+-- local cr, cg, cb = 0x4A/255, 0x92/255, 0xBF/255
+
+ if this.set_light_intensity then
+ local r1 = 0.2
+ local ci = intensity * (1-r1)
+ fill_rgba = r1 * fill_rgba + ci * fill_rgba
+ end
+
+ if this.fill_rgba_alpha then
+ fill_rgba:alpha(this.fill_rgba_alpha)
+ end
+
+ local obj = {
+ qf= world_qf,
+ intensity= intensity,
+ draw_overdraw= this.draw_overdraw,
+ texture= this.texture,
+ fill_rgba= fill_rgba,
+ stroke_rgba= this.stroke_rgba,
+ normal1_rgba= this.normal1_rgba,
+ normal2_rgba= this.normal2_rgba
+ }
+
+ table.insert(this.buffered_quads_, obj)
+ end
+ end
+ end
+ end
+end
+
+-- Sort an array of points by z axis.
+local function zSorter(x, y)
+ return x.qf.centroid.z < y.qf.centroid.z
+end
+
+function RendererMT.drawBuffer(this)
+ local win = this.window
+ local all_quads = this.buffered_quads_
+ local num_quads = #all_quads
+
+ win:clear()
+
+ -- Sort the quads by z-index for painters algorithm :(
+ -- We're looking down the z-axis in the negative direction, so we want
+ -- to paint the most negative z quads first.
+ if this.perform_z_sorting then
+ table.sort(all_quads, zSorter)
+ end
+
+ for j, obj in ipairs(all_quads) do
+ local qf = obj.qf
+
+ this:projectQuadFaceToCanvasIP(qf)
+
+ local is_triangle = qf:isTriangle()
+
+ -- Create our quad as a <canvas> path.
+ local qpath = path(qf.i0.x, qf.i0.y)
+ qpath:line_to(qf.i1.x, qf.i1.y)
+ qpath:line_to(qf.i2.x, qf.i2.y)
+ if not is_triangle then
+ qpath:line_to(qf.i3.x, qf.i3.y)
+ end
+ -- Don't bother closing it unless we need to.
+
+ -- Fill...
+ local frgba = obj.fill_rgba
+ if frgba then
+ if obj.draw_overdraw then
+ win:draw(qpath, frgba, {{'extend'}})
+ else
+ win:draw(qpath, frgba)
+ end
+ end
+
+ -- Stroke...
+ local srgba = obj.stroke_rgba
+ if srgba then
+ qpath:close()
+ win:draw(qpath, srgba, {{'stroke'}})
+ end
+
+ -- Normal lines (stroke)...
+ local n1r = obj.normal1_rgba
+ local n2r = obj.normal2_rgba
+ if n1r then
+ local screen_centroid = this:projectPointToCanvas(qf.centroid)
+ local screen_point = this:projectPointToCanvas(
+ addPoints3d(qf.centroid, unitVector3d(qf.normal1)))
+ local n1path = path(screen_centroid.x, screen_centroid.y)
+ n1path:line_to(screen_point.x, screen_point.y)
+ win:draw(n1path, n1r, {{'stroke'}})
+ end
+
+ if n2r then
+ local screen_centroid = this:projectPointToCanvas(qf.centroid)
+ local screen_point = this:projectPointToCanvas(
+ addPoints3d(qf.centroid, unitVector3d(qf.normal2)))
+ local n2path = path(screen_centroid.x, screen_centroid.y)
+ n2path:line_to(screen_point.x, screen_point.y)
+ win:addline(n2path, n2r, {{'stroke'}})
+ end
+ end
+
+ win:update()
+
+ return num_quads
+end
+
+-- Draw a Path. There is no buffering, because there is no culling or
+-- z-sorting. There is currently no filling, paths are only stroked. To
+-- control the render state, you should modify ctx directly, and set whatever
+-- properties you want (stroke color, etc). The drawing happens immediately.
+function RendererMT.drawPath(this, path, opts)
+ local plt = this.plt
+ opts = opts and opts or {}
+
+ local t = multiplyAffine(this.camera.transform.m, this.transform.m)
+
+ local screen_points = this:projectPointsToCanvas(
+ transformPoints(t, path.points))
+
+ -- Start the path at (0, 0, 0) unless there is an explicit starting point.
+ local start_point = ((not path.starting_point) and
+ this:projectPointToCanvas(transformPoint(t, {x= 0, y= 0, z= 0})) or
+ screen_points[path.starting_point])
+
+ local line = path(start_point.x, start_point.y)
+
+ local curves = path.curves
+ for j, curve in ipairs(curves) do
+ if curve:isQuadratic() then
+ local c0 = screen_points[curve.c0]
+ local ep = screen_points[curve.ep]
+ line:curve3(c0.x, c0.y, ep.x, ep.y)
+ else
+ local c0 = screen_points[curve.c0]
+ local c1 = screen_points[curve.c1]
+ local ep = screen_points[curve.ep]
+ line:curve3(c0.x, c0.y, c1.x, c1.y, ep.x, ep.y)
+ end
+ end
+
+ -- We've connected all our Curves into a <canvas> path, now draw it.
+ if opts.fill then
+ plt:add(line, opts.color)
+ else
+ plt:addline(line, opts.color)
+ end
+end
+
+return {
+ RGBA= RGBA,
+ AffineMatrix= AffineMatrix,
+ Transform= Transform,
+ QuadFace= QuadFace,
+ Shape= Shape,
+ Curve= Curve,
+ Path= Path,
+ Camera= Camera,
+ TextureInfo= TextureInfo,
+ Renderer= Renderer,
+ Math = {
+ crossProduct= crossProduct,
+ dotProduct2d= dotProduct2d,
+ dotProduct3d= dotProduct3d,
+ subPoints2d= subPoints2d,
+ subPoints3d= subPoints3d,
+ addPoints2d= addPoints2d,
+ addPoints3d= addPoints3d,
+ mulPoint2d= mulPoint2d,
+ mulPoint3d= mulPoint3d,
+ vecMag2d= vecMag2d,
+ vecMag3d= vecMag3d,
+ unitVector2d= unitVector2d,
+ unitVector3d= unitVector3d,
+ linearInterpolate= linearInterpolate,
+ linearInterpolatePoints3d= linearInterpolatePoints3d,
+ averagePoints= averagePoints
+ }
+}
diff --git a/pre3d/pre3d_shape_utils.lua b/pre3d/pre3d_shape_utils.lua
new file mode 100644
index 00000000..05776bd8
--- /dev/null
+++ b/pre3d/pre3d_shape_utils.lua
@@ -0,0 +1,836 @@
+-- Pre3d, a JavaScript software 3d renderer.
+-- (c) Dean McNamee <dean@gmail.com>, Dec 2008.
+--
+-- Permission is hereby granted, free of charge, to any person obtaining a copy
+-- of this software and associated documentation files (the "Software"), to
+-- deal in the Software without restriction, including without limitation the
+-- rights to use, copy, modify, merge, publish, distribute, sublicense, and/or
+-- sell copies of the Software, and to permit persons to whom the Software is
+-- furnished to do so, subject to the following conditions:
+--
+-- The above copyright notice and this permission notice shall be included in
+-- all copies or substantial portions of the Software.
+--
+-- THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
+-- IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
+-- FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
+-- AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
+-- LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
+-- FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS
+-- IN THE SOFTWARE.
+--
+-- This file implements helpers related to creating / modifying Shapes. Some
+-- routines exist for basic primitives (box, sphere, etc), along with some
+-- routines for procedural shape operations (extrude, subdivide, etc).
+--
+-- The procedural operations were inspired from the demoscene. A lot of the
+-- ideas are based on similar concepts in Farbrausch's werkkzeug1.
+
+local Pre3d = require 'pre3d/pre3d'
+
+-- TODO(deanm): Having to import all the math like this is a bummer.
+local crossProduct = Pre3d.Math.crossProduct;
+local dotProduct2d = Pre3d.Math.dotProduct2d;
+local dotProduct3d = Pre3d.Math.dotProduct3d;
+local subPoints2d = Pre3d.Math.subPoints2d;
+local subPoints3d = Pre3d.Math.subPoints3d;
+local addPoints2d = Pre3d.Math.addPoints2d;
+local addPoints3d = Pre3d.Math.addPoints3d;
+local mulPoint2d = Pre3d.Math.mulPoint2d;
+local mulPoint3d = Pre3d.Math.mulPoint3d;
+local vecMag2d = Pre3d.Math.vecMag2d;
+local vecMag3d = Pre3d.Math.vecMag3d;
+local unitVector2d = Pre3d.Math.unitVector2d;
+local unitVector3d = Pre3d.Math.unitVector3d;
+local linearInterpolate = Pre3d.Math.linearInterpolate;
+local linearInterpolatePoints3d = Pre3d.Math.linearInterpolatePoints3d;
+local averagePoints = Pre3d.Math.averagePoints;
+
+local k2PI = pi * 2;
+
+local push = table.insert
+
+-- averagePoints() specialized for averaging 2 points.
+local function averagePoints2(a, b)
+ return {
+ x= (a.x + b.x) * 0.5,
+ y= (a.y + b.y) * 0.5,
+ z= (a.z + b.z) * 0.5
+ }
+end
+
+-- Rebuild the pre-computed "metadata", for the Shape |shape|. This
+-- calculates the centroids and normal vectors for each QuadFace.
+local function rebuildMeta(shape)
+ local quads = shape.quads
+ local vertices = shape.vertices
+
+ -- TODO: It's possible we could save some work here, we could mark the
+ -- faces "dirty" which need their centroid or normal recomputed. Right now
+ -- if we do an operation on a single face, we rebuild all of them. A
+ -- simple scheme would be to track any writes to a QuadFace, and to set
+ -- centroid / normal1 / normal2 to null. This would also prevent bugs
+ -- where you forget to call rebuildMeta() and used stale metadata.
+
+ for i, qf in ipairs(quads) do
+ local centroid
+ local n1, n2
+
+ local vert0 = vertices[qf.i0]
+ local vert1 = vertices[qf.i1]
+ local vert2 = vertices[qf.i2]
+ local vec01 = subPoints3d(vert1, vert0)
+ local vec02 = subPoints3d(vert2, vert0)
+ local n1 = crossProduct(vec01, vec02)
+
+ if qf:isTriangle() then
+ n2 = n1
+ centroid = averagePoints({vert0, vert1, vert2})
+ else
+ local vert3 = vertices[qf.i3]
+ local vec03 = subPoints3d(vert3, vert0)
+ n2 = crossProduct(vec02, vec03)
+ centroid = averagePoints({vert0, vert1, vert2, vert3})
+ end
+
+ qf.centroid = centroid
+ qf.normal1 = n1
+ qf.normal2 = n2
+ end
+
+ return shape
+end
+
+-- Convert any quad faces into two triangle faces. After triangulation,
+-- |shape| should only consist of triangles.
+local function triangulate(shape)
+ local quads = shape.quads
+ local num_quads = #quads
+ for i=1, num_quads do
+ local qf = quads[i]
+ if not qf:isTriangle() then
+ -- TODO(deanm): Should we follow some clockwise rule here?
+ local newtri = Pre3d.QuadFace(qf.i0, qf.i2, qf.i3)
+ -- Convert the original quad into a triangle.
+ qf.i3 = nil
+ -- Add the new triangle to the list of faces.
+ table.insert(quads, newtri)
+ end
+ end
+ rebuildMeta(shape)
+ return shape
+end
+
+-- Call |func| for each face of |shape|. The callback |func| should return
+-- false to continue iteration, or true to stop. For example:
+-- forEachFace(shape, function(quad_face, quad_index, shape) {
+-- return false
+-- })
+local function forEachFace(shape, func)
+ local quads = shape.quads
+ for i, qf in ipairs(quads) do
+ if func(qf, i, shape) then
+ break
+ end
+ end
+end
+
+local function forEachVertex(shape, func)
+ local vertices = shape.vertices
+ for i, vert in ipairs(vertices) do
+ if func(vert, i, shape) then
+ break
+ end
+ end
+ return shape
+end
+
+local function makePlane(p1, p2, p3, p4)
+ local s = Pre3d.Shape()
+ s.vertices = {p1, p2, p3, p4}
+ s.quads = {Pre3d.QuadFace(1, 2, 3, 4)}
+ rebuildMeta(s)
+ return s
+end
+
+-- Make a box with width (x) |w|, height (y) |h|, and depth (z) |d|.
+local function makeBox(w, h, d)
+ local s = Pre3d.Shape()
+ s.vertices = {
+ {x= w, y= h, z= -d}, -- 0
+ {x= w, y= h, z= d}, -- 1
+ {x= w, y= -h, z= d}, -- 2
+ {x= w, y= -h, z= -d}, -- 3
+ {x= -w, y= h, z= -d}, -- 4
+ {x= -w, y= h, z= d}, -- 5
+ {x= -w, y= -h, z= d}, -- 6
+ {x= -w, y= -h, z= -d} -- 7
+ }
+
+ -- 4 -- 0
+ -- /| /| +y
+ -- 5 -- 1 | |__ +x
+ -- | 7 -|-3 /
+ -- |/ |/ +z
+ -- 6 -- 2
+
+ s.quads = {
+ Pre3d.QuadFace(1, 2, 3, 4), -- Right side
+ Pre4d.QuadFace(2, 6, 7, 3), -- Front side
+ Pre4d.QuadFace(6, 5, 8, 7), -- Left side
+ Pre4d.QuadFace(5, 1, 4, 8), -- Back side
+ Pre4d.QuadFace(1, 5, 6, 2), -- Top side
+ Pre4d.QuadFace(3, 7, 8, 4) -- Bottom side
+ }
+
+ rebuildMeta(s)
+
+ return s
+end
+
+ -- Make a cube with width, height, and depth |whd|.
+local function makeCube(whd)
+ return makeBox(whd, whd, whd)
+end
+
+local function makeBoxWithHole(w, h, d, hw, hh)
+ local s = Pre3d.Shape()
+ s.vertices = {
+ {x= w, y= h, z= -d}, -- 0
+ {x= w, y= h, z= d}, -- 1
+ {x= w, y= -h, z= d}, -- 2
+ {x= w, y= -h, z= -d}, -- 3
+ {x= -w, y= h, z= -d}, -- 4
+ {x= -w, y= h, z= d}, -- 5
+ {x= -w, y= -h, z= d}, -- 6
+ {x= -w, y= -h, z= -d}, -- 7
+
+ -- The front new points ...
+ {x= hw, y= h, z= d}, -- 8
+ {x= w, y= hh, z= d}, -- 9
+ {x= hw, y= hh, z= d}, -- 10
+ {x= hw, y= -h, z= d}, -- 11
+ {x= w, y= -hh, z= d}, -- 12
+ {x= hw, y= -hh, z= d}, -- 13
+
+ {x= -hw, y= h, z= d}, -- 14
+ {x= -w, y= hh, z= d}, -- 15
+ {x= -hw, y= hh, z= d}, -- 16
+ {x= -hw, y= -h, z= d}, -- 17
+ {x= -w, y= -hh, z= d}, -- 18
+ {x= -hw, y= -hh, z= d}, -- 19
+
+ -- The back new points ...
+ {x= hw, y= h, z= -d}, -- 20
+ {x= w, y= hh, z= -d}, -- 21
+ {x= hw, y= hh, z= -d}, -- 22
+ {x= hw, y= -h, z= -d}, -- 23
+ {x= w, y= -hh, z= -d}, -- 24
+ {x= hw, y= -hh, z= -d}, -- 25
+
+ {x= -hw, y= h, z= -d}, -- 26
+ {x= -w, y= hh, z= -d}, -- 27
+ {x= -hw, y= hh, z= -d}, -- 28
+ {x= -hw, y= -h, z= -d}, -- 29
+ {x= -w, y= -hh, z= -d}, -- 30
+ {x= -hw, y= -hh, z= -d} -- 31
+ }
+
+ -- Front Back (looking from front)
+ -- 4 - - 0 05 14 08 01 04 26 20 00
+ -- /| /|
+ -- 5 - - 1 | 15 16--10 09 27 28--22 21
+ -- | 7 - |-3 |////| |////|
+ -- |/ |/ 18 19--13 12 30 31--25 24
+ -- 6 - - 2
+ -- 06 17 11 02 07 29 23 03
+
+ s.quads = {
+ Pre3d.QuadFace( 2, 9, 11, 10),
+ Pre3d.QuadFace( 9, 15, 17, 11),
+ Pre3d.QuadFace(15, 6, 16, 17),
+ Pre3d.QuadFace(17, 16, 19, 20),
+ Pre3d.QuadFace(20, 19, 7, 18),
+ Pre3d.QuadFace(14, 20, 18, 12),
+ Pre3d.QuadFace(13, 14, 12, 3),
+ Pre3d.QuadFace( 10, 11, 14, 13),
+ -- Back side
+ Pre3d.QuadFace( 5, 27, 29, 28),
+ Pre3d.QuadFace(27, 21, 23, 29),
+ Pre3d.QuadFace(21, 1, 22, 23),
+ Pre3d.QuadFace(23, 22, 25, 26),
+ Pre3d.QuadFace(26, 25, 4, 24),
+ Pre3d.QuadFace(32, 26, 24, 30),
+ Pre3d.QuadFace(31, 32, 30, 8),
+ Pre3d.QuadFace(28, 29, 32, 31),
+ -- The hole
+ Pre3d.QuadFace(11, 17, 29, 23),
+ Pre3d.QuadFace(20, 32, 29, 17),
+ Pre3d.QuadFace(14, 26, 32, 20),
+ Pre3d.QuadFace(11, 23, 26, 14),
+ -- Bottom side
+ Pre3d.QuadFace( 7, 8, 30, 18),
+ Pre3d.QuadFace(18, 30, 24, 12),
+ Pre3d.QuadFace(12, 24, 4, 3),
+ -- Right side
+ Pre3d.QuadFace( 2, 10, 22, 1),
+ Pre3d.QuadFace( 10, 13, 25, 22),
+ Pre3d.QuadFace(13, 3, 4, 25),
+ -- Left side
+ Pre3d.QuadFace( 6, 5, 28, 16),
+ Pre3d.QuadFace(16, 28, 31, 19),
+ Pre3d.QuadFace(19, 31, 8, 7),
+ -- Top side
+ Pre3d.QuadFace(15, 27, 5, 6),
+ Pre3d.QuadFace( 9, 21, 27, 15),
+ Pre3d.QuadFace( 2, 1, 21, 9)
+ }
+
+ rebuildMeta(s)
+ return s
+end
+
+-- Tessellate a sphere. There will be |tess_y| + 2 vertices along the Y-axis
+-- (two extras are for zenith and azimuth). There will be |tess_x| vertices
+-- along the X-axis. It is centered on the Y-axis. It has a radius |r|.
+-- The implementation is probably still a bit convulted. We just handle the
+-- middle points like a grid, and special case zenith/aximuth, since we want
+-- them to share a vertex anyway. The math is pretty much standard spherical
+-- coordinates, except that we map {x, y, z} -> {z, x, y}. |tess_x| is phi,
+-- and |tess_y| is theta.
+-- TODO(deanm): This code could definitely be more efficent.
+local function makeSphere(r, tess_x, tess_y)
+ -- TODO(deanm): Preallocate the arrays to the final size.
+ local vertices = {}
+ local quads = {}
+
+ -- We walk theta 0 .. PI and phi from 0 .. 2PI.
+ local theta_step = pi / (tess_y + 1)
+ local phi_step = (k2PI) / tess_x
+
+ -- Create all of the vertices for the middle grid portion.
+ local theta = theta_step
+ for i=0, tess_y-1 do
+ theta = theta + theta_step
+ local sin_theta = sin(theta)
+ local cos_theta = cos(theta)
+ for j=0, tess_x-1 do
+ local phi = phi_step * j
+ table.insert(vertices, {
+ x= r * sin_theta * sin(phi),
+ y= r * cos_theta,
+ z= r * sin_theta * cos(phi)
+ })
+ end
+ end
+
+ -- Generate the quads for the middle grid portion.
+ for i=0, tess_y-2 do
+ local stride = i * tess_x
+ for j=1, tess_x do
+ local n = j % tess_x + 1
+ table.insert(quads, Pre3d.QuadFace(
+ stride + j,
+ stride + tess_x + j,
+ stride + tess_x + n,
+ stride + n
+ ))
+ end
+ end
+
+ -- Special case the zenith / azimuth (top / bottom) portion of triangles.
+ -- We make triangles (degenerated quads).
+ local last_row = #vertices - tess_x
+ local top_p_i = #vertices + 1
+ local bot_p_i = top_p_i + 1
+ table.insert(vertices, {x= 0, y= r, z= 0})
+ table.insert(vertices, {x= 0, y= -r, z= 0})
+
+ for i=1, tess_x do
+ -- Top triangles...
+ table.insert(quads, Pre3d.QuadFace(
+ top_p_i,
+ i,
+ i % tess_x + 1))
+
+ -- Bottom triangles...
+ table.insert(quads, Pre3d.QuadFace(
+ bot_p_i,
+ last_row + ((i + 1) % tess_x + 1),
+ last_row + (i % tess_x + 1)))
+ end
+
+ local s = Pre3d.Shape()
+ s.vertices = vertices
+ s.quads = quads
+ rebuildMeta(s)
+ return s
+end
+
+local function makeOctahedron()
+ local s = Pre3d.Shape()
+ s.vertices = {
+ {x= -1, y= 0, z= 0}, -- 0
+ {x= 0, y= 0, z= 1}, -- 1
+ {x= 1, y= 0, z= 0}, -- 2
+ {x= 0, y= 0, z= -1}, -- 3
+ {x= 0, y= 1, z= 0}, -- 4
+ {x= 0, y= -1, z= 0} -- 5
+ }
+
+ -- Top 4 triangles: 5 0 1, 5 1 2, 5 2 3, 5 3 0
+ -- Bottom 4 triangles: 0 5 1, 1 5 2, 2 5 3, 3 5 0
+ local quads = {}
+ for i=1, 4 do
+ local i2 = i % 4 + 1
+ quads[i*2-1] = Pre3d.QuadFace(5, i, i2)
+ quads[i*2] = Pre3d.QuadFace(i, 6, i2)
+ end
+
+ s.quads = quads
+ rebuildMeta(s)
+ return s
+end
+
+-- Smooth a Shape by averaging the vertices / faces. This is something like
+-- Catmull-Clark, but without the proper weighting. The |m| argument is the
+-- amount to smooth, between 0 and 1, 0 being no smoothing.
+local function averageSmooth(shape, m)
+ -- TODO(deanm): Remove this old compat code for calling without arguments.
+ if not m then m = 1 end
+
+ local vertices = shape.vertices
+ local psl = #vertices
+ local new_ps = {}
+
+ -- Build a connection mapping of vertex_index -> [ quad indexes ]
+ local connections = {}
+ for i=1, psl do
+ connections[i] = {}
+ end
+
+ for i, qf in ipairs(shape.quads) do
+ push(connections[qf.i0], i)
+ push(connections[qf.i1], i)
+ push(connections[qf.i2], i)
+ if not qf:isTriangle() then
+ push(connections[qf.i3], i)
+ end
+ end
+
+ -- For every vertex, average the centroids of the faces it's a part of.
+ for i, vert in ipairs(vertices) do
+ local cs = connections[i]
+ local avg = {x= 0, y= 0, z= 0}
+
+ -- Sum together the centroids of each face.
+ for j, csj in ipairs(cs) do
+ local quad = shape.quads[csj]
+ local p1 = vertices[quad.i0]
+ local p2 = vertices[quad.i1]
+ local p3 = vertices[quad.i2]
+ local p4 = vertices[quad.i3]
+ -- The centroid. TODO(deanm) can't shape just come from the QuadFace?
+ -- That would handle triangles better and avoid some duplication.
+ avg.x = avg.x + (p1.x + p2.x + p3.x + p4.x) / 4
+ avg.y = avg.y + (p1.y + p2.y + p3.y + p4.y) / 4
+ avg.z = avg.z + (p1.z + p2.z + p3.z + p4.z) / 4
+ -- TODO combine all the div / 4 into one divide?
+ end
+
+ -- We summed up all of the centroids, take the average for our new point.
+ local f = 1 / jl
+ avg.x = avg.x * f
+ avg.y = avg.y * f
+ avg.z = avg.z * f
+
+ -- Interpolate between the average and the original based on |m|.
+ new_ps[i] = linearInterpolatePoints3d(vertices[i], avg, m)
+ end
+
+ shape.vertices = new_ps
+
+ rebuildMeta(shape)
+ return shape
+end
+
+-- Small utility function like Array.prototype.map. Return a new array
+-- based on the result of the function on a current array.
+local function arrayMap(arr, func)
+ local out = {}
+ for i, v in ipairs(arr) do
+ out[i] = func(v, i, arr)
+ end
+ return out
+end
+
+local function mySort(ls)
+ table.sort(ls)
+ return ls
+end
+
+-- Divide each face of a Shape into 4 equal new faces.
+-- TODO(deanm): Better document, doesn't support triangles, etc.
+local function linearSubdivide(shape)
+ local share_points = {}
+ local num_quads = #shape.quads
+
+ for i=1, num_quads do
+ local quad = shape.quads[i]
+
+ local i0 = quad.i0
+ local i1 = quad.i1
+ local i2 = quad.i2
+ local i3 = quad.i3
+
+ local p0 = shape.vertices[i0]
+ local p1 = shape.vertices[i1]
+ local p2 = shape.vertices[i2]
+ local p3 = shape.vertices[i3]
+
+ -- p0 p1 p0 n0 p1
+ -- -> n3 n4 n1
+ -- p3 p2 p3 n2 p2
+
+ -- We end up with an array of vertex indices of the centroids of each
+ -- side of the quad and the middle centroid. We start with the vertex
+ -- indices that should be averaged. We cache centroids to make sure that
+ -- we share vertices instead of creating two on top of each other.
+ local ni = {
+ mySort({i0, i1}),
+ mySort({i1, i2}),
+ mySort({i2, i3}),
+ mySort({i3, i0}),
+ mySort({i0, i1, i2, i3})
+ }
+
+ for j, ps in ipairs(ni) do
+ local key = table.concat(ps, '-')
+ local centroid_index = share_points[key]
+ if not centroid_index then -- hasn't been seen before
+ centroid_index = #shape.vertices + 1
+ local s = shape
+ push(shape.vertices, averagePoints(
+ arrayMap(ps, function(x) return s.vertices[x] end)))
+ share_points[key] = centroid_index
+ end
+
+ ni[j] = centroid_index
+ end
+
+ -- New quads ...
+ local q0 = Pre3d.QuadFace( i0, ni[1], ni[5], ni[4])
+ local q1 = Pre3d.QuadFace(ni[1], i1, ni[2], ni[5])
+ local q2 = Pre3d.QuadFace(ni[5], ni[2], i2, ni[3])
+ local q3 = Pre3d.QuadFace(ni[4], ni[5], ni[3], i3)
+
+ shape.quads[i] = q0
+ push(shape.quads, q1)
+ push(shape.quads, q2)
+ push(shape.quads, q3)
+ end
+
+ rebuildMeta(shape)
+ return shape
+end
+
+-- Divide each triangle of a Shape into 4 new triangle faces. This is done
+-- by taking the mid point of each edge, and creating 4 new triangles. You
+-- can visualize it by inscribing a new upside-down triangle within the
+-- current triangle, which then defines 4 new sub-triangles.
+local function linearSubdivideTri(shape)
+ local share_points = { }
+ local num_quads = #shape.quads
+
+ for i=1, num_quads do
+ local tri = shape.quads[i]
+
+ local i0 = tri.i0
+ local i1 = tri.i1
+ local i2 = tri.i2
+
+ local p0 = shape.vertices[i0]
+ local p1 = shape.vertices[i1]
+ local p2 = shape.vertices[i2]
+
+ -- p0 p0
+ -- -> n0 n2
+ -- p1 p2 p1 n1 p2
+
+ -- We end up with an array of vertex indices of the centroids of each
+ -- side of the triangle. We start with the vertex indices that should be
+ -- averaged. We cache centroids to make sure that we share vertices
+ -- instead of creating two on top of each other.
+ local ni = {
+ mySort({i0, i1}),
+ mySort({i1, i2}),
+ mySort({i2, i0})
+ }
+
+ for j, ps in ipairs(ni) do
+ local key = table.concat(ps, '-')
+ local centroid_index = share_points[key]
+ if not centroid_index then -- hasn't been seen before
+ centroid_index = #shape.vertices + 1
+ local s = shape
+ push(shape.vertices, averagePoints(
+ arrayMap(ps, function(x) return s.vertices[x] end)))
+ share_points[key] = centroid_index
+ end
+
+ ni[j] = centroid_index
+ end
+
+ -- New triangles ...
+ local q0 = Pre3d.QuadFace( i0, ni[1], ni[3])
+ local q1 = Pre3d.QuadFace(ni[1], i1, ni[2])
+ local q2 = Pre3d.QuadFace(ni[3], ni[2], i2)
+ local q3 = Pre3d.QuadFace(ni[1], ni[2], ni[3])
+
+ shape.quads[i] = q0
+ push(shape.quads, q1)
+ push(shape.quads, q2)
+ push(shape.quads, q3)
+ end
+
+ rebuildMeta(shape)
+ return shape
+end
+
+local ExtruderMT = {}
+ExtruderMT.__index = ExtruderMT
+
+-- The Extruder implements extruding faces of a Shape. The class mostly
+-- exists as a place to hold all of the extrusion parameters. The properties
+-- are meant to be private, please use the getter/setter APIs.
+local function Extruder()
+ local this = {}
+ -- The total distance to extrude, if |count| > 1, then each segment will
+ -- just be a portion of the distance, and together they will be |distance|.
+ this.distance_ = 1.0
+ -- The number of segments / steps to perform. This is can be different
+ -- than just running extrude multiple times, since we only operate on the
+ -- originally faces, not our newly inserted faces.
+ this.count_ = 1
+ -- Selection mechanism. Access these through the selection APIs.
+ -- this.selector_ = nil
+ this:selectAll()
+
+ -- TODO(deanm): Need a bunch more settings, controlling which normal the
+ -- extrusion is performed along, etc.
+
+ -- Set scale and rotation. These are public, you can access them directly.
+ -- TODO(deanm): It would be great to use a Transform here, but there are
+ -- a few problems. Translate doesn't make sense, so it is not really an
+ -- affine. The real problem is that we need to interpolate across the
+ -- values, having them in a matrix is not helpful.
+ this.scale = {x= 1, y= 1, z= 1}
+ this.rotate = {x= 0, y= 0, z= 0}
+
+ setmetatable(this, ExtruderMT)
+ return this
+end
+
+-- Selection APIs, control which faces are extruded.
+function ExtruderMT.selectAll(this)
+ this.selector_ = function(shape, vertex_index) return true end
+end
+
+-- Select faces based on the function select_func. For example:
+-- extruder.selectCustom(function(shape, quad_index) {
+-- return quad_index == 0
+-- })
+-- The above would select only the first face for extrusion.
+function ExtruderMT.selectCustom(this, select_func)
+ this.selector_ = select_func
+end
+
+function ExtruderMT.distance(this)
+ return this.distance_
+end
+
+function ExtruderMT.set_distance(this, d)
+ this.distance_ = d
+end
+
+function ExtruderMT.count(this)
+ return this.count_
+end
+
+function ExtruderMT.set_count(this, c)
+ this.count_ = c
+end
+
+function ExtruderMT.extrude(this, shape)
+ local distance = this:distance()
+ local count = this:count()
+
+ local rx = this.rotate.x
+ local ry = this.rotate.y
+ local rz = this.rotate.z
+ local sx = this.scale.x
+ local sy = this.scale.y
+ local sz = this.scale.z
+
+ local vertices = shape.vertices
+ local quads = shape.quads
+
+ local faces = {}
+ for i=1, #quads do
+ if this.selector_(shape, i) then
+ push(faces, i)
+ end
+ end
+
+ for i, face_index in ipairs(faces) do
+ -- face_index is the index of the original face. It will eventually be
+ -- replaced with the last iteration's outside face.
+
+ -- As we proceed down a count, we always need to connect to the newest
+ -- new face. We start |quad| as the original face, and it will be
+ -- modified (in place) for each iteration, and then the next iteration
+ -- will connect back to the previous iteration, etc.
+ local qf = quads[face_index]
+ local original_cent = qf.centroid
+
+ -- This is the surface normal, used to project out the new face. It
+ -- will be rotated, but never scaled. It should be a unit vector.
+ local surface_normal = unitVector3d(addPoints3d(qf.normal1, qf.normal2))
+
+ local is_triangle = qf:isTriangle()
+
+ -- These are the normals inside the face, from the centroid out to the
+ -- vertices. They will be rotated and scaled to create the new faces.
+ local inner_normal0 = subPoints3d(vertices[qf.i0], original_cent)
+ local inner_normal1 = subPoints3d(vertices[qf.i1], original_cent)
+ local inner_normal2 = subPoints3d(vertices[qf.i2], original_cent)
+ local inner_normal3
+ if not is_triangle then
+ inner_normal3 = subPoints3d(vertices[qf.i3], original_cent)
+ end
+
+ for z=1, count do
+ local m = z / count
+
+ local t = Pre3d.Transform()
+ t:rotateX(rx * m)
+ t:rotateY(ry * m)
+ t:rotateZ(rz * m)
+
+ -- For our new point, we simply want to rotate the original normal
+ -- proportional to how many steps we're at. Then we want to just scale
+ -- it out based on our steps, and add it to the original centorid.
+ local new_cent = addPoints3d(original_cent,
+ mulPoint3d(t:transformPoint(surface_normal), m * distance))
+
+ -- We multiplied the centroid, which should not have been affected by
+ -- the scale. Now we want to scale the inner face normals.
+ t:scalePre(
+ linearInterpolate(1, sx, m),
+ linearInterpolate(1, sy, m),
+ linearInterpolate(1, sz, m))
+
+ local index_before = #vertices + 1
+
+ push(vertices, addPoints3d(new_cent, t:transformPoint(inner_normal0)))
+ push(vertices, addPoints3d(new_cent, t.transformPoint(inner_normal1)))
+ push(vertices, addPoints3d(new_cent, t.transformPoint(inner_normal2)))
+ if not is_triangle then
+ push(vertices,
+ addPoints3d(new_cent, t:transformPoint(inner_normal3)))
+ end
+
+ -- Add the new faces. These faces will always be quads, even if we
+ -- extruded a triangle. We will have 3 or 4 new side faces.
+ push(quads, Pre3d.QuadFace(
+ qf.i1,
+ index_before + 1,
+ index_before,
+ qf.i0))
+ push(quads, Pre3d.QuadFace(
+ qf.i2,
+ index_before + 2,
+ index_before + 1,
+ qf.i1))
+
+ if is_triangle then
+ push(quads, Pre3d.QuadFace(
+ qf.i0,
+ index_before,
+ index_before + 2,
+ qf.i2))
+ else
+ push(quads, Pre3d.QuadFace(
+ qf.i3,
+ index_before + 3,
+ index_before + 2,
+ qf.i2))
+ push(quads, Pre3d.QuadFace(
+ qf.i0,
+ index_before,
+ index_before + 3,
+ qf.i3))
+ end
+
+ -- Update (in place) the original face with the new extruded vertices.
+ qf.i0 = index_before
+ qf.i1 = index_before + 1
+ qf.i2 = index_before + 2
+ if not is_triangle then
+ qf.i3 = index_before + 3
+ end
+ end
+ end
+
+ rebuildMeta(shape) -- Compute all the new normals, etc.
+end
+
+local function makeXYFunction(f, xmin, ymin, xmax, ymax)
+ local nx, ny = 20, 20
+
+ local s = Pre3d.Shape()
+ for i=0, nx do
+ local x = xmin + (xmax - xmin)*i/nx
+ for j=0, ny do
+ local y = ymin + (ymax - ymin)*j/ny
+ local z = f(x, y)
+ push(s.vertices, {x= x, y= y, z= z})
+ end
+ end
+
+ local quads = {}
+ local i0 = 1
+ for i=1, nx do
+ for j=1, ny do
+ local i1, i2, i3 = i0+(ny+1), i0+(ny+1)+1, i0+1
+ push(quads, Pre3d.QuadFace(i0, i1, i2, i3))
+ i0 = i0+1
+ end
+ i0 = i0+1
+ end
+
+ s.quads = quads
+ rebuildMeta(s)
+ return s
+end
+
+return {
+ rebuildMeta= rebuildMeta,
+ triangulate= triangulate,
+ forEachFace= forEachFace,
+ forEachVertex= forEachVertex,
+
+ makePlane= makePlane,
+ makeCube= makeCube,
+ makeBox= makeBox,
+ makeBoxWithHole= makeBoxWithHole,
+ makeSphere= makeSphere,
+ makeOctahedron= makeOctahedron,
+ makeXYFunction= makeXYFunction,
+
+ averageSmooth= averageSmooth,
+ linearSubdivide= linearSubdivide,
+ linearSubdivideTri= linearSubdivideTri,
+
+ Extruder= Extruder
+}
diff --git a/pre3d/test.lua b/pre3d/test.lua
new file mode 100644
index 00000000..6823b67d
--- /dev/null
+++ b/pre3d/test.lua
@@ -0,0 +1,106 @@
+
+local Pre3d = require 'pre3d/pre3d'
+local ShapeUtils = require 'pre3d/pre3d_shape_utils'
+
+local function setTransform(ct, rx, ry)
+ ct:reset()
+ ct:rotateZ(0)
+ ct:rotateY(ry)
+ ct:rotateX(rx)
+ ct:translate(0, 0, -120)
+end
+
+local function draw(renderer, shape)
+ renderer:bufferShape(shape)
+ renderer:drawBuffer()
+ renderer:emptyBuffer()
+end
+
+function demo1()
+ local win = window('black')
+ win:transform(400, 400, 240, 240)
+
+ local renderer = Pre3d.Renderer(win)
+ -- shape = ShapeUtils.makeSphere(1, 12, 12)
+ local shape = ShapeUtils.makeOctahedron()
+
+ ShapeUtils.linearSubdivideTri(shape)
+ ShapeUtils.forEachVertex(shape,
+ function(v, i, s)
+ -- TODO(deanm): inplace.
+ s.vertices[i] = Pre3d.Math.unitVector3d(v)
+ return false
+ end
+ )
+ -- We need to rebuild the normals after extruding the vertices.
+ ShapeUtils.rebuildMeta(shape)
+
+ renderer.draw_overdraw = false
+ renderer.draw_backfaces = true
+ renderer.fill_rgba = rgb(0x4A/255, 0x92/255, 0xBF/255)
+ renderer.fill_rgba_backside = rgb(0xBF/255, 0x92/255, 0x4A/255)
+ renderer.set_light_intensity = true
+ renderer.fill_rgba_alpha = 0.95
+ renderer.stroke_rgba = rgb(0x66/255, 0x66/255, 0x66/255)
+
+ renderer.camera.focal_length = 30;
+
+ local N, tour = 256, 2*pi
+ for j=0, N do
+ local a = tour*j/N
+ setTransform(renderer.camera.transform, a, 0.15 * a)
+ draw(renderer, shape)
+ end
+end
+
+
+function demo2()
+ local win = window('white')
+ win:transform(300, 300, 240, 240)
+
+ local renderer = Pre3d.Renderer(win)
+ -- local shape = ShapeUtils.makeSphere(1, 12, 12)
+ local shape = ShapeUtils.makeXYFunction(|x,y| 1.2*exp(-x^2-y^2), -2, -2, 2, 2)
+
+ renderer.draw_overdraw = false
+ renderer.draw_backfaces = true
+ renderer.fill_rgba = rgb(0x4A/255, 0x92/255, 0xBF/255)
+ renderer.fill_rgba_backside = rgb(0xBF/255, 0x92/255, 0x4A/255)
+ renderer.set_light_intensity = true
+ renderer.draw_overdraw = true
+-- renderer.stroke_rgba = rgb(0x66/255, 0x66/255, 0x66/255)
+
+ renderer.camera.focal_length = 30;
+
+ local N, tour = 256, 2*pi
+ for j=0, N do
+ local a = tour*j/N
+ setTransform(renderer.camera.transform, -a, -0.15*a)
+ draw(renderer, shape)
+ end
+end
+
+function demo3()
+ local win = window('white')
+ win:transform(400, 400, 240, 240)
+
+ local renderer = Pre3d.Renderer(win)
+ local shape = ShapeUtils.makeSphere(1, 12, 12)
+
+ renderer.draw_overdraw = true
+ renderer.draw_backfaces = false
+ renderer.fill_rgba = rgb(0x4A/255, 0x92/255, 0xBF/255)
+ renderer.fill_rgba_backside = rgb(0xBF/255, 0x92/255, 0x4A/255)
+ renderer.set_light_intensity = true
+-- renderer.fill_rgba_alpha = 0.95
+-- renderer.stroke_rgba = rgb(0x66/255, 0x66/255, 0x66/255)
+
+ renderer.camera.focal_length = 30;
+
+ local N, tour = 256, 2*pi
+ for j=0, N do
+ local a = tour*j/N
+ setTransform(renderer.camera.transform, a, 0.15 * a)
+ draw(renderer, shape)
+ end
+end
diff --git a/scripts/project-dir-compare.py b/scripts/project-dir-compare.py
index 67f14943..42bc15ae 100644
--- a/scripts/project-dir-compare.py
+++ b/scripts/project-dir-compare.py
@@ -1,6 +1,11 @@
+#!/usr/bin/python
+
+import sys
import os
import re
-import sys
+import shutil
+
+from optparse import OptionParser
def differs(fna, fnb):
fa, fb = open(fna, 'r'), open(fnb, 'r')
@@ -15,19 +20,17 @@ def exists(fn):
except:
return False
-fulldira = sys.argv[1]
-fulldirb = sys.argv[2]
-
-# basedir = '/home/francesco/sviluppo'
+parser = OptionParser()
+parser.add_option("-r", "--report", action="store_false", dest="write")
+parser.add_option("-w", "--write", action="store_true", dest="write", default=False)
-# dira = 'gsl-shell'
-# dirb = 'gsl-shell-win-branch'
+(options, args) = parser.parse_args()
-# fulldira = os.path.join(basedir, dira)
-# fulldirb = os.path.join(basedir, dirb)
+dira = args[0]
+dirb = args[1]
dir_ignore = [r'\.(git|svn|deps|libs)$', r'^doc/html$', r'^www$']
-file_ignore = [r'~$', r'\.o$']
+file_ignore = ['^\.gitignore', '^gsl-shell$', r'^lua/src/luac?$', r'~$', r'\.o$', r'\.a$']
treated, absenta, absentb, differ = [], [], [], []
@@ -65,11 +68,11 @@ def project_scan(basedir):
yield rel
dir_filter(dirnames, rpath, basedir)
-for filename in project_scan(fulldira):
+for filename in project_scan(dira):
treated.append(filename)
- filenamea = os.path.join(fulldira, filename)
- filenameb = os.path.join(fulldirb, filename)
+ filenamea = os.path.join(dira, filename)
+ filenameb = os.path.join(dirb, filename)
if not exists(filenameb):
absentb.append(filename)
@@ -77,15 +80,45 @@ for filename in project_scan(fulldira):
if differs(filenamea, filenameb):
differ.append(filename)
-for filename in project_scan(fulldirb):
+for filename in project_scan(dirb):
if not filename in treated:
absenta.append(filename)
-print 'Absent A:'
+def copy_files(flist, src, dst):
+ for nm in flist:
+ sf = os.path.join(src, nm)
+ df = os.path.join(dst, nm)
+ print 'Copying', nm, 'into', dst, '...',
+ try:
+ mdestdir = os.path.dirname(df)
+ if not exists(mdestdir):
+ os.makedirs(mdestdir)
+ shutil.copy(sf, df)
+ shutil.copystat(sf, df)
+ except OSError as oserr:
+ print 'error:', oserr.strerror
+ else:
+ print 'ok'
+
+def sync_directories():
+ for nm in absenta:
+ nmb = os.path.join(dirb, nm)
+ print 'Removing', nmb, '...',
+ try:
+ os.remove(nmb)
+ except OSError as oserr:
+ print 'error:', oserr.strerror
+ else:
+ print 'ok'
+
+ copy_files(absentb, dira, dirb)
+ copy_files(differ, dira, dirb)
+
+print 'Absent from SOURCE project:'
for nm in absenta:
print '+ ', nm
-print 'Absent B:'
+print 'Absent from DESTINATION project:'
for nm in absentb:
print '- ', nm
@@ -95,7 +128,15 @@ for nm in differ:
print '-'*25 + ' DIFF OUTPUT ' + '-'*25
for nm in differ:
- cmd = 'diff -U 4 %s %s' % (os.path.join(fulldira, nm), os.path.join(fulldirb, nm))
+ cmd = 'diff -U 4 %s %s' % (os.path.join(dira, nm), os.path.join(dirb, nm))
diffout = os.popen(cmd)
for ln in diffout:
print ln,
+
+if options.write:
+ print 'Proceed to sync DESTINATION (%s) from SOURCE (%s) [Y/n] ? ' % (dirb, dira),
+ ans = sys.stdin.readline()
+ ans = ans.rstrip('\n')
+ if ans in ['Y', 'Yes', 'y', 'yes']:
+ print 'Syncing'
+ sync_directories()
generated by cgit v1.2.3 (git 2.39.1) at 2025年10月01日 05:38:53 +0000

AltStyle によって変換されたページ (->オリジナル) /