-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 |
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, }; @@ -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() |