Full Code of tyt2y3/vaserenderer for AI

master 93d291ccc7ff cached
86 files
385.6 KB
137.7k tokens
268 symbols
1 requests
Download .txt
Showing preview only (410K chars total). Download the full file or copy to clipboard to get everything.
Repository: tyt2y3/vaserenderer
Branch: master
Commit: 93d291ccc7ff
Files: 86
Total size: 385.6 KB

Directory structure:
gitextract_m2dfrshv/

├── .gitignore
├── LICENSE
├── README.md
├── cpp/
│   ├── samples/
│   │   ├── Makefile
│   │   ├── png_readwrite.cpp
│   │   ├── polyline
│   │   ├── polyline.cpp
│   │   ├── readme.txt
│   │   ├── samples
│   │   ├── samples.cpp
│   │   ├── segment
│   │   ├── segment.cpp
│   │   └── test1_base.cpp
│   ├── vaser/
│   │   ├── agg_curve4.cpp
│   │   ├── backend.h
│   │   ├── color.h
│   │   ├── curve.cpp
│   │   ├── gradient.cpp
│   │   ├── opengl.cpp
│   │   ├── point.h
│   │   ├── polyline.cpp
│   │   ├── vaser.cpp
│   │   ├── vaser.h
│   │   └── vertex_array_holder.h
│   └── workbench/
│       ├── base.html
│       ├── dragger.js
│       ├── drawer.js
│       ├── gradient_along_path.html
│       ├── knife_cut_test
│       ├── knife_cut_test.cpp
│       ├── layout.css
│       ├── outward_vector.html
│       ├── quad_reflex.html
│       ├── raphael-min.js
│       ├── readme.txt
│       ├── triangle_knife_cut.js
│       ├── triangles_dual_knife_cut.html
│       ├── triangles_knife_cut.html
│       └── vector_arc.html
├── csharp/
│   ├── Assets/
│   │   ├── Demo/
│   │   │   ├── Demo.cs
│   │   │   ├── Demo.cs.meta
│   │   │   ├── demo.unity
│   │   │   └── demo.unity.meta
│   │   ├── Demo.meta
│   │   ├── Resources/
│   │   │   ├── Vaser/
│   │   │   │   ├── Fade.mat
│   │   │   │   ├── Fade.mat.meta
│   │   │   │   ├── Fade.shader
│   │   │   │   └── Fade.shader.meta
│   │   │   └── Vaser.meta
│   │   ├── Resources.meta
│   │   ├── Vaser/
│   │   │   ├── Gradient.cs
│   │   │   ├── Gradient.cs.meta
│   │   │   ├── Polybezier.cs
│   │   │   ├── Polybezier.cs.meta
│   │   │   ├── Polyline.cs
│   │   │   ├── Polyline.cs.meta
│   │   │   ├── Vec2Ext.cs
│   │   │   ├── Vec2Ext.cs.meta
│   │   │   ├── VertexArrayHolder.cs
│   │   │   └── VertexArrayHolder.cs.meta
│   │   └── Vaser.meta
│   ├── Docs/
│   │   └── README.md
│   ├── Packages/
│   │   └── manifest.json
│   ├── ProjectSettings/
│   │   ├── AudioManager.asset
│   │   ├── ClusterInputManager.asset
│   │   ├── DynamicsManager.asset
│   │   ├── EditorBuildSettings.asset
│   │   ├── EditorSettings.asset
│   │   ├── GraphicsSettings.asset
│   │   ├── InputManager.asset
│   │   ├── NavMeshAreas.asset
│   │   ├── Physics2DSettings.asset
│   │   ├── PresetManager.asset
│   │   ├── ProjectSettings.asset
│   │   ├── ProjectVersion.txt
│   │   ├── QualitySettings.asset
│   │   ├── TagManager.asset
│   │   ├── TimeManager.asset
│   │   ├── UnityConnectSettings.asset
│   │   ├── VFXManager.asset
│   │   └── XRSettings.asset
│   └── README.md
└── docs/
    ├── API.html
    ├── getting_started.html
    ├── index.html
    └── style.css

================================================
FILE CONTENTS
================================================

================================================
FILE: .gitignore
================================================
# This .gitignore file should be placed at the root of your Unity project directory
#
# Get latest from https://github.com/github/gitignore/blob/master/Unity.gitignore
#
/[Ll]ibrary/
/[Tt]emp/
/[Oo]bj/
/[Bb]uild/
/[Bb]uilds/
/[Ll]ogs/
/[Mm]emoryCaptures/

# Never ignore Asset meta data
!/[Aa]ssets/**/*.meta

# Uncomment this line if you wish to ignore the asset store tools plugin
# /[Aa]ssets/AssetStoreTools*

# Autogenerated Jetbrains Rider plugin
[Aa]ssets/Plugins/Editor/JetBrains*

# Visual Studio cache directory
.vs/

# Gradle cache directory
.gradle/

# Autogenerated VS/MD/Consulo solution and project files
ExportedObj/
.consulo/
*.csproj
*.unityproj
*.sln
*.suo
*.tmp
*.user
*.userprefs
*.pidb
*.booproj
*.svd
*.pdb
*.mdb
*.opendb
*.VC.db

# Unity3D generated meta files
*.pidb.meta
*.pdb.meta
*.mdb.meta

# Unity3D generated file on crash reports
sysinfo.txt

# Builds
*.apk
*.unitypackage

# Crashlytics generated file
crashlytics-build.properties

# Sublime
*.sublime*

================================================
FILE: LICENSE
================================================
BSD 3-Clause License

Copyright (c) 2023 TSANG, Hao Fung (tyt2y7@gmail.com)

Redistribution and use in source and binary forms, with or without
modification, are permitted provided that the following conditions are met:

1. Redistributions of source code must retain the above copyright notice, this
   list of conditions and the following disclaimer.

2. Redistributions in binary form must reproduce the above copyright notice,
   this list of conditions and the following disclaimer in the documentation
   and/or other materials provided with the distribution.

3. Neither the name of the copyright holder nor the names of its
   contributors may be used to endorse or promote products derived from
   this software without specific prior written permission.

THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS "AS IS"
AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE
IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE ARE
DISCLAIMED. IN NO EVENT SHALL THE COPYRIGHT HOLDER OR CONTRIBUTORS BE LIABLE
FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL
DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR
SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) HOWEVER
CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, STRICT LIABILITY,
OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING IN ANY WAY OUT OF THE USE
OF THIS SOFTWARE, EVEN IF ADVISED OF THE POSSIBILITY OF SUCH DAMAGE.


================================================
FILE: README.md
================================================
> This project was started many, many years ago. The C++ implementation is a pre-OpenGL 3 relic. The C# implementation is more "modern" and uses fragment shader, which is better.

<body>
	<div class='vaser_wrap'>
		<div class='article_name'>
			<h1>VASE<span style='line-height:40px;'> renderer</span></h1>
		</div>
<h2>About</h2>
<p>VASE renderer version 0.42 (VASEr 0.42) is a tessellating library for rendering high quality 2D vector graphics. It is an attempt to address unconventional features in 2d graphics. It is intended for OpenGL 1.1+, but much of the code is API independent C++.</p>

<h2>Unconventional features</h2>
<h3>Per vertex coloring and weighting</h3>
<div style='position:relative; overflow:hidden; height:220px; float:left;'><img style='position:relative; top:-40px;' src='docs/sample_images/polyline_3.png' /></div>
<br><br>VASEr revolutionarily lets you control the color and thickness at each vertex for a polyline. This feature is unseen on any commonly available graphics library.
	<div style='clear:both'></div>

<h3>Linear gradient along curve</h3>
<div style='position:relative; overflow:hidden; height:200px; float:right;'><img style='position:relative; top:-60px;' src='docs/sample_images/bezier_2.png' /></div>
<br>A similar feature is also available to curves. Because there are so many vertices on a curve that it is impractical to specify data on each point, VASEr lets you define a linear gradient of color and thickness along the length of a curve, giving you two more degrees of freedom.
	<div style='clear:both'></div>

<h3>Feathering for brush like effects</h3>
<img src='docs/sample_images/bezier_3.png' style='float:left;' />
<img src='docs/sample_images/bezier_4.png' style='float:left;' />
<img src='docs/sample_images/bezier_5.png' style='float:left;' />
<br>VASEr must use an outsetting polygon anyway, so it is free (as in computational cost) to scale it up, and it is called feathering in VASEr.
	<div style='clear:both'></div>

<h3>Premium quality anti aliasing</h3>
<div style='background:#333; margin: 10px; border:5px solid #AAA; float:left; height: 150px; overflow:hidden;'>
<img src='docs/sample_images/fade_intro_1.png' />
<img src='docs/sample_images/fade_intro_2.png' />
<img src='docs/sample_images/fade_intro_3.png' />
<img src='docs/sample_images/fade_intro_4.png' />
</div>
<br><br>From left to right: raw polygon without anti aliasing, <br>
anti aliased with outset fade polygon, <br>
exaggerated outsetting polygon with wireframe, <br>
same as left without wireframe.
	<div style='clear:both'></div>
<p>Outset-fade polygon is the high quality, fast, portable, consistent and hassle free technique for anti aliasing. The difficulties are to calibrate the outsetting distance to achieve believable result and to perform tedious tessellation. Luckily VASEr did this for you.</p>
<p>Below are line rendering comparison between VASEr and Cairo and AGG:</p>
<div>
	<div id='ab_va'>VASEr<br><img src='docs/sample_images/ab_vaser_line_thickness1.png' /></div>
	<div id='ab_ca' style='display:none;'>Cairo<br><img src='docs/sample_images/ab_cairo_line_thickness.png' /></div>
</div>
<div>
	<div id='a_agg' style='display:none;'>AGG<br><img src='docs/sample_images/agg_line_thickness.png' /></div>
</div>
<p>There is small difference, but it is more a matter of taste than correctness. In terms of clarity VASEr is like between Cairo and AGG, and VASEr is the most crisp among the three. VASEr even do pixel alignment to 1px completely horizontal or vertical lines.</p>
<p>The flaw is, because the outsetting distance is resolution dependent, while the triangulation is unaffected under rotational transformation, fidelity will be lost under scale and sheer.</p>

<h2>Articles</h2>

+ <a href='http://artgrammer.blogspot.com/2011/07/drawing-polylines-by-tessellation.html' target='_blank'>Drawing polylines by tessellation.</a><br>
+ <a href='http://artgrammer.blogspot.hk/2011/05/drawing-nearly-perfect-2d-line-segments.html'>Drawing nearly perfect 2D line segments in OpenGL</a>

<h2>Related Work</h2>
<ul>
<li>If you only need uniform color & width and only wanted a clean mesh, you can try out <a href="https://github.com/CrushedPixel/Polyline2D">Polyline2D</a>.</li>
</ul>

<h2>Acknowledgement</h2>
<p>Bezier curve subdivision code is extracted from Anti-Grain Geometry V2.4 by Maxim Shemanarev (McSeem).</p>
<p>The open-source graphics library AGG inspired me a lot during my teenage.</p>

</body>


================================================
FILE: cpp/samples/Makefile
================================================
CPP = polyline.cpp segment.cpp samples.cpp
BIN = $(CPP:.cpp=)
VASER = ../vaser/*

all: $(BIN)

polyline: polyline.cpp png_readwrite.cpp test1_base.cpp $(VASER)
	fltk-config --use-gl --use-images --compile polyline.cpp

segment: segment.cpp test1_base.cpp $(VASER)
	fltk-config --use-gl --compile segment.cpp

samples: samples.cpp test1_base.cpp $(VASER)
	fltk-config --use-gl --compile samples.cpp

win32: $(CPP) $(VASER)
	$(foreach var,$(BIN),g++ -I/usr/local/include -mwindows -DWIN32 -DUSE_OPENGL32 -D_LARGEFILE_SOURCE -D_LARGEFILE64_SOURCE -o $(var) $(var).cpp -mwindows /usr/local/lib/libfltk_gl.a -static -lpng -lz -lglu32 -lopengl32 /usr/local/lib/libfltk.a -lole32 -luuid -lcomctl32 -static-libgcc -static-libstdc++ & )


================================================
FILE: cpp/samples/png_readwrite.cpp
================================================
#include <stdio.h>
#include <png.h>

int	// 0 on failure, 1 on success
save_png(const char *filename,	// filename
	unsigned char* buffer,		// buffer of an image
	int w,		// width of image
	int h,		// height
	int b)		// color depth/ bytes per pixel
{
	int	y;						// Current row
	const unsigned char	*ptr;	// Pointer to image data
	FILE				*fp;	// File pointer
	png_structp			pp;		// PNG data
	png_infop			info;	// PNG image info

	if ( !filename)
	{
		printf("invalid filename.\n");
		return 0;
	}
	if ( !buffer)
	{
		printf("null pointer buffer.\n");
		return 0;
	}
	
	// Create the output file...
	if ((fp = fopen(filename, "wb")) == NULL)
	{
		printf("Unable to create PNG image.\n");
		return (0);
	}

	// Create the PNG image structures...
	pp = png_create_write_struct(PNG_LIBPNG_VER_STRING, 0, 0, 0);
	if (!pp)
	{
		fclose(fp);
		printf("Unable to create PNG data.\n");
		return (0);
	}

	info = png_create_info_struct(pp);
	if (!info)
	{
		fclose(fp);
		png_destroy_write_struct(&pp, 0);
		printf("Unable to create PNG image information.\n");
		return (0);
	}

	if (setjmp(png_jmpbuf(pp)))
	{
		fclose(fp);
		png_destroy_write_struct(&pp, &info);
		printf("Unable to write PNG image.\n");
		return (0);
	}

	png_init_io(pp, fp);

	png_set_compression_level(pp, Z_BEST_COMPRESSION);
	png_set_IHDR(pp, info, w, h, 8,
	       b==3? PNG_COLOR_TYPE_RGB : PNG_COLOR_TYPE_RGB_ALPHA,
	       PNG_INTERLACE_NONE, PNG_COMPRESSION_TYPE_DEFAULT,
	       PNG_FILTER_TYPE_DEFAULT);
	png_set_sRGB(pp, info, PNG_sRGB_INTENT_PERCEPTUAL);
	png_set_sRGB_gAMA_and_cHRM(pp, info, PNG_INFO_sRGB);
	
	png_write_info(pp, info);
	
	ptr = buffer;
	
	for (y = 0; y < h; y ++)
	{
		png_write_row(pp, (png_byte *)ptr);
		ptr += w*b;
	}

	png_write_end(pp, info);
	png_destroy_write_struct(&pp, 0);

	fclose(fp);
	return (1);
}


unsigned char*	// 0 on failure, address of memory buffer on success
				// must *delete[]* the buffer manually after use
read_png( const char* filename, // filename
	int* width,			// store image width into pointer
	int* height,		// store image height into pointer
	int* channels)		// number of channels
{
	int		i;			// Looping var
	FILE		*fp;	// File pointer
	png_structp	pp;		// PNG read pointer
	png_infop	info;	// PNG info pointers
	png_bytep	*rows;	// PNG row pointers

	// Open the PNG file...
	if ((fp = fopen(filename, "rb")) == NULL)
	{
		printf( "Unable to open file %s.\n", filename);
		return 0;
	}

	// Setup the PNG data structures...
	pp   = png_create_read_struct(PNG_LIBPNG_VER_STRING, NULL, NULL, NULL);
	info = png_create_info_struct(pp);

	if (setjmp(pp->jmpbuf))
	{
		printf("Unable to read. png file %s contains errors.\n", filename);
		return 0;
	}

	// Initialize the PNG read "engine"...
	png_init_io(pp, fp);

	// Get the image dimensions and convert to grayscale or RGB...
	png_read_info(pp, info);

	if (info->color_type == PNG_COLOR_TYPE_PALETTE)
	png_set_expand(pp);

	if (info->color_type & PNG_COLOR_MASK_COLOR)
	*channels = 3;
	else
	*channels = 1;

	if ((info->color_type & PNG_COLOR_MASK_ALPHA) || info->num_trans)
	(*channels) ++;

	if ( *channels != 3 && *channels != 4)
	{
		printf("png image other than 3 or 4 channels is not supported.\n");
		return 0;
	}

	*width = info->width;
	*height = info->height;

	if (info->bit_depth < 8)
	{
	png_set_packing(pp);
	png_set_expand(pp);
	}
	else if (info->bit_depth == 16)
	png_set_strip_16(pp);

	// Handle transparency...
	if (png_get_valid(pp, info, PNG_INFO_tRNS))
	png_set_tRNS_to_alpha(pp);

	unsigned char* array = new unsigned char[(*width) * (*height) * (*channels)];

	// Allocate pointers...
	rows = new png_bytep[(*height)];

	for (i = 0; i < (*height); i ++)
	rows[i] = (png_bytep)(array + i * (*width) * (*channels));

	// Read the image, handling interlacing as needed...
	for (i = png_set_interlace_handling(pp); i > 0; i --)
	png_read_rows(pp, rows, NULL, (*height));

	#ifdef WIN32
	// Some Windows graphics drivers don't honor transparency when RGB == white
	if (*channels == 4) {
	// Convert RGB to 0 when alpha == 0...
	unsigned char* ptr = (unsigned char*) array;
	for (i = (*width) * (*height); i > 0; i --, ptr += 4)
	if (!ptr[3]) ptr[0] = ptr[1] = ptr[2] = 0;
	}
	#endif //WIN32

	// Free memory
	delete[] rows;

	png_read_end(pp, info);
	png_destroy_read_struct(&pp, &info, NULL);

	fclose(fp);

	return array;
}


================================================
FILE: cpp/samples/polyline.cpp
================================================
/*config.h is generated by fltk in your system
 * this file is used with fltk 1.3 with gl enabled.
 * compile by: fltk-config --use-gl --use-images --compile polyline.cpp
 * or something like: g++ -lX11 -lGL -lpng 'polyline.cpp' -o 'polyline'
*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include "config.h" //config.h must always be placed before any Fl header
#include <FL/gl.h>
#include <FL/Fl_Box.H>
#include <FL/Fl_Tabs.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Value_Slider.H>
#include <FL/Fl_Radio_Light_Button.H>

namespace VASEr
{
	struct Vec2 { double x,y;};
	struct Color { float r,g,b,a;};
}
#define VASER_DEBUG
#include "../vaser/vaser.cpp"
#pragma GCC diagnostic ignored "-Wdeprecated-declarations"
#include "png_readwrite.cpp"

using namespace VASEr;
void test_draw();
#include "test1_base.cpp"

const int buf_size=20;
Vec2 AP[buf_size];
int size_of_AP=0;
Color AC[buf_size];
double AW[buf_size];

Fl_Window* main_wnd;
Gl_Window* gl_wnd;
Fl_Slider *weight, *feathering;
Fl_Button *poly, *bezi;
Fl_Button *feather, *no_feather_at_cap, *no_feather_at_core;
Fl_Button *jt_miter, *jt_bevel, *jt_round;
Fl_Button *jc_butt, *jc_round, *jc_square, *jc_rect;
Fl_Button *jc_both, *jc_first, *jc_last, *jc_none;
Fl_Button *colored, *alphaed, *weighted;
Fl_Button *skeleton, *triangulate;
Fl_Slider *bz_ap, *bz_ag, *bz_cu;

void line_update()
{
	Color cc[3];
	Color grey = {.4,.4,.4, 1};
	
	{ Color col={1 , 0, 0, 1}; cc[0]=col;}
	{ Color col={.8,.8, 0, 1}; cc[1]=col;}
	{ Color col={ 0, 0, 1, 1}; cc[2]=col;}
	
	for ( int i=0; i<size_of_AP; i++)
	{
		if ( colored->value())
			AC[i] = cc[i%3];
		else
			AC[i] = grey;
		
		if ( alphaed->value())
			AC[i].a = 0.5f;
		else
			AC[i].a = 1.0f;
			
		if ( weighted->value())
		{
			AW[i] = weight->value() * (0.05 + double(i*2)/size_of_AP);
		}
		else
		{
			AW[i] = weight->value();
		}
	}
}
void line_init( int N)
{
	switch (N)
	{
	case 2:
		AP[0].x=180; AP[0].y=270;
		AP[1].x=220; AP[1].y=30;
		size_of_AP = 2;
	break;
	
	case 3:
		AP[0].x=200; AP[0].y=100;
		AP[1].x=100; AP[1].y=200;
		AP[2].x=300; AP[2].y=200;
		size_of_AP = 3;
	break;
	
	case 4:
		AP[0].x=200; AP[0].y=50;
		AP[1].x=100; AP[1].y=150;
		AP[2].x=300; AP[2].y=150;
		AP[3].x=200; AP[3].y=250;
		size_of_AP = 4;
	break;
	
	case 5:
		AP[0].x=60; AP[0].y=250;
		AP[1].x=120; AP[1].y=50;
		AP[2].x=180; AP[2].y=250;
		AP[3].x=240; AP[3].y=50;
		AP[4].x=300; AP[4].y=250;
		size_of_AP = 5;
	break;
	
	case 6:
		AP[0].x=280; AP[0].y=110;
		AP[1].x=200; AP[1].y=50;
		AP[2].x=100; AP[2].y=150;
		AP[3].x=300; AP[3].y=150;
		AP[4].x=200; AP[4].y=250;
		AP[5].x=120; AP[5].y=190;
		size_of_AP = 6;
	break;

	case 7:
		AP[0].x=280; AP[0].y=110;
		AP[1].x=200; AP[1].y=50;
		AP[2].x=100; AP[2].y=50;
		AP[3].x=200; AP[3].y=150;
		AP[4].x=300; AP[4].y=250;
		AP[5].x=200; AP[5].y=250;
		AP[6].x=120; AP[6].y=190;
		size_of_AP = 7;
	break;
	}
	line_update();
	gl_wnd->set_drag_target( AP, size_of_AP); 
}
char get_joint_type()
{
	if ( jt_miter->value())
		return PLJ_miter;
	else if ( jt_bevel->value())
		return PLJ_bevel;
	else if ( jt_round->value())
		return PLJ_round;
	else
		return 0;
}
char get_cap_type()
{
	char cap;
	if ( jc_butt->value())
		cap=PLC_butt;
	else if ( jc_round->value())
		cap=PLC_round;
	else if ( jc_square->value())
		cap=PLC_square;
	else if ( jc_rect->value())
		cap=PLC_rect;

	if ( jc_both->value())
		cap+=PLC_both;
	else if ( jc_first->value())
		cap+=PLC_first;
	else if ( jc_last->value())
		cap+=PLC_last;
	else if ( jc_none->value())
		cap+=PLC_none;
	return cap;
}
void np_cb(Fl_Widget* W, void*)
{
	int n=3;
	sscanf( W->label(), "%d", &n);
	line_init(n);
	if( n<4)
	{
		poly->value(1);
		bezi->value(0);
	}
	line_update();
	gl_wnd->redraw();
}
void line_bezier(Fl_Widget* W, void*)
{
	gl_wnd->redraw();
}
void drag_cb(Fl_Widget* W, void*)
{
	line_update();
	gl_wnd->redraw();
}
void exportimg_cb(Fl_Widget* W, void*)
{
	Image img = renderer::get_image();
	if( img.buf)
	{
		save_png("capture.png",img.buf,img.width,img.height,4);
		free(img.buf);
	}
}

void make_form()
{
	Fl_Tabs* tabs = new Fl_Tabs(400,0,200,300);

	{ Fl_Group* tab = new Fl_Group(400, 20, 200, 300, "config");
		Fl_Box* o = new Fl_Box(FL_NO_BOX,400,25,200,20,"general configurations");
		//weight
		weight = new Fl_Value_Slider(400,50,200,20,"weight");
		weight->type(FL_HOR_SLIDER);
		weight->bounds(0.02,30.0);
		weight->callback(drag_cb);
		weight->value(8.0);
		
		{ Fl_Group* o = new Fl_Group(400,90,200,20);
			poly = new Fl_Radio_Light_Button(400,90,100,20,"polyline");
			bezi = new Fl_Radio_Light_Button(500,90,100,20,"polybezier");
			poly->callback(line_bezier);
			bezi->callback(line_bezier);
			poly->value(1);
			o->end();
		}

		//number of points
		(new Fl_Button(400,110,80,20,"2 points"))->callback(np_cb);
		(new Fl_Button(480,110,20,20,"3"))->callback(np_cb);
		(new Fl_Button(500,110,20,20,"4"))->callback(np_cb);
		(new Fl_Button(520,110,20,20,"5"))->callback(np_cb);
		(new Fl_Button(540,110,20,20,"7"))->callback(np_cb);

		//test options
		colored = new Fl_Light_Button(400,130,60,20,"colored");
		colored->callback(drag_cb);
		colored->value(1);
		alphaed = new Fl_Light_Button(460,130,70,20,"alpha-ed");
		alphaed->callback(drag_cb);
		alphaed->value(1);
		weighted = new Fl_Light_Button(530,130,70,20,"weighted");
		weighted->callback(drag_cb);
		skeleton = new Fl_Light_Button(400,150,80,20,"skeleton");
		skeleton->value(0);
		skeleton->callback(drag_cb);
		triangulate = new Fl_Light_Button(480,150,120,20,"triangulation");
		triangulate->value(0);
		triangulate->callback(drag_cb);
		//export button
		Fl_Button *exportimg;
		exportimg = new Fl_Button(400,170,200,20,"export image");
		exportimg->callback(exportimg_cb);
	tab->end();}

	{ Fl_Group* tab = new Fl_Group(400, 20, 200, 300, "polyline");
		Fl_Box* o = new Fl_Box(FL_NO_BOX,400,25,200,20,"polyline_opt");

		//feathering
		feather = new Fl_Light_Button(400,50,100,20,"feather");
		feather->value(1);
		feather->callback(drag_cb);
		feathering = new Fl_Value_Slider(400,70,200,20,"feathering");
		feathering->type(FL_HOR_SLIDER);
		feathering->bounds(1.0,10.0);
		feathering->callback(drag_cb);
		feathering->value(1.0);
		no_feather_at_cap  = new Fl_Light_Button(430,110,170,15,"no_feather_at_cap");
		no_feather_at_core = new Fl_Light_Button(430,125,170,15,"no_feather_at_core");
		no_feather_at_cap ->value(0);
		no_feather_at_core->value(0);
		no_feather_at_cap ->callback(drag_cb);
		no_feather_at_core->callback(drag_cb);

		//joint type
		{ Fl_Group* o = new Fl_Group(400,145,200,30);
			new Fl_Box(400,145,80,15,"joint type");
			jt_miter = new Fl_Radio_Light_Button(420,160,60,15,"miter");
			jt_bevel = new Fl_Radio_Light_Button(480,160,60,15,"bevel");
			jt_round = new Fl_Radio_Light_Button(540,160,60,15,"round");
			o->end();
			jt_miter->value(1);
			jt_miter->callback(drag_cb);
			jt_bevel->callback(drag_cb);
			jt_round->callback(drag_cb);
		}
		//cap type
		{ Fl_Group* o = new Fl_Group(400,180,200,45);
			new Fl_Box(400,180,80,15,"cap type");
			jc_butt   = new Fl_Radio_Light_Button(440,195,80,15,"butt");
			jc_round  = new Fl_Radio_Light_Button(520,195,80,15,"round");
			jc_square = new Fl_Radio_Light_Button(440,210,80,15,"square");
			jc_rect   = new Fl_Radio_Light_Button(520,210,80,15,"rect");
			o->end();
			jc_butt   ->value(1);
			jc_butt   ->callback(drag_cb);
			jc_round  ->callback(drag_cb);
			jc_square ->callback(drag_cb);
			jc_rect   ->callback(drag_cb);
		}
		{ Fl_Group* o = new Fl_Group(400,230,200,45);
			new Fl_Box(400,230,80,15,"cap parts");
			jc_both   = new Fl_Radio_Light_Button(440,245,80,15,"both");
			jc_first  = new Fl_Radio_Light_Button(520,245,80,15,"first");
			jc_last   = new Fl_Radio_Light_Button(440,260,80,15,"last");
			jc_none   = new Fl_Radio_Light_Button(520,260,80,15,"none");
			jc_both->value(1);
			jc_both ->callback(drag_cb);
			jc_first->callback(drag_cb);
			jc_last ->callback(drag_cb);
			jc_none ->callback(drag_cb);
		}
	tab->end();}

	/*{ Fl_Group* tab = new Fl_Group(400, 20, 200, 300, "BZ");
		Fl_Box* o = new Fl_Box(FL_NO_BOX,400,25,200,20,"polybezier_opt");
		bz_ap = new Fl_Value_Slider(400,70,200,15,"approximation_scale");
		bz_ag = new Fl_Value_Slider(400,105,200,15,"angle_tolerance");
		bz_cu = new Fl_Value_Slider(400,140,200,15,"cusp_limit");
		bz_ap->type(FL_HOR_SLIDER);
		bz_ag->type(FL_HOR_SLIDER);
		bz_cu->type(FL_HOR_SLIDER);
		bz_ap->bounds(0.1,5.0); bz_ap->value(BZ_default_approximation_scale); bz_ap->callback(drag_cb);
		bz_ag->bounds(0.01,0.52); bz_ag->value(BZ_default_angle_tolerance); bz_ag->callback(drag_cb);
		bz_cu->bounds(0.1,5.0); bz_cu->value(BZ_default_cusp_limit); bz_cu->callback(drag_cb);
	tab->end();}*/

	/*{ Fl_Group* tab = new Fl_Group(400, 20, 200, 300, "TS");
		Fl_Box* o = new Fl_Box(FL_NO_BOX,400,25,200,20,"tessellator");
	tab->end();}*/

	tabs->end();
}
void test_draw()
{	//main rendering

	renderer::before();

	polyline_opt opt={0};
	tessellator_opt tess={0};
	opt.feather    = feather->value();
	opt.feathering = feathering->value();
	opt.no_feather_at_cap = no_feather_at_cap->value();
	opt.no_feather_at_core = no_feather_at_core->value();
	opt.joint = get_joint_type();
	opt.cap   = get_cap_type();
	opt.tess = &tess;
	tess.triangulation = triangulate->value();

	Color cc={1,1,1,1};
	if ( bezi->value())
	{
		polybezier_opt bz_opt={0};
		bz_opt.poly = &opt;
		//bz_opt.approximation_scale = bz_ap->value();
		//bz_opt.angle_tolerance = bz_ag->value();
		//bz_opt.cusp_limit = bz_cu->value();
		gradient grad = {0};
		gradient_stop stop[10] = {0};
		grad.stops = stop;
		grad.length = 10;
		stop[0].t = 0.00; stop[0].type = GS_rgba; stop[0].color = AC[0];
		stop[1].t = 0.33; stop[1].type = GS_rgba; stop[1].color = AC[1];
		stop[2].t = 0.66; stop[2].type = GS_rgba; stop[2].color = AC[2];
		stop[3].t = 1.00; stop[3].type = GS_rgba; stop[3].color = AC[3];
		stop[4].t = 0.00; stop[4].type = GS_weight; stop[4].weight = AW[0];
		stop[5].t = 0.33; stop[5].type = GS_weight; stop[5].weight = AW[1];
		stop[6].t = 0.66; stop[6].type = GS_weight; stop[6].weight = AW[2];
		stop[7].t = 1.00; stop[7].type = GS_weight; stop[7].weight = AW[3];
		polybezier( AP, &grad, size_of_AP, &bz_opt);
		Color black={0,0,0,1};
		if ( skeleton->value())
		{
			polybezier( AP, cc, 1.0, size_of_AP, 0);
			polyline( AP, black, 1.0, size_of_AP, 0); //control lines
		}
	}
	else
	{
		polyline( AP, AC, AW, size_of_AP, &opt);
		if ( skeleton->value())
			polyline( AP, cc, 1.0, size_of_AP, 0);
	}

	renderer::after();
}

int main(int argc, char **argv)
{
	main_wnd = new Fl_Window( 600,300,"VASEr - polyline and bezier example");
		make_form(); //initialize
		gl_wnd = new Gl_Window( 0,0,400,300);  gl_wnd->end(); //create gl window
		line_init(4);
	main_wnd->end();
	main_wnd->show();
	main_wnd->redraw();
	
	return Fl::run();
}


================================================
FILE: cpp/samples/readme.txt
================================================
FLTK 1.3.x is required http://www.fltk.org/
libpng is required for png support


================================================
FILE: cpp/samples/samples.cpp
================================================
/*config.h is generated by fltk in your system
 * this file is used with fltk 1.3 with gl enabled.
 * compile by: fltk-config --use-gl --compile samples.cpp
*/
#include <math.h>
#include <stdio.h>
#include <stdlib.h>

#include "config.h" //config.h must always be placed before any Fl header
#include <FL/gl.h>
#include <FL/Fl_Window.H>
#include <FL/Fl_Button.H>

namespace VASEr
{
	struct Vec2 { double x,y;};
	struct Color { float r,g,b,a;};
}
#include "../vaser/vaser.cpp"
using namespace VASEr;

void test_draw();
#include "test1_base.cpp"

Fl_Window* main_wnd;
Gl_Window* gl_wnd;
int current=0;

void drag_cb(Fl_Widget* W, void*)
{
	sscanf( W->label(), "sample%d", &current);
	gl_wnd->redraw();
}
void make_form()
{
	for( int i=0; i<6; i++)
	{
		char* buf=(char*)malloc(10);
		snprintf(buf, 10, "sample%d",i);
		(new Fl_Button(600,20*i,100,20,buf))->callback(drag_cb);
	}
}
void sample0()
{
	int size_of_AP=4;
	Vec2 AP[size_of_AP];
		AP[0].x=200; AP[0].y=50;
		AP[1].x=100; AP[1].y=150;
		AP[2].x=300; AP[2].y=150;
		AP[3].x=200; AP[3].y=250;
	Color col={ 0.5, 0.8, 1.0, 1};
	polyline( AP, col, 8.0, size_of_AP, 0);
}
void sample1()
{
	int size_of_AP=4;
	Vec2 AP[size_of_AP];
		AP[0].x=200; AP[0].y=50;
		AP[1].x=100; AP[1].y=150;
		AP[2].x=300; AP[2].y=150;
		AP[3].x=200; AP[3].y=250;
	Color AC[size_of_AP];
		{ Color col={1 , 0, 0, 0.5}; AC[0]=col;}
		{ Color col={.8,.8, 0, 0.5}; AC[1]=col;}
		{ Color col={ 0, 0, 1, 0.5}; AC[2]=col;}
		{ Color col={1 , 0, 0, 0.5}; AC[3]=col;}
	double AW[size_of_AP];
		AW[0] = 1.0;
		AW[1] = 15.0;
		AW[2] = 15.0;
		AW[3] = 1.0;
	
	polyline( AP, AC, AW, size_of_AP, 0);
}
void sample2()
{	//spectrum
	for ( int i=0; i < 20; i++)
	{
		Vec2  P1 = { 5+29.7*i, 187};
		Vec2  P2 = { 35+29.7*i, 8};
		Color C1 = { 1.0,0.0,0.5, 1.0};
		Color C2 = { 0.5,0.0,1.0, 1.0};
		double W1= 0.3*(i+1);
		double W2= W1;
		
		segment(P1,P2, C1,C2, W1,W2, 0);
	}
}
void sample3()
{	//radial spectrum
	for ( double ag=0, i=0; ag < 2*vaser_pi-0.1; ag+=vaser_pi/12, i+=1)
	{
		double r1 = 30.0;
		double r2 = 90.0;
		
		double tx2=r2*cos(ag);
		double ty2=r2*sin(ag);
		double tx1=r1*cos(ag);
		double ty1=r1*sin(ag);
		double Ox = 120;
		double Oy = 194+97;
		
		Vec2  P1 = { Ox+tx1,Oy-ty1};
		Vec2  P2 = { Ox+tx2,Oy-ty2};
		Color C1 = { 1.0,0.0,0.5, 1.0};
		Color C2 = { 0.5,0.0,1.0, 1.0};
		double W1= 0.3*(i+1);
		double W2= W1;
		
		polyline_opt opt={0};
		opt.cap = PLC_round;
		opt.joint = PLJ_round;
		segment(P1,P2, C1,C2, W1,W2, &opt);
	}
}
void sample4()
{
	int size_of_AP=4;
	Vec2 AP[size_of_AP];
		AP[0].x=200; AP[0].y=50;
		AP[1].x=100; AP[1].y=150;
		AP[2].x=300; AP[2].y=150;
		AP[3].x=200; AP[3].y=250;
	Color col={ 0.5, 0.8, 1.0, 1};
	polybezier( AP, col, 12.0, size_of_AP, 0);
}
void sample5()
{
	int size_of_AP=4;
	Vec2 AP[size_of_AP];
		AP[0].x=200; AP[0].y=50;
		AP[1].x=100; AP[1].y=150;
		AP[2].x=300; AP[2].y=150;
		AP[3].x=200; AP[3].y=250;
	Color AC[size_of_AP];
		{ Color col={1 , 0, 0, 0.5}; AC[0]=col;}
		{ Color col={.8,.8, 0, 0.5}; AC[1]=col;}
		{ Color col={ 0, 0, 1, 0.5}; AC[2]=col;}
		{ Color col={1 , 0, 0, 0.5}; AC[3]=col;}
	double AW[size_of_AP];
		AW[0] = 1.0;
		AW[1] = 15.0;
		AW[2] = 15.0;
		AW[3] = 1.0;
	gradient grad = {0};
		gradient_stop stop[10] = {0};
		grad.stops = stop;
		grad.length = 10;
		stop[0].t = 0.00; stop[0].type = GS_rgba; stop[0].color = AC[0];
		stop[1].t = 0.50; stop[1].type = GS_rgba; stop[1].color = AC[2];
		stop[2].t = 1.00; stop[2].type = GS_rgba; stop[2].color = AC[3];
		stop[3].t = 0.00; stop[3].type = GS_weight; stop[3].weight = AW[0];
		stop[4].t = 0.33; stop[4].type = GS_weight; stop[4].weight = AW[1];
		stop[5].t = 0.66; stop[5].type = GS_weight; stop[5].weight = AW[2];
		stop[6].t = 1.00; stop[6].type = GS_weight; stop[6].weight = AW[3];
	polybezier( AP, &grad, size_of_AP, 0);
}
void sample6()
{
}
void sample7()
{
}
void sample8()
{
}
void sample9()
{
}
void test_draw()
{
	renderer::before();
	switch (current)
	{
		case 0: sample0(); break;
		case 1: sample1(); break;
		case 2: sample2(); break;
		case 3: sample3(); break;
		case 4: sample4(); break;
		case 5: sample5(); break;
		case 6: sample6(); break;
		case 7: sample7(); break;
		case 8: sample8(); break;
		case 9: sample9(); break;
	}
	renderer::after();
}

int main(int argc, char **argv)
{
	main_wnd = new Fl_Window( 700,400,"VASEr - demo");
		make_form(); //initialize
		gl_wnd = new Gl_Window( 0,0,600,400);  gl_wnd->end(); //create gl window
	main_wnd->end();
	main_wnd->show();
	main_wnd->redraw();
	
	return Fl::run();
}


================================================
FILE: cpp/samples/segment.cpp
================================================
/*config.h is generated by fltk in your system
 * this file is used with fltk 1.3 with gl enabled.
 * compile by: fltk-config --use-gl --compile segment.cpp
 * or something like: g++ -lX11 -lGL 'segment.cpp' -o 'segment'
*/
#include <math.h>
#include <stdio.h>

#include "config.h" //config.h must always be placed before any Fl header
#include <FL/gl.h>
#include <FL/Fl_Box.H>
#include <FL/Fl_Value_Slider.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Radio_Light_Button.H>

namespace VASEr
{
	struct Vec2 { double x,y;};
	struct Color { float r,g,b,a;};
}
#include "../vaser/vaser.cpp"
using namespace VASEr;

void test_draw();
#include "test1_base.cpp"

Fl_Window* main_wnd;
Gl_Window* gl_wnd;
Fl_Slider *start_weight, *feathering;
Fl_Button *feather, *no_feather_at_cap, *no_feather_at_core;
Fl_Button *jc_butt, *jc_round, *jc_square, *jc_rect;
Fl_Button *colored, *alphaed, *weighted;

char get_cap_type()
{
	if ( jc_butt->value())
		return PLC_butt;
	else if ( jc_round->value())
		return PLC_round;
	else if ( jc_square->value())
		return PLC_square;
	else if ( jc_rect->value())
		return PLC_rect;
	else
		return 0;
}
void drag_cb(Fl_Widget* W, void*)
{
	gl_wnd->redraw();
}
void make_form()
{
	const int LD = 606;
	//weight feathering
	start_weight = new Fl_Value_Slider(LD,0,200,20,"spectrum start weight");
	start_weight->type(FL_HOR_SLIDER);
	start_weight->bounds(0.3,30.0);
	start_weight->step(0.3*20.0);
	start_weight->value(0.0);
	start_weight->callback(drag_cb);
	
	feathering = new Fl_Value_Slider(LD,40,200,20,"feathering");
	feathering->type(FL_HOR_SLIDER);
	feathering->bounds(1.0,10.0);
	feathering->callback(drag_cb);
	feathering->value(1.0);
	
	//feather, no_feather_at_cap, no_feather_at_core
	feather		   = new Fl_Light_Button(LD,80,100,15,"feather");
	no_feather_at_cap  = new Fl_Light_Button(LD+50,95,150,15,"no_feather_at_cap");
	no_feather_at_core = new Fl_Light_Button(LD+50,110,150,15,"no_feather_at_core");
	feather		  ->value(1);
	no_feather_at_cap ->value(0);
	no_feather_at_core->value(0);
	feather		  ->callback(drag_cb);
	no_feather_at_cap ->callback(drag_cb);
	no_feather_at_core->callback(drag_cb);
	
	//cap type
	{
	Fl_Group* o = new Fl_Group(LD,160,200,30);
		new Fl_Box(LD,160,80,15,"cap type:");
		jc_butt   = new Fl_Radio_Light_Button(LD+20,175,40,15,"butt");
		jc_round  = new Fl_Radio_Light_Button(LD+60,175,50,15,"round");
		jc_square = new Fl_Radio_Light_Button(LD+110,175,50,15,"square");
		jc_rect   = new Fl_Radio_Light_Button(LD+160,175,40,15,"rect");
	o->end();
	jc_butt   ->value(1);
	jc_butt   ->callback(drag_cb);
	jc_round  ->callback(drag_cb);
	jc_square ->callback(drag_cb);
	jc_rect   ->callback(drag_cb);
	}
	
	//test options
	colored = new Fl_Light_Button(LD,250,60,15,"colored");
	colored->callback(drag_cb);
	colored->value(1);
	alphaed = new Fl_Light_Button(LD+60,250,70,15,"alpha-ed");
	alphaed->callback(drag_cb);
	alphaed->value(1);
	weighted = new Fl_Light_Button(LD+130,250,70,15,"weighted");
	weighted->callback(drag_cb);
}
void test_draw()
{
	renderer::before();
	
	polyline_opt opt={0};
	opt.feather    = feather->value();
	opt.feathering = feathering->value();
	opt.no_feather_at_cap = no_feather_at_cap->value();
	opt.no_feather_at_core = no_feather_at_core->value();
	opt.cap   = get_cap_type();
	
	for ( int i=0; i<20; i++)
	{
		Vec2  P1 = { 5+29.7*i, 187};
		Vec2  P2 = { 35+29.7*i, 8};
		Color C1 = { 0,0,0, 1};
		Color C2 = C1;
		double W1= 0.3*(i+1) + start_weight->value();
		double W2= W1;
		if ( colored->value())
		{
			Color cc1 = { 1.0,0.0,0.5, 1.0};
			C1 = cc1;
			Color cc2 = { 0.5,0.0,1.0, 1.0};
			C2 = cc2;
		}
		if ( alphaed->value())
		{
			C1.a = 0.5f;
			C2.a = 0.5f;
		}
		if ( weighted->value())
		{
			W1 = 0.1;
		}
		if ( opt.cap != PLC_butt)
		{
			double end_weight = 0.3*(20) + start_weight->value();
			P1.y -= end_weight*0.5;
			P2.y += end_weight*0.5;
		}
		segment(P1, P2,       //coordinates
			C1, C2,       //colors
			W1, W2,       //weights
			&opt);        //extra options
	}
	
	const double pi=3.14159265;
	for ( double ag=0, i=0; ag<2*pi-0.1; ag+=pi/12, i+=1)
	{
		double r1 = 0.0;
		double r2 = 90.0;
		if ( !weighted->value())
			r1 = 30.0;
		if ( opt.cap != PLC_butt)
		{
			double end_weight = 0.3*(12) + start_weight->value();
			r2 -= end_weight*0.5;
		}
		
		double tx2=r2*cos(ag);
		double ty2=r2*sin(ag);
		double tx1=r1*cos(ag);
		double ty1=r1*sin(ag);
		double Ox = 120;
		double Oy = 194+97;
		
		Vec2  P1 = { Ox+tx1,Oy-ty1};
		Vec2  P2 = { Ox+tx2,Oy-ty2};
		Color C1 = { 0,0,0, 1};
		Color C2 = C1;
		double W1= 0.3*(i+1) + start_weight->value();
		double W2= W1;
		if ( colored->value())
		{
			Color cc1 = { 1.0,0.0,0.5, 1.0};
			C1 = cc1;
			Color cc2 = { 0.5,0.0,1.0, 1.0};
			C2 = cc2;
		}
		if ( alphaed->value())
		{
			C1.a = 0.5f;
			C2.a = 0.5f;
		}
		if ( weighted->value())
		{
			W1 = 0.1;
		}
		segment(P1, P2,       //coordinates
			C1, C2,       //colors
			W1, W2,       //weights
			&opt);        //extra options
	}
	
	renderer::after();
}

int main(int argc, char **argv)
{
	main_wnd = new Fl_Window( 606+200,194*2,"Vase Renderer - segment() example - fltk/opengl");
		make_form(); //initialize
		gl_wnd = new Gl_Window( 0,0,606,194*2);  gl_wnd->end(); //create gl window
	main_wnd->end();
	main_wnd->show();
	main_wnd->redraw();
	
	return Fl::run();
}


================================================
FILE: cpp/samples/test1_base.cpp
================================================
#include "config.h"
#include <FL/Fl.H>
#include <FL/gl.h>
#include <FL/Fl_Window.H>
#include <FL/Fl_Gl_Window.H>

class Gl_Window : public Fl_Gl_Window
{
	//methods
	void init();
	void draw();
	int handle(int);
	void left_mouse_button_down();
	void left_mouse_button_up();
	void leftclick_drag();
	
	//variables
	short curx,cury, lastx,lasty;
	short cur_drag; //index of point which is being draged currently
			// -1 means none
	int tsize;
	Vec2* target;

	public:
	Gl_Window(int x,int y,int w,int h,const char *l=0)
		: Fl_Gl_Window(x,y,w,h,l) { init();}
	Gl_Window(int w,int h,const char *l=0)
		: Fl_Gl_Window(w,h,l) { init();}
	void set_drag_target( Vec2* target, int size_of_target)
		{ this->target=target; this->tsize=size_of_target;}
};
void Gl_Window::init()
{
	curx=0; cury=0; lastx=0; lasty=0;
	cur_drag=-1;
	tsize=0;
	target=0;
	
}
void Gl_Window::draw()
{
	if (!valid())
	{
		glMatrixMode(GL_PROJECTION);
			glLoadIdentity();
			glOrtho( 0,w(),h(),0,0.0f,100.0f);
			glClearColor( 1.0, 1.0, 1.0, 1.0);
			glClearDepth( 0.0f);
	}
	glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
	
	test_draw();
}
void Gl_Window::left_mouse_button_down()
{
	for ( int i=0; i<tsize; i++)
	{
		const short ALW=10;
		short dx=curx-target[i].x;
		short dy=cury-target[i].y;
		if ( -ALW<dx && dx<ALW)
		if ( -ALW<dy && dy<ALW)
		{
			cur_drag=i;
			return;
		}
	}
	cur_drag=-1;
};
void Gl_Window::left_mouse_button_up()
{
};
void Gl_Window::leftclick_drag()
{
	if ( target)
	if ( cur_drag >=0 && cur_drag< tsize)
	{
		target[cur_drag].x=curx;
		target[cur_drag].y=cury;
		this->redraw();
	}
};
int Gl_Window::handle(int event)
{
	switch(event)
	{
		case FL_PUSH:
		case FL_RELEASE:
		case FL_DRAG:
		case FL_MOVE:
			curx = Fl::event_x();
			cury = Fl::event_y();
			switch(event)
			{
				case FL_PUSH:
					if ( Fl::event_button() == FL_LEFT_MOUSE)
						left_mouse_button_down();
				case FL_RELEASE:
					if ( Fl::event_button() == FL_LEFT_MOUSE)
						left_mouse_button_up();
				case FL_DRAG:
					if ( (Fl::event_state()&FL_BUTTON1)==FL_BUTTON1)
						leftclick_drag();
				case FL_MOVE:
					;
			}
			lastx=curx;
			lasty=cury;
		return 1;
		
		default:
		return Fl_Window::handle(event);
	} //end of switch(event)
};


================================================
FILE: cpp/vaser/agg_curve4.cpp
================================================
//-----------------------------------------------------------------------
// The Anti-Grain Geometry Project
// A high quality rendering engine for C++
// http://antigrain.com
// 
// Anti-Grain Geometry has dual licensing model. The Modified BSD 
// License was first added in version v2.4 just for convenience.
// It is a simple, permissive non-copyleft free software license, 
// compatible with the GNU GPL. It's well proven and recognizable.
// See http://www.fsf.org/licensing/licenses/index_html#ModifiedBSD
// for details.
// 
// Note that the Modified BSD license DOES NOT restrict your rights 
// if you choose the Anti-Grain Geometry Public License.
// 
// Anti-Grain Geometry Public License
// ====================================================
// 
// Anti-Grain Geometry - Version 2.4 
// Copyright (C) 2002-2005 Maxim Shemanarev (McSeem) 
// 
// Permission to copy, use, modify, sell and distribute this software 
// is granted provided this copyright notice appears in all copies. 
// This software is provided "as is" without express or implied
// warranty, and with no claim as to its suitability for any purpose.
// 
// 
// 
// Modified BSD License
// ====================================================
// Anti-Grain Geometry - Version 2.4 
// Copyright (C) 2002-2005 Maxim Shemanarev (McSeem) 
// 
// Redistribution and use in source and binary forms, with or without 
// modification, are permitted provided that the following conditions 
// are met:
// 
//   1. Redistributions of source code must retain the above copyright 
//      notice, this list of conditions and the following disclaimer. 
// 
//   2. Redistributions in binary form must reproduce the above copyright
//      notice, this list of conditions and the following disclaimer in 
//      the documentation and/or other materials provided with the 
//      distribution. 
// 
//   3. The name of the author may not be used to endorse or promote 
//      products derived from this software without specific prior 
//      written permission. 
// 
// THIS SOFTWARE IS PROVIDED BY THE AUTHOR ``AS IS'' AND ANY EXPRESS OR 
// IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED 
// WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE 
// ARE DISCLAIMED. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY DIRECT, 
// INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR CONSEQUENTIAL DAMAGES 
// (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF SUBSTITUTE GOODS OR 
// SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS INTERRUPTION) 
// HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN CONTRACT, 
// STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE) ARISING 
// IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE 
// POSSIBILITY OF SUCH DAMAGE.
//
//-----------------------------------------------------------------------
// Contact: mcseem@antigrain.com
//          mcseemagg@yahoo.com
//          http://www.antigrain.com
//-----------------------------------------------------------------------

#include <math.h>
#ifndef M_PI
	#define M_PI 3.141592653589793238462643
#endif

double calc_sq_distance(double x1, double y1, double x2, double y2)
{
	double dx = x2-x1;
	double dy = y2-y1;
	return dx * dx + dy * dy;
}

void recursive_bezier(   double x1, double y1, 
				double x2, double y2, 
				double x3, double y3, 
				double x4, double y4,
				unsigned level,
				double m_angle_tolerance,
				double m_cusp_limit,
				double m_distance_tolerance_square,
				void (*add_point)(void*,double,double),
				void* obj )
{
	const double curve_distance_epsilon                  = 1e-30;
	const double curve_collinearity_epsilon              = 1e-30;
	const double curve_angle_tolerance_epsilon           = 0.01;
	const int curve_recursion_limit = 32;

	if(level > curve_recursion_limit) 
	{
		return;
	}

	// Calculate all the mid-points of the line segments
	//----------------------
	double x12   = (x1 + x2) / 2;
	double y12   = (y1 + y2) / 2;
	double x23   = (x2 + x3) / 2;
	double y23   = (y2 + y3) / 2;
	double x34   = (x3 + x4) / 2;
	double y34   = (y3 + y4) / 2;
	double x123  = (x12 + x23) / 2;
	double y123  = (y12 + y23) / 2;
	double x234  = (x23 + x34) / 2;
	double y234  = (y23 + y34) / 2;
	double x1234 = (x123 + x234) / 2;
	double y1234 = (y123 + y234) / 2;

	// Try to approximate the full cubic curve by a single straight line
	//------------------
	double dx = x4-x1;
	double dy = y4-y1;

	double d2 = fabs(((x2 - x4) * dy - (y2 - y4) * dx));
	double d3 = fabs(((x3 - x4) * dy - (y3 - y4) * dx));
	double da1, da2, k;

	switch((int(d2 > curve_collinearity_epsilon) << 1) +
	int(d3 > curve_collinearity_epsilon))
	{
		case 0:
			// All collinear OR p1==p4
			//----------------------
			k = dx*dx + dy*dy;
			if(k == 0)
			{
				d2 = calc_sq_distance(x1, y1, x2, y2);
				d3 = calc_sq_distance(x4, y4, x3, y3);
			}
			else
			{
				k   = 1 / k;
				da1 = x2 - x1;
				da2 = y2 - y1;
				d2  = k * (da1*dx + da2*dy);
				da1 = x3 - x1;
				da2 = y3 - y1;
				d3  = k * (da1*dx + da2*dy);
				if(d2 > 0 && d2 < 1 && d3 > 0 && d3 < 1)
				{
					// Simple collinear case, 1---2---3---4
					// We can leave just two endpoints
					return;
				}
				if(d2 <= 0) d2 = calc_sq_distance(x2, y2, x1, y1);
				else if(d2 >= 1) d2 = calc_sq_distance(x2, y2, x4, y4);
				else             d2 = calc_sq_distance(x2, y2, x1 + d2*dx, y1 + d2*dy);

				if(d3 <= 0) d3 = calc_sq_distance(x3, y3, x1, y1);
				else if(d3 >= 1) d3 = calc_sq_distance(x3, y3, x4, y4);
				else             d3 = calc_sq_distance(x3, y3, x1 + d3*dx, y1 + d3*dy);
			}
			if(d2 > d3)
			{
				if(d2 < m_distance_tolerance_square)
				{
					add_point(obj, x2,y2);
					return;
				}
			}
			else
			{
				if(d3 < m_distance_tolerance_square)
				{
					add_point(obj, x3,y3);
					return;
				}
			}
		break;

		case 1:
			// p1,p2,p4 are collinear, p3 is significant
			//----------------------
			if(d3 * d3 <= m_distance_tolerance_square * (dx*dx + dy*dy))
			{
				if(m_angle_tolerance < curve_angle_tolerance_epsilon)
				{
					add_point(obj, x23,y23);
					return;
				}

				// Angle Condition
				//----------------------
				da1 = fabs(atan2(y4 - y3, x4 - x3) - atan2(y3 - y2, x3 - x2));
				if(da1 >= M_PI) da1 = 2*M_PI - da1;

				if(da1 < m_angle_tolerance)
				{
					add_point(obj, x2, y2);
					add_point(obj, x3, y3);
					return;
				}

				if(m_cusp_limit != 0.0)
				{
					if(da1 > m_cusp_limit)
					{
						add_point(obj, x3, y3);
						return;
					}
				}
			}
		break;

		case 2:
			// p1,p3,p4 are collinear, p2 is significant
			//----------------------
			if(d2 * d2 <= m_distance_tolerance_square * (dx*dx + dy*dy))
			{
				if(m_angle_tolerance < curve_angle_tolerance_epsilon)
				{
					add_point(obj, x23, y23);
					return;
				}

				// Angle Condition
				//----------------------
				da1 = fabs(atan2(y3 - y2, x3 - x2) - atan2(y2 - y1, x2 - x1));
				if(da1 >= M_PI) da1 = 2*M_PI - da1;

				if(da1 < m_angle_tolerance)
				{
					add_point(obj, x2, y2);
					add_point(obj, x3, y3);
					return;
				}

				if(m_cusp_limit != 0.0)
				{
					if(da1 > m_cusp_limit)
					{
						add_point(obj, x2, y2);
						return;
					}
				}
			}
		break;

		case 3: 
			// Regular case
			//-----------------
			if((d2 + d3)*(d2 + d3) <= m_distance_tolerance_square * (dx*dx + dy*dy))
			{
				// If the curvature doesn't exceed the distance_tolerance value
				// we tend to finish subdivisions.
				//----------------------
				if(m_angle_tolerance < curve_angle_tolerance_epsilon)
				{
					add_point(obj, x23, y23);
					return;
				}

				// Angle & Cusp Condition
				//----------------------
				k   = atan2(y3 - y2, x3 - x2);
				da1 = fabs(k - atan2(y2 - y1, x2 - x1));
				da2 = fabs(atan2(y4 - y3, x4 - x3) - k);
				if(da1 >= M_PI) da1 = 2*M_PI - da1;
				if(da2 >= M_PI) da2 = 2*M_PI - da2;

				if(da1 + da2 < m_angle_tolerance)
				{
					// Finally we can stop the recursion
					//----------------------
					add_point(obj, x23, y23);
					return;
				}

				if(m_cusp_limit != 0.0)
				{
					if(da1 > m_cusp_limit)
					{
						add_point(obj, x2, y2);
						return;
					}

					if(da2 > m_cusp_limit)
					{
						add_point(obj, x3, y3);
						return;
					}
				}
			}
		break;
	}

	// Continue subdivision
	//----------------------
	recursive_bezier(x1, y1, x12, y12, x123, y123, x1234, y1234, level + 1,
		m_angle_tolerance, m_cusp_limit, m_distance_tolerance_square,
		add_point, obj); 
	recursive_bezier(x1234, y1234, x234, y234, x34, y34, x4, y4, level + 1,
		m_angle_tolerance, m_cusp_limit, m_distance_tolerance_square,
		add_point, obj); 
}

int curve4_div(double x1, double y1, 
		double x2, double y2, 
		double x3, double y3,
		double x4, double y4,
		double m_approximation_scale,
		double m_angle_tolerance,
		double m_cusp_limit,
		void (*add_point)(void*,double,double),
		void* obj )
{
	double m_distance_tolerance_square = 0.5 / m_approximation_scale;
	m_distance_tolerance_square *= m_distance_tolerance_square;
	int m_count = 0;
	add_point(obj, x1, y1);
	recursive_bezier(x1, y1, x2, y2, x3, y3, x4, y4, 0,
		m_angle_tolerance, m_cusp_limit, m_distance_tolerance_square,
		add_point, obj);
	add_point(obj, x4, y4);
}

#undef M_PI


================================================
FILE: cpp/vaser/backend.h
================================================
class backend
{
public:
	static void vah_draw(vertex_array_holder& vah);
	static void polyline( const Vec2*, Color, double W, int length, const polyline_opt*); //constant color and weight
};


================================================
FILE: cpp/vaser/color.h
================================================
float& Color_get( Color& C, int index)
{
	switch (index)
	{
		case 0: return C.r;
		case 1: return C.g;
		case 2: return C.b;
		default:return C.r;
	}
}
bool Color_valid_range(float t)
{
	return t>=0.0f && t<=1.0f;
}
Color Color_between( const Color& A, const Color& B, float t=0.5f)
{
	if ( t<0.0f) t = 0.0f;
	if ( t>1.0f) t = 1.0f;
	
	float kt = 1.0f - t;
	Color C =
	{
		A.r *kt + B.r *t,
		A.g *kt + B.g *t,
		A.b *kt + B.b *t,
		A.a *kt + B.a *t
	};
	return C;
}
void sRGBtolinear( Color& C, bool exact=false)
{	//de-Gamma 2.2
	//from: http://www.xsi-blog.com/archives/133
	if (exact)
	{
		for ( int i=0; i<3; i++)
		{
			float& cc = Color_get( C,i);
			
			if ( cc > 0.04045)
				cc = pow( (cc+0.055)/1.055, 2.4);
			else
				cc /= 12.92;
		}
	}
	else
	{	//approximate
		for ( int i=0; i<3; i++)
		{
			float& cc = Color_get( C,i);
			cc = pow(cc,2.2);
		}
	}
}
void lineartosRGB( Color& C, bool exact=false)
{	//Gamma 2.2
	if (exact)
	{
		for ( int i=0; i<3; i++)
		{
			float& cc = Color_get( C,i);
			if ( cc > 0.0031308)
				cc = 1.055 * pow(cc,1.0/2.4) - 0.055;
			else
				cc *= 12.92;
		}
	}
	else
	{	//approximate
		for ( int i=0; i<3; i++)
		{
			float& cc = Color_get( C,i);
			cc = pow(cc,1.0/2.2);
		}
	}
}
float color_max( float r,float g,float b)
{
	return r>g? (g>b?r:(r>b?r:b)) : (g>b?g:b);
}
float color_min( float r, float g, float b)
{
	return -color_max( -r,-g,-b);
}
void RGBtoHSV( float r, float g, float b, float *h, float *s, float *v )
{	//from: http://www.cs.rit.edu/~ncs/color/t_convert.html
	// r,g,b values are from 0 to 1
	// h = [0,360], s = [0,1], v = [0,1]
	//		if s == 0, then h = -1 (undefined)
	float min, max, delta;
	min = color_min( r, g, b );
	max = color_max( r, g, b );
	*v = max;				// v
	delta = max - min;
	if( max != 0 )
		*s = delta / max;		// s
	else {
		// r = g = b = 0		// s = 0, v is undefined
		*s = 0;
		*h = -1;
		return;
	}
	if( r == max )
		*h = ( g - b ) / delta;		// between yellow & magenta
	else if( g == max )
		*h = 2 + ( b - r ) / delta;	// between cyan & yellow
	else
		*h = 4 + ( r - g ) / delta;	// between magenta & cyan
	*h *= 60;				// degrees
	if( *h < 0 )
		*h += 360;
}
void HSVtoRGB( float *r, float *g, float *b, float h, float s, float v )
{
	int i;
	float f, p, q, t;
	if( s == 0 ) {
		// achromatic (grey)
		*r = *g = *b = v;
		return;
	}
	h /= 60;			// sector 0 to 5
	i = floor( h );
	f = h - i;			// factorial part of h
	p = v * ( 1 - s );
	q = v * ( 1 - s * f );
	t = v * ( 1 - s * ( 1 - f ) );
	switch( i ){
		case 0:
			*r = v; *g = t; *b = p;		break;
		case 1:
			*r = q; *g = v;	*b = p;		break;
		case 2:
			*r = p;	*g = v;	*b = t;		break;
		case 3:
			*r = p;	*g = q;	*b = v;		break;
		case 4:
			*r = t;	*g = p;	*b = v;		break;
		default:		// case 5:
			*r = v;	*g = p;	*b = q;		break;
	}
}


================================================
FILE: cpp/vaser/curve.cpp
================================================
namespace VASErin
{	//VASEr internal namespace

class polyline_buffer
{
public:
	std::vector<Vec2>   P;
	std::vector<Color>  C;
	std::vector<double> W;
	std::vector<double> L; //length along polyline
	int N;
	double path_length; //total segment length

	polyline_buffer(): P(),C(),W(),L()
	{
		N=0;
		path_length=0.0;
		L.push_back(0.0);
	}
	void point(double x, double y)
	{
		Vec2 V={x,y};
		addvertex(V);
	}
	void point(Vec2 V)
	{
		addvertex(V);
	}
	static void point_cb(void* obj, double x, double y)
	{
		polyline_buffer* This = (polyline_buffer*)obj;
		Vec2 V={x,y};
		This->addvertex(V);
	}
	void vertex(Vec2 V, Color cc)
	{
		addvertex(V,&cc);
	}
	void vertex(Vec2 V, Color cc, double ww)
	{
		addvertex(V,& cc,ww);
	}
	void set_color(Color cc)
	{
		if( !C.size())
			C.push_back(cc);
		else
			C.back()=cc;
	}
	void set_weight(double ww)
	{
		if( !W.size())
			W.push_back(ww);
		else
			W.back()=ww;
	}
	void gradient( const gradient* grad)
	{
		gradient_apply(grad,&C[0],&W[0],&L[0],N,path_length);
	}
	void draw(const polyline_opt* options)
	{
		if( !N) return;
		polyline_inopt in_options={0};
		in_options.segment_length = &L[0];
		polyline( &P[0],&C[0],&W[0],N,options,&in_options);
	}
private:
	bool addvertex(const Vec2& V, const Color* cc=0, double ww=0.0)
	{
		if( N && P[N-1].x==V.x && P[N-1].y==V.y)
			return false; //duplicate
		else
		{
			//point
			P.push_back(V);
			if( N>0)
			{
				double len = (Point(V)-Point(P[N-1])).length();
				path_length += len;
				L.push_back(len);
			}
			//color
			if( cc)
				C.push_back(*cc);
			else
			{
				if( !C.size())
					C.push_back(default_color);
				else
					C.push_back(C.back());
			}
			//weight
			if( ww)
				W.push_back(ww);
			else
			{
				if( !W.size())
					W.push_back(default_weight);
				else
					W.push_back(W.back());
			}
			//finally
			N++;
			return true;
		}
	}
};

struct polybezier_inopt
{
	bool evaluate_only;
	polyline_buffer* target;
};

void polybezier( const Vec2* P, const gradient* grad, int length, const polybezier_opt* options, const polybezier_inopt* in_options)
{
	polybezier_opt opt={0};
	polybezier_inopt inopt={0};
	polyline_opt poly={0};
	poly.joint = PLJ_bevel;
	if( options) opt = *options;
	if( in_options) inopt = *in_options;
	if( !opt.poly) opt.poly = &poly;

	polyline_buffer _buf;
	polyline_buffer& buf = _buf;
	if( inopt.target)
		buf = *inopt.target;

	const double BZ_default_approximation_scale = 0.5;
	const double BZ_default_angle_tolerance = 15.0/180*vaser_pi;
	const double BZ_default_cusp_limit = 5.0;

	for( int i=0; i<length-3; i+=3)
	{
		curve4_div(
			P[i+0].x,P[i+0].y,
			P[i+1].x,P[i+1].y,
			P[i+2].x,P[i+2].y,
			P[i+3].x,P[i+3].y,
			BZ_default_approximation_scale,
			BZ_default_angle_tolerance,
			BZ_default_cusp_limit,
			polyline_buffer::point_cb,
			&buf);
	}
	if( grad)
		buf.gradient(grad);
	if( !inopt.evaluate_only)
		buf.draw(opt.poly);
}

} //sub namespace VASErin

//export implementations

void polybezier( const Vec2* P, const gradient* grad, int length, const polybezier_opt* options)
{
	VASErin::polybezier(P,grad,length,options,0);
}

void polybezier( const Vec2* P, Color cc, double ww, int length, const polybezier_opt* options)
{
	gradient grad = {0};
	gradient_stop stop[2] = {0};
	grad.stops = stop;
	grad.length = 2;
	stop[0].type = GS_rgba; stop[0].color = cc;
	stop[1].type = GS_weight; stop[1].weight = ww;
	VASErin::polybezier(P,&grad,length,options,0);
}


================================================
FILE: cpp/vaser/gradient.cpp
================================================
namespace VASErin
{	//VASEr internal namespace

double grad_getstep(double A,double B,double t,double T)
{
	return ((T-t)*A + t*B) / T;
}

void gradient_apply(const gradient* gradp, Color* C, double* W, const double* L, int length, double path_length)
{
	if( !gradp) return;
	if( !gradp->stops || !gradp->length) return;
	const gradient& grad = *gradp;
	const gradient_stop* stop = grad.stops;

	//current stops
	int las_c=0, las_a=0, las_w=0, //last
		cur_c=0, cur_a=0, cur_w=0, //current
		nex_c=0, nex_a=0, nex_w=0; //next
	double length_along=0.0;
	if( grad.length>1)
	for( int i=0; i<length; i++)
	{
		length_along += L[i];
		double p;
		if( grad.unit==GD_ratio)
			p = length_along/path_length;
		else if ( grad.unit==GD_length)
			p = length_along;

		{	//lookup for cur
			for( nex_w=cur_w; nex_w < grad.length; nex_w++)
				if( stop[nex_w].type==GS_weight && p <= stop[nex_w].t)
					{ cur_w=nex_w; break;}
			for( nex_c=cur_c; nex_c < grad.length; nex_c++)
				if( (stop[nex_c].type==GS_rgba || stop[nex_c].type==GS_rgb ) &&
					p <= stop[nex_c].t)
					{ cur_c=nex_c; break;}
			for( nex_a=cur_a; nex_a < grad.length; nex_a++)
				if( (stop[nex_a].type==GS_rgba || stop[nex_a].type==GS_alpha ) &&
					p <= stop[nex_a].t)
					{ cur_a=nex_a; break;}
			//look for las
			for( nex_w=cur_w; nex_w >= 0; nex_w--)
				if( stop[nex_w].type==GS_weight && p >= stop[nex_w].t)
					{ las_w=nex_w; break;}
			for( nex_c=cur_c; nex_c >= 0; nex_c--)
				if( (stop[nex_c].type==GS_rgba || stop[nex_c].type==GS_rgb ) &&
					p >= stop[nex_c].t)
					{ las_c=nex_c; break;}
			for( nex_a=cur_a; nex_a >= 0; nex_a--)
				if( (stop[nex_a].type==GS_rgba || stop[nex_a].type==GS_alpha ) &&
					p >= stop[nex_a].t)
					{ las_a=nex_a; break;}
		}
		#define SC(x) stop[x].color
		#define Sw(x) stop[x].weight
		#define Sa(x) stop[x].color.a
		#define St(x) stop[x].t
		if ( cur_c==las_c)
			C[i] = SC(cur_c);
		else
			C[i] = Color_between(SC(las_c),SC(cur_c), (p-St(las_c))/(St(cur_c)-St(las_c)));
		if ( cur_w==las_w)
			W[i] = Sw(cur_w);
		else
			W[i] = grad_getstep(Sw(las_w),Sw(cur_w), p-St(las_w), St(cur_w)-St(las_w));
		if ( cur_a==las_a)
			C[i].a = Sa(cur_a);
		else
			C[i].a = grad_getstep(Sa(las_a),Sa(cur_a), p-St(las_a), St(cur_a)-St(las_a));
		#undef SC
		#undef Sw
		#undef Sa
		#undef St
	}
}

} //sub namespace VASErin


================================================
FILE: cpp/vaser/opengl.cpp
================================================
/* OpenGL 1.1 renderer and backend */

namespace VASErin
{	//VASEr internal namespace

void backend::vah_draw(vertex_array_holder& vah)
{
	if ( vah.count > 0) //save some effort
	{
		glVertexPointer(2, GL_FLOAT, 0, &vah.vert[0]);
		glColorPointer (4, GL_FLOAT, 0, &vah.color[0]);
		glDrawArrays (vah.glmode, 0, vah.count);
	}
}

void backend::polyline( const Vec2* P, Color C, double W, int length, const polyline_opt*) //constant color and weight
{
	int type=0;
	if( sizeof(Vec2)==16)
		type = GL_DOUBLE;
	else if( sizeof (Vec2)==8)
		type = GL_FLOAT;

	glColor4f(C.r,C.g,C.b,C.a);
	glLineWidth(W);
	if( type)
	{
		glEnableClientState(GL_VERTEX_ARRAY);
		glDisableClientState(GL_COLOR_ARRAY);
		glVertexPointer(2, type, 0, P);
		glDrawArrays(GL_LINE_STRIP, 0, length);
		glEnableClientState(GL_COLOR_ARRAY);
	}
	else
	{
		glBegin(GL_LINE_STRIP);
		for( int i=0; i<length; i++)
			glVertex2f(P[i].x,P[i].y);
		glEnd();
	}
	glLineWidth(1);
}

void swap_pixel(unsigned char* img, int w, int x1,int y1, int x2,int y2)
{
	const int b=4;
	unsigned char R = img[y1*w*b+x1*b];
	unsigned char G = img[y1*w*b+x1*b+1];
	unsigned char B = img[y1*w*b+x1*b+2];
	unsigned char A = img[y1*w*b+x1*b+3];
	
	img[y1*w*b+x1*b] = img[y2*w*b+x2*b];
	img[y1*w*b+x1*b+1] = img[y2*w*b+x2*b+1];
	img[y1*w*b+x1*b+2] = img[y2*w*b+x2*b+2];
	img[y1*w*b+x1*b+3] = img[y2*w*b+x2*b+3];
	
	img[y2*w*b+x2*b] = R;
	img[y2*w*b+x2*b+1] = G;
	img[y2*w*b+x2*b+2] = B;
	img[y2*w*b+x2*b+3] = A;
}

} //sub namespace VASErin

void renderer::init()
{
}

void renderer::before()
{
	glEnableClientState(GL_VERTEX_ARRAY);
	glEnableClientState(GL_COLOR_ARRAY);
	
	glDisableClientState(GL_EDGE_FLAG_ARRAY);
	//glDisableClientState(GL_FOG_COORD_ARRAY);
	glDisableClientState(GL_INDEX_ARRAY);
	glDisableClientState(GL_NORMAL_ARRAY);
	//glDisableClientState(GL_SECONDARY_COLOR_ARRAY);
	glDisableClientState(GL_TEXTURE_COORD_ARRAY);
	
	glEnable(GL_BLEND);
	glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
}
void renderer::after()
{
	glDisableClientState(GL_VERTEX_ARRAY);
	glDisableClientState(GL_COLOR_ARRAY);
	glDisable(GL_BLEND);
}
Image renderer::get_image()
{
	int res[4];
	glGetIntegerv(GL_VIEWPORT,res);
	Image im={0};
	im.width=res[2];
	im.height=res[3];
	im.buf=(unsigned char*)malloc(im.width*im.height*4);
	if( !im.buf)
		return im;
	glReadPixels(0, 0, im.width, im.height, GL_RGBA, GL_UNSIGNED_BYTE, (GLvoid *)im.buf);
	//flip horizontally
	for( int i=0; i<im.height/2; i++)
		for( int j=0; j<im.width; j++)
			VASErin::swap_pixel(im.buf,im.width,j,i,j,im.height-i-1);
	return im;
}



================================================
FILE: cpp/vaser/point.h
================================================
class Point : public Vec2
{
public:
	Point() { clear();}
	Point(const Vec2& P) { set(P.x,P.y);}
	Point(const Point& P) { set(P.x,P.y);}
	Point(double X, double Y) { set(X,Y);}
	
	void clear()			{ x=0.0; y=0.0;}
	void set(double X, double Y)	{ x=X;   y=Y;}
	
	Vec2 vec() {
		Vec2 V;
		V.x = x; V.y = y;
		return V;
	}
	
	//attributes
	double length() const
	{
		return sqrt(x*x+y*y);
	}
	double slope() const
	{
		return y/x;
	}
	static double signed_area(const Point& P1, const Point& P2, const Point& P3)
	{
		return (P2.x-P1.x)*(P3.y-P1.y) - (P3.x-P1.x)*(P2.y-P1.y);
	}
	
	//vector operators	
	Point operator+(const Point &b) const
	{
		return Point( x+b.x, y+b.y);
	}
	Point operator-() const
	{
		return Point( -x, -y);
	}
	Point operator-(const Point &b) const
	{
		return Point( x-b.x, y-b.y);
	}
	Point operator*(double k) const
	{
		return Point( x*k, y*k);
	}
	
	Point& operator=(const Point& b) {
		x = b.x; y = b.y;
		return *this;
	}
	Point& operator+=(const Point& b)
	{
		x += b.x; y += b.y;
		return *this;
	}
	Point& operator-=(const Point& b)
	{
		x -= b.x; y -= b.y;
		return *this;
	}
	Point& operator*=(const double k)
	{
		x *= k; y *= k;
		return *this;
	}
	Point& operator/=(const double k)
	{
		x /= k; y /= k;
		return *this;
	}
	
	static void dot( const Point& a, const Point& b, Point& o) //dot product: o = a dot b
	{
		o.x = a.x * b.x;
		o.y = a.y * b.y;
	}
	Point dot_prod( const Point& b) const //return dot product
	{
		return Point( x*b.x, y*b.y);
	}
	
	//self operations
	void opposite()
	{
		x = -x;
		y = -y;
	}
	void opposite_of( const Point& a)
	{
		x = -a.x;
		y = -a.y;
	}
	double normalize()
	{
		double L = length();
		if ( L > vaser_min_alw)
		{
			x /= L; y /= L;
		}
		return L;
	}
	void perpen() //perpendicular: anti-clockwise 90 degrees
	{
		double y_value=y;
		y=x;
		x=-y_value;
	}
	void follow_signs( const Point& a)
	{
		if ( (x>0) != (a.x>0))	x = -x;
		if ( (y>0) != (a.y>0))	y = -y;
	}
	void follow_magnitude( const Point& a);
	void follow_direction( const Point& a);
	
	//judgements
	static inline bool negligible( double M)
	{
		return -vaser_min_alw < M && M < vaser_min_alw;
	}
	bool negligible() const
	{
		return Point::negligible(x) && Point::negligible(y);
	}
	bool non_negligible() const
	{
		return !negligible();
	}
	bool is_zero() const
	{
		return x==0.0 && y==0.0;
	}
	bool non_zero() const
	{
		return !is_zero();
	}
	static bool intersecting( const Point& A, const Point& B,
		const Point& C, const Point& D)
	{	//return true if AB intersects CD
		return signed_area(A,B,C)>0 != signed_area(A,B,D)>0;
	}
	
	//operations require 2 input points
	static double distance_squared( const Point& A, const Point& B)
	{
		double dx=A.x-B.x;
		double dy=A.y-B.y;
		return (dx*dx+dy*dy);
	}
	static inline double distance( const Point& A, const Point& B)
	{
		return sqrt( distance_squared(A,B));
	}
	static Point midpoint( const Point& A, const Point& B)
	{
		return (A+B)*0.5;
	}
	static bool opposite_quadrant( const Point& P1, const Point& P2)
	{
		char P1x = P1.x>0? 1:(P1.x<0?-1:0);
		char P1y = P1.y>0? 1:(P1.y<0?-1:0);
		char P2x = P2.x>0? 1:(P2.x<0?-1:0);
		char P2y = P2.y>0? 1:(P2.y<0?-1:0);
		
		if ( P1x!=P2x) {
			if ( P1y!=P2y)
				return true;
			if ( P1y==0 || P2y==0)
				return true;
		}
		if ( P1y!=P2y) {
			if ( P1x==0 || P2x==0)
				return true;
		}
		return false;
	}
	
	//operations of 3 points
	static inline bool anchor_outward_D( Point& V, const Point& b, const Point& c)
	{
		return (b.x*V.x - c.x*V.x + b.y*V.y - c.y*V.y) > 0;
	}
	static bool anchor_outward( Point& V, const Point& b, const Point& c, bool reverse=false)
	{ //put the correct outward vector at V, with V placed on b, comparing distances from c
		bool determinant = anchor_outward_D ( V,b,c);
		if ( determinant == (!reverse)) { //when reverse==true, it means inward
			//positive V is the outward vector
			return false;
		} else {
			//negative V is the outward vector
			V.x=-V.x;
			V.y=-V.y;
			return true; //return whether V is changed
		}
	}
	static void anchor_inward( Point& V, const Point& b, const Point& c)
	{
		anchor_outward( V,b,c,true);
	}
	
	//operations of 4 points
	static char intersect( const Point& P1, const Point& P2,  //line 1
			const Point& P3, const Point& P4, //line 2
			Point& Pout,			  //the output point
			double* ua_out=0, double* ub_out=0)
	{ //Determine the intersection point of two line segments
		double mua,mub;
		double denom,numera,numerb;

		denom  = (P4.y-P3.y) * (P2.x-P1.x) - (P4.x-P3.x) * (P2.y-P1.y);
		numera = (P4.x-P3.x) * (P1.y-P3.y) - (P4.y-P3.y) * (P1.x-P3.x);
		numerb = (P2.x-P1.x) * (P1.y-P3.y) - (P2.y-P1.y) * (P1.x-P3.x);

		if( negligible(numera) &&
			negligible(numerb) &&
			negligible(denom)) {
		Pout.x = (P1.x + P2.x) * 0.5;
		Pout.y = (P1.y + P2.y) * 0.5;
		return 2; //meaning the lines coincide
		}

		if ( negligible(denom)) {
			Pout.x = 0;
			Pout.y = 0;
			return 0; //meaning lines are parallel
		}

		mua = numera / denom;
		mub = numerb / denom;
		if ( ua_out) *ua_out = mua;
		if ( ub_out) *ub_out = mub;

		Pout.x = P1.x + mua * (P2.x - P1.x);
		Pout.y = P1.y + mua * (P2.y - P1.y);
		
		bool out1 = mua < 0 || mua > 1;
		bool out2 = mub < 0 || mub > 1;
		
		if ( out1 & out2) {
			return 5; //the intersection lies outside both segments
		} else if ( out1) {
			return 3; //the intersection lies outside segment 1
		} else if ( out2) {
			return 4; //the intersection lies outside segment 2
		} else {
			return 1; //great
		}
	//http://paulbourke.net/geometry/lineline2d/
	}
}; //end of class Point

/* after all,
 * sizeof(Vec2)=16  sizeof(Point)=16
 * Point is not heavier, just more powerful :)
*/


================================================
FILE: cpp/vaser/polyline.cpp
================================================
namespace VASErin
{	//VASEr internal namespace

/*visual testes:
 * A. points (geometry test)
 *  1. arbitrary polyline of only 2 point
 *  2. polylines of 3 points, making arbitrary included angle
 *  3. arbitrary polyline of 4 or more points
 * B. colors
 *  1. different colors on each point
 * C. weight
 *  1. varying weight
 * D. other drawing options
 *  1. feathering
 *  2. different joint types
 *  3. different cap types
 * E. memory test
 *  1. drawing a polyline of 1000 points
 */

/*known visual bugs: ( ",," simply means "go wild" as it is too hard to describe)
 * 1.  [solved]when 2 segments are exactly at 90 degrees, the succeeding line segment is reflexed.
 * 1.1 [solved]when 2 segments are exactly at 180 degrees,,
 * 2.  [solved]when polyline is dragged, caps seem to have pseudo-podia.
 * 3.  [solved]when 2 segments are exactly horizontal/ vertical, both segments are reflexed.
 * 3.1 [solved]when a segment is exactly horizontal/ vertical, the cap disappears
 * 4.  [solved]when 2 segments make < 5 degrees,,
 * 4.1 [solved]when 2 segments make exactly 0 degree,,
 * 5.  when a segment is shorter than its own width,,
 */

/* const double vaser_actual_PPI = 96.0;
	const double vaser_standard_PPI = 111.94; //the PPI I used for calibration
 */

//critical weight to do approximation rather than real joint processing
const double cri_segment_approx=1.6;

void determine_t_r ( double w, double& t, double& R)
{
	//efficiency: can cache one set of w,t,R values
	// i.e. when a polyline is of uniform thickness, the same w is passed in repeatedly
	double f=w-static_cast<int>(w);
	
	/*   */if ( w>=0.0 && w<1.0) {
		t=0.05; R=0.768;//R=0.48+0.32*f;
	} else if ( w>=1.0 && w<2.0) {
		t=0.05+f*0.33; R=0.768+0.312*f;
	} else if ( w>=2.0 && w<3.0){
		t=0.38+f*0.58; R=1.08;
	} else if ( w>=3.0 && w<4.0){
		t=0.96+f*0.48; R=1.08;
	} else if ( w>=4.0 && w<5.0){
		t=1.44+f*0.46; R=1.08;
	} else if ( w>=5.0 && w<6.0){
		t=1.9+f*0.6; R=1.08;
	} else if ( w>=6.0){
		double ff=w-6.0;
		t=2.5+ff*0.50; R=1.08;
	}
	
	/* //PPI correction
	double PPI_correction = vaser_standard_PPI / vaser_actual_PPI;
	const double up_bound = 1.6; //max value of w to receive correction
	const double start_falloff = 1.0;
	if ( w>0.0 && w<up_bound)
	{	//here we gracefully apply the correction
		// so that the effect of correction diminishes starting from w=start_falloff
		//   and completely disappears when w=up_bound
		double correction = 1.0 + (PPI_correction-1.0)*(up_bound-w)/(up_bound-start_falloff);
		t *= PPI_correction;
		R *= PPI_correction;
	} */
}
float get_PLJ_round_dangle(float t, float r)
{
	float dangle;
	float sum = t+r;
	if ( sum <= 1.44f+1.08f) //w<=4.0, feathering=1.0
		dangle = 0.6f/(t+r);
	else if ( sum <= 3.25f+1.08f) //w<=6.5, feathering=1.0
		dangle = 2.8f/(t+r);
	else
		dangle = 4.2f/(t+r);
	return dangle;
}

void make_T_R_C( Point& P1, Point& P2, Point* T, Point* R, Point* C,
				double w, const polyline_opt& opt,
				double* rr, double* tt, float* dist,
				bool seg_mode=false)
{
	double t=1.0,r=0.0;
	Point DP=P2-P1;
	
	//calculate t,r
	determine_t_r( w,t,r);
	
	if ( opt.feather && !opt.no_feather_at_core && opt.feathering != 1.0)
		r *= opt.feathering;
	else if ( seg_mode)
	{
		//TODO: handle correctly for hori/vert segments in a polyline
		if ( Point::negligible(DP.x) && P1.x==(int)P1.x) {
			if ( w>0.0 && w<=1.0) {
				t=0.5; r=0.0;
				P2.x = P1.x = (int)P1.x+0.5;
			}
		} else if ( Point::negligible(DP.y) && P1.y==(int)P1.y) {
			if ( w>0.0 && w<=1.0) {
				t=0.5; r=0.0;
				P2.y = P1.y = (int)P1.y+0.5;
			}
		}
	}
	
	//output t,r
	if (tt) *tt = t;
	if (rr) *rr = r;
	
	//calculate T,R,C
	double len = DP.normalize();
	if (dist) *dist = (float)len;
	if (C) *C = DP;
	DP.perpen();
	
	if (T) *T = DP*t;
	if (R) *R = DP*r;
}

void same_side_of_line( Point& V, const Point& ref, const Point& a, const Point& b)
{
	double sign1 = Point::signed_area( a+ref,a,b);
	double sign2 = Point::signed_area( a+V,  a,b);
	if ( (sign1>=0) != (sign2>=0))
	{
		V.opposite();
	}
}

struct st_polyline
//the struct to hold info for anchor_late() to perform triangluation
{
	//for all joints
	Point vP; //vector to intersection point
	Point vR; //fading vector at sharp end
		//all vP,vR are outward
	
	//for djoint==PLJ_bevel
	Point T; //core thickness of a line
	Point R; //fading edge of a line
	Point bR; //out stepping vector, same direction as cap
	Point T1,R1; //alternate vectors, same direction as T21
		//all T,R,T1,R1 are outward
	
	//for djoint==PLJ_round
	float t,r;
	
	//for degeneration case
	bool degenT; //core degenerated
	bool degenR; //fade degenerated
	bool pre_full; //draw the preceding segment in full
	Point PT,PR;
	float pt; //parameter at intersection
	bool R_full_degen;
	
	char djoint; //determined joint
			// e.g. originally a joint is PLJ_miter. but it is smaller than critical angle, should then set djoint to PLJ_bevel
};

struct st_anchor
//the struct to hold memory for the working of anchor()
{
	Point P[3]; //point
	Color C[3]; //color
	double W[3];//weight
	
	Point cap_start, cap_end;
	st_polyline SL[3];
	vertex_array_holder vah;
};

void inner_arc( vertex_array_holder& hold, const Point& P,
		const Color& C, const Color& C2,
		float dangle, float angle1, float angle2,
		float r, float r2, bool ignor_ends,
		Point* apparent_P)	//(apparent center) center of fan
//draw the inner arc between angle1 and angle2 with dangle at each step.
// -the arc has thickness, r is the outer radius and r2 is the inner radius,
//    with color C and C2 respectively.
//    in case when inner radius r2=0.0f, it gives a pie.
// -when ignor_ends=false, the two edges of the arc lie exactly on angle1
//    and angle2. when ignor_ends=true, the two edges of the arc do not touch
//    angle1 or angle2.
// -P is the mathematical center of the arc.
// -when apparent_P points to a valid Point (apparent_P != 0), r2 is ignored,
//    apparent_P is then the apparent origin of the pie.
// -the result is pushed to hold, in form of a triangle strip
// -an inner arc is an arc which is always shorter than or equal to a half circumference
{
	const double& m_pi = vaser_pi;
	
	bool incremental=true;
	
	if ( angle2 > angle1)
	{
		if ( angle2-angle1>m_pi)
		{
			angle2=angle2-2*m_pi;
		}
	}
	else
	{
		if ( angle1-angle2>m_pi)
		{
			angle1=angle1-2*m_pi;
		}
	}
	if ( angle1>angle2)
	{
		incremental = false; //means decremental
	}
	
	if ( incremental)
	{
		if ( ignor_ends)
		{
			int i=0;
			for ( float a=angle1+dangle; a<angle2; a+=dangle, i++)
			{
				float x=cos(a);
				float y=sin(a);
				
				#define INNER_ARC_PUSH \
					hold.push( Point(P.x+x*r,P.y-y*r), C);\
					if ( !apparent_P)\
						hold.push( Point(P.x+x*r2,P.y-y*r2), C2);\
					else\
						hold.push( *apparent_P, C2);
				
				INNER_ARC_PUSH
				
				if ( i>100) {
					DEBUG("trapped in loop: inc,ig_end angle1=%.2f, angle2=%.2f, dangle=%.2f\n", angle1, angle2, dangle);
					break;
				}
			}
			//DEBUG( "steps=%d ",i); fflush(stdout);
		}
		else
		{
			int i=0;
			for ( float a=angle1; ; a+=dangle, i++)
			{
				if ( a>angle2)
					a=angle2;
				
				float x=cos(a);
				float y=sin(a);
				
				INNER_ARC_PUSH
				
				if ( a>=angle2)
					break;
				
				if ( i>100) {
					DEBUG("trapped in loop: inc,end angle1=%.2f, angle2=%.2f, dangle=%.2f\n", angle1, angle2, dangle);
					break;
				}
			}
		}
	}
	else //decremental
	{
		if ( ignor_ends)
		{
			int i=0;
			for ( float a=angle1-dangle; a>angle2; a-=dangle, i++)
			{
				float x=cos(a);
				float y=sin(a);
				
				INNER_ARC_PUSH
				
				if ( i>100) {
					DEBUG("trapped in loop: dec,ig_end angle1=%.2f, angle2=%.2f, dangle=%.2f\n", angle1, angle2, dangle);
					break;
				}
			}
		}
		else
		{
			int i=0;
			for ( float a=angle1; ; a-=dangle, i++)
			{
				if ( a<angle2)
					a=angle2;
				
				float x=cos(a);
				float y=sin(a);
				
				INNER_ARC_PUSH
				#undef INNER_ARC_PUSH
				
				if ( a<=angle2)
					break;
				
				if ( i>100) {
					DEBUG("trapped in loop: dec,end angle1=%.2f, angle2=%.2f, dangle=%.2f\n", angle1, angle2, dangle);
					break;
				}
			}
		}
	}
}
void vectors_to_arc( vertex_array_holder& hold, const Point& P,
		const Color& C, const Color& C2,
		Point A, Point B, float dangle, float r, float r2, bool ignor_ends,
		Point* apparent_P)
//triangulate an inner arc between vectors A and B,
//  A and B are position vectors relative to P
{
	const double& m_pi = vaser_pi;
	A *= 1/r;
	B *= 1/r;
	if ( A.x > 1.0-vaser_min_alw) A.x = 1.0-vaser_min_alw;
	if ( A.x <-1.0+vaser_min_alw) A.x =-1.0+vaser_min_alw;
	if ( B.x > 1.0-vaser_min_alw) B.x = 1.0-vaser_min_alw;
	if ( B.x <-1.0+vaser_min_alw) B.x =-1.0+vaser_min_alw;
	
	float angle1 = acos(A.x);
	float angle2 = acos(B.x);
	if ( A.y>0){ angle1=2*m_pi-angle1;}
	if ( B.y>0){ angle2=2*m_pi-angle2;}
	//printf( "steps=%d ",int((angle2-angle1)/den*r));
	
	inner_arc( hold, P, C,C2, dangle,angle1,angle2, r,r2, ignor_ends, apparent_P);
}

#ifdef VASER_DEBUG
void annotate( const Point& P, Color cc, int I=-1)
{
	static int i=0;
	if ( I != -1) i=I;
	
	glBegin(GL_LINES);
		glColor3f(1,0,0);
		glVertex2f(P.x-4,P.y-4);
		glVertex2f(P.x+4,P.y+4);
		glVertex2f(P.x-4,P.y+4);
		glVertex2f(P.x+4,P.y-4);
	glEnd();
	
	char str[10];
	sprintf(str,"%d",i);
	gl_font( FL_HELVETICA, 8);
	gl_draw( str,float(P.x+2),float(P.y));
	i++;
}
void annotate( const Point& P)
{
	Color cc;
	annotate(P,cc);
}
void draw_vector( const Point& P, const Point& V, const char* name)
{
	Point P2 = P+V;
	glBegin(GL_LINES);
		glColor3f(1,0,0);
		glVertex2f(P.x,P.y);
		glColor3f(1,0.9,0.9);
		glVertex2f(P2.x,P2.y);
	glEnd();
	if ( name)
	{
		glColor3f(0,0,0);
		gl_font( FL_HELVETICA, 8);
		gl_draw( name,float(P2.x+2),float(P2.y));
	}
}
void printpoint( const Point& P, const char* name)
{
	printf("%s(%.4f,%.4f) ",name,P.x,P.y);
	fflush(stdout);
}
#endif
/*
Point plus_minus( const Point& a, const Point& b, bool plus)
{
	if (plus) return a+b;
	else return a-b;
}
Point plus_minus( const Point& a, bool plus)
{
	if (plus) return a;
	else return -a;
}
bool quad_is_reflexed( const Point& P0, const Point& P1, const Point& P2, const Point& P3)
{
	//points:
	//   1------3
	//  /      /
	// 0------2
	// vector 01 parallel to 23
	
	return Point::distance_squared(P1,P3) + Point::distance_squared(P0,P2)
		> Point::distance_squared(P0,P3) + Point::distance_squared(P1,P2);
}
void push_quad_safe( vertex_array_holder& core,
		const Point& P2, const Color& cc2, bool transparent2,
		const Point& P3, const Color& cc3, bool transparent3)
{
	//push 2 points to form a quad safely(without reflex)
	Point P0 = core.get_relative_end(-2);
	Point P1 = core.get_relative_end(-1);
	
	if ( !quad_is_reflexed(P0,P1,P2,P3))
	{
		core.push(P2,cc2,transparent2);
		core.push(P3,cc3,transparent3);
	}
	else
	{
		core.push(P3,cc3,transparent3);
		core.push(P2,cc2,transparent2);
	}
}*/

#define push_quad(A0,A1,A2,A3,A4,A5,A6,A7,A8) push_quad_(__LINE__,A0,A1,A2,A3,A4,A5,A6,A7,A8)
#define push_quadf(A0,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12) push_quadf_(__LINE__,A0,A1,A2,A3,A4,A5,A6,A7,A8,A9,A10,A11,A12)

void push_quad_( short line, vertex_array_holder& core,
		const Point& P0, const Point& P1, const Point& P2, const Point& P3,
		const Color& c0, const Color& c1, const Color& c2, const Color& c3)
{
	if( P0.is_zero()) DEBUG("pushed P0 (0,0) at %d\n",line);
	if( P1.is_zero()) DEBUG("pushed P1 (0,0) at %d\n",line);
	if( P2.is_zero()) DEBUG("pushed P2 (0,0) at %d\n",line);
	if( P3.is_zero()) DEBUG("pushed P3 (0,0) at %d\n",line);
	//interpret P0 to P3 as triangle strip
	core.push3( P0, P1, P2,
		c0, c1, c2);
	core.push3( P1, P2, P3,
		c1, c2, c3);
}
void push_quadf_( short line, vertex_array_holder& core,
		const Point& P0, const Point& P1, const Point& P2, const Point& P3,
		const Color& c0, const Color& c1, const Color& c2, const Color& c3,
		bool trans0, bool trans1, bool trans2, bool trans3)
{
	if( P0.is_zero()) DEBUG("pushed P0 (0,0) at %d\n",line);
	if( P1.is_zero()) DEBUG("pushed P1 (0,0) at %d\n",line);
	if( P2.is_zero()) DEBUG("pushed P2 (0,0) at %d\n",line);
	if( P3.is_zero()) DEBUG("pushed P3 (0,0) at %d\n",line);
	//interpret P0 to P3 as triangle strip
	core.push3( P0, P1, P2,
		c0, c1, c2,
		trans0, trans1, trans2);
	core.push3( P1, P2, P3,
		c1, c2, c3,
		trans1, trans2, trans3);
}

struct st_knife_cut
{
	Point T1[4]; //retained polygon, also serves as input triangle
	Color C1[4]; //
	
	Point T2[4]; //cut away polygon
	Color C2[4]; //
	
	int T1c, T2c; //count of T1 & T2
		//must be 0,3 or 4
};
int triangle_knife_cut( const Point& kn1, const Point& kn2, const Point& kn_out, //knife
			       const Color* kC0, const Color* kC1, //color of knife
		st_knife_cut& ST)//will modify for output
//see knife_cut_test for more info
{	//return number of points cut away
	int points_cut_away = 0;
	
	bool kn_colored = kC0 && kC1; //if true, use the colors of knife instead
	bool std_sign = Point::signed_area( kn1,kn2,kn_out) > 0;
	bool s1 = Point::signed_area( kn1,kn2,ST.T1[0])>0 == std_sign; //true means this point should be cut
	bool s2 = Point::signed_area( kn1,kn2,ST.T1[1])>0 == std_sign;
	bool s3 = Point::signed_area( kn1,kn2,ST.T1[2])>0 == std_sign;
	int sums = int(s1)+int(s2)+int(s3);
	
	if ( sums == 0)
	{	//all 3 points are retained
		ST.T1c = 3;
		ST.T2c = 0;
		
		points_cut_away = 0;
	}
	else if ( sums == 3)
	{	//all 3 are cut away
		ST.T1c = 0;
		ST.T2c = 3;
		
		ST.T2[0] = ST.T1[0];
		ST.T2[1] = ST.T1[1];
		ST.T2[2] = ST.T1[2];
			ST.C2[0] = ST.C1[0];
			ST.C2[1] = ST.C1[1];
			ST.C2[2] = ST.C1[2];
		
		points_cut_away = 3;
	}
	else
	{
		if ( sums == 2) {
			s1 = !s1; 
			s2 = !s2;
			s3 = !s3;
		}
		//
		Point ip1,ip2, outp;
		Color iC1,iC2, outC;
		if ( s1) { //here assume one point is cut away
				// thus only one of s1,s2,s3 is true
			outp= ST.T1[0];  outC= ST.C1[0];
			ip1 = ST.T1[1];  iC1 = ST.C1[1];
			ip2 = ST.T1[2];  iC2 = ST.C1[2];
		} else if ( s2) {
			outp= ST.T1[1];  outC= ST.C1[1];
			ip1 = ST.T1[0];  iC1 = ST.C1[0];
			ip2 = ST.T1[2];  iC2 = ST.C1[2];
		} else if ( s3) {
			outp= ST.T1[2];  outC= ST.C1[2];
			ip1 = ST.T1[0];  iC1 = ST.C1[0];
			ip2 = ST.T1[1];  iC2 = ST.C1[1];
		}
		
		Point interP1,interP2;
		Color interC1,interC2;
		double ble1,kne1, ble2,kne2;
		Point::intersect( kn1,kn2, ip1,outp, interP1, &kne1,&ble1);
		Point::intersect( kn1,kn2, ip2,outp, interP2, &kne2,&ble2);
		
		{	if ( kn_colored && Color_valid_range(kne1))
				interC1 = Color_between( *kC0, *kC1, kne1);
			else
				interC1 = Color_between( iC1, outC, ble1);
		}
		
		{	if ( kn_colored && Color_valid_range(kne2))
				interC2 = Color_between( *kC0, *kC1, kne2);
			else
				interC2 = Color_between( iC2, outC, ble2);
		}
		
		//ip2 first gives a polygon
		//ip1 first gives a triangle strip
		
		if ( sums == 1) {
			//one point is cut away
			ST.T1[0] = ip1;      ST.C1[0] = iC1;
			ST.T1[1] = ip2;      ST.C1[1] = iC2;
			ST.T1[2] = interP1;  ST.C1[2] = interC1;
			ST.T1[3] = interP2;  ST.C1[3] = interC2;
			ST.T1c = 4;
			
			ST.T2[0] = outp;     ST.C2[0] = outC;
			ST.T2[1] = interP1;  ST.C2[1] = interC1;
			ST.T2[2] = interP2;  ST.C2[2] = interC2;
			ST.T2c = 3;
			
			points_cut_away = 1;
		} else if ( sums == 2) {
			//two points are cut away
			ST.T2[0] = ip1;      ST.C2[0] = iC1;
			ST.T2[1] = ip2;      ST.C2[1] = iC2;
			ST.T2[2] = interP1;  ST.C2[2] = interC1;
			ST.T2[3] = interP2;  ST.C2[3] = interC2;
			ST.T2c = 4;
			
			ST.T1[0] = outp;     ST.C1[0] = outC;
			ST.T1[1] = interP1;  ST.C1[1] = interC1;
			ST.T1[2] = interP2;  ST.C1[2] = interC2;
			ST.T1c = 3;
			
			points_cut_away = 2;
		}
		
		/*if ( (0.0-vaser_min_alw < kne1 && kne1 < 1.0+vaser_min_alw) ||
		     (0.0-vaser_min_alw < kne2 && kne2 < 1.0+vaser_min_alw) )
		{	//highlight the wound
			glBegin(GL_LINE_STRIP);
				glColor3f(1,0,0);
				glVertex2f(ST.T1[0].x,ST.T1[0].y);
				glVertex2f(ST.T1[1].x,ST.T1[1].y);
				glVertex2f(ST.T1[2].x,ST.T1[2].y);
				glVertex2f(ST.T1[0].x,ST.T1[0].y);
			glEnd();
			
			if ( ST.T1c > 3)
			glBegin(GL_LINE_STRIP);
				glVertex2f(ST.T1[1].x,ST.T1[1].y);
				glVertex2f(ST.T1[2].x,ST.T1[2].y);
				glVertex2f(ST.T1[3].x,ST.T1[3].y);
				glVertex2f(ST.T1[1].x,ST.T1[1].y);
			glEnd();
		}*/
	}
	
	return points_cut_away;
}
void vah_knife_cut( vertex_array_holder& core, //serves as both input and output
		const Point& kn1, const Point& kn2, const Point& kn_out)
//perform knife cut on all triangles (GL_TRIANGLES) in core
{
	st_knife_cut ST;
	for ( int i=0; i<core.count; i+=3)
	{
		ST.T1[0] = core.get(i);
		ST.T1[1] = core.get(i+1);
		ST.T1[2] = core.get(i+2);
		ST.C1[0] = core.get_color(i);
		ST.C1[1] = core.get_color(i+1);
		ST.C1[2] = core.get_color(i+2);
		ST.T1c = 3; //will be ignored anyway
		
		int result = triangle_knife_cut( kn1,kn2,kn_out,0,0, ST);
		
		switch (result)
		{
		case 0:
			//do nothing
		break;
		
		case 3:	//degenerate the triangle
			core.move(i+1,i); //move i into i+1
			core.move(i+2,i);
		break;
		
		case 1:
		case 2:
			core.replace(i,  ST.T1[0],ST.C1[0]);
			core.replace(i+1,ST.T1[1],ST.C1[1]);
			core.replace(i+2,ST.T1[2],ST.C1[2]);
			
			if ( result==1)
			{	//create a new triangle
				Point dump_P;
				Color dump_C;
				int a1,a2,a3;
				a1 = core.push( dump_P, dump_C);
				a2 = core.push( dump_P, dump_C);
				a3 = core.push( dump_P, dump_C);
				
				//copy the original points
				core.move( a1, i+1);
				core.move( a2, i+2);
				core.move( a3, i+2);
				
				//make the new point
				core.replace( a3, ST.T1[3],ST.C1[3]);
			}
		break;
		
		}
	}
}
void vah_N_knife_cut( vertex_array_holder& in, vertex_array_holder& out,
		const Point* kn0, const Point* kn1, const Point* kn2,
		const Color* kC0, const Color* kC1,
		int N)
{	//an iterative implementation
	const int MAX_ST = 10;
	st_knife_cut ST[MAX_ST];
	
	bool kn_colored = kC0 && kC1;
	
	if ( N > MAX_ST)
	{
		DEBUG("vah_N_knife_cut: max N for current build is %d\n", MAX_ST);
		N = MAX_ST;
	}
	
	for ( int i=0; i<in.count; i+=3) //each input triangle
	{
		int ST_count = 1;
		ST[0].T1[0] = in.get(i);
		ST[0].T1[1] = in.get(i+1);
		ST[0].T1[2] = in.get(i+2);
			ST[0].C1[0] = in.get_color(i);
			ST[0].C1[1] = in.get_color(i+1);
			ST[0].C1[2] = in.get_color(i+2);
		ST[0].T1c = 3;
		
		for ( int k=0; k<N; k++) //each knife
		{
			int cur_count = ST_count;
			for ( int p=0; p<cur_count; p++) //each triangle to be cut
			{
				//perform cut
				if ( ST[p].T1c > 0)
					if ( kn_colored)
					triangle_knife_cut( kn0[k], kn1[k], kn2[k],
							   &kC0[k],&kC1[k],
								ST[p]);
					else
					triangle_knife_cut( kn0[k],kn1[k],kn2[k],
							    0,0,ST[p]);
				
				//push retaining part
				if ( ST[p].T1c > 0) {
					out.push( ST[p].T1[0], ST[p].C1[0]);
					out.push( ST[p].T1[1], ST[p].C1[1]);
					out.push( ST[p].T1[2], ST[p].C1[2]);
					if ( ST[p].T1c > 3) {
						out.push( ST[p].T1[1], ST[p].C1[1]);
						out.push( ST[p].T1[2], ST[p].C1[2]);
						out.push( ST[p].T1[3], ST[p].C1[3]);
					}
				}
				
				//store cut away part to be cut again
				if ( ST[p].T2c > 0)
				{
					ST[p].T1[0] = ST[p].T2[0];
					ST[p].T1[1] = ST[p].T2[1];
					ST[p].T1[2] = ST[p].T2[2];
						ST[p].C1[0] = ST[p].C2[0];
						ST[p].C1[1] = ST[p].C2[1];
						ST[p].C1[2] = ST[p].C2[2];
					ST[p].T1c = 3;
					
					if ( ST[p].T2c > 3)
					{
						ST[ST_count].T1[0] = ST[p].T2[1];
						ST[ST_count].T1[1] = ST[p].T2[2];
						ST[ST_count].T1[2] = ST[p].T2[3];
							ST[ST_count].C1[0] = ST[p].C2[1];
							ST[ST_count].C1[1] = ST[p].C2[2];
							ST[ST_count].C1[2] = ST[p].C2[3];
						ST[ST_count].T1c = 3;
						ST_count++;
					}
				}
				else
				{
					ST[p].T1c = 0;
				}
			}
		}
	}
}

const float cri_core_adapt = 0.0001f;
void anchor_late( const Vec2* P, const Color* C, st_polyline* SL,
		vertex_array_holder& tris,
		Point cap1, Point cap2)
{	const int size_of_P = 3;

	tris.set_gl_draw_mode(GL_TRIANGLES);
	
	Point P_0, P_1, P_2;
	P_0 = Point(P[0]);
	P_1 = Point(P[1]);
	P_2 = Point(P[2]);
	if ( SL[0].djoint==PLC_butt || SL[0].djoint==PLC_square)
		P_0 -= cap1;
	if ( SL[2].djoint==PLC_butt || SL[2].djoint==PLC_square)
		P_2 -= cap2;
	
	Point P0, P1, P2, P3, P4, P5, P6, P7;
	Point P0r,P1r,P2r,P3r,P4r,P5r,P6r,P7r; //fade
	
	P0 = P_1 + SL[1].vP;
		P0r = P0 + SL[1].vR;
	P1 = P_1 - SL[1].vP;
		P1r = P1 - SL[1].vR;
	
	P2 = P_1 + SL[1].T1;
		P2r = P2 + SL[1].R1 + SL[0].bR;
	P3 = P_0 + SL[0].T;
		P3r = P3 + SL[0].R;
	P4 = P_0 - SL[0].T;
		P4r = P4 - SL[0].R;
	
	P5 = P_1 + SL[1].T;
		P5r = P5 + SL[1].R - SL[1].bR;
	P6 = P_2 + SL[2].T;
		P6r = P6 + SL[2].R;
	P7 = P_2 - SL[2].T;
		P7r = P7 - SL[2].R;
		/* annotate( P0,C[0],0);
		annotate( P1);
		annotate( P2);
		annotate( P3);
		annotate( P4);
		annotate( P5);
		annotate( P6);
		annotate( P7); */

	int normal_line_core_joint = 1; //0:dont draw, 1:draw, 2:outer only

	//consider these as inline child functions
	#define normal_first_segment \
			tris.push3( P3,  P2,  P1, \
				  C[0],C[1],C[1]);\
			tris.push3( P1,  P3,  P4, \
				  C[1],C[0],C[0])
	
	#define normal_last_segment \
			tris.push3( P1,  P5,  P6, \
				  C[1],C[1],C[2]);\
			tris.push3( P1,  P6,  P7, \
				  C[1],C[2],C[2])
	
	Color Cpt; //color at PT
	if ( SL[1].degenT || SL[1].degenR)
	{
		float pt = sqrt(SL[1].pt);
		if ( SL[1].pre_full)
			Cpt = Color_between(C[0],C[1], pt);
		else
			Cpt = Color_between(C[1],C[2], 1-pt);
	}
	
	if ( SL[1].degenT)
	{	//degen line core
		P1 = SL[1].PT;
			if( SL[1].degenR)
				P1r = SL[1].PR;
		
		tris.push3( P3,  P2,  P1,
			  C[0],C[1],C[1]); //fir seg
		tris.push3( P1,  P5,  P6,
			  C[1],C[1],C[2]); //las seg
		
		if ( SL[1].pre_full)
		{	tris.push3( P1,  P3,  P4,
				  C[1],C[0],C[0]);
		}
		else
		{	tris.push3( P1,  P6,  P7,
				  C[1],C[2],C[2]);
		}
	}
	else if ( SL[1].degenR && SL[1].pt > cri_core_adapt) //&& ! SL[1].degenT
	{	//line core adapted for degenR
		if ( SL[1].pre_full)
		{
			normal_last_segment;

			//special first segment
			Point P9 = SL[1].PT;
			tris.push3( P3,  P2,  P1,
				  C[0],C[1],C[1]);
			tris.push3( P3,  P9,  P1,
				  C[0], Cpt,C[1]);
			tris.push3( P3,  P9,  P4,
				  C[0], Cpt,C[0]);
		}
		else
		{
			normal_first_segment;
			
			//special last segment
			Point P9 = SL[1].PT;
			push_quad( tris,
				  P5,  P1,  P6,  P9,
				C[1],C[1],C[2], Cpt);
			tris.push3( P7,  P9,  P6,
				  C[2], Cpt,C[2]);
				/*annotate(P1,C[1],1);
				annotate(P5,C[1],5);
				annotate(P6,C[1],6);
				annotate(P7,C[1],7);
				annotate(P9,C[1],9);*/
		}
	}
	else
	{
		normal_first_segment;
		normal_last_segment;
	#undef normal_first_segment
	#undef normal_last_segment
	}
	
	if (normal_line_core_joint)
	{
		switch( SL[1].djoint)
		{
			case PLJ_miter:
				tris.push3( P2,  P5,  P0,
					  C[1],C[1],C[1]);
			case PLJ_bevel:
				if ( normal_line_core_joint==1)
				tris.push3( P2,  P5,  P1,
					  C[1],C[1],C[1]);
			break;
			
			case PLJ_round: {
				vertex_array_holder strip;
				strip.set_gl_draw_mode(GL_TRIANGLE_STRIP);
				
			if ( normal_line_core_joint==1)
				vectors_to_arc( strip, P_1, C[1], C[1],
				SL[1].T1, SL[1].T,
				get_PLJ_round_dangle(SL[1].t,SL[1].r),
				SL[1].t, 0.0f, false, &P1);
			else if ( normal_line_core_joint==2)
				vectors_to_arc( strip, P_1, C[1], C[1],
				SL[1].T1, SL[1].T,
				get_PLJ_round_dangle(SL[1].t,SL[1].r),
				SL[1].t, 0.0f, false, &P5);
				
				tris.push(strip);
			} break;
		}
	}
	
	if ( SL[1].degenR)
	{	//degen inner fade
		Point P9 = SL[1].PT;
			Point P9r = SL[1].PR;
		//annotate(P9,C[0],9);
		//annotate(P9r);

		Color ccpt=Cpt;
		if ( SL[1].degenT)
			ccpt = C[1];
		
		if ( SL[1].pre_full)
		{	push_quadf( tris,
				    P9,  P4, P9r, P4r,
				  ccpt,C[0],C[1],C[0],
				     0,   0,   1,   1); //fir seg

			if ( !SL[1].degenT)
			{
				Point mid = Point::midpoint(P9,P7);
				tris.push3( P1,  P9, mid,
					  C[1], Cpt,C[1],
					     0,   0,   1);
				tris.push3( P1,  P7, mid,
					  C[1],C[2],C[1],
					     0,   0,   1);
			}
		}
		else
		{	push_quadf( tris,
				    P9,  P7, P9r, P7r,
				  ccpt,C[2],C[1],C[2],
				     0,   0,   1,   1); //las seg
			
			if ( !SL[1].degenT)
			{
				Point mid = Point::midpoint(P9,P4);
				tris.push3( P1,  P9, mid,
					  C[1], Cpt,C[1],
					     0,   0,   1);
				tris.push3( P1,  P4, mid,
					  C[1],C[0],C[1],
					     0,   0,   1);
			}
		}
	}
	else
	{	//normal inner fade
		push_quadf( tris,
			    P1,  P4, P1r, P4r,
			  C[1],C[0],C[1],C[0],
			     0,   0,   1,   1); //fir seg
		push_quadf( tris,
			    P1,  P7, P1r, P7r,
			  C[1],C[2],C[1],C[2],
			     0,   0,   1,   1); //las seg
	}
	
	{	//outer fade, whether degen or normal
		push_quadf( tris,
			    P2,  P3, P2r, P3r,
			  C[1],C[0],C[1],C[0],
			     0,   0,   1,   1); //fir seg
		push_quadf( tris,
			    P5,  P6, P5r, P6r,
			  C[1],C[2],C[1],C[2],
			     0,   0,   1,   1); //las seg
		switch( SL[1].djoint)
		{	//line fade joint
			case PLJ_miter:
				push_quadf( tris,
					    P0,  P5, P0r, P5r,
					  C[1],C[1],C[1],C[1],
					     0,   0,   1,   1);
				push_quadf( tris,
					    P0,  P2, P0r, P2r,
					  C[1],C[1],C[1],C[1],
					     0,   0,   1,   1);
			break;
			case PLJ_bevel:
				push_quadf( tris,
					    P2,  P5, P2r, P5r,
					  C[1],C[1],C[1],C[1],
					     0,   0,   1,   1);
			break;
			
			case PLJ_round: {
				vertex_array_holder strip;
				strip.set_gl_draw_mode(GL_TRIANGLE_STRIP);
				Color C2 = C[1]; C2.a = 0.0f;
				vectors_to_arc( strip, P_1, C[1], C2,
				SL[1].T1, SL[1].T,
				get_PLJ_round_dangle(SL[1].t,SL[1].r),
				SL[1].t, SL[1].t+SL[1].r, false, 0);
				
				tris.push(strip);
			} break;
		}
	}
} //anchor_late

void anchor_cap( const Vec2* P, const Color* C, st_polyline* SL,
		vertex_array_holder& tris,
		Point cap1, Point cap2)
{
	Point P4 = Point(P[0]) - SL[0].T;
	Point P7 = Point(P[2]) - SL[2].T;
	for ( int i=0,k=0; k<=1; i=2, k++)
	{
		vertex_array_holder cap;
		Point& cur_cap = i==0? cap1:cap2;
		if ( cur_cap.non_zero())
		{
			cap.set_gl_draw_mode(GL_TRIANGLES);
			bool perform_cut = ( SL[1].degenR && SL[1].R_full_degen) &&
					((k==0 && !SL[1].pre_full) ||
					 (k==1 &&  SL[1].pre_full) );
			
			Point P3 = Point(P[i])-SL[i].T*2-SL[i].R+cur_cap;
			
			if ( SL[i].djoint == PLC_round)
			{	//round caps
				{	vertex_array_holder strip;
					strip.set_gl_draw_mode(GL_TRIANGLE_STRIP);
					
					Color C2 = C[i]; C2.a = 0.0f;
					Point O  = Point(P[i]);
					Point app_P = O+SL[i].T;
					Point bR = SL[i].bR;
					bR.follow_signs(cur_cap);
					float dangle = get_PLJ_round_dangle(SL[i].t,SL[i].r);
					
					vectors_to_arc( strip, O, C[i], C[i],
					SL[i].T+bR, -SL[i].T+bR,
					dangle,
					SL[i].t, 0.0f, false, &app_P);
						strip.push( O-SL[i].T,C[i]);
						strip.push( app_P,C[i]);

					strip.jump();

					Point a1 = O+SL[i].T;
					Point a2 = O+SL[i].T*(1/SL[i].t)*(SL[i].t+SL[i].r);
					Point b1 = O-SL[i].T;
					Point b2 = O-SL[i].T*(1/SL[i].t)*(SL[i].t+SL[i].r);
					
						strip.push( a1,C[i]);
						strip.push( a2,C2);
					vectors_to_arc( strip, O, C[i], C2,
					SL[i].T+bR, -SL[i].T+bR,
					dangle,
					SL[i].t, SL[i].t+SL[i].r, false, 0);						
						strip.push( b1,C[i]);
						strip.push( b2,C2);
					cap.push(strip);
				}
				if ( perform_cut)
				{
					Point P4k;
					if ( !SL[1].pre_full)
						P4k = P7; //or P7r ?
					else
						P4k = P4;
					
					vah_knife_cut( cap, SL[1].PT, P4k, P3);
					/*annotate(SL[1].PT,C[i],0);
					annotate(P3,C[i],3);
					annotate(P4k,C[i],4);*/
				}
			}
			else //if ( SL[i].djoint == PLC_butt | SL[i].cap == PLC_square | SL[i].cap == PLC_rect)
			{	//rectangle caps
				Point P_cur = P[i];
				bool degen_nxt=0, degen_las=0;
				if ( k == 0)
					if ( SL[0].djoint==PLC_butt || SL[0].djoint==PLC_square)
						P_cur -= cap1;
				if ( k == 1)
					if ( SL[2].djoint==PLC_butt || SL[2].djoint==PLC_square)
						P_cur -= cap2;
				
				Point P0,P1,P2,P3,P4,P5,P6;
				
				P0 = P_cur+SL[i].T+SL[i].R;
				P1 = P0+cur_cap;
				P2 = P_cur+SL[i].T;
				P4 = P_cur-SL[i].T;
				P3 = P4-SL[i].R+cur_cap;
				P5 = P4-SL[i].R;
				
				cap.push( P0, C[i],true);
				cap.push( P1, C[i],true);
				cap.push( P2, C[i]);
				
						cap.push( P1, C[i],true);
					cap.push( P2, C[i]);
				cap.push( P3, C[i],true);
				
						cap.push( P2, C[i]);
					cap.push( P3, C[i],true);
				cap.push( P4, C[i]);
				
						cap.push( P3, C[i],true);
					cap.push( P4, C[i]);
				cap.push( P5, C[i],true);
				//say if you want to use triangle strip,
				//  just push P0~ P5 in sequence
				if ( perform_cut)
				{
					vah_knife_cut( cap, SL[1].PT, SL[1].PR, P3);
					/*annotate(SL[1].PT,C[i],0);
					annotate(SL[1].PR);
					annotate(P3);
					annotate(P4);*/
				}
			}
		}
		tris.push(cap);
	}
} //anchor_cap

void segment_late( const Vec2* P, const Color* C, st_polyline* SL,
		vertex_array_holder& tris,
		Point cap1, Point cap2)
{
	tris.set_gl_draw_mode(GL_TRIANGLES);
	
	Point P_0, P_1, P_2;
	P_0 = Point(P[0]);
	P_1 = Point(P[1]);
	if ( SL[0].djoint==PLC_butt || SL[0].djoint==PLC_square)
		P_0 -= cap1;
	if ( SL[1].djoint==PLC_butt || SL[1].djoint==PLC_square)
		P_1 -= cap2;
	
	Point P1, P2, P3, P4;  //core
	Point P1c,P2c,P3c,P4c; //cap
	Point P1r,P2r,P3r,P4r; //fade

	P1 = P_0 + SL[0].T;
		P1r = P1 + SL[0].R;
		P1c = P1r + cap1;
	P2 = P_0 - SL[0].T;
		P2r = P2 - SL[0].R;
		P2c = P2r + cap1;
	P3 = P_1 + SL[1].T;
		P3r = P3 + SL[1].R;
		P3c = P3r + cap2;
	P4 = P_1 - SL[1].T;
		P4r = P4 - SL[1].R;
		P4c = P4r + cap2;
	//core
	push_quad( tris,
		  P1,  P2,  P3,  P4,
		C[0],C[0],C[1],C[1] );
	//fade
	push_quadf( tris,
		  P1, P1r,  P3, P3r,
		C[0],C[0],C[1],C[1],
		   0,   1,   0,   1 );
	push_quadf( tris,
		  P2, P2r,  P4, P4r,
		C[0],C[0],C[1],C[1],
		   0,   1,   0,   1 );
	//caps
	for ( int j=0; j<2; j++)
	{
		vertex_array_holder cap;
		cap.set_gl_draw_mode(GL_TRIANGLE_STRIP);
		Point &cur_cap = j==0?cap1:cap2;
		if( cur_cap.is_zero())
			continue;
		
		if ( SL[j].djoint == PLC_round)
		{	//round cap
			Color C2 = C[j]; C2.a = 0.0f;
			Point O  = Point(P[j]);
			Point app_P = O+SL[j].T;
			Point bR = SL[j].bR;
			bR.follow_signs( j==0?cap1:cap2);
			float dangle = get_PLJ_round_dangle(SL[j].t,SL[j].r);
			
			vectors_to_arc( cap, O, C[j], C[j],
			SL[j].T+bR, -SL[j].T+bR,
			dangle,
			SL[j].t, 0.0f, false, &app_P);
				cap.push( O-SL[j].T,C[j]);
				cap.push( app_P,C[j]);
			
			cap.jump();
			
			{	//fade
				Point a1 = O+SL[j].T;
				Point a2 = O+SL[j].T*(1/SL[j].t)*(SL[j].t+SL[j].r);
				Point b1 = O-SL[j].T;
				Point b2 = O-SL[j].T*(1/SL[j].t)*(SL[j].t+SL[j].r);
				
					cap.push( a1,C[j]);
					cap.push( a2,C2);
				vectors_to_arc( cap, O, C[j], C2,
				SL[j].T+bR, -SL[j].T+bR,
				dangle,
				SL[j].t, SL[j].t+SL[j].r, false, 0);						
					cap.push( b1,C[j]);
					cap.push( b2,C2);
			}
		}
		else //if ( SL[j].djoint == PLC_butt | SL[j].cap == PLC_square | SL[j].cap == PLC_rect)
		{	//rectangle cap
			Point Pj,Pjr,Pjc, Pk,Pkr,Pkc;
			if ( j==0)
			{
				Pj = P1;
				Pjr= P1r;
				Pjc= P1c;
				
				Pk = P2;
				Pkr= P2r;
				Pkc= P2c;
			}
			else
			{
				Pj = P3;
				Pjr= P3r;
				Pjc= P3c;
				
				Pk = P4;
				Pkr= P4r;
				Pkc= P4c;
			}
			
			cap.push( Pkr, C[j], 1);
			cap.push( Pkc, C[j], 1);
			cap.push( Pk , C[j], 0);
			cap.push( Pjc, C[j], 1);
			cap.push( Pj , C[j], 0);
			cap.push( Pjr, C[j], 1);
		}
		tris.push(cap);
	}
	
	/*annotate(P1,C[0],1);
	annotate(P2,C[0],2);
	annotate(P3,C[0],3);
	annotate(P4,C[0],4);
		annotate(P1c,C[0],11);
		annotate(P2c,C[0],21);
		annotate(P3c,C[0],31);
		annotate(P4c,C[0],41);
		
		annotate(P1r,C[0],12);
		annotate(P2r,C[0],22);
		annotate(P3r,C[0],32);
		annotate(P4r,C[0],42);
	*/
}

void segment( st_anchor& SA, const polyline_opt* options,  bool cap_first, bool cap_last, char last_cap_type=-1)
{
	double* weight = SA.W;
	if ( !SA.P || !SA.C || !weight) return;
	
	Point P[2]; P[0]=SA.P[0]; P[1]=SA.P[1];
	Color C[2]; C[0]=SA.C[0]; C[1]=SA.C[1];
	
	polyline_opt opt={0};
	if ( options)
		opt = (*options);
	
	Point T1,T2;
	Point R1,R2;
	Point bR;
	double t,r;
	
	bool varying_weight = !(weight[0]==weight[1]);
	
	Point cap_start, cap_end;
	st_polyline SL[2];
	
	for ( int i=0; i<2; i++)
	{
		if ( weight[i]>=0.0 && weight[i]<1.0)
		{
			double f=weight[i]-static_cast<int>(weight[i]);
			C[i].a *=f;
		}
	}
	
	{	int i=0;
		make_T_R_C( P[i], P[i+1], &T2,&R2,&bR, weight[i],opt, &r,&t,0, true);
		
		if ( cap_first)
		{
			if ( opt.cap==PLC_square)
			{
				P[0] = Point(P[0]) - bR * (t+r);
			}
			cap_start = bR;
			cap_start.opposite(); if ( opt.feather && !opt.no_feather_at_cap)
			cap_start*=opt.feathering;
		}
		
		SL[i].djoint=opt.cap;
		SL[i].t=t;
		SL[i].r=r;
		SL[i].T=T2;
		SL[i].R=R2;
		SL[i].bR=bR*0.01;
		SL[i].degenT = false;
		SL[i].degenR = false;
	}
	
	{	int i=1;
		if ( varying_weight)
			make_T_R_C( P[i-1], P[i], &T2,&R2,&bR,weight[i],opt, &r,&t,0, true);
		
		last_cap_type = last_cap_type==-1 ? opt.cap:last_cap_type;
		
		if ( cap_last)
		{
			if ( last_cap_type==PLC_square)
			{
				P[1] = Point(P[1]) + bR * (t+r);
			}
			cap_end = bR;
			if ( opt.feather && !opt.no_feather_at_cap)
				cap_end*=opt.feathering;
		}
		
		SL[i].djoint = last_cap_type;
		SL[i].t=t;
		SL[i].r=r;
		SL[i].T=T2;
		SL[i].R=R2;
		SL[i].bR=bR*0.01;
		SL[i].degenT = false;
		SL[i].degenR = false;
	}
	
	segment_late( P,C,SL,SA.vah, cap_start,cap_end);
}

int anchor( st_anchor& SA, const polyline_opt* options, bool cap_first, bool cap_last)
{
	polyline_opt opt={0};
	if ( options)
		opt = (*options);
	
	Point* P = SA.P;
	Color* C = SA.C;
	double* weight = SA.W;

	{	st_polyline emptySL;
		SA.SL[0]=emptySL; SA.SL[1]=emptySL; SA.SL[2]=emptySL;
	}
	st_polyline* SL = SA.SL;
	SA.vah.set_gl_draw_mode(GL_TRIANGLES);
	SA.cap_start = Point();
	SA.cap_end = Point();
	
	//const double critical_angle=11.6538;
	//	critical angle in degrees where a miter is force into bevel
	//	it is _similar_ to cairo_set_miter_limit () but cairo works with ratio while VASEr works with included angle
	const double cos_cri_angle=0.979386; //cos(critical_angle)
	
	bool varying_weight = !(weight[0]==weight[1] & weight[1]==weight[2]);
	
	double combined_weight=weight[1]+(opt.feather?opt.feathering:0.0);
	if ( combined_weight < cri_segment_approx)
	{
		segment( SA, &opt, cap_first,false, opt.joint==PLJ_round?PLC_round:PLC_butt);
		char ori_cap = opt.cap;
		opt.cap = opt.joint==PLJ_round?PLC_round:PLC_butt;
		SA.P[0]=SA.P[1]; SA.P[1]=SA.P[2];
		SA.C[0]=SA.C[1]; SA.C[1]=SA.C[2];
		SA.W[0]=SA.W[1]; SA.W[1]=SA.W[2];
		segment( SA, &opt, false,cap_last, ori_cap);
		return 0;
	}
	
	Point T1,T2,T21,T31;		//]these are for calculations in early stage
	Point R1,R2,R21,R31;		//]
	
	for ( int i=0; i<3; i++)
	{	//lower the transparency for weight < 1.0
		if ( weight[i]>=0.0 && weight[i]<1.0)
		{
			double f=weight[i];
			C[i].a *=f;
		}
	}
	
	{	int i=0;
	
		Point cap1;
		double r,t;		
		make_T_R_C( P[i], P[i+1], &T2,&R2,&cap1, weight[i],opt, &r,&t,0);
		if ( varying_weight) {
		make_T_R_C( P[i], P[i+1], &T31,&R31,0, weight[i+1],opt, 0,0,0);
		} else {
			T31 = T2;
			R31 = R2;
		}
		Point::anchor_outward(R2, P[i+1],P[i+2] /*,inward_first->value()*/);
			T2.follow_signs(R2);
		
		SL[i].bR=cap1;
		
		if ( cap_first)
		{
			if ( opt.cap==PLC_square)
			{
				P[0] = Point(P[0]) - cap1 * (t+r);
			}
			cap1.opposite(); if ( opt.feather && !opt.no_feather_at_cap)
			cap1*=opt.feathering;
			SA.cap_start = cap1;
		}
		
		SL[i].djoint=opt.cap;
		SL[i].T=T2;
		SL[i].R=R2;
		SL[i].t=(float)t;
		SL[i].r=(float)r;
		SL[i].degenT = false;
		SL[i].degenR = false;
		
		SL[i+1].T1=T31;
		SL[i+1].R1=R31;
	}
	
	if ( cap_last)
	{	int i=2;

		Point cap2;
		double t,r;
		make_T_R_C( P[i-1],P[i], 0,0,&cap2,weight[i],opt, &r,&t,0);
		if ( opt.cap==PLC_square)
		{
			P[2] = Point(P[2]) + cap2 * (t+r);
		}
		
		SL[i].bR=cap2;
		
		if ( opt.feather && !opt.no_feather_at_cap)
			cap2*=opt.feathering;
		SA.cap_end = cap2;
	}
	
	{	int i=1;
	
		double r,t;
		Point P_cur = P[i]; //current point //to avoid calling constructor repeatedly
		Point P_nxt = P[i+1]; //next point
		Point P_las = P[i-1]; //last point
		if ( opt.cap==PLC_butt || opt.cap==PLC_square)
		{
			P_nxt -= SA.cap_end;
			P_las -= SA.cap_start;
		}
		
		{
		Point bR; float length_cur, length_nxt;
		make_T_R_C( P_las, P_cur,  &T1,&R1, 0, weight[i-1],opt,0,0, &length_cur);
		if ( varying_weight) {
		make_T_R_C( P_las, P_cur, &T21,&R21,0, weight[i],opt,  0,0,0);
		} else {
			T21 = T1;
			R21 = R1;
		}
		
		make_T_R_C( P_cur, P_nxt,  &T2,&R2,&bR, weight[i],opt, &r,&t, &length_nxt);
		if ( varying_weight) {
		make_T_R_C( P_cur, P_nxt, &T31,&R31,0, weight[i+1],opt, 0,0,0);
		} else {
			T31 = T2;
			R31 = R2;
		}
		
		SL[i].T=T2;
		SL[i].R=R2;
		SL[i].bR=bR;
		SL[i].t=(float)t;
		SL[i].r=(float)r;
		SL[i].degenT = false;
		SL[i].degenR = false;
		
		SL[i+1].T1=T31;
		SL[i+1].R1=R31;
		}
		
		{	//2nd to 2nd last point
			
			//find the angle between the 2 line segments
			Point ln1,ln2, V;
			ln1 = P_cur - P_las;
			ln2 = P_nxt - P_cur;
			ln1.normalize();
			ln2.normalize();
			Point::dot(ln1,ln2, V);
			double cos_tho=-V.x-V.y;
			bool zero_degree = Point::negligible(cos_tho-1);
			bool d180_degree = cos_tho < -1+0.0001;
			bool smaller_than_30_degree = cos_tho > 0.8660254;
			char result3 = 1;
			
			if ( (cos_tho < 0 && opt.joint==PLJ_bevel) ||
			     (opt.joint!=PLJ_bevel && opt.cap==PLC_round) ||
			     (opt.joint==PLJ_round)
			   )
			{	//when greater than 90 degrees
				SL[i-1].bR *= 0.01;
				SL[i]  .bR *= 0.01;
				SL[i+1].bR *= 0.01;
				//to solve an overdraw in bevel and round joint
			}
			
			Point::anchor_outward( T1, P_cur,P_nxt);
				R1.follow_signs(T1);
			Point::anchor_outward( T21, P_cur,P_nxt);
				R21.follow_signs(T21);
				SL[i].T1.follow_signs(T21);
				SL[i].R1.follow_signs(T21);
			Point::anchor_outward( T2, P_cur,P_las);
				R2.follow_signs(T2);
				SL[i].T.follow_signs(T2);
				SL[i].R.follow_signs(T2);
			Point::anchor_outward( T31, P_cur,P_las);
				R31.follow_signs(T31);
			
			{ //must do intersection
				Point interP, vP;
				result3 = Point::intersect( P_las+T1, P_cur+T21,
							P_nxt+T31, P_cur+T2,
							interP);
				
				if ( result3) {
					vP = interP - P_cur;
					SL[i].vP=vP;
					SL[i].vR=vP*(r/t);
				} else {
					SL[i].vP=SL[i].T;
					SL[i].vR=SL[i].R;
					DEBUG( "intersection failed: cos(angle)=%.4f, angle=%.4f(degree)\n", cos_tho, acos(cos_tho)*180/3.14159);
				}
			}
			
			T1.opposite();		//]inward
				R1.opposite();
			T21.opposite();
				R21.opposite();
			T2.opposite();
				R2.opposite();
			T31.opposite();
				R31.opposite();
			
			//make intersections
			Point PR1,PR2, PT1,PT2;
			double pt1,pt2;
			
			char result1r = Point::intersect( P_nxt-T31-R31, P_nxt+T31+R31,
						P_las+T1+R1, P_cur+T21+R21, //knife1
						PR1); //fade
			char result2r = Point::intersect( P_las-T1-R1, P_las+T1+R1,
						P_nxt+T31+R31, P_cur+T2+R2, //knife2
						PR2);
			bool is_result1r = result1r == 1;
			bool is_result2r = result2r == 1;
			//
			char result1t = Point::intersect( P_nxt-T31, P_nxt+T31,
						P_las+T1, P_cur+T21, //knife1_a
						PT1, 0,&pt1); //core
			char result2t = Point::intersect( P_las-T1, P_las+T1,
						P_nxt+T31, P_cur+T2, //knife2_a
						PT2, 0,&pt2);
			bool is_result1t = result1t == 1;
			bool is_result2t = result2t == 1;
			//
			bool inner_sec = Point::intersecting( P_las+T1+R1, P_cur+T21+R21,
						P_nxt+T31+R31, P_cur+T2+R2);
			//
			if ( zero_degree)
			{
				bool pre_full = is_result1t;
				opt.no_feather_at_cap=true;
				if ( pre_full)
				{
					segment( SA, &opt, true,cap_last, opt.joint==PLJ_round?PLC_round:PLC_butt);
				}
				else
				{
					char ori_cap = opt.cap;
					opt.cap = opt.joint==PLJ_round?PLC_round:PLC_butt;
					SA.P[0]=SA.P[1]; SA.P[1]=SA.P[2];
					SA.C[0]=SA.C[1]; SA.C[1]=SA.C[2];
					SA.W[0]=SA.W[1]; SA.W[1]=SA.W[2];
					segment( SA, &opt, true,cap_last, ori_cap);
				}
				return 0;
			}
			
			if ( (is_result1r | is_result2r) && !inner_sec)
			{	//fade degeneration
				SL[i].degenR=true;
				SL[i].PT = is_result1r? PT1:PT2; //this is is_result1r!!
				SL[i].PR = is_result1r? PR1:PR2;
				SL[i].pt = float(is_result1r? pt1:pt2);
					if ( SL[i].pt < 0)
						SL[i].pt = cri_core_adapt;
				SL[i].pre_full = is_result1r;
				SL[i].R_full_degen = false;
				
				Point P_nxt = P[i+1]; //override that in the parent scope
				Point P_las = P[i-1];
				Point PR;
				if ( opt.cap==PLC_rect || opt.cap==PLC_round)
				{
					P_nxt += SA.cap_end;
					P_las += SA.cap_start;
				}
				char result2;
				if ( is_result1r)
				{
					result2 = Point::intersect( P_nxt-T31-R31, P_nxt+T31,
						P_las+T1+R1, P_cur+T21+R21, //knife1
						PR); 	//fade
				}
				else
				{
					result2 = Point::intersect( P_las-T1-R1, P_las+T1,
						P_nxt+T31+R31, P_cur+T2+R2, //knife2
						PR);
				}
				if ( result2 == 1)
				{
					SL[i].R_full_degen = true;
					SL[i].PR = PR;
				}
			}
			
			if ( is_result1t | is_result2t)
			{	//core degeneration
				SL[i].degenT=true;
				SL[i].pre_full=is_result1t;
				SL[i].PT = is_result1t? PT1:PT2;
				SL[i].pt = float(is_result1t? pt1:pt2);
			}
			
			//make joint
			SL[i].djoint = opt.joint;
			if ( opt.joint == PLJ_miter)
				if ( cos_tho >= cos_cri_angle)
					SL[i].djoint=PLJ_bevel;
			
			/*if ( varying_weight && smaller_than_30_degree)
			{	//not sure why, but it appears to solve a visual bug for varing weight
				Point interR,vR;
				char result3 = Point::intersect( P_las-T1-R1, P_cur-T21-R21,
							P_nxt-T31-R31, P_cur-T2-R2,
							interR);
				SL[i].vR = P_cur-interR-SL[i].vP;
				annotate(interR,C[i],9);
				draw_vector(P_las-T1-R1, P_cur-T21-R21 - P_las+T1+R1,"1");
				draw_vector(P_nxt-T31-R31, P_cur-T2-R2 - P_nxt+T31+R31,"2");
			}*/
			
			if ( d180_degree | !result3)
			{	//to solve visual bugs 3 and 1.1
				//efficiency: if color and weight is same as previous and next point
				// ,do not generate vertices
				same_side_of_line( SL[i].R, SL[i-1].R, P_cur,P_las);
					SL[i].T.follow_signs(SL[i].R);
				SL[i].vP=SL[i].T;
				SL[i].T1.follow_signs(SL[i].T);
				SL[i].R1.follow_signs(SL[i].T);
				SL[i].vR=SL[i].R;
				SL[i].djoint=PLJ_miter;
			}
		} //2nd to 2nd last point
	}
	
	{	int i=2;

		double r,t;
		make_T_R_C( P[i-1],P[i], &T2,&R2,0,weight[i],opt,  &r,&t,0);
			same_side_of_line( R2, SL[i-1].R, P[i-1],P[i]);
				T2.follow_signs(R2);
		
		SL[i].djoint=opt.cap;
		SL[i].T=T2;
		SL[i].R=R2;
		SL[i].t=(float)t;
		SL[i].r=(float)r;
		SL[i].degenT = false;
		SL[i].degenR = false;
	}
	
	if( cap_first || cap_last)
		anchor_cap( SA.P,SA.C, SA.SL,SA.vah, SA.cap_start,SA.cap_end);
	anchor_late( SA.P,SA.C, SA.SL,SA.vah, SA.cap_start,SA.cap_end);
	return 1;
} //anchor

#ifdef VASE_RENDERER_EXPER
template <typename T>
class circular_array
{
	const int size;
	int cur; //current
	T* array;
public:
	circular_array(int size_) : size(size_)
	{
		array = new T[size];
		cur = 0;
	}
	
	~circular_array()
	{
		delete[] array;
	}
	
	void push( T obj)
	{
		array[cur] = obj;
		move(1);
	}
	
	int get_size() const
		{ return size;}
	
	int get_i( int i) const //get valid index relative to current
	{
		int des = cur + i%size;
		if ( des > size-1)
		{
			des -= size;
		}
		if ( des < 0)
		{
			des = size+i;
		}
		return des;
	}
	
	void move( int i) //move current relatively
	{
		cur = get_i(i);
	}
	
	T& operator[] (int i) //get element at relative position
	{
		return array[get_i(i)];
	}
};
#endif //VASE_RENDERER_EXPER

struct polyline_inopt
{
	bool const_color;
	bool const_weight;
	bool no_cap_first;
	bool no_cap_last;
	bool join_first;
	bool join_last;
	double* segment_length; //array of length of each segment
};

void poly_point_inter( const Point* P, const Color* C, const double* W, const polyline_inopt* inopt,
		Point& p, Color& c, double& w,
		int at, double t)
{
	#define color(I)  C[inopt&&inopt->const_color?0: I]
	#define weight(I) W[inopt&&inopt->const_weight?0: I]
	if( t==0.0)
	{
		p = P[at];
		c = color(at);
		w = weight(at);
	}
	else if( t==1.0)
	{
		p = P[at+1];
		c = color(at+1);
		w = weight(at+1);
	}
	else
	{
		p = (P[at]+P[at+1]) * t;
		c = Color_between(color(at),color(at+1), t);
		w = (weight(at)+weight(at+1)) * t;
	}
	#undef color
	#undef weight
}

void polyline_approx(
	const Vec2* points,
	const Color* C,
	const double* W,
	int length,
	const polyline_opt* opt,
	const polyline_inopt* inopt)
{
	const Point* P=(Point*)points;
	bool cap_first= inopt? !inopt->no_cap_first :true;
	bool cap_last=  inopt? !inopt->no_cap_last  :true;
	double* seg_len=inopt? inopt->segment_length: 0;

	st_anchor SA1,SA2;
	vertex_array_holder vcore;  //curve core
	vertex_array_holder vfadeo; //outer fade
	vertex_array_holder vfadei; //inner fade
	vcore.set_gl_draw_mode(GL_TRIANGLE_STRIP);
	vfadeo.set_gl_draw_mode(GL_TRIANGLE_STRIP);
	vfadei.set_gl_draw_mode(GL_TRIANGLE_STRIP);

	if( length<2)
		return;

	#define color(I)  C[inopt&&inopt->const_color?0: I]
	#define weight(I) W[inopt&&inopt->const_weight?0: I]

	for( int i=1; i<length-1; i++)
	{
		double t,r;
		determine_t_r(weight(i),t,r);
		if ( opt && opt->feather && !opt->no_feather_at_core)
			r*=opt->feathering;
		Point V=P[i]-P[i-1];
		V.perpen();
		V.normalize();
		Point F=V*r;
		V*=t;
		vcore.push( P[i]+V,  color(i));
		vcore.push( P[i]-V,  color(i));
		vfadeo.push( P[i]+V, color(i));
		vfadeo.push( P[i]+V+F, color(i), 1);
		vfadei.push( P[i]-V,   color(i));
		vfadei.push( P[i]-V-F, color(i), 1);
	}
	Point P_las,P_fir;
	Color C_las,C_fir;
	double W_las,W_fir;
	poly_point_inter( P,C,W,inopt, P_las,C_las,W_las, length-2, 0.5);
	{
		double t,r;
		determine_t_r(W_las,t,r);
		if ( opt && opt->feather && !opt->no_feather_at_core)
			r*=opt->feathering;
		Point V=P[length-1]-P[length-2];
		V.perpen();
		V.normalize();
		Point F=V*r;
		V*=t;
		vcore.push( P_las+V, C_las);
		vcore.push( P_las-V, C_las);
		vfadeo.push( P_las+V, C_las);
		vfadeo.push( P_las+V+F, C_las, 1);
		vfadei.push( P_las-V, C_las);
		vfadei.push( P_las-V-F, C_las, 1);
	}

	//first caps
	{
		poly_point_inter( P,C,W,inopt, P_fir,C_fir,W_fir, 0, inopt&&inopt->join_first? 0.5:0.0);
		SA1.P[0] = P_fir;
		SA1.P[1] = P[1];
		SA1.C[0] = C_fir;
		SA1.C[1] = color(1);
		SA1.W[0] = W_fir;
		SA1.W[1] = weight(1);
		segment( SA1, opt, cap_first,false);
	}
	//last cap
	if( !(inopt&&inopt->join_last))
	{
		SA2.P[0] = P_las;
		SA2.P[1] = P[length-1];
		SA2.C[0] = C_las;
		SA2.C[1] = color(length-1);
		SA2.W[0] = W_las;
		SA2.W[1] = weight(length-1);
		segment( SA2, opt, false,cap_last);
	}

	#undef color
	#undef weight

	if( opt && opt->tess && opt->tess->tessellate_only && opt->tess->holder)
	{
		vertex_array_holder& holder = *(vertex_array_holder*)opt->tess->holder;
		holder.push(vcore);
		holder.push(vfadeo);
		holder.push(vfadei);
		holder.push(SA1.vah);
		holder.push(SA2.vah);
	}
	else
	{
		vcore.draw();
		vfadeo.draw();
		vfadei.draw();
		SA1.vah.draw();
		SA2.vah.draw();
	}

	if ( opt && opt->tess && opt->tess->triangulation)
	{
		vcore.draw_triangles();
		vfadeo.draw_triangles();
		vfadei.draw_triangles();
		SA1.vah.draw_triangles();
		SA2.vah.draw_triangles();
	}
}

void polyline_exact(
	const Vec2* P,
	const Color* C,
	const double* W,
	int size_of_P,
	const polyline_opt* opt,
	const polyline_inopt* inopt)
{
	bool cap_first= inopt? !inopt->no_cap_first :true;
	bool cap_last=  inopt? !inopt->no_cap_last  :true;
	bool join_first = inopt && inopt->join_first;
	bool join_last =  inopt && inopt->join_last;

	#define color(I)  C[inopt&&inopt->const_color?0: I]
	#define weight(I) W[inopt&&inopt->const_weight?0: I]

	Point mid_l, mid_n; //the last and the next mid point
	Color c_l, c_n;
	double w_l, w_n;
	{	//init for the first anchor
		poly_point_inter( (Point*)P,C,W,inopt, mid_l,c_l,w_l, 0, join_first?0.5:0);
	}

	st_anchor SA;
	if ( size_of_P == 2)
	{
		SA.P[0] = P[0];
		SA.P[1] = P[1];
		SA.C[0] = color(0);
		SA.C[1] = color(1);
		SA.W[0] = weight(0);
		SA.W[1] = weight(1);
		segment( SA, opt, cap_first, cap_last);
	}
	else
	for ( int i=1; i<size_of_P-1; i++)
	{
		if ( i==size_of_P-2 && !join_last)
			poly_point_inter( (Point*)P,C,W,inopt, mid_n,c_n,w_n, i, 1.0);
		else
			poly_point_inter( (Point*)P,C,W,inopt, mid_n,c_n,w_n, i, 0.5);

		SA.P[0]=mid_l.vec(); SA.C[0]=c_l;  SA.W[0]=w_l;
		SA.P[2]=mid_n.vec(); SA.C[2]=c_n;  SA.W[2]=w_n;

		SA.P[1]=P[i];
		SA.C[1]=color(i);
		SA.W[1]=weight(i);

		anchor( SA, opt, i==1&&cap_first, i==size_of_P-2&&cap_last);

		mid_l = mid_n;
		c_l = c_n;
		w_l = w_n;
	}
	//draw or not
	if( opt && opt->tess && opt->tess->tessellate_only && opt->tess->holder)
		(*(vertex_array_holder*)opt->tess->holder).push(SA.vah);
	else
		SA.vah.draw();
	//draw triangles
	if( opt && opt->tess && opt->tess->triangulation)
		SA.vah.draw_triangles();

	#undef color
	#undef weight
}

void polyline_range(
	const Vec2* P,
	const Color* C,
	const double* W,
	int length,
	const polyline_opt* opt,
	const polyline_inopt* in_options,
	int from, int to,
	bool approx)
{
	polyline_inopt inopt={0};
	if( in_options) inopt=*in_options;
	if( from>0) from-=1;
	inopt.join_first = from!=0;
	inopt.join_last = to!=(length-1);
	inopt.no_cap_first = inopt.no_cap_first || inopt.join_first;
	inopt.no_cap_last = inopt.no_cap_last || inopt.join_last;

	if( approx)
		polyline_approx( P+from, C+(inopt.const_color?0:from), W+(inopt.const_weight?0:from), to-from+1, opt, &inopt);
	else
		polyline_exact ( P+from, C+(inopt.const_color?0:from), W+(inopt.const_weight?0:from), to-from+1, opt, &inopt);
}

void polyline(
	const Vec2* PP,  //pointer to array of point of a polyline
	const Color* C,  //array of color
	const double* W, //array of weight
	int length, //size of the buffer P
	const polyline_opt* options, //options
	const polyline_inopt* in_options) //internal options
{
	polyline_opt   opt={0};
	polyline_inopt inopt={0};
	if( options)    opt=*options;
	if( in_options) inopt=*in_options;

	/* if( opt.fallback)
	{
		backend::polyline(PP,C[0],W[0],length,0);
		return;
	} */

	if( opt.cap >= 10)
	{
		char dec=(opt.cap/10)*10;
		if( dec==PLC_first || dec==PLC_none)
			inopt.no_cap_last=true;
		if( dec==PLC_last || dec==PLC_none)
			inopt.no_cap_first=true;
		opt.cap -= dec;
	}

	if( inopt.const_weight && W[0] < cri_segment_approx)
	{
		polyline_exact(PP,C,W,length,&opt,&inopt);
		return;
	}

	const Point* P=(Point*)PP;
	int A=0,B=0;
	bool on=false;
	for( int i=1; i<length-1; i++)
	{
		Point V1=P[i]-P[i-1];
		Point V2=P[i+1]-P[i];
		double len=0.0;
		if( inopt.segment_length)
		{
			V1 /= inopt.segment_length[i];
			V2 /= inopt.segment_length[i+1];
			len += (inopt.segment_length[i]+inopt.segment_length[i+1])*0.5;
		}
		else
		{
			len += V1.normalize()*0.5;
			len += V2.normalize()*0.5;
		}
		double costho = V1.x*V2.x+V1.y*V2.y;
		//double angle = acos(costho)*180/vaser_pi;
		const double cos_a = cos(15*vaser_pi/180);
		const double cos_b = cos(10*vaser_pi/180);
		const double cos_c = cos(25*vaser_pi/180);
		double weight = W[inopt.const_weight?0:i];
		bool approx = false;
		if( (weight<7 && costho>cos_a) ||
			(costho>cos_b) || //when the angle difference at an anchor is smaller than a critical degree, do polyline approximation
			(len<weight && costho>cos_c) ) //when vector length is smaller than weight, do approximation
			approx = true;
		if( approx && !on)
		{
			A=i; if( A==1) A=0;
			on=true;
			if( A>1)
				polyline_range(PP,C,W,length,&opt,&inopt,B,A,false);
		}
		else if( !approx && on)
		{
			B=i;
			on=false;
			polyline_range(PP,C,W,length,&opt,&inopt,A,B,true);
		}
	}
	if( on && B<length-1)
	{
		B=length-1;
		polyline_range(PP,C,W,length,&opt,&inopt,A,B,true);
	}
	else if( !on && A<length-1)
	{
		A=length-1;
		polyline_range(PP,C,W,length,&opt,&inopt,B,A,false);
	}
}

#undef push_quad
#undef push_quadf

} //sub namespace VASErin

//export implementations

void polyline( const Vec2* P, const Color* C, const double* W, int length, const polyline_opt* opt)
{
	VASErin::polyline(P,C,W,length,opt,0);
}
void polyline( const Vec2* P, Color C, double W, int length, const polyline_opt* opt) //constant color and weight
{
	VASErin::polyline_inopt inopt={0};
	inopt.const_color=true;
	inopt.const_weight=true;
	VASErin::polyline(P,&C,&W,length,opt,&inopt);
}
void polyline( const Vec2* P, const Color* C, double W, int length, const polyline_opt* opt) //constant weight
{
	VASErin::polyline_inopt inopt={0};
	inopt.const_weight=true;
	VASErin::polyline(P,C,&W,length,opt,&inopt);
}
void polyline( const Vec2* P, Color C, const double* W, int length, const polyline_opt* opt) //constant color
{
	VASErin::polyline_inopt inopt={0};
	inopt.const_color=true;
	VASErin::polyline(P,&C,W,length,opt,&inopt);
}

void segment(  Vec2 P1, Vec2 P2, Color C1, Color C2, double W1, double W2, const polyline_opt* options)
{
	Vec2   AP[2];
	Color  AC[2];
	double AW[2];
		AP[0] = P1; AC[0] = C1; AW[0] = W1;
		AP[1] = P2; AC[1] = C2; AW[1] = W2;
	polyline( AP, AC, AW, 2, options);
}
void segment(  Vec2 P1, Vec2 P2, Color C, double W, const polyline_opt* options) //constant color and weight
{
	Vec2   AP[2];
		AP[0] = P1;
		AP[1] = P2;
	polyline( AP, C, W, 2, options);
}
void segment(  Vec2 P1, Vec2 P2, Color C1, Color C2, double W, const polyline_opt* options) //constant weight
{
	Vec2   AP[2];
	Color  AC[2];
	double AW[2];
		AP[0] = P1; AC[0] = C1; AW[0] = W;
		AP[1] = P2; AC[1] = C2; AW[1] = W;
	polyline( AP, AC, AW, 2, options);
}
void segment(  Vec2 P1, Vec2 P2, Color C, double W1, double W2, const polyline_opt* options) //const color
{
	Vec2   AP[2];
	Color  AC[2];
	double AW[2];
		AP[0] = P1; AC[0] = C; AW[0] = W1;
		AP[1] = P2; AC[1] = C; AW[1] = W2;
	polyline( AP, AC, AW, 2, options);
}


================================================
FILE: cpp/vaser/vaser.cpp
================================================
#ifndef VASER_CPP
#define VASER_CPP

#include "vaser.h"

#ifdef VASER_DEBUG
	#define DEBUG printf
#else
	#define DEBUG ;//
#endif

#include <math.h>
#include <vector>
#include <stdlib.h>

namespace VASEr
{
namespace VASErin
{	//VASEr internal namespace
const double vaser_min_alw=0.00000000001; //smallest value not regarded as zero
const Color default_color = {0,0,0,1};
const double default_weight = 1.0;
#include "point.h"
#include "color.h"
class vertex_array_holder;
#include "backend.h"
#include "vertex_array_holder.h"
#include "agg_curve4.cpp"
}
#include "opengl.cpp"
#include "polyline.cpp"
#include "gradient.cpp"
#include "curve.cpp"

} //namespace VASEr

#undef DEBUG
#endif


================================================
FILE: cpp/vaser/vaser.h
================================================
#ifndef VASER_H
#define VASER_H
/* Vase Renderer first draft, version 0.3 */

/* Basic usage
* You should provide these structs before any vase_renderer include, using
struct Vec2 { double x,y;};
struct Color { float r,g,b,a;};
* or
typedef your_vec2 Vec2;
typedef your_color Color;
*/

namespace VASEr
{
const double vaser_pi=3.141592653589793;

struct gradient_stop
{
	double t; //position
	char type; //GS_xx
	union
	{
		Color color;
		double weight;
	};
};
	const char GS_none  =0;
	const char GS_rgba  =1;
	const char GS_rgb   =2; //rgb only
	const char GS_alpha =3;
	const char GS_weight=4;
struct gradient
{
	gradient_stop* stops; //array must be sorted in ascending order of t
	int length; //number of stops
	char unit; //use_GD_XX
};
	const char GD_ratio =0; //default
	const char GD_length=1;

struct Image
{
	unsigned char* buf; //must **free** buffer manually
	short width;
	short height;
};

class renderer
{
public:
	static void init();
	static void before();
	static void after();
	static Image get_image();
};

struct tessellator_opt
{
	//set the whole structure to 0 will give default options
	bool triangulation;
	char parts; //use TS_xx
	bool tessellate_only;
	void* holder; //used as (VASErin::vertex_array_holder*) if tessellate_only is true
};
	//for tessellator_opt.parts
	const char TS_core_fade =0; //default
	const char TS_core      =1;
	const char TS_outer_fade=2;
	const char TS_inner_fade=3;

struct polyline_opt
{
	//set the whole structure to 0 will give default options
	const tessellator_opt* tess;
	char joint; //use PLJ_xx
	char cap;   //use PLC_xx
	bool feather;
		double feathering;
		bool no_feather_at_cap;
		bool no_feather_at_core;
};
	//for polyline_opt.joint
	const char PLJ_miter =0; //default
	const char PLJ_bevel =1;
	const char PLJ_round =2;
	//for polyline_opt.cap
	const char PLC_butt  =0; //default
	const char PLC_round =1;
	const char PLC_square=2;
	const char PLC_rect  =3;
	const char PLC_both  =0; //default
	const char PLC_first =10;
	const char PLC_last  =20;
	const char PLC_none  =30;

void polyline( const Vec2*, const Color*, const double*, int length, const polyline_opt*);
void polyline( const Vec2*, Color, double W, int length, const polyline_opt*); //constant color and weight
void polyline( const Vec2*, const Color*, double W, int length, const polyline_opt*); //constant weight
void polyline( const Vec2*, Color, const double* W, int length, const polyline_opt*); //constant color
void segment( Vec2, Vec2, Color, Color, double W1, double W2, const polyline_opt*);
void segment( Vec2, Vec2, Color, double W, const polyline_opt*); //constant color and weight
void segment( Vec2, Vec2, Color, Color, double W, const polyline_opt*); //constant weight
void segment( Vec2, Vec2, Color, double W1, double W2, const polyline_opt*); //const color

struct polybezier_opt
{
	//set the whole structure to 0 will give default options
	const polyline_opt* poly;
};

void polybezier( const Vec2*, const gradient*, int length, const polybezier_opt*);
void polybezier( const Vec2*, Color, double W, int length, const polybezier_opt*);

} //namespace VASEr

#endif


================================================
FILE: cpp/vaser/vertex_array_holder.h
================================================
#ifndef VASER_VERTEX_ARRAY_HOLDER_H
#define VASER_VERTEX_ARRAY_HOLDER_H

class vertex_array_holder
{
public:
	int count; //counter
	int glmode; //drawing mode in opengl
	bool jumping;
	std::vector<float> vert; //because it holds 2d vectors
	std::vector<float> color; //RGBA

	vertex_array_holder()
	{
		count = 0;
		glmode = GL_TRIANGLES;
		jumping = false;
	}
	
	void set_gl_draw_mode( int gl_draw_mode)
	{
		glmode = gl_draw_mode;
	}
	
	void clear()
	{
		count = 0;
	}
	void move( int a, int b) //move b into a
	{
		vert[a*2]   = vert[b*2];
		vert[a*2+1] = vert[b*2+1];
		
		color[a*4]  = color[b*4];
		color[a*4+1]= color[b*4+1];
		color[a*4+2]= color[b*4+2];
		color[a*4+3]= color[b*4+3];
	}
	void replace( int a, Point P, Color C)
	{
		vert[a*2]   = P.x;
		vert[a*2+1] = P.y;
		
		color[a*4]  = C.r;
		color[a*4+1]= C.g;
		color[a*4+2]= C.b;
		color[a*4+3]= C.a;
	}
	
	/* int draw_and_flush()
	{
		int& i = count;
		draw();
		switch( glmode)
		{
			case GL_POINTS:
				i=0;
			break;
			
			case GL_LINES:
				if ( i%2 == 0) {
					i=0;
				} else {
					goto copy_the_last_point;
				}
			break;
			
			case GL_TRIANGLES:
				if ( i%3 == 0) {
					i=0;
				} else if ( i%3 == 1) {
					goto copy_the_last_point;
				} else {
					goto copy_the_last_2_points;
				}
			break;
			
			case GL_LINE_STRIP: case GL_LINE_LOOP:
			//for line loop it is not correct
			copy_the_last_point:
				move(0,MAX_VERT-1);
				i=1;
			break;
			
			case GL_TRIANGLE_STRIP:
			copy_the_last_2_points:
				move(0,MAX_VERT-2);
				move(1,MAX_VERT-1);
				i=2;
			break;
			
			case GL_TRIANGLE_FAN:
				//retain the first point,
				// and copy the last point
				move(1,MAX_VERT-1);
				i=2;
			break;
			
			case GL_QUAD_STRIP:
			case GL_QUADS:
			case GL_POLYGON:
			//let it be and I cannot help
				i=0;
			break;
		}
		if ( i == MAX_VERT) //as a double check
			i=0;
	}*/
	
	int push( const Point& P, const Color& cc, bool trans=false)
	{
		int cur = count;
		vert.push_back(P.x);
		vert.push_back(P.y);
		color.push_back(cc.r);
		color.push_back(cc.g);
		color.push_back(cc.b);
		color.push_back(trans?0.0f:cc.a);

		count++;
		if ( jumping)
		{
			jumping=false;
			repeat_last_push();
		}
		return cur;
	}
	
	void push3( const Point& P1, const Point& P2, const Point& P3,
			const Color& C1, const Color& C2, const Color& C3,
			bool trans1=0, bool trans2=0, bool trans3=0)
	{
		push( P1,C1,trans1);
		push( P2,C2,trans2);
		push( P3,C3,trans3);
	}
	
	void push( const vertex_array_holder& hold)
	{
		if ( glmode == hold.glmode)
		{
			count += hold.count;
			vert.insert(vert.end(), hold.vert.begin(), hold.vert.end());
			color.insert(color.end(), hold.color.begin(), hold.color.end());
		}
		else if ( glmode == GL_TRIANGLES &&
			hold.glmode == GL_TRIANGLE_STRIP)
		{		
			int& a = count;
			for (int b=2; b < hold.count; b++)
			{
				for ( int k=0; k<3; k++,a++)
				{
					int B = b-2 + k;
					vert.push_back(hold.vert[B*2]);
					vert.push_back(hold.vert[B*2+1]);
					color.push_back(hold.color[B*4]);
					color.push_back(hold.color[B*4+1]);
					color.push_back(hold.color[B*4+2]);
					color.push_back(hold.color[B*4+3]);
				}
			}
		}
		else
		{
			DEBUG( "vertex_array_holder:push: unknown type\n");
		}
	}
	
	Point get(int i)
	{
		Point P;
		P.x = vert[i*2];
		P.y = vert[i*2+1];
		return P;
	}
	Color get_color(int b)
	{
		Color C;
		C.r = color[b*4];
		C.g = color[b*4+1];
		C.b = color[b*4+2];
		C.a = color[b*4+3];
		return C;
	}
	Point get_relative_end(int di=-1)
	{	//di=-1 is the last one
		int i = count+di;
		if ( i<0) i=0;
		if ( i>=count) i=count-1;
		return get(i);
	}
	void repeat_last_push()
	{
		Point P;
		Color cc;
		
		int i = count-1;
		
		P.x = vert[i*2];
		P.y = vert[i*2+1];
		cc.r = color[i*4];
		cc.g = color[i*4+1];
		cc.b = color[i*4+2];
		cc.a = color[i*4+3];
		
		push(P,cc);
	}
	void jump() //to make a jump in triangle strip by degenerated triangles
	{
		if ( glmode == GL_TRIANGLE_STRIP)
		{
			repeat_last_push();
			jumping=true;
		}
	}
	void draw()
	{
		backend::vah_draw(*this);
	}
	void draw_triangles()
	{
		Color col={1 , 0, 0, 0.5};
		if ( glmode == GL_TRIANGLES)
		{
			for ( int i=0; i<count; i++)
			{
				Point P[4];
				P[0] = get(i); i++;
				P[1] = get(i); i++;
				P[2] = get(i);
				P[3] = P[0];
				polyline((Vec2*)P,col,1.0,4,0);
			}
		}
		else if ( glmode == GL_TRIANGLE_STRIP)
		{
			for ( int i=2; i<count; i++)
			{
				Point P[3];
				P[0] = get(i-2);
				P[1] = get(i);
				P[2] = get(i-1);
				polyline((Vec2*)P,col,1.0,3,0);
			}
		}
	}
	void swap(vertex_array_holder& B)
	{
		int hold_count=count;
		int hold_glmode=glmode;
		bool hold_jumping=jumping;
		count = B.count;
		glmode = B.glmode;
		jumping = B.jumping;
		B.count = hold_count;
		B.glmode = hold_glmode;
		B.jumping = hold_jumping;
		vert.swap(B.vert);
		color.swap(B.color);
	}
};

#endif


================================================
FILE: cpp/workbench/base.html
================================================
<body>
<script src='raphael-min.js'></script>
<script src='dragger.js'></script>
<script src='drawer.js'></script>
<link rel="stylesheet" type="text/css" href="layout.css" />
<div class='side_pane'>
	This is description.
</div>

<script>
//an application contains:
//-TP (drawer)
//-DG (dragger)
//-canvas1 (html element)
//-rl (Raphael canvas)
var app_count=0;

function demo_application1()
{
	var i=this.i=app_count;
	var parent=this;
	app_count++;
	document.write("<div class='main_canvas' id='canvas"+i+"'></div>");
	
	this.TP = new drawer();
	this.DG = new dragger(this.TP,'canvas'+i);
	this.rl = Raphael('canvas'+i);
	//
	this.TP.px=[30,30,200,200];
	this.TP.py=[100,200,100,200];
	
	this.TP.redraw=function()
	{
		var rl=parent.rl;
		var TP=parent.TP;
		rl.clear();
		rl.rect(0,0,800-1,600-1);
		
		//drawings,,,
		rl.text(120,80,"I am application "+parent.i+"!");
		for ( var i=0; i<4; i++)
		{
			TP.drawpoint(TP.px[i],TP.py[i], 4,0);
		}
		
		TP.drawpath(rl);
	}
	this.TP.redraw();
}

app1 = new demo_application1();
//app2 = new demo_application1();
</script>
</body>


================================================
FILE: cpp/workbench/dragger.js
================================================
function dragger(TP,div_id)
{
	this.drag=-1;
	this.TP=TP;

	this.onMouseDown = function(e)
	{
		if (e.token=='in') {
			this.parent=e.parent;
		} else {
			var T=this.parent;
			var TP=T.TP;

			e=e?e:event;
			T.xmouse=e.clientX-T.canx+document.body.scrollLeft;
			T.ymouse=e.clientY-T.cany+document.body.scrollTop;
			T.lastxmouse=T.xmouse;
			T.lastymouse=T.ymouse;
			//
			for ( var i=0; i<TP.px.length; i++)
			{
				if (Math.abs(T.xmouse - TP.px[i]) < 12 && Math.abs(T.ymouse - TP.py[i]) < 12)
				{
					T.drag = i;
					return 1;
				}
			}
			T.drag = -1;
		}
	}

	this.onMouseUp = function(e)
	{
		if (e.token=='in') {
			this.parent=e.parent;
		} else {
			var T=this.parent;
			var TP=T.TP;

			T.drag=-1;
		}
	}
	this.onMouseMove = function(e)
	{
		if (e.token=='in') {
			this.parent=e.parent;
		} else {
			var T=this.parent;
			var TP=T.TP;

			e=e?e:event;
			T.xmouse=e.clientX-T.canx+document.body.scrollLeft;
			T.ymouse=e.clientY-T.cany+document.body.scrollTop;
			if ( T.drag!=-1)
			{
				var i = T.drag;
				TP.px[i] = T.xmouse;
				TP.py[i] = T.ymouse;
				TP.redraw();
			}
			T.lastxmouse=T.xmouse;
			T.lastymouse=T.ymouse;
		}
	}
	this.printpoints = function(TP)
	{
		xstr = "this.TP.px=[";
		ystr = "this.TP.py=[";
		for ( var i=0; i<TP.px.length; i++)
		{
			if ( i != 0)
			{
				xstr+=",";
				ystr+=",";
			}
			xstr += TP.px[i];
			ystr += TP.py[i];
		}
		xstr += "];";
		ystr += "];";
		alert(xstr+"\n"+ystr);
	}

	tardiv = document.getElementById(div_id);
	
	function getPositionLeft(This){
		var el=This;var pL=0;
		while(el){pL+=el.offsetLeft;el=el.offsetParent;}
		return pL
	}
	function getPositionTop(This){
		var el=This;var pT=0;
		while(el){pT+=el.offsetTop;el=el.offsetParent;}
		return pT
	}
	this.canx=getPositionLeft(tardiv);
	this.cany=getPositionTop(tardiv);
	
	tardiv.onmousedown	=this.onMouseDown;
	tardiv.onmouseup	=this.onMouseUp;
	tardiv.onmousemove	=this.onMouseMove;

	tardiv.onmousedown({token:'in', parent:this});
	tardiv.onmouseup({token:'in', parent:this});
	tardiv.onmousemove({token:'in', parent:this});
}


================================================
FILE: cpp/workbench/drawer.js
================================================
function drawer()
{
	var T=this;
	T.canx=0; T.cany=0;
	T.linestring=["","","","",""];
	T.linecolor=["#000","#F00","#0F0","#00B","#AAA"];
	T.moveTo=function( x,y,C)
	{
		if ( C==null) {C=4;}
		T.linestring[C] += " M "+x+","+y;
	}
	T.lineTo=function( x,y,C)
	{
		if ( C==null) {C=4;}
		T.linestring[C] += " L "+x+","+y;
	}
	T.drawpoint=function(x,y,s,C)
	{
		if (!s) {s=2;}
		if (C==null) {C=0;}
		T.moveTo(x-s,y-s,C);
		T.lineTo(x+s,y+s,C);
		T.moveTo(x-s,y+s,C);
		T.lineTo(x+s,y-s,C);
		T.moveTo(x,y,C);
	}
	T.drawarrow=function( ax,ay,bx,by, size,C)
	{
		if ( size != null)
		{
			var vx=ay-by;
			var vy=bx-ax;
			var nsize=Math.sqrt(vx*vx+vy*vy);
			var cx=vy*size/nsize;
			var cy=vx*size/nsize;
			var bx=ax+cx;
			var by=ay-cy;
		}
		T.moveTo(ax,ay, C);
		T.lineTo(bx,by, C);
		//
		var R=0.9; var K=0.04;
		var vx=ay-by;
		var vy=bx-ax;
		var acx=vy*R+ax;
		var acy=-vx*R+ay;
		//
		T.moveTo(acx+vx*K,acy+vy*K, C)
		T.lineTo(bx,by, C);
		T.lineTo(acx-vx*K,acy-vy*K, C);
	}
	T.drawpath=function(rl)
	{
		for ( var i=0; i<5; i++)
			rl.path( T.linestring[i]).attr( {stroke: T.linecolor[i], "stroke-width": 0.5});
		T.linestring=["","","","",""];
	}
	T.fillpath=function(rl,i)
	{
		rl.path( T.linestring[i]).attr( {fill: T.linecolor[i]});
		T.linestring[i]="";
	}
}


================================================
FILE: cpp/workbench/gradient_along_path.html
================================================
<!DOCTYPE html>
<meta charset="utf-8">
<body>
<script src="http://d3js.org/d3.v3.min.js"></script>
<script>
//code comes from https://gist.github.com/mbostock/4163057

var points = [
  [86, 388],
  [788, 40],
  [805, 447],
  [93, 72]
];
 
var width = 960,
    height = 500;
 
var color = d3.interpolateLab("#008000", "#c83a22");
 
var svg = d3.select("body").append("svg")
    .attr("width", width)
    .attr("height", height);
 
var line = d3.svg.line()
    .interpolate("basis");
 
svg.selectAll("path")
    .data(quad(sample(line(points), 8)))
  .enter().append("path")
    .style("fill", function(d) { return color(d.t); })
    .style("stroke", function(d) { return color(d.t); })
    .attr("d", function(d) { return lineJoin(d[0], d[1], d[2], d[3], 32); });
 
// Sample the SVG path string "d" uniformly with the specified precision.
function sample(d, precision) {
  var path = document.createElementNS(d3.ns.prefix.svg, "path");
  path.setAttribute("d", d);
 
  var n = path.getTotalLength(), t = [0], i = 0, dt = precision;
  while ((i += dt) < n) t.push(i);
  t.push(n);
 
  return t.map(function(t) {
    var p = path.getPointAtLength(t), a = [p.x, p.y];
    a.t = t / n;
    return a;
  });
}
 
// Compute quads of adjacent points [p0, p1, p2, p3].
function quad(points) {
  return d3.range(points.length - 1).map(function(i) {
    var a = [points[i - 1], points[i], points[i + 1], points[i + 2]];
    a.t = (points[i].t + points[i + 1].t) / 2;
    return a;
  });
}
 
// Compute stroke outline for segment p12.
function lineJoin(p0, p1, p2, p3, width) {
  var u12 = perp(p1, p2),
      r = width / 2,
      a = [p1[0] + u12[0] * r, p1[1] + u12[1] * r],
      b = [p2[0] + u12[0] * r, p2[1] + u12[1] * r],
      c = [p2[0] - u12[0] * r, p2[1] - u12[1] * r],
      d = [p1[0] - u12[0] * r, p1[1] - u12[1] * r];
 
  if (p0) { // clip ad and dc using average of u01 and u12
    var u01 = perp(p0, p1), e = [p1[0] + u01[0] + u12[0], p1[1] + u01[1] + u12[1]];
    a = lineIntersect(p1, e, a, b);
    d = lineIntersect(p1, e, d, c);
  }
 
  if (p3) { // clip ab and dc using average of u12 and u23
    var u23 = perp(p2, p3), e = [p2[0] + u23[0] + u12[0], p2[1] + u23[1] + u12[1]];
    b = lineIntersect(p2, e, a, b);
    c = lineIntersect(p2, e, d, c);
  }
 
  return "M" + a + "L" + b + " " + c + " " + d + "Z";
}
 
// Compute intersection of two infinite lines ab and cd.
function lineIntersect(a, b, c, d) {
  var x1 = c[0], x3 = a[0], x21 = d[0] - x1, x43 = b[0] - x3,
      y1 = c[1], y3 = a[1], y21 = d[1] - y1, y43 = b[1] - y3,
      ua = (x43 * (y1 - y3) - y43 * (x1 - x3)) / (y43 * x21 - x43 * y21);
  return [x1 + ua * x21, y1 + ua * y21];
}
 
// Compute unit vector perpendicular to p01.
function perp(p0, p1) {
  var u01x = p0[1] - p1[1], u01y = p1[0] - p0[0],
      u01d = Math.sqrt(u01x * u01x + u01y * u01y);
  return [u01x / u01d, u01y / u01d];
}
 
</script>


================================================
FILE: cpp/workbench/knife_cut_test.cpp
================================================
/*config.h is generated by fltk in your system
 * this file is used with fltk 1.3 with gl enabled.
 * compile by: fltk-config --use-gl --compile knife_cut_test.cpp
 * or something like: g++ -lX11 -lGL 'test1_drag.cpp' -o 'test1_drag'
*/
#include <math.h>
#include <stdio.h>

#include "../samples/config.h" //config.h must always be placed before any Fl header
#include <FL/gl.h>
#include <FL/Fl_Box.H>
#include <FL/Fl_Value_Slider.H>
#include <FL/Fl_Window.H>
#include <FL/Fl_Radio_Light_Button.H>

namespace VASEr
{
struct Vec2 { double x,y;};
struct Color { float r,g,b,a;};
}
#include "../vaser/vaser.cpp"
using namespace VASEr;

void test_draw();
#include "../samples/test1_base.cpp"

const int buf_size=20;

Vec2 AP[buf_size];
Color AC[buf_size];
int size_of_AP = 0;

Fl_Window* main_wnd;
Gl_Window* gl_wnd;
Fl_Box* text;
Fl_Button *nk1, *nk2, *nk3;

using namespace VASEr;
using namespace VASErin;

//application
void line_update()
{
	Color cc[3];
	{ Color col={1 , 0, 0, 1}; cc[0]=col;}
	{ Color col={.8,.8, 0, 1}; cc[1]=col;}
	{ Color col={ 0, 0, 1, 1}; cc[2]=col;}
	Color grey={.5,.5,.5, 1};
	
	for ( int i=0; i<size_of_AP; i++)
	{
		if ( i<3) {
			AC[i] = cc[i%3];
			AC[i].a = 0.5f;
		} else {
			AC[i] = grey;
			AC[i].a = 0.2f;
		}
	}
}
void line_init( int)
{
	AP[0].x=300; AP[0].y=30;
	AP[1].x=120; AP[1].y=250;
	AP[2].x=480; AP[2].y=250;
	
	AP[3].x=240; AP[3].y=130;
	AP[4].x=360; AP[4].y=130;
	AP[5].x=300; AP[5].y=220;
	size_of_AP = 6;
	
	line_update();
	gl_wnd->set_drag_target( AP, size_of_AP); 
}
void drag_cb(Fl_Widget* W, void*)
{
	gl_wnd->redraw();
}
void nk_cb(Fl_Widget* W, void*)
{
	gl_wnd->redraw();
}
int N_knife()
{
	if ( nk1->value())
		return 1;
	else if ( nk2->value())
		return 2;
	else if ( nk3->value())
		return 3;
}
void make_form()
{
	text = new Fl_Box(FL_FRAME_BOX,0,300,600,80,
	"This is the test of general knife cut. "
	"The grey triangle define the knifes, which cut the colored "
	"triangle into parts. By using the 3 sides of a triangle as knives, "
	"you can obtain the result of triangle A minus triangle B. "
	"Note that the knife cut method takes care also the interpolation "
	"of colors. \n"
	"Drag the points of the triangles to play."
	);
	text->align( FL_ALIGN_TOP | FL_ALIGN_LEFT | FL_ALIGN_INSIDE | FL_ALIGN_WRAP);
	
	{	Fl_Group* o = new Fl_Group(0,380,600,20);
		new Fl_Box(0,380,30,20,"N =");
		nk1 = new Fl_Radio_Light_Button(30,380,80,20,"1 knife");
		nk2 = new Fl_Radio_Light_Button(110,380,30,20,"2");
		nk3 = new Fl_Radio_Light_Button(140,380,30,20,"3");
		
		nk1->callback(nk_cb);
		nk2->callback(nk_cb);
		nk3->callback(nk_cb);
		
		nk1->value(1);
		o->end();
	}
}
void draw_triangles_outline( vertex_array_holder& tris)
{
	for ( int i=0; i<tris.count; i++)
	{
		Point P1 = tris.get(i); i++;
		Point P2 = tris.get(i); i++;
		Point P3 = tris.get(i);
		
		glBegin(GL_LINE_STRIP);
			glColor3f(1,0,0);
			glVertex2f( P1.x,P1.y);
			glVertex2f( P2.x,P2.y);
			glVertex2f( P3.x,P3.y);
			glVertex2f( P1.x,P1.y);
		glEnd();
	}
}
void test_draw()
{
	renderer::before();
	
	{	vertex_array_holder tri_in,tri_out;
		tri_in .set_gl_draw_mode(GL_TRIANGLES);
		tri_out.set_gl_draw_mode(GL_TRIANGLES);
		
		for ( int i=0; i<3; i++)
		{
			tri_in.push( AP[i], AC[i]);
		}
		
		Point kn0[3]={AP[3],AP[4],AP[5]};
		Point kn1[3]={AP[4],AP[5],AP[3]};
		Point kn2[3]={AP[5],AP[3],AP[4]};
		
		vah_N_knife_cut( tri_in, tri_out, kn0,kn1,kn2, 0,0,N_knife());
		tri_out.draw();
		draw_triangles_outline( tri_out);
	}
	
	{	vertex_array_holder kn;
		kn.set_gl_draw_mode(GL_TRIANGLES);
		for ( int i=3; i<6; i++)
		{
			kn.push( AP[i],AC[i]);
		}
		kn.draw();
	}
	
	renderer::after();
}

int main(int argc, char **argv)
{
	main_wnd = new Fl_Window( 600,400,"knife cut test (fltk-opengl)");
		make_form(); //initialize
		gl_wnd = new Gl_Window( 0,0,600,300);  gl_wnd->end(); //create gl window
		line_init(3);
	main_wnd->end();
	main_wnd->show();
	main_wnd->redraw();
	
	return Fl::run();
}


================================================
FILE: cpp/workbench/layout.css
================================================
.main_canvas
{
	width:800px; height:600px;
}

.side_pane
{
	position:absolute;
	left:810px; top:10px;
}


================================================
FILE: cpp/workbench/outward_vector.html
================================================
<body>
<script src='raphael-min.js'></script>
<script src='dragger.js'></script>
<script src='drawer.js'></script>
<link rel="stylesheet" type="text/css" href="layout.css" />

<div class='main_canvas' id='canvas1'></div>
<div class='side_pane'>
Blue vectors are outward perpendicular vectors.<br />
Red ones are outward bisecting vectors.<br />
This method only works for these 2 special case: outward perpendicular
and outward bisecting vectors.
Drag control points to observe.
</div>

<script>
TP = new drawer();
DG = new dragger(TP,'canvas1');
TP.px=[300,300,500,500]; //feel free to expand these arrays
TP.py=[400,200,200,400];

TP.redraw=function()
{
	rl.clear();
	rl.rect(0,0,800-1,600-1);

	TP.moveTo( TP.px[0],TP.py[0],0);
	for ( var i=0; i<TP.px.length; i++)
	{
		TP.lineTo( TP.px[i],TP.py[i],0);
		TP.drawpoint( TP.px[i],TP.py[i],4,0);
	}
	make_perpen_vector( TP.px,TP.py);
	make_outward_vector( TP.px,TP.py);

	TP.drawpath(rl);
}

function distance( ax,ay,bx,by)
{
	return Math.sqrt( (bx-ax)*(bx-ax) + (by-ay)*(by-ay));
}
function normalize( V)
{
	L= Math.sqrt(V.x*V.x+V.y*V.y);
	return { x:V.x/L, y:V.y/L};
}
function choose_outward(V, b,c)
{
	/* //the brute force way to calculate determinant:
	var determinant = distance(b.x+V.x,b.y+V.y,c.x,c.y) - 
			distance(b.x-V.x,b.y-V.y,c.x,c.y);*/
	//a simplified way:
	var determinant = b.x*V.x - V.x*c.x + b.y*V.y - V.y*c.y;
	
	if ( determinant>0)
	{
		//positive V is the outward vector
	} else {
		//negative V is the outward vector
		V.x=-V.x;
		V.y=-V.y;
	}
	return V;
}
function signed_area(p1,p2,p3)
{
	var D = (p2.x-p1.x)*(p3.y-p1.y)-(p3.x-p1.x)*(p2.y-p1.y);
	return D;
}

function make_perpen_vector( px,py)
{
	var L={x:0,y:0};
	var V={x:0,y:0};
	for ( var i=0; i<px.length; i++)
	{
		var j1,j0;
		if ( i==0) {
			j1=1; j0=0;
		} else if ( i==px.length-1) {
			j1=px.length-2; j0=px.length-1;
		} else {
			j0=i; j1=i-1;
		}

		L.x=px[j1]-px[j0]; //line vector
		L.y=py[j1]-py[j0];
		V.x=-L.y; //perpendicular vector
		V.y=L.x;
		
		V=normalize(V);
		V.x*=30; //make it longer for observation
		V.y*=30;
		if ( i==0) {
			V=choose_outward( V,{x:px[j1],y:py[j1]},{x:px[j1+1],y:py[j1+1]} );	
		} else if ( i==px.length-1) {
			V=choose_outward( V,{x:px[j1],y:py[j1]},{x:px[j1-1],y:py[j1-1]} );
		} else {
			V=choose_outward( V,{x:px[j0],y:py[j0]},{x:px[j0+1],y:py[j0+1]} );
		}
		
		TP.drawarrow( px[j0],py[j0],px[j0]+V.x,py[j0]+V.y,null,3);
	}
}
function make_outward_vector( px,py)
{
	var L1={x:0,y:0};
	var L2={x:0,y:0};
	var V={x:0,y:0};
	for ( var i=1; i<px.length-1; i++)
	{
		L1.x=px[i]-px[i-1];
		L1.y=py[i]-py[i-1];
		L2.x=px[i+1]-px[i];
		L2.y=py[i+1]-py[i];
		L1=normalize(L1);
		L2=normalize(L2);

		V.x=L1.x-L2.x; //average vector
		V.y=L1.y-L2.y;
		V=normalize(V);
		V.x*=30; //make it longer for observation
		V.y*=30;
		V=choose_outward( V,{x:px[i],y:py[i]},{x:px[i+1],y:py[i+1]} );		

		TP.drawarrow( px[i],py[i],px[i]+V.x,py[i]+V.y,null,1);
	}
}

rl = Raphael('canvas1');
TP.redraw();
</script>
</body>


================================================
FILE: cpp/workbench/quad_reflex.html
================================================
<body>
<script src='raphael-min.js'></script>
<script src='dragger.js'></script>
<script src='drawer.js'></script>
<link rel="stylesheet" type="text/css" href="layout.css" />
<div class='side_pane'>
	Note that we always draw the 2 blue lines correctly, even when
	the quad is reflexed.<br />
	This is especially useful when drawing quad by triangle strip
	to ensure there is no reflex.<br />
	<button onclick="app1.DG.printpoints(app1.TP)">print points</button>
</div>

<script>
var app_count=0;

function distance_squared(x1,y1,x2,y2)
{
	dx = x2-x1;
	dy = y2-y1;
	return (dx*dx+dy*dy);
}

function quad_is_reflexed(px,py)
{
	//points:
	//   1------3
	//  /      /
	// 0------2
	// vector 01 parallel to 23
	
	return distance_squared(px[1],py[1],px[3],py[3]) + distance_squared(px[0],py[0],px[2],py[2])
		> distance_squared(px[0],py[0],px[3],py[3]) + distance_squared(px[1],py[1],px[2],py[2]);
	
	//when dist(1,3)+dist(0,2) > dist(0,3)+dist(1,2),
	//  then the quad is reflexed
}

function demo_application1()
{
	var i=this.i=app_count;
	var parent=this;
	app_count++;
	document.write("<div class='main_canvas' id='canvas"+i+"'></div>");
	
	this.TP = new drawer();
	this.DG = new dragger(this.TP,'canvas'+i);
	this.rl = Raphael('canvas'+i);
	//
	this.TP.px=[219,248,441,540];
	this.TP.py=[335,223,338,231];
	
	this.TP.redraw=function()
	{
		var rl=parent.rl;
		var TP=parent.TP;
		rl.clear();
		rl.rect(0,0,800-1,600-1);
		
		TP.drawarrow( TP.px[0],TP.py[0], TP.px[1],TP.py[1], null, 0);
		TP.drawarrow( TP.px[2],TP.py[2], TP.px[3],TP.py[3], null, 0);
		
		if ( !quad_is_reflexed(TP.px,TP.py))
		{
			TP.moveTo( TP.px[0],TP.py[0],3);
			TP.lineTo( TP.px[2],TP.py[2],3);
			
			TP.moveTo( TP.px[1],TP.py[1],3);
			TP.lineTo( TP.px[3],TP.py[3],3);
		}
		else
		{
			TP.moveTo( TP.px[0],TP.py[0],3);
			TP.lineTo( TP.px[3],TP.py[3],3);
			
			TP.moveTo( TP.px[1],TP.py[1],3);
			TP.lineTo( TP.px[2],TP.py[2],3);
		}
		
		TP.drawpath(rl);
	}
	this.TP.redraw();
}

app1 = new demo_application1();
</script>
</body>


================================================
FILE: cpp/workbench/raphael-min.js
================================================
/*
 * Raphael 1.5.2 - JavaScript Vector Library
 *
 * Copyright (c) 2010 Dmitry Baranovskiy (http://raphaeljs.com)
 * Licensed under the MIT (http://raphaeljs.com/license.html) license.
 */
(function(){function a(){if(a.is(arguments[0],G)){var b=arguments[0],d=bV[m](a,b.splice(0,3+a.is(b[0],E))),e=d.set();for(var g=0,h=b[w];g<h;g++){var i=b[g]||{};c[f](i.type)&&e[L](d[i.type]().attr(i))}return e}return bV[m](a,arguments)}a.version="1.5.2";var b=/[, ]+/,c={circle:1,rect:1,path:1,ellipse:1,text:1,image:1},d=/\{(\d+)\}/g,e="prototype",f="hasOwnProperty",g=document,h=window,i={was:Object[e][f].call(h,"Raphael"),is:h.Raphael},j=function(){this.customAttributes={}},k,l="appendChild",m="apply",n="concat",o="createTouch"in g,p="",q=" ",r=String,s="split",t="click dblclick mousedown mousemove mouseout mouseover mouseup touchstart touchmove touchend orientationchange touchcancel gesturestart gesturechange gestureend"[s](q),u={mousedown:"touchstart",mousemove:"touchmove",mouseup:"touchend"},v="join",w="length",x=r[e].toLowerCase,y=Math,z=y.max,A=y.min,B=y.abs,C=y.pow,D=y.PI,E="number",F="string",G="array",H="toString",I="fill",J=Object[e][H],K={},L="push",M=/^url\(['"]?([^\)]+?)['"]?\)$/i,N=/^\s*((#[a-f\d]{6})|(#[a-f\d]{3})|rgba?\(\s*([\d\.]+%?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsba?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\)|hsla?\(\s*([\d\.]+(?:deg|\xb0|%)?\s*,\s*[\d\.]+%?\s*,\s*[\d\.]+(?:%?\s*,\s*[\d\.]+)?)%?\s*\))\s*$/i,O={"NaN":1,Infinity:1,"-Infinity":1},P=/^(?:cubic-)?bezier\(([^,]+),([^,]+),([^,]+),([^\)]+)\)/,Q=y.round,R="setAttribute",S=parseFloat,T=parseInt,U=" progid:DXImageTransform.Microsoft",V=r[e].toUpperCase,W={blur:0,"clip-rect":"0 0 1e9 1e9",cursor:"default",cx:0,cy:0,fill:"#fff","fill-opacity":1,font:"10px \"Arial\"","font-family":"\"Arial\"","font-size":"10","font-style":"normal","font-weight":400,gradient:0,height:0,href:"http://raphaeljs.com/",opacity:1,path:"M0,0",r:0,rotation:0,rx:0,ry:0,scale:"1 1",src:"",stroke:"#000","stroke-dasharray":"","stroke-linecap":"butt","stroke-linejoin":"butt","stroke-miterlimit":0,"stroke-opacity":1,"stroke-width":1,target:"_blank","text-anchor":"middle",title:"Raphael",translation:"0 0",width:0,x:0,y:0},X={along:"along",blur:E,"clip-rect":"csv",cx:E,cy:E,fill:"colour","fill-opacity":E,"font-size":E,height:E,opacity:E,path:"path",r:E,rotation:"csv",rx:E,ry:E,scale:"csv",stroke:"colour","stroke-opacity":E,"stroke-width":E,translation:"csv",width:E,x:E,y:E},Y="replace",Z=/^(from|to|\d+%?)$/,$=/\s*,\s*/,_={hs:1,rg:1},ba=/,?([achlmqrstvxz]),?/gi,bb=/([achlmqstvz])[\s,]*((-?\d*\.?\d*(?:e[-+]?\d+)?\s*,?\s*)+)/ig,bc=/(-?\d*\.?\d*(?:e[-+]?\d+)?)\s*,?\s*/ig,bd=/^r(?:\(([^,]+?)\s*,\s*([^\)]+?)\))?/,be=function(a,b){return a.key-b.key};a.type=h.SVGAngle||g.implementation.hasFeature("http://www.w3.org/TR/SVG11/feature#BasicStructure","1.1")?"SVG":"VML";if(a.type=="VML"){var bf=g.createElement("div"),bg;bf.innerHTML="<v:shape adj=\"1\"/>";bg=bf.firstChild;bg.style.behavior="url(#default#VML)";if(!(bg&&typeof bg.adj=="object"))return a.type=null;bf=null}a.svg=!(a.vml=a.type=="VML");j[e]=a[e];k=j[e];a._id=0;a._oid=0;a.fn={};a.is=function(a,b){b=x.call(b);if(b=="finite")return!O[f](+a);return b=="null"&&a===null||b==typeof a||b=="object"&&a===Object(a)||b=="array"&&Array.isArray&&Array.isArray(a)||J.call(a).slice(8,-1).toLowerCase()==b};a.angle=function(b,c,d,e,f,g){{if(f==null){var h=b-d,i=c-e;if(!h&&!i)return 0;return((h<0)*180+y.atan(-i/-h)*180/D+360)%360}return a.angle(b,c,f,g)-a.angle(d,e,f,g)}};a.rad=function(a){return a%360*D/180};a.deg=function(a){return a*180/D%360};a.snapTo=function(b,c,d){d=a.is(d,"finite")?d:10;if(a.is(b,G)){var e=b.length;while(e--)if(B(b[e]-c)<=d)return b[e]}else{b=+b;var f=c%b;if(f<d)return c-f;if(f>b-d)return c-f+b}return c};function bh(){var a=[],b=0;for(;b<32;b++)a[b]=(~(~(y.random()*16)))[H](16);a[12]=4;a[16]=(a[16]&3|8)[H](16);return"r-"+a[v]("")}a.setWindow=function(a){h=a;g=h.document};var bi=function(b){if(a.vml){var c=/^\s+|\s+$/g,d;try{var e=new ActiveXObject("htmlfile");e.write("<body>");e.close();d=e.body}catch(a){d=createPopup().document.body}var f=d.createTextRange();bi=bm(function(a){try{d.style.color=r(a)[Y](c,p);var b=f.queryCommandValue("ForeColor");b=(b&255)<<16|b&65280|(b&16711680)>>>16;return"#"+("000000"+b[H](16)).slice(-6)}catch(a){return"none"}})}else{var h=g.createElement("i");h.title="Raphaël Colour Picker";h.style.display="none";g.body[l](h);bi=bm(function(a){h.style.color=a;return g.defaultView.getComputedStyle(h,p).getPropertyValue("color")})}return bi(b)},bj=function(){return"hsb("+[this.h,this.s,this.b]+")"},bk=function(){return"hsl("+[this.h,this.s,this.l]+")"},bl=function(){return this.hex};a.hsb2rgb=function(b,c,d,e){if(a.is(b,"object")&&"h"in b&&"s"in b&&"b"in b){d=b.b;c=b.s;b=b.h;e=b.o}return a.hsl2rgb(b,c,d/2,e)};a.hsl2rgb=function(b,c,d,e){if(a.is(b,"object")&&"h"in b&&"s"in b&&"l"in b){d=b.l;c=b.s;b=b.h}if(b>1||c>1||d>1){b/=360;c/=100;d/=100}var f={},g=["r","g","b"],h,i,j,k,l,m;if(c){d<0.5?h=d*(1+c):h=d+c-d*c;i=2*d-h;for(var n=0;n<3;n++){j=b+1/3*-(n-1);j<0&&j++;j>1&&j--;j*6<1?f[g[n]]=i+(h-i)*6*j:j*2<1?f[g[n]]=h:j*3<2?f[g[n]]=i+(h-i)*(2/3-j)*6:f[g[n]]=i}}else f={r:d,g:d,b:d};f.r*=255;f.g*=255;f.b*=255;f.hex="#"+(16777216|f.b|f.g<<8|f.r<<16).toString(16).slice(1);a.is(e,"finite")&&(f.opacity=e);f.toString=bl;return f};a.rgb2hsb=function(b,c,d){if(c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b){d=b.b;c=b.g;b=b.r}if(c==null&&a.is(b,F)){var e=a.getRGB(b);b=e.r;c=e.g;d=e.b}if(b>1||c>1||d>1){b/=255;c/=255;d/=255}var f=z(b,c,d),g=A(b,c,d),h,i,j=f;{if(g==f)return{h:0,s:0,b:f,toString:bj};var k=f-g;i=k/f;b==f?h=(c-d)/k:c==f?h=2+(d-b)/k:h=4+(b-c)/k;h/=6;h<0&&h++;h>1&&h--}return{h:h,s:i,b:j,toString:bj}};a.rgb2hsl=function(b,c,d){if(c==null&&a.is(b,"object")&&"r"in b&&"g"in b&&"b"in b){d=b.b;c=b.g;b=b.r}if(c==null&&a.is(b,F)){var e=a.getRGB(b);b=e.r;c=e.g;d=e.b}if(b>1||c>1||d>1){b/=255;c/=255;d/=255}var f=z(b,c,d),g=A(b,c,d),h,i,j=(f+g)/2,k;if(g==f)k={h:0,s:0,l:j};else{var l=f-g;i=j<0.5?l/(f+g):l/(2-f-g);b==f?h=(c-d)/l:c==f?h=2+(d-b)/l:h=4+(b-c)/l;h/=6;h<0&&h++;h>1&&h--;k={h:h,s:i,l:j}}k.toString=bk;return k};a._path2string=function(){return this.join(",")[Y](ba,"$1")};function bm(a,b,c){function d(){var g=Array[e].slice.call(arguments,0),h=g[v]("►"),i=d.cache=d.cache||{},j=d.count=d.count||[];if(i[f](h))return c?c(i[h]):i[h];j[w]>=1000&&delete i[j.shift()];j[L](h);i[h]=a[m](b,g);return c?c(i[h]):i[h]}return d}a.getRGB=bm(function(b){if(!b||!(!((b=r(b)).indexOf("-")+1)))return{r:-1,g:-1,b:-1,hex:"none",error:1};if(b=="none")return{r:-1,g:-1,b:-1,hex:"none"};!(_[f](b.toLowerCase().substring(0,2))||b.charAt()=="#")&&(b=bi(b));var c,d,e,g,h,i,j,k=b.match(N);if(k){if(k[2]){g=T(k[2].substring(5),16);e=T(k[2].substring(3,5),16);d=T(k[2].substring(1,3),16)}if(k[3]){g=T((i=k[3].charAt(3))+i,16);e=T((i=k[3].charAt(2))+i,16);d=T((i=k[3].charAt(1))+i,16)}if(k[4]){j=k[4][s]($);d=S(j[0]);j[0].slice(-1)=="%"&&(d*=2.55);e=S(j[1]);j[1].slice(-1)=="%"&&(e*=2.55);g=S(j[2]);j[2].slice(-1)=="%"&&(g*=2.55);k[1].toLowerCase().slice(0,4)=="rgba"&&(h=S(j[3]));j[3]&&j[3].slice(-1)=="%"&&(h/=100)}if(k[5]){j=k[5][s]($);d=S(j[0]);j[0].slice(-1)=="%"&&(d*=2.55);e=S(j[1]);j[1].slice(-1)=="%"&&(e*=2.55);g=S(j[2]);j[2].slice(-1)=="%"&&(g*=2.55);(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360);k[1].toLowerCase().slice(0,4)=="hsba"&&(h=S(j[3]));j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsb2rgb(d,e,g,h)}if(k[6]){j=k[6][s]($);d=S(j[0]);j[0].slice(-1)=="%"&&(d*=2.55);e=S(j[1]);j[1].slice(-1)=="%"&&(e*=2.55);g=S(j[2]);j[2].slice(-1)=="%"&&(g*=2.55);(j[0].slice(-3)=="deg"||j[0].slice(-1)=="°")&&(d/=360);k[1].toLowerCase().slice(0,4)=="hsla"&&(h=S(j[3]));j[3]&&j[3].slice(-1)=="%"&&(h/=100);return a.hsl2rgb(d,e,g,h)}k={r:d,g:e,b:g};k.hex="#"+(16777216|g|e<<8|d<<16).toString(16).slice(1);a.is(h,"finite")&&(k.opacity=h);return k}return{r:-1,g:-1,b:-1,hex:"none",error:1}},a);a.getColor=function(a){var b=this.getColor.start=this.getColor.start||{h:0,s:1,b:a||0.75},c=this.hsb2rgb(b.h,b.s,b.b);b.h+=0.075;if(b.h>1){b.h=0;b.s-=0.2;b.s<=0&&(this.getColor.start={h:0,s:1,b:b.b})}return c.hex};a.getColor.reset=function(){delete this.start};a.parsePathString=bm(function(b){if(!b)return null;var c={a:7,c:6,h:1,l:2,m:2,q:4,s:4,t:2,v:1,z:0},d=[];a.is(b,G)&&a.is(b[0],G)&&(d=bo(b));d[w]||r(b)[Y](bb,function(a,b,e){var f=[],g=x.call(b);e[Y](bc,function(a,b){b&&f[L](+b)});if(g=="m"&&f[w]>2){d[L]([b][n](f.splice(0,2)));g="l";b=b=="m"?"l":"L"}while(f[w]>=c[g]){d[L]([b][n](f.splice(0,c[g])));if(!c[g])break}});d[H]=a._path2string;return d});a.findDotsAtSegment=function(a,b,c,d,e,f,g,h,i){var j=1-i,k=C(j,3)*a+C(j,2)*3*i*c+j*3*i*i*e+C(i,3)*g,l=C(j,3)*b+C(j,2)*3*i*d+j*3*i*i*f+C(i,3)*h,m=a+2*i*(c-a)+i*i*(e-2*c+a),n=b+2*i*(d-b)+i*i*(f-2*d+b),o=c+2*i*(e-c)+i*i*(g-2*e+c),p=d+2*i*(f-d)+i*i*(h-2*f+d),q=(1-i)*a+i*c,r=(1-i)*b+i*d,s=(1-i)*e+i*g,t=(1-i)*f+i*h,u=90-y.atan((m-o)/(n-p))*180/D;(m>o||n<p)&&(u+=180);return{x:k,y:l,m:{x:m,y:n},n:{x:o,y:p},start:{x:q,y:r},end:{x:s,y:t},alpha:u}};var bn=bm(function(a){if(!a)return{x:0,y:0,width:0,height:0};a=bw(a);var b=0,c=0,d=[],e=[],f;for(var g=0,h=a[w];g<h;g++){f=a[g];if(f[0]=="M"){b=f[1];c=f[2];d[L](b);e[L](c)}else{var i=bv(b,c,f[1],f[2],f[3],f[4],f[5],f[6]);d=d[n](i.min.x,i.max.x);e=e[n](i.min.y,i.max.y);b=f[5];c=f[6]}}var j=A[m](0,d),k=A[m](0,e);return{x:j,y:k,width:z[m](0,d)-j,height:z[m](0,e)-k}}),bo=function(b){var c=[];if(!a.is(b,G)||!a.is(b&&b[0],G))b=a.parsePathString(b);for(var d=0,e=b[w];d<e;d++){c[d]=[];for(var f=0,g=b[d][w];f<g;f++)c[d][f]=b[d][f]}c[H]=a._path2string;return c},bp=bm(function(b){if(!a.is(b,G)||!a.is(b&&b[0],G))b=a.parsePathString(b);var c=[],d=0,e=0,f=0,g=0,h=0;if(b[0][0]=="M"){d=b[0][1];e=b[0][2];f=d;g=e;h++;c[L](["M",d,e])}for(var i=h,j=b[w];i<j;i++){var k=c[i]=[],l=b[i];if(l[0]!=x.call(l[0])){k[0]=x.call(l[0]);switch(k[0]){case"a":k[1]=l[1];k[2]=l[2];k[3]=l[3];k[4]=l[4];k[5]=l[5];k[6]=+(l[6]-d).toFixed(3);k[7]=+(l[7]-e).toFixed(3);break;case"v":k[1]=+(l[1]-e).toFixed(3);break;case"m":f=l[1];g=l[2];default:for(var m=1,n=l[w];m<n;m++)k[m]=+(l[m]-(m%2?d:e)).toFixed(3)}}else{k=c[i]=[];if(l[0]=="m"){f=l[1]+d;g=l[2]+e}for(var o=0,p=l[w];o<p;o++)c[i][o]=l[o]}var q=c[i][w];switch(c[i][0]){case"z":d=f;e=g;break;case"h":d+=+c[i][q-1];break;case"v":e+=+c[i][q-1];break;default:d+=+c[i][q-2];e+=+c[i][q-1]}}c[H]=a._path2string;return c},0,bo),bq=bm(function(b){if(!a.is(b,G)||!a.is(b&&b[0],G))b=a.parsePathString(b);var c=[],d=0,e=0,f=0,g=0,h=0;if(b[0][0]=="M"){d=+b[0][1];e=+b[0][2];f=d;g=e;h++;c[0]=["M",d,e]}for(var i=h,j=b[w];i<j;i++){var k=c[i]=[],l=b[i];if(l[0]!=V.call(l[0])){k[0]=V.call(l[0]);switch(k[0]){case"A":k[1]=l[1];k[2]=l[2];k[3]=l[3];k[4]=l[4];k[5]=l[5];k[6]=+(l[6]+d);k[7]=+(l[7]+e);break;case"V":k[1]=+l[1]+e;break;case"H":k[1]=+l[1]+d;break;case"M":f=+l[1]+d;g=+l[2]+e;default:for(var m=1,n=l[w];m<n;m++)k[m]=+l[m]+(m%2?d:e)}}else for(var o=0,p=l[w];o<p;o++)c[i][o]=l[o];switch(k[0]){case"Z":d=f;e=g;break;case"H":d=k[1];break;case"V":e=k[1];break;case"M":f=c[i][c[i][w]-2];g=c[i][c[i][w]-1];default:d=c[i][c[i][w]-2];e=c[i][c[i][w]-1]}}c[H]=a._path2string;return c},null,bo),br=function(a,b,c,d){return[a,b,c,d,c,d]},bs=function(a,b,c,d,e,f){var g=1/3,h=2/3;return[g*a+h*c,g*b+h*d,g*e+h*c,g*f+h*d,e,f]},bt=function(a,b,c,d,e,f,g,h,i,j){var k=D*120/180,l=D/180*(+e||0),m=[],o,p=bm(function(a,b,c){var d=a*y.cos(c)-b*y.sin(c),e=a*y.sin(c)+b*y.cos(c);return{x:d,y:e}});if(j){G=j[0];H=j[1];E=j[2];F=j[3]}else{o=p(a,b,-l);a=o.x;b=o.y;o=p(h,i,-l);h=o.x;i=o.y;var q=y.cos(D/180*e),r=y.sin(D/180*e),t=(a-h)/2,u=(b-i)/2,x=t*t/(c*c)+u*u/(d*d);if(x>1){x=y.sqrt(x);c=x*c;d=x*d}var z=c*c,A=d*d,C=(f==g?-1:1)*y.sqrt(B((z*A-z*u*u-A*t*t)/(z*u*u+A*t*t))),E=C*c*u/d+(a+h)/2,F=C*-d*t/c+(b+i)/2,G=y.asin(((b-F)/d).toFixed(9)),H=y.asin(((i-F)/d).toFixed(9));G=a<E?D-G:G;H=h<E?D-H:H;G<0&&(G=D*2+G);H<0&&(H=D*2+H);g&&G>H&&(G=G-D*2);!g&&H>G&&(H=H-D*2)}var I=H-G;if(B(I)>k){var J=H,K=h,L=i;H=G+k*(g&&H>G?1:-1);h=E+c*y.cos(H);i=F+d*y.sin(H);m=bt(h,i,c,d,e,0,g,K,L,[H,J,E,F])}I=H-G;var M=y.cos(G),N=y.sin(G),O=y.cos(H),P=y.sin(H),Q=y.tan(I/4),R=4/3*c*Q,S=4/3*d*Q,T=[a,b],U=[a+R*N,b-S*M],V=[h+R*P,i-S*O],W=[h,i];U[0]=2*T[0]-U[0];U[1]=2*T[1]-U[1];{if(j)return[U,V,W][n](m);m=[U,V,W][n](m)[v]()[s](",");var X=[];for(var Y=0,Z=m[w];Y<Z;Y++)X[Y]=Y%2?p(m[Y-1],m[Y],l).y:p(m[Y],m[Y+1],l).x;return X}},bu=function(a,b,c,d,e,f,g,h,i){var j=1-i;return{x:C(j,3)*a+C(j,2)*3*i*c+j*3*i*i*e+C(i,3)*g,y:C(j,3)*b+C(j,2)*3*i*d+j*3*i*i*f+C(i,3)*h}},bv=bm(function(a,b,c,d,e,f,g,h){var i=e-2*c+a-(g-2*e+c),j=2*(c-a)-2*(e-c),k=a-c,l=(-j+y.sqrt(j*j-4*i*k))/2/i,n=(-j-y.sqrt(j*j-4*i*k))/2/i,o=[b,h],p=[a,g],q;B(l)>"1e12"&&(l=0.5);B(n)>"1e12"&&(n=0.5);if(l>0&&l<1){q=bu(a,b,c,d,e,f,g,h,l);p[L](q.x);o[L](q.y)}if(n>0&&n<1){q=bu(a,b,c,d,e,f,g,h,n);p[L](q.x);o[L](q.y)}i=f-2*d+b-(h-2*f+d);j=2*(d-b)-2*(f-d);k=b-d;l=(-j+y.sqrt(j*j-4*i*k))/2/i;n=(-j-y.sqrt(j*j-4*i*k))/2/i;B(l)>"1e12"&&(l=0.5);B(n)>"1e12"&&(n=0.5);if(l>0&&l<1){q=bu(a,b,c,d,e,f,g,h,l);p[L](q.x);o[L](q.y)}if(n>0&&n<1){q=bu(a,b,c,d,e,f,g,h,n);p[L](q.x);o[L](q.y)}return{min:{x:A[m](0,p),y:A[m](0,o)},max:{x:z[m](0,p),y:z[m](0,o)}}}),bw=bm(function(a,b){var c=bq(a),d=b&&bq(b),e={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},f={x:0,y:0,bx:0,by:0,X:0,Y:0,qx:null,qy:null},g=function(a,b){var c,d;if(!a)return["C",b.x,b.y,b.x,b.y,b.x,b.y];!(a[0]in{T:1,Q:1})&&(b.qx=b.qy=null);switch(a[0]){case"M":b.X=a[1];b.Y=a[2];break;case"A":a=["C"][n](bt[m](0,[b.x,b.y][n](a.slice(1))));break;case"S":c=b.x+(b.x-(b.bx||b.x));d=b.y+(b.y-(b.by||b.y));a=["C",c,d][n](a.slice(1));break;case"T":b.qx=b.x+(b.x-(b.qx||b.x));b.qy=b.y+(b.y-(b.qy||b.y));a=["C"][n](bs(b.x,b.y,b.qx,b.qy,a[1],a[2]));break;case"Q":b.qx=a[1];b.qy=a[2];a=["C"][n](bs(b.x,b.y,a[1],a[2],a[3],a[4]));break;case"L":a=["C"][n](br(b.x,b.y,a[1],a[2]));break;case"H":a=["C"][n](br(b.x,b.y,a[1],b.y));break;case"V":a=["C"][n](br(b.x,b.y,b.x,a[1]));break;case"Z":a=["C"][n](br(b.x,b.y,b.X,b.Y));break}return a},h=function(a,b){if(a[b][w]>7){a[b].shift();var e=a[b];while(e[w])a.splice(b++,0,["C"][n](e.splice(0,6)));a.splice(b,1);k=z(c[w],d&&d[w]||0)}},i=function(a,b,e,f,g){if(a&&b&&a[g][0]=="M"&&b[g][0]!="M"){b.splice(g,0,["M",f.x,f.y]);e.bx=0;e.by=0;e.x=a[g][1];e.y=a[g][2];k=z(c[w],d&&d[w]||0)}};for(var j=0,k=z(c[w],d&&d[w]||0);j<k;j++){c[j]=g(c[j],e);h(c,j);d&&(d[j]=g(d[j],f));d&&h(d,j);i(c,d,e,f,j);i(d,c,f,e,j);var l=c[j],o=d&&d[j],p=l[w],q=d&&o[w];e.x=l[p-2];e.y=l[p-1];e.bx=S(l[p-4])||e.x;e.by=S(l[p-3])||e.y;f.bx=d&&(S(o[q-4])||f.x);f.by=d&&(S(o[q-3])||f.y);f.x=d&&o[q-2];f.y=d&&o[q-1]}return d?[c,d]:c},null,bo),bx=bm(function(b){var c=[];for(var d=0,e=b[w];d<e;d++){var f={},g=b[d].match(/^([^:]*):?([\d\.]*)/);f.color=a.getRGB(g[1]);if(f.color.error)return null;f.color=f.color.hex;g[2]&&(f.offset=g[2]+"%");c[L](f)}for(d=1,e=c[w]-1;d<e;d++){if(!c[d].offset){var h=S(c[d-1].offset||0),i=0;for(var j=d+1;j<e;j++){if(c[j].offset){i=c[j].offset;break}}if(!i){i=100;j=e}i=S(i);var k=(i-h)/(j-d+1);for(;d<j;d++){h+=k;c[d].offset=h+"%"}}}return c}),by=function(b,c,d,e){var f;if(a.is(b,F)||a.is(b,"object")){f=a.is(b,F)?g.getElementById(b):b;if(f.tagName)return c==null?{container:f,width:f.style.pixelWidth||f.offsetWidth,height:f.style.pixelHeight||f.offsetHeight}:{container:f,width:c,height:d}}else return{container:1,x:b,y:c,width:d,height:e}},bz=function(a,b){var c=this;for(var d in b){if(b[f](d)&&!(d in a))switch(typeof b[d]){case"function":(function(b){a[d]=a===c?b:function(){return b[m](c,arguments)}})(b[d]);break;case"object":a[d]=a[d]||{};bz.call(this,a[d],b[d]);break;default:a[d]=b[d];break}}},bA=function(a,b){a==b.top&&(b.top=a.prev);a==b.bottom&&(b.bottom=a.next);a.next&&(a.next.prev=a.prev);a.prev&&(a.prev.next=a.next)},bB=function(a,b){if(b.top===a)return;bA(a,b);a.next=null;a.prev=b.top;b.top.next=a;b.top=a},bC=function(a,b){if(b.bottom===a)return;bA(a,b);a.next=b.bottom;a.prev=null;b.bottom.prev=a;b.bottom=a},bD=function(a,b,c){bA(a,c);b==c.top&&(c.top=a);b.next&&(b.next.prev=a);a.next=b.next;a.prev=b;b.next=a},bE=function(a,b,c){bA(a,c);b==c.bottom&&(c.bottom=a);b.prev&&(b.prev.next=a);a.prev=b.prev;b.prev=a;a.next=b},bF=function(a){return function(){throw new Error("Raphaël: you are calling to method “"+a+"” of removed object")}};a.pathToRelative=bp;if(a.svg){k.svgns="http://www.w3.org/2000/svg";k.xlink="http://www.w3.org/1999/xlink";Q=function(a){return+a+(~(~a)===a)*0.5};var bG=function(a,b){if(b)for(var c in b)b[f](c)&&a[R](c,r(b[c]));else{a=g.createElementNS(k.svgns,a);a.style.webkitTapHighlightColor="rgba(0,0,0,0)";return a}};a[H]=function(){return"Your browser supports SVG.\nYou are running Raphaël "+this.version};var bH=function(a,b){var c=bG("path");b.canvas&&b.canvas[l](c);var d=new bN(c,b);d.type="path";bK(d,{fill:"none",stroke:"#000",path:a});return d},bI=function(a,b,c){var d="linear",e=0.5,f=0.5,h=a.style;b=r(b)[Y](bd,function(a,b,c){d="radial";if(b&&c){e=S(b);f=S(c);var g=(f>0.5)*2-1;C(e-0.5,2)+C(f-0.5,2)>0.25&&(f=y.sqrt(0.25-C(e-0.5,2))*g+0.5)&&f!=0.5&&(f=f.toFixed(5)-0.00001*g)}return p});b=b[s](/\s*\-\s*/);if(d=="linear"){var i=b.shift();i=-S(i);if(isNaN(i))return null;var j=[0,0,y.cos(i*D/180),y.sin(i*D/180)],k=1/(z(B(j[2]),B(j[3]))||1);j[2]*=k;j[3]*=k;if(j[2]<0){j[0]=-j[2];j[2]=0}if(j[3]<0){j[1]=-j[3];j[3]=0}}var m=bx(b);if(!m)return null;var n=a.getAttribute(I);n=n.match(/^url\(#(.*)\)$/);n&&c.defs.removeChild(g.getElementById(n[1]));var o=bG(d+"Gradient");o.id=bh();bG(o,d=="radial"?{fx:e,fy:f}:{x1:j[0],y1:j[1],x2:j[2],y2:j[3]});c.defs[l](o);for(var q=0,t=m[w];q<t;q++){var u=bG("stop");bG(u,{offset:m[q].offset?m[q].offset:q?"100%":"0%","stop-color":m[q].color||"#fff"});o[l](u)}bG(a,{fill:"url(#"+o.id+")",opacity:1,"fill-opacity":1});h.fill=p;h.opacity=1;h.fillOpacity=1;return 1},bJ=function(b){var c=b.getBBox();bG(b.pattern,{patternTransform:a.format("translate({0},{1})",c.x,c.y)})},bK=function(c,d){var e={"":[0],none:[0],"-":[3,1],".":[1,1],"-.":[3,1,1,1],"-..":[3,1,1,1,1,1],". ":[1,3],"- ":[4,3],"--":[8,3],"- .":[4,3,1,3],"--.":[8,3,1,3],"--..":[8,3,1,3,1,3]},h=c.node,i=c.attrs,j=c.rotate(),k=function(a,b){b=e[x.call(b)];if(b){var c=a.attrs["stroke-width"]||"1",f=({round:c,square:c,butt:0})[a.attrs["stroke-linecap"]||d["stroke-linecap"]]||0,g=[],i=b[w];while(i--)g[i]=b[i]*c+(i%2?1:-1)*f;bG(h,{"stroke-dasharray":g[v](",")})}};d[f]("rotation")&&(j=d.rotation);var m=r(j)[s](b);if(m.length-1){m[1]=+m[1];m[2]=+m[2]}else m=null;S(j)&&c.rotate(0,true);for(var n in d){if(d[f](n)){if(!W[f](n))continue;var o=d[n];i[n]=o;switch(n){case"blur":c.blur(o);break;case"rotation":c.rotate(o,true);break;case"href":case"title":case"target":var t=h.parentNode;if(x.call(t.tagName)!="a"){var u=bG("a");t.insertBefore(u,h);u[l](h);t=u}n=="target"&&o=="blank"?t.setAttributeNS(c.paper.xlink,"show","new"):t.setAttributeNS(c.paper.xlink,n,o);break;case"cursor":h.style.cursor=o;break;case"clip-rect":var y=r(o)[s](b);if(y[w]==4){c.clip&&c.clip.parentNode.parentNode.removeChild(c.clip.parentNode);var z=bG("clipPath"),A=bG("rect");z.id=bh();bG(A,{x:y[0],y:y[1],width:y[2],height:y[3]});z[l](A);c.paper.defs[l](z);bG(h,{"clip-path":"url(#"+z.id+")"});c.clip=A}if(!o){var B=g.getElementById(h.getAttribute("clip-path")[Y](/(^url\(#|\)$)/g,p));B&&B.parentNode.removeChild(B);bG(h,{"clip-path":p});delete c.clip}break;case"path":c.type=="path"&&bG(h,{d:o?i.path=bq(o):"M0,0"});break;case"width":h[R](n,o);if(i.fx){n="x";o=i.x}else break;case"x":i.fx&&(o=-i.x-(i.width||0));case"rx":if(n=="rx"&&c.type=="rect")break;case"cx":m&&(n=="x"||n=="cx")&&(m[1]+=o-i[n]);h[R](n,o);c.pattern&&bJ(c);break;case"height":h[R](n,o);if(i.fy){n="y";o=i.y}else break;case"y":i.fy&&(o=-i.y-(i.height||0));case"ry":if(n=="ry"&&c.type=="rect")break;case"cy":m&&(n=="y"||n=="cy")&&(m[2]+=o-i[n]);h[R](n,o);c.pattern&&bJ(c);break;case"r":c.type=="rect"?bG(h,{rx:o,ry:o}):h[R](n,o);break;case"src":c.type=="image"&&h.setAttributeNS(c.paper.xlink,"href",o);break;case"stroke-width":h.style.strokeWidth=o;h[R](n,o);i["stroke-dasharray"]&&k(c,i["stroke-dasharray"]);break;case"stroke-dasharray":k(c,o);break;case"translation":var C=r(o)[s](b);C[0]=+C[0]||0;C[1]=+C[1]||0;if(m){m[1]+=C[0];m[2]+=C[1]}cz.call(c,C[0],C[1]);break;case"scale":C=r(o)[s](b);c.scale(+C[0]||1,+C[1]||+C[0]||1,isNaN(S(C[2]))?null:+C[2],isNaN(S(C[3]))?null:+C[3]);break;case I:var D=r(o).match(M);if(D){z=bG("pattern");var E=bG("image");z.id=bh();bG(z,{x:0,y:0,patternUnits:"userSpaceOnUse",height:1,width:1});bG(E,{x:0,y:0});E.setAttributeNS(c.paper.xlink,"href",D[1]);z[l](E);var F=g.createElement("img");F.style.cssText="position:absolute;left:-9999em;top-9999em";F.onload=function(){bG(z,{width:this.offsetWidth,height:this.offsetHeight});bG(E,{width:this.offsetWidth,height:this.offsetHeight});g.body.removeChild(this);c.paper.safari()};g.body[l](F);F.src=D[1];c.paper.defs[l](z);h.style.fill="url(#"+z.id+")";bG(h,{fill:"url(#"+z.id+")"});c.pattern=z;c.pattern&&bJ(c);break}var G=a.getRGB(o);if(G.error)if((({circle:1,ellipse:1})[f](c.type)||r(o).charAt()!="r")&&bI(h,o,c.paper)){i.gradient=o;i.fill="none";break}else{delete d.gradient;delete i.gradient;!a.is(i.opacity,"undefined")&&a.is(d.opacity,"undefined")&&bG(h,{opacity:i.opacity});!a.is(i["fill-opacity"],"undefined")&&a.is(d["fill-opacity"],"undefined")&&bG(h,{"fill-opacity":i["fill-opacity"]})}G[f]("opacity")&&bG(h,{"fill-opacity":G.opacity>1?G.opacity/100:G.opacity});case"stroke":G=a.getRGB(o);h[R](n,G.hex);n=="stroke"&&G[f]("opacity")&&bG(h,{"stroke-opacity":G.opacity>1?G.opacity/100:G.opacity});break;case"gradient":(({circle:1,ellipse:1})[f](c.type)||r(o).charAt()!="r")&&bI(h,o,c.paper);break;case"opacity":i.gradient&&!i[f]("stroke-opacity")&&bG(h,{"stroke-opacity":o>1?o/100:o});case"fill-opacity":if(i.gradient){var H=g.getElementById(h.getAttribute(I)[Y](/^url\(#|\)$/g,p));if(H){var J=H.getElementsByTagName("stop");J[J[w]-1][R]("stop-opacity",o)}break}default:n=="font-size"&&(o=T(o,10)+"px");var K=n[Y](/(\-.)/g,function(a){return V.call(a.substring(1))});h.style[K]=o;h[R](n,o);break}}}bM(c,d);m?c.rotate(m.join(q)):S(j)&&c.rotate(j,true)},bL=1.2,bM=function(b,c){if(b.type!="text"||!(c[f]("text")||c[f]("font")||c[f]("font-size")||c[f]("x")||c[f]("y")))return;var d=b.attrs,e=b.node,h=e.firstChild?T(g.defaultView.getComputedStyle(e.firstChild,p).getPropertyValue("font-size"),10):10;if(c[f]("text")){d.text=c.text;while(e.firstChild)e.removeChild(e.firstChild);var i=r(c.text)[s]("\n");for(var j=0,k=i[w];j<k;j++)if(i[j]){var m=bG("tspan");j&&bG(m,{dy:h*bL,x:d.x});m[l](g.createTextNode(i[j]));e[l](m)}}else{i=e.getElementsByTagName("tspan");for(j=0,k=i[w];j<k;j++)j&&bG(i[j],{dy:h*bL,x:d.x})}bG(e,{y:d.y});var n=b.getBBox(),o=d.y-(n.y+n.height/2);o&&a.is(o,"finite")&&bG(e,{y:d.y+o})},bN=function(b,c){var d=0,e=0;this[0]=b;this.id=a._oid++;this.node=b;b.raphael=this;this.paper=c;this.attrs=this.attrs||{};this.transformations=[];this._={tx:0,ty:0,rt:{deg:0,cx:0,cy:0},sx:1,sy:1};!c.bottom&&(c.bottom=this);this.prev=c.top;c.top&&(c.top.next=this);c.top=this;this.next=null},bO=bN[e];bN[e].rotate=function(c,d,e){if(this.removed)return this;if(c==null){if(this._.rt.cx)return[this._.rt.deg,this._.rt.cx,this._.rt.cy][v](q);return this._.rt.deg}var f=this.getBBox();c=r(c)[s](b);if(c[w]-1){d=S(c[1]);e=S(c[2])}c=S(c[0]);d!=null&&d!==false?this._.rt.deg=c:this._.rt.deg+=c;e==null&&(d=null);this._.rt.cx=d;this._.rt.cy=e;d=d==null?f.x+f.width/2:d;e=e==null?f.y+f.height/2:e;if(this._.rt.deg){this.transformations[0]=a.format("rotate({0} {1} {2})",this._.rt.deg,d,e);this.clip&&bG(this.clip,{transform:a.format("rotate({0} {1} {2})",-this._.rt.deg,d,e)})}else{this.transformations[0]=p;this.clip&&bG(this.clip,{transform:p})}bG(this.node,{transform:this.transformations[v](q)});return this};bN[e].hide=function(){!this.removed&&(this.node.style.display="none");return this};bN[e].show=function(){!this.removed&&(this.node.style.display="");return this};bN[e].remove=function(){if(this.removed)return;bA(this,this.paper);this.node.parentNode.removeChild(this.node);for(var a in this)delete this[a];this.removed=true};bN[e].getBBox=function(){if(this.removed)return this;if(this.type=="path")return bn(this.attrs.path);if(this.node.style.display=="none"){this.show();var a=true}var b={};try{b=this.node.getBBox()}catch(a){}finally{b=b||{}}if(this.type=="text"){b={x:b.x,y:Infinity,width:0,height:0};for(var c=0,d=this.node.getNumberOfChars();c<d;c++){var e=this.node.getExtentOfChar(c);e.y<b.y&&(b.y=e.y);e.y+e.height-b.y>b.height&&(b.height=e.y+e.height-b.y);e.x+e.width-b.x>b.width&&(b.width=e.x+e.width-b.x)}}a&&this.hide();return b};bN[e].attr=function(b,c){if(this.removed)return this;if(b==null){var d={};for(var e in this.attrs)this.attrs[f](e)&&(d[e]=this.attrs[e]);this._.rt.deg&&(d.rotation=this.rotate());(this._.sx!=1||this._.sy!=1)&&(d.scale=this.scale());d.gradient&&d.fill=="none"&&(d.fill=d.gradient)&&delete d.gradient;return d}if(c==null&&a.is(b,F)){if(b=="translation")return cz.call(this);if(b=="rotation")return this.rotate();if(b=="scale")return this.scale();if(b==I&&this.attrs.fill=="none"&&this.attrs.gradient)return this.attrs.gradient;return this.attrs[b]}if(c==null&&a.is(b,G)){var g={};for(var h=0,i=b.length;h<i;h++)g[b[h]]=this.attr(b[h]);return g}if(c!=null){var j={};j[b]=c}else b!=null&&a.is(b,"object")&&(j=b);for(var k in this.paper.customAttributes)if(this.paper.customAttributes[f](k)&&j[f](k)&&a.is(this.paper.customAttributes[k],"function")){var l=this.paper.customAttributes[k].apply(this,[][n](j[k]));this.attrs[k]=j[k];for(var m in l)l[f](m)&&(j[m]=l[m])}bK(this,j);return this};bN[e].toFront=function(){if(this.removed)return this;this.node.parentNode[l](this.node);var a=this.paper;a.top!=this&&bB(this,a);return this};bN[e].toBack=function(){if(this.removed)return this;if(this.node.parentNode.firstChild!=this.node){this.node.parentNode.insertBefore(this.node,this.node.parentNode.firstChild);bC(this,this.paper);var a=this.paper}return this};bN[e].insertAfter=function(a){if(this.removed)return this;var b=a.node||a[a.length-1].node;b.nextSibling?b.parentNode.insertBefore(this.node,b.nextSibling):b.parentNode[l](this.node);bD(this,a,this.paper);return this};bN[e].insertBefore=function(a){if(this.removed)return this;var b=a.node||a[0].node;b.parentNode.insertBefore(this.node,b);bE(this,a,this.paper);return this};bN[e].blur=function(a){var b=this;if(+a!==0){var c=bG("filter"),d=bG("feGaussianBlur");b.attrs.blur=a;c.id=bh();bG(d,{stdDeviation:+a||1.5});c.appendChild(d);b.paper.defs.appendChild(c);b._blur=c;bG(b.node,{filter:"url(#"+c.id+")"})}else{if(b._blur){b._blur.parentNode.removeChild(b._blur);delete b._blur;delete b.attrs.blur}b.node.removeAttribute("filter")}};var bP=function(a,b,c,d){var e=bG("circle");a.canvas&&a.canvas[l](e);var f=new bN(e,a);f.attrs={cx:b,cy:c,r:d,fill:"none",stroke:"#000"};f.type="circle";bG(e,f.attrs);return f},bQ=function(a,b,c,d,e,f){var g=bG("rect");a.canvas&&a.canvas[l](g);var h=new bN(g,a);h.attrs={x:b,y:c,width:d,height:e,r:f||0,rx:f||0,ry:f||0,fill:"none",stroke:"#000"};h.type="rect";bG(g,h.attrs);return h},bR=function(a,b,c,d,e){var f=bG("ellipse");a.canvas&&a.canvas[l](f);var g=new bN(f,a);g.attrs={cx:b,cy:c,rx:d,ry:e,fill:"none",stroke:"#000"};g.type="ellipse";bG(f,g.attrs);return g},bS=function(a,b,c,d,e,f){var g=bG("image");bG(g,{x:c,y:d,width:e,height:f,preserveAspectRatio:"none"});g.setAttributeNS(a.xlink,"href",b);a.canvas&&a.canvas[l](g);var h=new bN(g,a);h.attrs={x:c,y:d,width:e,height:f,src:b};h.type="image";return h},bT=function(a,b,c,d){var e=bG("text");bG(e,{x:b,y:c,"text-anchor":"middle"});a.canvas&&a.canvas[l](e);var f=new bN(e,a);f.attrs={x:b,y:c,"text-anchor":"middle",text:d,font:W.font,stroke:"none",fill:"#000"};f.type="text";bK(f,f.attrs);return f},bU=function(a,b){this.width=a||this.width;this.height=b||this.height;this.canvas[R]("width",this.width);this.canvas[R]("height",this.height);return this},bV=function(){var b=by[m](0,arguments),c=b&&b.container,d=b.x,e=b.y,f=b.width,h=b.height;if(!c)throw new Error("SVG container not found.");var i=bG("svg");d=d||0;e=e||0;f=f||512;h=h||342;bG(i,{xmlns:"http://www.w3.org/2000/svg",version:1.1,width:f,height:h});if(c==1){i.style.cssText="position:absolute;left:"+d+"px;top:"+e+"px";g.body[l](i)}else c.firstChild?c.insertBefore(i,c.firstChild):c[l](i);c=new j;c.width=f;c.height=h;c.canvas=i;bz.call(c,c,a.fn);c.clear();return c};k.clear=function(){var a=this.canvas;while(a.firstChild)a.removeChild(a.firstChild);this.bottom=this.top=null;(this.desc=bG("desc"))[l](g.createTextNode("Created with Raphaël"));a[l](this.desc);a[l](this.defs=bG("defs"))};k.remove=function(){this.canvas.parentNode&&this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]=bF(a)}}if(a.vml){var bW={M:"m",L:"l",C:"c",Z:"x",m:"t",l:"r",c:"v",z:"x"},bX=/([clmz]),?([^clmz]*)/gi,bY=/ progid:\S+Blur\([^\)]+\)/g,bZ=/-?[^,\s-]+/g,b$=1000+q+1000,b_=10,ca={path:1,rect:1},cb=function(a){var b=/[ahqstv]/ig,c=bq;r(a).match(b)&&(c=bw);b=/[clmz]/g;if(c==bq&&!r(a).match(b)){var d=r(a)[Y](bX,function(a,b,c){var d=[],e=x.call(b)=="m",f=bW[b];c[Y](bZ,function(a){if(e&&d[w]==2){f+=d+bW[b=="m"?"l":"L"];d=[]}d[L](Q(a*b_))});return f+d});return d}var e=c(a),f,g;d=[];for(var h=0,i=e[w];h<i;h++){f=e[h];g=x.call(e[h][0]);g=="z"&&(g="x");for(var j=1,k=f[w];j<k;j++)g+=Q(f[j]*b_)+(j!=k-1?",":p);d[L](g)}return d[v](q)};a[H]=function(){return"Your browser doesn’t support SVG. Falling down to VML.\nYou are running Raphaël "+this.version};bH=function(a,b){var c=cd("group");c.style.cssText="position:absolute;left:0;top:0;width:"+b.width+"px;height:"+b.height+"px";c.coordsize=b.coordsize;c.coordorigin=b.coordorigin;var d=cd("shape"),e=d.style;e.width=b.width+"px";e.height=b.height+"px";d.coordsize=b$;d.coordorigin=b.coordorigin;c[l](d);var f=new bN(d,c,b),g={fill:"none",stroke:"#000"};a&&(g.path=a);f.type="path";f.path=[];f.Path=p;bK(f,g);b.canvas[l](c);return f};bK=function(c,d){c.attrs=c.attrs||{};var e=c.node,h=c.attrs,i=e.style,j,k=(d.x!=h.x||d.y!=h.y||d.width!=h.width||d.height!=h.height||d.r!=h.r)&&c.type=="rect",m=c;for(var n in d)d[f](n)&&(h[n]=d[n]);if(k){h.path=cc(h.x,h.y,h.width,h.height,h.r);c.X=h.x;c.Y=h.y;c.W=h.width;c.H=h.height}d.href&&(e.href=d.href);d.title&&(e.title=d.title);d.target&&(e.target=d.target);d.cursor&&(i.cursor=d.cursor);"blur"in d&&c.blur(d.blur);if(d.path&&c.type=="path"||k)e.path=cb(h.path);d.rotation!=null&&c.rotate(d.rotation,true);if(d.translation){j=r(d.translation)[s](b);cz.call(c,j[0],j[1]);if(c._.rt.cx!=null){c._.rt.cx+=+j[0];c._.rt.cy+=+j[1];c.setBox(c.attrs,j[0],j[1])}}if(d.scale){j=r(d.scale)[s](b);c.scale(+j[0]||1,+j[1]||+j[0]||1,+j[2]||null,+j[3]||null)}if("clip-rect"in d){var o=r(d["clip-rect"])[s](b);if(o[w]==4){o[2]=+o[2]+ +o[0];o[3]=+o[3]+ +o[1];var q=e.clipRect||g.createElement("div"),t=q.style,u=e.parentNode;t.clip=a.format("rect({1}px {2}px {3}px {0}px)",o);if(!e.clipRect){t.position="absolute";t.top=0;t.left=0;t.width=c.paper.width+"px";t.height=c.paper.height+"px";u.parentNode.insertBefore(q,u);q[l](u);e.clipRect=q}}d["clip-rect"]||e.clipRect&&(e.clipRect.style.clip=p)}c.type=="image"&&d.src&&(e.src=d.src);if(c.type=="image"&&d.opacity){e.filterOpacity=U+".Alpha(opacity="+d.opacity*100+")";i.filter=(e.filterMatrix||p)+(e.filterOpacity||p)}d.font&&(i.font=d.font);d["font-family"]&&(i.fontFamily="\""+d["font-family"][s](",")[0][Y](/^['"]+|['"]+$/g,p)+"\"");d["font-size"]&&(i.fontSize=d["font-size"]);d["font-weight"]&&(i.fontWeight=d["font-weight"]);d["font-style"]&&(i.fontStyle=d["font-style"]);if(d.opacity!=null||d["stroke-width"]!=null||d.fill!=null||d.stroke!=null||d["stroke-width"]!=null||d["stroke-opacity"]!=null||d["fill-opacity"]!=null||d["stroke-dasharray"]!=null||d["stroke-miterlimit"]!=null||d["stroke-linejoin"]!=null||d["stroke-linecap"]!=null){e=c.shape||e;var v=e.getElementsByTagName(I)&&e.getElementsByTagName(I)[0],x=false;!v&&(x=v=cd(I));if("fill-opacity"in d||"opacity"in d){var y=((+h["fill-opacity"]+1||2)-1)*((+h.opacity+1||2)-1)*((+a.getRGB(d.fill).o+1||2)-1);y=A(z(y,0),1);v.opacity=y}d.fill&&(v.on=true);if(v.on==null||d.fill=="none")v.on=false;if(v.on&&d.fill){var B=d.fill.match(M);if(B){v.src=B[1];v.type="tile"}else{v.color=a.getRGB(d.fill).hex;v.src=p;v.type="solid";if(a.getRGB(d.fill).error&&(m.type in{circle:1,ellipse:1}||r(d.fill).charAt()!="r")&&bI(m,d.fill)){h.fill="none";h.gradient=d.fill}}}x&&e[l](v);var C=e.getElementsByTagName("stroke")&&e.getElementsByTagName("stroke")[0],D=false;!C&&(D=C=cd("stroke"));if(d.stroke&&d.stroke!="none"||d["stroke-width"]||d["stroke-opacity"]!=null||d["stroke-dasharray"]||d["stroke-miterlimit"]||d["stroke-linejoin"]||d["stroke-linecap"])C.on=true;(d.stroke=="none"||C.on==null||d.stroke==0||d["stroke-width"]==0)&&(C.on=false);var E=a.getRGB(d.stroke);C.on&&d.stroke&&(C.color=E.hex);y=((+h["stroke-opacity"]+1||2)-1)*((+h.opacity+1||2)-1)*((+E.o+1||2)-1);var F=(S(d["stroke-width"])||1)*0.75;y=A(z(y,0),1);d["stroke-width"]==null&&(F=h["stroke-width"]);d["stroke-width"]&&(C.weight=F);F&&F<1&&(y*=F)&&(C.weight=1);C.opacity=y;d["stroke-linejoin"]&&(C.joinstyle=d["stroke-linejoin"]||"miter");C.miterlimit=d["stroke-miterlimit"]||8;d["stroke-linecap"]&&(C.endcap=d["stroke-linecap"]=="butt"?"flat":d["stroke-linecap"]=="square"?"square":"round");if(d["stroke-dasharray"]){var G={"-":"shortdash",".":"shortdot","-.":"shortdashdot","-..":"shortdashdotdot",". ":"dot","- ":"dash","--":"longdash","- .":"dashdot","--.":"longdashdot","--..":"longdashdotdot"};C.dashstyle=G[f](d["stroke-dasharray"])?G[d["stroke-dasharray"]]:p}D&&e[l](C)}if(m.type=="text"){i=m.paper.span.style;h.font&&(i.font=h.font);h["font-family"]&&(i.fontFamily=h["font-family"]);h["font-size"]&&(i.fontSize=h["font-size"]);h["font-weight"]&&(i.fontWeight=h["font-weight"]);h["font-style"]&&(i.fontStyle=h["font-style"]);m.node.string&&(m.paper.span.innerHTML=r(m.node.string)[Y](/</g,"&#60;")[Y](/&/g,"&#38;")[Y](/\n/g,"<br>"));m.W=h.w=m.paper.span.offsetWidth;m.H=h.h=m.paper.span.offsetHeight;m.X=h.x;m.Y=h.y+Q(m.H/2);switch(h["text-anchor"]){case"start":m.node.style["v-text-align"]="left";m.bbx=Q(m.W/2);break;case"end":m.node.style["v-text-align"]="right";m.bbx=-Q(m.W/2);break;default:m.node.style["v-text-align"]="center";break}}};bI=function(a,b){a.attrs=a.attrs||{};var c=a.attrs,d,e="linear",f=".5 .5";a.attrs.gradient=b;b=r(b)[Y](bd,function(a,b,c){e="radial";if(b&&c){b=S(b);c=S(c);C(b-0.5,2)+C(c-0.5,2)>0.25&&(c=y.sqrt(0.25-C(b-0.5,2))*((c>0.5)*2-1)+0.5);f=b+q+c}return p});b=b[s](/\s*\-\s*/);if(e=="linear"){var g=b.shift();g=-S(g);if(isNaN(g))return null}var h=bx(b);if(!h)return null;a=a.shape||a.node;d=a.getElementsByTagName(I)[0]||cd(I);!d.parentNode&&a.appendChild(d);if(h[w]){d.on=true;d.method="none";d.color=h[0].color;d.color2=h[h[w]-1].color;var i=[];for(var j=0,k=h[w];j<k;j++)h[j].offset&&i[L](h[j].offset+q+h[j].color);d.colors&&(d.colors.value=i[w]?i[v]():"0% "+d.color);if(e=="radial"){d.type="gradientradial";d.focus="100%";d.focussize=f;d.focusposition=f}else{d.type="gradient";d.angle=(270-g)%360}}return 1};bN=function(b,c,d){var e=0,f=0,g=0,h=1;this[0]=b;this.id=a._oid++;this.node=b;b.raphael=this;this.X=0;this.Y=0;this.attrs={};this.Group=c;this.paper=d;this._={tx:0,ty:0,rt:{deg:0},sx:1,sy:1};!d.bottom&&(d.bottom=this);this.prev=d.top;d.top&&(d.top.next=this);d.top=this;this.next=null};bO=bN[e];bO.rotate=function(a,c,d){if(this.removed)return this;if(a==null){if(this._.rt.cx)return[this._.rt.deg,this._.rt.cx,this._.rt.cy][v](q);return this._.rt.deg}a=r(a)[s](b);if(a[w]-1){c=S(a[1]);d=S(a[2])}a=S(a[0]);c!=null?this._.rt.deg=a:this._.rt.deg+=a;d==null&&(c=null);this._.rt.cx=c;this._.rt.cy=d;this.setBox(this.attrs,c,d);this.Group.style.rotation=this._.rt.deg;return this};bO.setBox=function(a,b,c){if(this.removed)return this;var d=this.Group.style,e=this.shape&&this.shape.style||this.node.style;a=a||{};for(var g in a)a[f](g)&&(this.attrs[g]=a[g]);b=b||this._.rt.cx;c=c||this._.rt.cy;var h=this.attrs,i,j,k,l;switch(this.type){case"circle":i=h.cx-h.r;j=h.cy-h.r;k=l=h.r*2;break;case"ellipse":i=h.cx-h.rx;j=h.cy-h.ry;k=h.rx*2;l=h.ry*2;break;case"image":i=+h.x;j=+h.y;k=h.width||0;l=h.height||0;break;case"text":this.textpath.v=["m",Q(h.x),", ",Q(h.y-2),"l",Q(h.x)+1,", ",Q(h.y-2)][v](p);i=h.x-Q(this.W/2);j=h.y-this.H/2;k=this.W;l=this.H;break;case"rect":case"path":if(this.attrs.path){var m=bn(this.attrs.path);i=m.x;j=m.y;k=m.width;l=m.height}else{i=0;j=0;k=this.paper.width;l=this.paper.height}break;default:i=0;j=0;k=this.paper.width;l=this.paper.height;break}b=b==null?i+k/2:b;c=c==null?j+l/2:c;var n=b-this.paper.width/2,o=c-this.paper.height/2,q;d.left!=(q=n+"px")&&(d.left=q);d.top!=(q=o+"px")&&(d.top=q);this.X=ca[f](this.type)?-n:i;this.Y=ca[f](this.type)?-o:j;this.W=k;this.H=l;if(ca[f](this.type)){e.left!=(q=-n*b_+"px")&&(e.left=q);e.top!=(q=-o*b_+"px")&&(e.top=q)}else if(this.type=="text"){e.left!=(q=-n+"px")&&(e.left=q);e.top!=(q=-o+"px")&&(e.top=q)}else{d.width!=(q=this.paper.width+"px")&&(d.width=q);d.height!=(q=this.paper.height+"px")&&(d.height=q);e.left!=(q=i-n+"px")&&(e.left=q);e.top!=(q=j-o+"px")&&(e.top=q);e.width!=(q=k+"px")&&(e.width=q);e.height!=(q=l+"px")&&(e.height=q)}};bO.hide=function(){!this.removed&&(this.Group.style.display="none");return this};bO.show=function(){!this.removed&&(this.Group.style.display="block");return this};bO.getBBox=function(){if(this.removed)return this;if(ca[f](this.type))return bn(this.attrs.path);return{x:this.X+(this.bbx||0),y:this.Y,width:this.W,height:this.H}};bO.remove=function(){if(this.removed)return;bA(this,this.paper);this.node.parentNode.removeChild(this.node);this.Group.parentNode.removeChild(this.Group);this.shape&&this.shape.parentNode.removeChild(this.shape);for(var a in this)delete this[a];this.removed=true};bO.attr=function(b,c){if(this.removed)return this;if(b==null){var d={};for(var e in this.attrs)this.attrs[f](e)&&(d[e]=this.attrs[e]);this._.rt.deg&&(d.rotation=this.rotate());(this._.sx!=1||this._.sy!=1)&&(d.scale=this.scale());d.gradient&&d.fill=="none"&&(d.fill=d.gradient)&&delete d.gradient;return d}if(c==null&&a.is(b,"string")){if(b=="translation")return cz.call(this);if(b=="rotation")return this.rotate();if(b=="scale")return this.scale();if(b==I&&this.attrs.fill=="none"&&this.attrs.gradient)return this.attrs.gradient;return this.attrs[b]}if(this.attrs&&c==null&&a.is(b,G)){var g,h={};for(e=0,g=b[w];e<g;e++)h[b[e]]=this.attr(b[e]);return h}var i;if(c!=null){i={};i[b]=c}c==null&&a.is(b,"object")&&(i=b);if(i){for(var j in this.paper.customAttributes)if(this.paper.customAttributes[f](j)&&i[f](j)&&a.is(this.paper.customAttributes[j],"function")){var k=this.paper.customAttributes[j].apply(this,[][n](i[j]));this.attrs[j]=i[j];for(var l in k)k[f](l)&&(i[l]=k[l])}i.text&&this.type=="text"&&(this.node.string=i.text);bK(this,i);i.gradient&&(({circle:1,ellipse:1})[f](this.type)||r(i.gradient).charAt()!="r")&&bI(this,i.gradient);(!ca[f](this.type)||this._.rt.deg)&&this.setBox(this.attrs)}return this};bO.toFront=function(){!this.removed&&this.Group.parentNode[l](this.Group);this.paper.top!=this&&bB(this,this.paper);return this};bO.toBack=function(){if(this.removed)return this;if(this.Group.parentNode.firstChild!=this.Group){this.Group.parentNode.insertBefore(this.Group,this.Group.parentNode.firstChild);bC(this,this.paper)}return this};bO.insertAfter=function(a){if(this.removed)return this;a.constructor==cC&&(a=a[a.length-1]);a.Group.nextSibling?a.Group.parentNode.insertBefore(this.Group,a.Group.nextSibling):a.Group.parentNode[l](this.Group);bD(this,a,this.paper);return this};bO.insertBefore=function(a){if(this.removed)return this;a.constructor==cC&&(a=a[0]);a.Group.parentNode.insertBefore(this.Group,a.Group);bE(this,a,this.paper);return this};bO.blur=function(b){var c=this.node.runtimeStyle,d=c.filter;d=d.replace(bY,p);if(+b!==0){this.attrs.blur=b;c.filter=d+q+U+".Blur(pixelradius="+(+b||1.5)+")";c.margin=a.format("-{0}px 0 0 -{0}px",Q(+b||1.5))}else{c.filter=d;c.margin=0;delete this.attrs.blur}};bP=function(a,b,c,d){var e=cd("group"),f=cd("oval"),g=f.style;e.style.cssText="position:absolute;left:0;top:0;width:"+a.width+"px;height:"+a.height+"px";e.coordsize=b$;e.coordorigin=a.coordorigin;e[l](f);var h=new bN(f,e,a);h.type="circle";bK(h,{stroke:"#000",fill:"none"});h.attrs.cx=b;h.attrs.cy=c;h.attrs.r=d;h.setBox({x:b-d,y:c-d,width:d*2,height:d*2});a.canvas[l](e);return h};function cc(b,c,d,e,f){return f?a.format("M{0},{1}l{2},0a{3},{3},0,0,1,{3},{3}l0,{5}a{3},{3},0,0,1,{4},{3}l{6},0a{3},{3},0,0,1,{4},{4}l0,{7}a{3},{3},0,0,1,{3},{4}z",b+f,c,d-f*2,f,-f,e-f*2,f*2-d,f*2-e):a.format("M{0},{1}l{2},0,0,{3},{4},0z",b,c,d,e,-d)}bQ=function(a,b,c,d,e,f){var g=cc(b,c,d,e,f),h=a.path(g),i=h.attrs;h.X=i.x=b;h.Y=i.y=c;h.W=i.width=d;h.H=i.height=e;i.r=f;i.path=g;h.type="rect";return h};bR=function(a,b,c,d,e){var f=cd("group"),g=cd("oval"),h=g.style;f.style.cssText="position:absolute;left:0;top:0;width:"+a.width+"px;height:"+a.height+"px";f.coordsize=b$;f.coordorigin=a.coordorigin;f[l](g);var i=new bN(g,f,a);i.type="ellipse";bK(i,{stroke:"#000"});i.attrs.cx=b;i.attrs.cy=c;i.attrs.rx=d;i.attrs.ry=e;i.setBox({x:b-d,y:c-e,width:d*2,height:e*2});a.canvas[l](f);return i};bS=function(a,b,c,d,e,f){var g=cd("group"),h=cd("image");g.style.cssText="position:absolute;left:0;top:0;width:"+a.width+"px;height:"+a.height+"px";g.coordsize=b$;g.coordorigin=a.coordorigin;h.src=b;g[l](h);var i=new bN(h,g,a);i.type="image";i.attrs.src=b;i.attrs.x=c;i.attrs.y=d;i.attrs.w=e;i.attrs.h=f;i.setBox({x:c,y:d,width:e,height:f});a.canvas[l](g);return i};bT=function(b,c,d,e){var f=cd("group"),g=cd("shape"),h=g.style,i=cd("path"),j=i.style,k=cd("textpath");f.style.cssText="position:absolute;left:0;top:0;width:"+b.width+"px;height:"+b.height+"px";f.coordsize=b$;f.coordorigin=b.coordorigin;i.v=a.format("m{0},{1}l{2},{1}",Q(c*10),Q(d*10),Q(c*10)+1);i.textpathok=true;h.width=b.width;h.height=b.height;k.string=r(e);k.on=true;g[l](k);g[l](i);f[l](g);var m=new bN(k,f,b);m.shape=g;m.textpath=i;m.type="text";m.attrs.text=e;m.attrs.x=c;m.attrs.y=d;m.attrs.w=1;m.attrs.h=1;bK(m,{font:W.font,stroke:"none",fill:"#000"});m.setBox();b.canvas[l](f);return m};bU=function(a,b){var c=this.canvas.style;a==+a&&(a+="px");b==+b&&(b+="px");c.width=a;c.height=b;c.clip="rect(0 "+a+" "+b+" 0)";return this};var cd;g.createStyleSheet().addRule(".rvml","behavior:url(#default#VML)");try{!g.namespaces.rvml&&g.namespaces.add("rvml","urn:schemas-microsoft-com:vml");cd=function(a){return g.createElement("<rvml:"+a+" class=\"rvml\">")}}catch(a){cd=function(a){return g.createElement("<"+a+" xmlns=\"urn:schemas-microsoft.com:vml\" class=\"rvml\">")}}bV=function(){var b=by[m](0,arguments),c=b.container,d=b.height,e,f=b.width,h=b.x,i=b.y;if(!c)throw new Error("VML container not found.");var k=new j,n=k.canvas=g.createElement("div"),o=n.style;h=h||0;i=i||0;f=f||512;d=d||342;f==+f&&(f+="px");d==+d&&(d+="px");k.width=1000;k.height=1000;k.coordsize=b_*1000+q+b_*1000;k.coordorigin="0 0";k.span=g.createElement("span");k.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";n[l](k.span);o.cssText=a.format("top:0;left:0;width:{0};height:{1};display:inline-block;position:relative;clip:rect(0 {0} {1} 0);overflow:hidden",f,d);if(c==1){g.body[l](n);o.left=h+"px";o.top=i+"px";o.position="absolute"}else c.firstChild?c.insertBefore(n,c.firstChild):c[l](n);bz.call(k,k,a.fn);return k};k.clear=function(){this.canvas.innerHTML=p;this.span=g.createElement("span");this.span.style.cssText="position:absolute;left:-9999em;top:-9999em;padding:0;margin:0;line-height:1;display:inline;";this.canvas[l](this.span);this.bottom=this.top=null};k.remove=function(){this.canvas.parentNode.removeChild(this.canvas);for(var a in this)this[a]=bF(a);return true}}var ce=navigator.userAgent.match(/Version\\x2f(.*?)\s/);navigator.vendor=="Apple Computer, Inc."&&(ce&&ce[1]<4||navigator.platform.slice(0,2)=="iP")?k.safari=function(){var a=this.rect(-99,-99,this.width+99,this.height+99).attr({stroke:"none"});h.setTimeout(function(){a.remove()})}:k.safari=function(){};var cf=function(){this.returnValue=false},cg=function(){return this.originalEvent.preventDefault()},ch=function(){this.cancelBubble=true},ci=function(){return this.originalEvent.stopPropagation()},cj=(function(){{if(g.addEventListener)return function(a,b,c,d){var e=o&&u[b]?u[b]:b,g=function(e){if(o&&u[f](b))for(var g=0,h=e.targetTouches&&e.targetTouches.length;g<h;g++){if(e.targetTouches[g].target==a){var i=e;e=e.targetTouches[g];e.originalEvent=i;e.preventDefault=cg;e.stopPropagation=ci;break}}return c.call(d,e)};a.addEventListener(e,g,false);return function(){a.removeEventListener(e,g,false);return true}};if(g.attachEvent)return function(a,b,c,d){var e=function(a){a=a||h.event;a.preventDefault=a.preventDefault||cf;a.stopPropagation=a.stopPropagation||ch;return c.call(d,a)};a.attachEvent("on"+b,e);var f=function(){a.detachEvent("on"+b,e);return true};return f}}})(),ck=[],cl=function(a){var b=a.clientX,c=a.clientY,d=g.documentElement.scrollTop||g.body.scrollTop,e=g.documentElement.scrollLeft||g.body.scrollLeft,f,h=ck.length;while(h--){f=ck[h];if(o){var i=a.touches.length,j;while(i--){j=a.touches[i];if(j.identifier==f.el._drag.id){b=j.clientX;c=j.clientY;(a.originalEvent?a.originalEvent:a).preventDefault();break}}}else a.preventDefault();b+=e;c+=d;f.move&&f.move.call(f.move_scope||f.el,b-f.el._drag.x,c-f.el._drag.y,b,c,a)}},cm=function(b){a.unmousemove(cl).unmouseup(cm);var c=ck.length,d;while(c--){d=ck[c];d.el._drag={};d.end&&d.end.call(d.end_scope||d.start_scope||d.move_scope||d.el,b)}ck=[]};for(var cn=t[w];cn--;)(function(b){a[b]=bN[e][b]=function(c,d){if(a.is(c,"function")){this.events=this.events||[];this.events.push({name:b,f:c,unbind:cj(this.shape||this.node||g,b,c,d||this)})}return this};a["un"+b]=bN[e]["un"+b]=function(a){var c=this.events,d=c[w];while(d--)if(c[d].name==b&&c[d].f==a){c[d].unbind();c.splice(d,1);!c.length&&delete this.events;return this}return this}})(t[cn]);bO.hover=function(a,b,c,d){return this.mouseover(a,c).mouseout(b,d||c)};bO.unhover=function(a,b){return this.unmouseover(a).unmouseout(b)};bO.drag=function(b,c,d,e,f,h){this._drag={};this.mousedown(function(i){(i.originalEvent||i).preventDefault();var j=g.documentElement.scrollTop||g.body.scrollTop,k=g.documentElement.scrollLeft||g.body.scrollLeft;this._drag.x=i.clientX+k;this._drag.y=i.clientY+j;this._drag.id=i.identifier;c&&c.call(f||e||this,i.clientX+k,i.clientY+j,i);!ck.length&&a.mousemove(cl).mouseup(cm);ck.push({el:this,move:b,end:d,move_scope:e,start_scope:f,end_scope:h})});return this};bO.undrag=function(b,c,d){var e=ck.length;while(e--)ck[e].el==this&&(ck[e].move==b&&ck[e].end==d)&&ck.splice(e++,1);!ck.length&&a.unmousemove(cl).unmouseup(cm)};k.circle=function(a,b,c){return bP(this,a||0,b||0,c||0)};k.rect=function(a,b,c,d,e){return bQ(this,a||0,b||0,c||0,d||0,e||0)};k.ellipse=function(a,b,c,d){return bR(this,a||0,b||0,c||0,d||0)};k.path=function(b){b&&!a.is(b,F)&&!a.is(b[0],G)&&(b+=p);return bH(a.format[m](a,arguments),this)};k.image=function(a,b,c,d,e){return bS(this,a||"about:blank",b||0,c||0,d||0,e||0)};k.text=function(a,b,c){return bT(this,a||0,b||0,r(c))};k.set=function(a){arguments[w]>1&&(a=Array[e].splice.call(arguments,0,arguments[w]));return new cC(a)};k.setSize=bU;k.top=k.bottom=null;k.raphael=a;function co(){return this.x+q+this.y}bO.resetScale=function(){if(this.removed)return this;this._.sx=1;this._.sy=1;this.attrs.scale="1 1"};bO.scale=function(a,b,c,d){if(this.removed)return this;if(a==null&&b==null)return{x:this._.sx,y:this._.sy,toString:co};b=b||a;!(+b)&&(b=a);var e,f,g,h,i=this.attrs;if(a!=0){var j=this.getBBox(),k=j.x+j.width/2,l=j.y+j.height/2,m=B(a/this._.sx),o=B(b/this._.sy);c=+c||c==0?c:k;d=+d||d==0?d:l;var r=this._.sx>0,s=this._.sy>0,t=~(~(a/B(a))),u=~(~(b/B(b))),x=m*t,y=o*u,z=this.node.style,A=c+B(k-c)*x*(k>c==r?1:-1),C=d+B(l-d)*y*(l>d==s?1:-1),D=a*t>b*u?o:m;switch(this.type){case"rect":case"image":var E=i.width*m,F=i.height*o;this.attr({height:F,r:i.r*D,width:E,x:A-E/2,y:C-F/2});break;case"circle":case"ellipse":this.attr({rx:i.rx*m,ry:i.ry*o,r:i.r*D,cx:A,cy:C});break;case"text":this.attr({x:A,y:C});break;case"path":var G=bp(i.path),H=true,I=r?x:m,J=s?y:o;for(var K=0,L=G[w];K<L;K++){var M=G[K],N=V.call(M[0]);{if(N=="M"&&H)continue;H=false}if(N=="A"){M[G[K][w]-2]*=I;M[G[K][w]-1]*=J;M[1]*=m;M[2]*=o;M[5]=+(t+u?!(!(+M[5])):!(+M[5]))}else if(N=="H")for(var O=1,P=M[w];O<P;O++)M[O]*=I;else if(N=="V")for(O=1,P=M[w];O<P;O++)M[O]*=J;else for(O=1,P=M[w];O<P;O++)M[O]*=O%2?I:J}var Q=bn(G);e=A-Q.x-Q.width/2;f=C-Q.y-Q.height/2;G[0][1]+=e;G[0][2]+=f;this.attr({path:G});break}if(this.type in{text:1,image:1}&&(t!=1||u!=1))if(this.transformations){this.transformations[2]="scale("[n](t,",",u,")");this.node[R]("transform",this.transformations[v](q));e=t==-1?-i.x-(E||0):i.x;f=u==-1?-i.y-(F||0):i.y;this.attr({x:e,y:f});i.fx=t-1;i.fy=u-1}else{this.node.filterMatrix=U+".Matrix(M11="[n](t,", M12=0, M21=0, M22=",u,", Dx=0, Dy=0, sizingmethod='auto expand', filtertype='bilinear')");z.filter=(this.node.filterMatrix||p)+(this.node.filterOpacity||p)}else if(this.transformations){this.transformations[2]=p;this.node[R]("transform",this.transformations[v](q));i.fx=0;i.fy=0}else{this.node.filterMatrix=p;z.filter=(this.node.filterMatrix||p)+(this.node.filterOpacity||p)}i.scale=[a,b,c,d][v](q);this._.sx=a;this._.sy=b}return this};bO.clone=function(){if(this.removed)return null;var a=this.attr();delete a.scale;delete a.translation;return this.paper[this.type]().attr(a)};var cp={},cq=function(b,c,d,e,f,g,h,i,j){var k=0,l=100,m=[b,c,d,e,f,g,h,i].join(),n=cp[m],o,p;!n&&(cp[m]=n={data:[]});n.timer&&clearTimeout(n.timer);n.timer=setTimeout(function(){delete cp[m]},2000);if(j!=null){var q=cq(b,c,d,e,f,g,h,i);l=~(~q)*10}for(var r=0;r<l+1;r++){if(n.data[j]>r)p=n.data[r*l];else{p=a.findDotsAtSegment(b,c,d,e,f,g,h,i,r/l);n.data[r]=p}r&&(k+=C(C(o.x-p.x,2)+C(o.y-p.y,2),0.5));if(j!=null&&k>=j)return p;o=p}if(j==null)return k},cr=function(b,c){return function(d,e,f){d=bw(d);var g,h,i,j,k="",l={},m,n=0;for(var o=0,p=d.length;o<p;o++){i=d[o];if(i[0]=="M"){g=+i[1];h=+i[2]}else{j=cq(g,h,i[1],i[2],i[3],i[4],i[5],i[6]);if(n+j>e){if(c&&!l.start){m=cq(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);k+=["C",m.start.x,m.start.y,m.m.x,m.m.y,m.x,m.y];if(f)return k;l.start=k;k=["M",m.x,m.y+"C",m.n.x,m.n.y,m.end.x,m.end.y,i[5],i[6]][v]();n+=j;g=+i[5];h=+i[6];continue}if(!b&&!c){m=cq(g,h,i[1],i[2],i[3],i[4],i[5],i[6],e-n);return{x:m.x,y:m.y,alpha:m.alpha}}}n+=j;g=+i[5];h=+i[6]}k+=i}l.end=k;m=b?n:c?l:a.findDotsAtSegment(g,h,i[1],i[2],i[3],i[4],i[5],i[6],1);m.alpha&&(m={x:m.x,y:m.y,alpha:m.alpha});return m}},cs=cr(1),ct=cr(),cu=cr(0,1);bO.getTotalLength=function(){if(this.type!="path")return;if(this.node.getTotalLength)return this.node.getTotalLength();return cs(this.attrs.path)};bO.getPointAtLength=function(a){if(this.type!="path")return;return ct(this.attrs.path,a)};bO.getSubpath=function(a,b){if(this.type!="path")return;if(B(this.getTotalLength()-b)<"1e-6")return cu(this.attrs.path,a).end;var c=cu(this.attrs.path,b,1);return a?cu(c,a).end:c};a.easing_formulas={linear:function(a){return a},"<":function(a){return C(a,3)},">":function(a){return C(a-1,3)+1},"<>":function(a){a=a*2;if(a<1)return C(a,3)/2;a-=2;return(C(a,3)+2)/2},backIn:function(a){var b=1.70158;return a*a*((b+1)*a-b)},backOut:function(a){a=a-1;var b=1.70158;return a*a*((b+1)*a+b)+1},elastic:function(a){if(a==0||a==1)return a;var b=0.3,c=b/4;return C(2,-10*a)*y.sin((a-c)*(2*D)/b)+1},bounce:function(a){var b=7.5625,c=2.75,d;if(a<1/c)d=b*a*a;else if(a<2/c){a-=1.5/c;d=b*a*a+0.75}else if(a<2.5/c){a-=2.25/c;d=b*a*a+0.9375}else{a-=2.625/c;d=b*a*a+0.984375}return d}};var cv=[],cw=function(){var b=+(new Date);for(var c=0;c<cv[w];c++){var d=cv[c];if(d.stop||d.el.removed)continue;var e=b-d.start,g=d.ms,h=d.easing,i=d.from,j=d.diff,k=d.to,l=d.t,m=d.el,n={},o;if(e<g){var r=h(e/g);for(var s in i)if(i[f](s)){switch(X[s]){case"along":o=r*g*j[s];k.back&&(o=k.len-o);var t=ct(k[s],o);m.translate(j.sx-j.x||0,j.sy-j.y||0);j.x=t.x;j.y=t.y;m.translate(t.x-j.sx,t.y-j.sy);k.rot&&m.rotate(j.r+t.alpha,t.x,t.y);break;case E:o=+i[s]+r*g*j[s];break;case"colour":o="rgb("+[cy(Q(i[s].r+r*g*j[s].r)),cy(Q(i[s].g+r*g*j[s].g)),cy(Q(i[s].b+r*g*j[s].b))][v](",")+")";break;case"path":o=[];for(var u=0,x=i[s][w];u<x;u++){o[u]=[i[s][u][0]];for(var y=1,z=i[s][u][w];y<z;y++)o[u][y]=+i[s][u][y]+r*g*j[s][u][y];o[u]=o[u][v](q)}o=o[v](q);break;case"csv":switch(s){case"translation":var A=r*g*j[s][0]-l.x,B=r*g*j[s][1]-l.y;l.x+=A;l.y+=B;o=A+q+B;break;case"rotation":o=+i[s][0]+r*g*j[s][0];i[s][1]&&(o+=","+i[s][1]+","+i[s][2]);break;case"scale":o=[+i[s][0]+r*g*j[s][0],+i[s][1]+r*g*j[s][1],2 in k[s]?k[s][2]:p,3 in k[s]?k[s][3]:p][v](q);break;case"clip-rect":o=[];u=4;while(u--)o[u]=+i[s][u]+r*g*j[s][u];break}break;default:var C=[].concat(i[s]);o=[];u=m.paper.customAttributes[s].length;while(u--)o[u]=+C[u]+r*g*j[s][u];break}n[s]=o}m.attr(n);m._run&&m._run.call(m)}else{if(k.along){t=ct(k.along,k.len*!k.back);m.translate(j.sx-(j.x||0)+t.x-j.sx,j.sy-(j.y||0)+t.y-j.sy);k.rot&&m.rotate(j.r+t.alpha,t.x,t.y)}(l.x||l.y)&&m.translate(-l.x,-l.y);k.scale&&(k.scale+=p);m.attr(k);cv.splice(c--,1)}}a.svg&&m&&m.paper&&m.paper.safari();cv[w]&&setTimeout(cw)},cx=function(b,c,d,e,f){var g=d-e;c.timeouts.push(setTimeout(function(){a.is(f,"function")&&f.call(c);c.animate(b,g,b.easing)},e))},cy=function(a){return z(A(a,255),0)},cz=function(a,b){if(a==null)return{x:this._.tx,y:this._.ty,toString:co};this._.tx+=+a;this._.ty+=+b;switch(this.type){case"circle":case"ellipse":this.attr({cx:+a+this.attrs.cx,cy:+b+this.attrs.cy});break;case"rect":case"image":case"text":this.attr({x:+a+this.attrs.x,y:+b+this.attrs.y});break;case"path":var c=bp(this.attrs.path);c[0][1]+=+a;c[0][2]+=+b;this.attr({path:c});break}return this};bO.animateWith=function(a,b,c,d,e){for(var f=0,g=cv.length;f<g;f++)cv[f].el.id==a.id&&(b.start=cv[f].start);return this.animate(b,c,d,e)};bO.animateAlong=cA();bO.animateAlongBack=cA(1);function cA(b){return function(c,d,e,f){var g={back:b};a.is(e,"function")?f=e:g.rot=e;c&&c.constructor==bN&&(c=c.attrs.path);c&&(g.along=c);return this.animate(g,d,f)}}function cB(a,b,c,d,e,f){var g=3*b,h=3*(d-b)-g,i=1-g-h,j=3*c,k=3*(e-c)-j,l=1-j-k;function m(a){return((i*a+h)*a+g)*a}function n(a,b){var c=o(a,b);return((l*c+k)*c+j)*c}function o(a,b){var c,d,e,f,j,k;for(e=a,k=0;k<8;k++){f=m(e)-a;if(B(f)<b)return e;j=(3*i*e+2*h)*e+g;if(B(j)<0.000001)break;e=e-f/j}c=0;d=1;e=a;if(e<c)return c;if(e>d)return d;while(c<d){f=m(e);if(B(f-a)<b)return e;a>f?c=e:d=e;e=(d-c)/2+c}return e}return n(a,1/(200*f))}bO.onAnimation=function(a){this._run=a||0;return this};bO.animate=function(c,d,e,g){var h=this;h.timeouts=h.timeouts||[];if(a.is(e,"function")||!e)g=e||null;if(h.removed){g&&g.call(h);return h}var i={},j={},k=false,l={};for(var m in c)if(c[f](m)){if(X[f](m)||h.paper.customAttributes[f](m)){k=true;i[m]=h.attr(m);i[m]==null&&(i[m]=W[m]);j[m]=c[m];switch(X[m]){case"along":var n=cs(c[m]),o=ct(c[m],n*!(!c.back)),p=h.getBBox();l[m]=n/d;l.tx=p.x;l.ty=p.y;l.sx=o.x;l.sy=o.y;j.rot=c.rot;j.back=c.back;j.len=n;c.rot&&(l.r=S(h.rotate())||0);break;case E:l[m]=(j[m]-i[m])/d;break;case"colour":i[m]=a.getRGB(i[m]);var q=a.getRGB(j[m]);l[m]={r:(q.r-i[m].r)/d,g:(q.g-i[m].g)/d,b:(q.b-i[m].b)/d};break;case"path":var t=bw(i[m],j[m]);i[m]=t[0];var u=t[1];l[m]=[];for(var v=0,x=i[m][w];v<x;v++){l[m][v]=[0];for(var y=1,z=i[m][v][w];y<z;y++)l[m][v][y]=(u[v][y]-i[m][v][y])/d}break;case"csv":var A=r(c[m])[s](b),B=r(i[m])[s](b);switch(m){case"translation":i[m]=[0,0];l[m]=[A[0]/d,A[1]/d];break;case"rotation":i[m]=B[1]==A[1]&&B[2]==A[2]?B:[0,A[1],A[2]];l[m]=[(A[0]-i[m][0])/d,0,0];break;case"scale":c[m]=A;i[m]=r(i[m])[s](b);l[m]=[(A[0]-i[m][0])/d,(A[1]-i[m][1])/d,0,0];break;case"clip-rect":i[m]=r(i[m])[s](b);l[m]=[];v=4;while(v--)l[m][v]=(A[v]-i[m][v])/d;break}j[m]=A;break;default:A=[].concat(c[m]);B=[].concat(i[m]);l[m]=[];v=h.paper.customAttributes[m][w];while(v--)l[m][v]=((A[v]||0)-(B[v]||0))/d;break}}}if(k){var G=a.easing_formulas[e];if(!G){G=r(e).match(P);if(G&&G[w]==5){var H=G;G=function(a){return cB(a,+H[1],+H[2],+H[3],+H[4],d)}}else G=function(a){return a}}cv.push({start:c.start||+(new Date),ms:d,easing:G,from:i,diff:l,to:j,el:h,t:{x:0,y:0}});a.is(g,"function")&&(h._ac=setTimeout(function(){g.call(h)},d));cv[w]==1&&setTimeout(cw)}else{var C=[],D;for(var F in c)if(c[f](F)&&Z.test(F)){m={value:c[F]};F=="from"&&(F=0);F=="to"&&(F=100);m.key=T(F,10);C.push(m)}C.sort(be);C[0].key&&C.unshift({key:0,value:h.attrs});for(v=0,x=C[w];v<x;v++)cx(C[v].value,h,d/100*C[v].key,d/100*(C[v-1]&&C[v-1].key||0),C[v-1]&&C[v-1].value.callback);D=C[C[w]-1].value.callback;D&&h.timeouts.push(setTimeout(function(){D.call(h)},d))}return this};bO.stop=function(){for(var a=0;a<cv.length;a++)cv[a].el.id==this.id&&cv.splice(a--,1);for(a=0,ii=this.timeouts&&this.timeouts.length;a<ii;a++)clearTimeout(this.timeouts[a]);this.timeouts=[];clearTimeout(this._ac);delete this._ac;return this};bO.translate=function(a,b){return this.attr({translation:a+" "+b})};bO[H]=function(){return"Raphaël’s object"};a.ae=cv;var cC=function(a){this.items
Download .txt
gitextract_m2dfrshv/

├── .gitignore
├── LICENSE
├── README.md
├── cpp/
│   ├── samples/
│   │   ├── Makefile
│   │   ├── png_readwrite.cpp
│   │   ├── polyline
│   │   ├── polyline.cpp
│   │   ├── readme.txt
│   │   ├── samples
│   │   ├── samples.cpp
│   │   ├── segment
│   │   ├── segment.cpp
│   │   └── test1_base.cpp
│   ├── vaser/
│   │   ├── agg_curve4.cpp
│   │   ├── backend.h
│   │   ├── color.h
│   │   ├── curve.cpp
│   │   ├── gradient.cpp
│   │   ├── opengl.cpp
│   │   ├── point.h
│   │   ├── polyline.cpp
│   │   ├── vaser.cpp
│   │   ├── vaser.h
│   │   └── vertex_array_holder.h
│   └── workbench/
│       ├── base.html
│       ├── dragger.js
│       ├── drawer.js
│       ├── gradient_along_path.html
│       ├── knife_cut_test
│       ├── knife_cut_test.cpp
│       ├── layout.css
│       ├── outward_vector.html
│       ├── quad_reflex.html
│       ├── raphael-min.js
│       ├── readme.txt
│       ├── triangle_knife_cut.js
│       ├── triangles_dual_knife_cut.html
│       ├── triangles_knife_cut.html
│       └── vector_arc.html
├── csharp/
│   ├── Assets/
│   │   ├── Demo/
│   │   │   ├── Demo.cs
│   │   │   ├── Demo.cs.meta
│   │   │   ├── demo.unity
│   │   │   └── demo.unity.meta
│   │   ├── Demo.meta
│   │   ├── Resources/
│   │   │   ├── Vaser/
│   │   │   │   ├── Fade.mat
│   │   │   │   ├── Fade.mat.meta
│   │   │   │   ├── Fade.shader
│   │   │   │   └── Fade.shader.meta
│   │   │   └── Vaser.meta
│   │   ├── Resources.meta
│   │   ├── Vaser/
│   │   │   ├── Gradient.cs
│   │   │   ├── Gradient.cs.meta
│   │   │   ├── Polybezier.cs
│   │   │   ├── Polybezier.cs.meta
│   │   │   ├── Polyline.cs
│   │   │   ├── Polyline.cs.meta
│   │   │   ├── Vec2Ext.cs
│   │   │   ├── Vec2Ext.cs.meta
│   │   │   ├── VertexArrayHolder.cs
│   │   │   └── VertexArrayHolder.cs.meta
│   │   └── Vaser.meta
│   ├── Docs/
│   │   └── README.md
│   ├── Packages/
│   │   └── manifest.json
│   ├── ProjectSettings/
│   │   ├── AudioManager.asset
│   │   ├── ClusterInputManager.asset
│   │   ├── DynamicsManager.asset
│   │   ├── EditorBuildSettings.asset
│   │   ├── EditorSettings.asset
│   │   ├── GraphicsSettings.asset
│   │   ├── InputManager.asset
│   │   ├── NavMeshAreas.asset
│   │   ├── Physics2DSettings.asset
│   │   ├── PresetManager.asset
│   │   ├── ProjectSettings.asset
│   │   ├── ProjectVersion.txt
│   │   ├── QualitySettings.asset
│   │   ├── TagManager.asset
│   │   ├── TimeManager.asset
│   │   ├── UnityConnectSettings.asset
│   │   ├── VFXManager.asset
│   │   └── XRSettings.asset
│   └── README.md
└── docs/
    ├── API.html
    ├── getting_started.html
    ├── index.html
    └── style.css
Download .txt
SYMBOL INDEX (268 symbols across 27 files)

FILE: cpp/samples/png_readwrite.cpp
  function save_png (line 4) | int	// 0 on failure, 1 on success

FILE: cpp/samples/polyline.cpp
  type VASEr (line 18) | namespace VASEr
    type Vec2 (line 20) | struct Vec2 { double x,y;}
    type Color (line 21) | struct Color { float r,g,b,a;}
  function line_update (line 50) | void line_update()
  function line_init (line 81) | void line_init( int N)
  function get_joint_type (line 139) | char get_joint_type()
  function get_cap_type (line 150) | char get_cap_type()
  function np_cb (line 172) | void np_cb(Fl_Widget* W, void*)
  function line_bezier (line 185) | void line_bezier(Fl_Widget* W, void*)
  function drag_cb (line 189) | void drag_cb(Fl_Widget* W, void*)
  function exportimg_cb (line 194) | void exportimg_cb(Fl_Widget* W, void*)
  function make_form (line 204) | void make_form()
  function test_draw (line 332) | void test_draw()
  function main (line 386) | int main(int argc, char **argv)

FILE: cpp/samples/samples.cpp
  type VASEr (line 14) | namespace VASEr
    type Vec2 (line 16) | struct Vec2 { double x,y;}
    type Color (line 17) | struct Color { float r,g,b,a;}
  function drag_cb (line 29) | void drag_cb(Fl_Widget* W, void*)
  function make_form (line 34) | void make_form()
  function sample0 (line 43) | void sample0()
  function sample1 (line 54) | void sample1()
  function sample2 (line 75) | void sample2()
  function sample3 (line 89) | void sample3()
  function sample4 (line 116) | void sample4()
  function sample5 (line 127) | void sample5()
  function sample6 (line 158) | void sample6()
  function sample7 (line 161) | void sample7()
  function sample8 (line 164) | void sample8()
  function sample9 (line 167) | void sample9()
  function test_draw (line 170) | void test_draw()
  function main (line 189) | int main(int argc, char **argv)

FILE: cpp/samples/segment.cpp
  type VASEr (line 16) | namespace VASEr
    type Vec2 (line 18) | struct Vec2 { double x,y;}
    type Color (line 19) | struct Color { float r,g,b,a;}
  function get_cap_type (line 34) | char get_cap_type()
  function drag_cb (line 47) | void drag_cb(Fl_Widget* W, void*)
  function make_form (line 51) | void make_form()
  function test_draw (line 105) | void test_draw()
  function main (line 203) | int main(int argc, char **argv)

FILE: cpp/samples/test1_base.cpp
  class Gl_Window (line 7) | class Gl_Window : public Fl_Gl_Window
    method Gl_Window (line 25) | Gl_Window(int x,int y,int w,int h,const char *l=0)
    method Gl_Window (line 27) | Gl_Window(int w,int h,const char *l=0)
    method set_drag_target (line 29) | void set_drag_target( Vec2* target, int size_of_target)

FILE: cpp/vaser/agg_curve4.cpp
  function calc_sq_distance (line 73) | double calc_sq_distance(double x1, double y1, double x2, double y2)
  function recursive_bezier (line 80) | void recursive_bezier(   double x1, double y1,
  function curve4_div (line 304) | int curve4_div(double x1, double y1,

FILE: cpp/vaser/backend.h
  function class (line 1) | class backend

FILE: cpp/vaser/color.h
  function Color_valid_range (line 11) | bool Color_valid_range(float t)
  function color_max (line 76) | float color_max( float r,float g,float b)
  function color_min (line 80) | float color_min( float r, float g, float b)
  function RGBtoHSV (line 84) | void RGBtoHSV( float r, float g, float b, float *h, float *s, float *v )
  function HSVtoRGB (line 112) | void HSVtoRGB( float *r, float *g, float *b, float h, float s, float v )

FILE: cpp/vaser/curve.cpp
  type VASErin (line 1) | namespace VASErin
    class polyline_buffer (line 4) | class polyline_buffer
      method polyline_buffer (line 14) | polyline_buffer(): P(),C(),W(),L()
      method point (line 20) | void point(double x, double y)
      method point (line 25) | void point(Vec2 V)
      method point_cb (line 29) | static void point_cb(void* obj, double x, double y)
      method vertex (line 35) | void vertex(Vec2 V, Color cc)
      method vertex (line 39) | void vertex(Vec2 V, Color cc, double ww)
      method set_color (line 43) | void set_color(Color cc)
      method set_weight (line 50) | void set_weight(double ww)
      method gradient (line 57) | void gradient( const gradient* grad)
      method draw (line 61) | void draw(const polyline_opt* options)
      method addvertex (line 69) | bool addvertex(const Vec2& V, const Color* cc=0, double ww=0.0)
    type polybezier_inopt (line 110) | struct polybezier_inopt
    function polybezier (line 116) | void polybezier( const Vec2* P, const gradient* grad, int length, cons...
  function polybezier (line 158) | void polybezier( const Vec2* P, const gradient* grad, int length, const ...
  function polybezier (line 163) | void polybezier( const Vec2* P, Color cc, double ww, int length, const p...

FILE: cpp/vaser/gradient.cpp
  type VASErin (line 1) | namespace VASErin
    function grad_getstep (line 4) | double grad_getstep(double A,double B,double t,double T)
    function gradient_apply (line 9) | void gradient_apply(const gradient* gradp, Color* C, double* W, const ...

FILE: cpp/vaser/opengl.cpp
  type VASErin (line 3) | namespace VASErin
    function swap_pixel (line 44) | void swap_pixel(unsigned char* img, int w, int x1,int y1, int x2,int y2)
  function Image (line 90) | Image renderer::get_image()

FILE: cpp/vaser/point.h
  function class (line 1) | class Point : public Vec2
  function clear (line 9) | void clear()			{ x=0.0; y=0.0;}
  function set (line 10) | void set(double X, double Y)	{ x=X;   y=Y;}
  function Vec2 (line 12) | Vec2 vec() {
  function signed_area (line 27) | static double signed_area(const Point& P1, const Point& P2, const Point&...
  function Point (line 80) | Point dot_prod( const Point& b) const //return dot product
  function opposite (line 86) | void opposite()
  function opposite_of (line 91) | void opposite_of( const Point& a)
  function normalize (line 96) | double normalize()
  function perpen (line 105) | void perpen() //perpendicular: anti-clockwise 90 degrees
  function follow_signs (line 111) | void follow_signs( const Point& a)
  function negligible (line 120) | static inline bool negligible( double M)
  function negligible (line 124) | bool negligible() const
  function intersecting (line 140) | static bool intersecting( const Point& A, const Point& B,
  function distance_squared (line 147) | static double distance_squared( const Point& A, const Point& B)
  function distance (line 153) | static inline double distance( const Point& A, const Point& B)
  function Point (line 157) | static Point midpoint( const Point& A, const Point& B)
  function opposite_quadrant (line 161) | static bool opposite_quadrant( const Point& P1, const Point& P2)
  function anchor_outward_D (line 182) | static inline bool anchor_outward_D( Point& V, const Point& b, const Poi...
  function anchor_inward (line 199) | static void anchor_inward( Point& V, const Point& b, const Point& c)

FILE: cpp/vaser/polyline.cpp
  type VASErin (line 1) | namespace VASErin
    function determine_t_r (line 39) | void determine_t_r ( double w, double& t, double& R)
    function get_PLJ_round_dangle (line 75) | float get_PLJ_round_dangle(float t, float r)
    function make_T_R_C (line 88) | void make_T_R_C( Point& P1, Point& P2, Point* T, Point* R, Point* C,
    function same_side_of_line (line 131) | void same_side_of_line( Point& V, const Point& ref, const Point& a, co...
    type st_polyline (line 141) | struct st_polyline
    type st_anchor (line 171) | struct st_anchor
    function inner_arc (line 183) | void inner_arc( vertex_array_holder& hold, const Point& P,
    function vectors_to_arc (line 316) | void vectors_to_arc( vertex_array_holder& hold, const Point& P,
    function annotate (line 341) | void annotate( const Point& P, Color cc, int I=-1)
    function annotate (line 360) | void annotate( const Point& P)
    function draw_vector (line 365) | void draw_vector( const Point& P, const Point& V, const char* name)
    function printpoint (line 381) | void printpoint( const Point& P, const char* name)
    function push_quad_ (line 432) | void push_quad_( short line, vertex_array_holder& core,
    function push_quadf_ (line 446) | void push_quadf_( short line, vertex_array_holder& core,
    type st_knife_cut (line 464) | struct st_knife_cut
    function triangle_knife_cut (line 475) | int triangle_knife_cut( const Point& kn1, const Point& kn2, const Poin...
    function vah_knife_cut (line 609) | void vah_knife_cut( vertex_array_holder& core, //serves as both input ...
    function vah_N_knife_cut (line 665) | void vah_N_knife_cut( vertex_array_holder& in, vertex_array_holder& out,
    function anchor_late (line 752) | void anchor_late( const Vec2* P, const Color* C, st_polyline* SL,
    function anchor_cap (line 1018) | void anchor_cap( const Vec2* P, const Color* C, st_polyline* SL,
    function segment_late (line 1138) | void segment_late( const Vec2* P, const Color* C, st_polyline* SL,
    function segment (line 1274) | void segment( st_anchor& SA, const polyline_opt* options,  bool cap_fi...
    function anchor (line 1359) | int anchor( st_anchor& SA, const polyline_opt* options, bool cap_first...
    class circular_array (line 1722) | class circular_array
      method circular_array (line 1728) | circular_array(int size_) : size(size_)
      method push (line 1739) | void push( T obj)
      method get_size (line 1745) | int get_size() const
      method get_i (line 1748) | int get_i( int i) const //get valid index relative to current
      method move (line 1762) | void move( int i) //move current relatively
      method T (line 1767) | T& operator[] (int i) //get element at relative position
    type polyline_inopt (line 1774) | struct polyline_inopt
    function poly_point_inter (line 1785) | void poly_point_inter( const Point* P, const Color* C, const double* W...
    function polyline_approx (line 1813) | void polyline_approx(
    function polyline_exact (line 1934) | void polyline_exact(
    function polyline_range (line 2002) | void polyline_range(
    function polyline (line 2026) | void polyline(
  function polyline (line 2124) | void polyline( const Vec2* P, const Color* C, const double* W, int lengt...
  function polyline (line 2128) | void polyline( const Vec2* P, Color C, double W, int length, const polyl...
  function polyline (line 2135) | void polyline( const Vec2* P, const Color* C, double W, int length, cons...
  function polyline (line 2141) | void polyline( const Vec2* P, Color C, const double* W, int length, cons...
  function segment (line 2148) | void segment(  Vec2 P1, Vec2 P2, Color C1, Color C2, double W1, double W...
  function segment (line 2157) | void segment(  Vec2 P1, Vec2 P2, Color C, double W, const polyline_opt* ...
  function segment (line 2164) | void segment(  Vec2 P1, Vec2 P2, Color C1, Color C2, double W, const pol...
  function segment (line 2173) | void segment(  Vec2 P1, Vec2 P2, Color C, double W1, double W2, const po...

FILE: cpp/vaser/vaser.cpp
  type VASEr (line 16) | namespace VASEr
    type VASErin (line 18) | namespace VASErin
      class vertex_array_holder (line 25) | class vertex_array_holder

FILE: cpp/vaser/vaser.h
  function namespace (line 14) | namespace VASEr

FILE: cpp/vaser/vertex_array_holder.h
  function class (line 4) | class vertex_array_holder
  function push (line 138) | void push( const vertex_array_holder& hold)
  function Point (line 170) | Point get(int i)
  function Color (line 177) | Color get_color(int b)
  function repeat_last_push (line 193) | void repeat_last_push()
  function jump (line 209) | void jump() //to make a jump in triangle strip by degenerated triangles
  function draw (line 217) | void draw()
  function draw_triangles (line 221) | void draw_triangles()
  function swap (line 248) | void swap(vertex_array_holder& B)

FILE: cpp/workbench/dragger.js
  function dragger (line 1) | function dragger(TP,div_id)

FILE: cpp/workbench/drawer.js
  function drawer (line 1) | function drawer()

FILE: cpp/workbench/knife_cut_test.cpp
  type VASEr (line 16) | namespace VASEr
    type Vec2 (line 18) | struct Vec2 { double x,y;}
    type Color (line 19) | struct Color { float r,g,b,a;}
  function line_update (line 42) | void line_update()
  function line_init (line 61) | void line_init( int)
  function drag_cb (line 75) | void drag_cb(Fl_Widget* W, void*)
  function nk_cb (line 79) | void nk_cb(Fl_Widget* W, void*)
  function N_knife (line 83) | int N_knife()
  function make_form (line 92) | void make_form()
  function draw_triangles_outline (line 119) | void draw_triangles_outline( vertex_array_holder& tris)
  function test_draw (line 136) | void test_draw()
  function main (line 170) | int main(int argc, char **argv)

FILE: cpp/workbench/raphael-min.js
  function a (line 7) | function a(){if(a.is(arguments[0],G)){var b=arguments[0],d=bV[m](a,b.spl...
  function bh (line 7) | function bh(){var a=[],b=0;for(;b<32;b++)a[b]=(~(~(y.random()*16)))[H](1...
  function bm (line 7) | function bm(a,b,c){function d(){var g=Array[e].slice.call(arguments,0),h...
  function cc (line 7) | function cc(b,c,d,e,f){return f?a.format("M{0},{1}l{2},0a{3},{3},0,0,1,{...
  function co (line 7) | function co(){return this.x+q+this.y}
  function cA (line 7) | function cA(b){return function(c,d,e,f){var g={back:b};a.is(e,"function"...
  function cB (line 7) | function cB(a,b,c,d,e,f){var g=3*b,h=3*(d-b)-g,i=1-g-h,j=3*c,k=3*(e-c)-j...

FILE: cpp/workbench/triangle_knife_cut.js
  function triangle_knife_cut (line 1) | function triangle_knife_cut(p1,p2,p3,kn1,kn2,kn3)
  function signed_area (line 71) | function signed_area(p1,p2,p3)
  function intersect (line 76) | function intersect( P1,P2,  //line 1

FILE: csharp/Assets/Demo/Demo.cs
  class Demo (line 5) | public class Demo : MonoBehaviour {
    method Start (line 12) | private void Start() {
    method Update (line 148) | void Update()
    method ComputeSineCurve (line 188) | private List<Vector2> ComputeSineCurve(float time, float amplitude)

FILE: csharp/Assets/Vaser/Gradient.cs
  class Gradient (line 6) | public class Gradient
    method Gradient (line 20) | public Gradient()
    method Gradient (line 25) | public Gradient(List<Stop> stopss)
    method Gradient (line 30) | public Gradient(Color cc, float ww)
    type Stop (line 37) | public struct Stop
      method Stop (line 44) | public Stop(float tt, Color cc)
      method Stop (line 52) | public Stop(float tt, float ww)
    method Apply (line 61) | public void Apply(List<Color> C, List<float> W, List<float> L, int lim...
    method ColorBetween (line 171) | public static Color ColorBetween(Color A, Color B, float t)

FILE: csharp/Assets/Vaser/Polybezier.cs
  class Polybezier (line 6) | public class Polybezier
    class Opt (line 10) | public class Opt
    method Polybezier (line 15) | public Polybezier(List<Vector2> P, Color cc, float ww, Opt opt)
    method Polybezier (line 21) | public Polybezier(List<Vector2> P, Gradient grad, Opt opt)
    method Render (line 48) | public Polyline Render(Polyline.Opt opt)
    class Buffer (line 53) | public class Buffer
      method Buffer (line 61) | public Buffer()
      method Gradient (line 71) | public void Gradient(Gradient grad)
      method Render (line 76) | public Polyline Render(Polyline.Opt opt)
      method AddPoint (line 86) | public void AddPoint(float x, float y)
      method AddPoint (line 91) | public void AddPoint(double x, double y)
      method AddPoint (line 96) | public void AddPoint(Vector2 V)
      method AddPoints (line 101) | public void AddPoints(List<Vector2> points)
      method AddVertex (line 109) | private bool AddVertex(Vector2 V, Color cc, float ww)
    method RecursiveBezier (line 196) | private void RecursiveBezier(
    method Curve4Div (line 408) | private void Curve4Div(

FILE: csharp/Assets/Vaser/Polyline.cs
  class Polyline (line 6) | public class Polyline
    class Opt (line 10) | public class Opt
    class Inopt (line 36) | public class Inopt
      method ShallowCopy (line 47) | public Inopt ShallowCopy()
    method Polyline (line 53) | public Polyline()
    method Polyline (line 58) | public Polyline(
    method Polyline (line 65) | public Polyline(
    method Polyline (line 74) | public Polyline(
    method GetMesh (line 149) | public Mesh GetMesh()
    method Append (line 159) | public void Append(Polyline polyline)
    type StPolyline (line 164) | private struct StPolyline
    class StAnchor (line 190) | private class StAnchor
    method PolyPointInter (line 201) | private static void PolyPointInter(
    method PolylineApprox (line 227) | private static void PolylineApprox(
    method PolylineExact (line 302) | private static void PolylineExact(
    method PolylineRange (line 367) | private static void PolylineRange(
    method DrawTriangles (line 393) | private static void DrawTriangles(VertexArrayHolder triangles, VertexA...
    method DrawDigit (line 423) | private static void DrawDigit(VertexArrayHolder holder, Vector2 pp, fl...
    method DetermineTr (line 486) | private static void DetermineTr(float w, ref float t, ref float R, flo...
    method MakeTrc (line 519) | private static void MakeTrc(
    method Segment (line 545) | private static void Segment(StAnchor SA, Opt opt, bool capFirst, bool ...
    method SegmentLate (line 630) | private static void SegmentLate(
    method Anchor (line 737) | private static void Anchor(StAnchor SA, Opt opt, bool capFirst, bool c...
    method AnchorLate (line 971) | private static void AnchorLate(
    method VectorsToArc (line 1046) | private static void VectorsToArc(
    method GetPljRoundDangle (line 1160) | private static float GetPljRoundDangle(float t, float r, float scale)

FILE: csharp/Assets/Vaser/Vec2Ext.cs
  class Vec2Ext (line 5) | public static class Vec2Ext
    method Length (line 7) | public static float Length(this Vector2 a)
    method SignedArea (line 11) | public static float SignedArea(Vector2 P1, Vector2 P2, Vector2 P3)
    method Dot (line 15) | public static void Dot(Vector2 a, Vector2 b, ref Vector2 o) //dot prod...
    method Opposite (line 20) | public static void Opposite(ref Vector2 a)
    method Normalize (line 25) | public static float Normalize(ref Vector2 a)
    method Perpen (line 35) | public static void Perpen(ref Vector2 a) //perpendicular: anti-clockwi...
    method FollowSigns (line 41) | public static void FollowSigns(ref Vector2 a, Vector2 b)
    method Negligible (line 48) | public static bool Negligible(float M)
    method Negligible (line 53) | public static bool Negligible(double M)
    method Negligible (line 58) | public static bool Negligible(this Vector2 a)
    method IsZero (line 62) | public static bool IsZero(this Vector2 a)
    method Intersecting (line 66) | public static bool Intersecting(Vector2 A, Vector2 B, Vector2 C, Vecto...
    method OppositeQuadrant (line 70) | public static bool OppositeQuadrant(Vector2 P1, Vector2 P2)
    method AnchorOutward (line 92) | public static bool AnchorOutward(ref Vector2 V, Vector2 b, Vector2 c, ...
    method Intersect (line 107) | public static int Intersect(
    method Octant (line 159) | public static int Octant(this Vector2 a)

FILE: csharp/Assets/Vaser/VertexArrayHolder.cs
  class VertexArrayHolder (line 6) | public class VertexArrayHolder
    method GetCount (line 22) | public int GetCount()
    method SetGlDrawMode (line 27) | public void SetGlDrawMode(int gl_draw_mode)
    method Push (line 32) | public int Push(Vector2 P, Color cc, float fade0=0)
    method PushF (line 47) | public int PushF(Vector2 P, Color C)
    method Push3 (line 53) | public void Push3(
    method Push4 (line 63) | public void Push4(
    method Dot (line 73) | public void Dot(Vector2 P, float size)
    method Push (line 86) | public void Push(VertexArrayHolder hold)
    method Get (line 109) | public Vector2 Get(int i)
    method GetColor (line 117) | public Color GetColor(int b)
    method GetRelativeEnd (line 122) | public Vector2 GetRelativeEnd(int di = -1)
    method RepeatLastPush (line 132) | public void RepeatLastPush()
    method Jump (line 138) | public void Jump() //to make a jump in triangle strip by degenerated t...
    method GetVertices (line 147) | public List<Vector3> GetVertices()
    method GetTriangles (line 152) | public List<int> GetTriangles()
    method GetUVs (line 165) | public List<Vector4> GetUVs()
    method GetColors (line 305) | public List<Color> GetColors()
Condensed preview — 86 files, each showing path, character count, and a content snippet. Download the .json file or copy for the full structured content (425K chars).
[
  {
    "path": ".gitignore",
    "chars": 985,
    "preview": "# This .gitignore file should be placed at the root of your Unity project directory\n#\n# Get latest from https://github.c"
  },
  {
    "path": "LICENSE",
    "chars": 1520,
    "preview": "BSD 3-Clause License\n\nCopyright (c) 2023 TSANG, Hao Fung (tyt2y7@gmail.com)\n\nRedistribution and use in source and binary"
  },
  {
    "path": "README.md",
    "chars": 4459,
    "preview": "> This project was started many, many years ago. The C++ implementation is a pre-OpenGL 3 relic. The C# implementation i"
  },
  {
    "path": "cpp/samples/Makefile",
    "chars": 728,
    "preview": "CPP = polyline.cpp segment.cpp samples.cpp\nBIN = $(CPP:.cpp=)\nVASER = ../vaser/*\n\nall: $(BIN)\n\npolyline: polyline.cpp pn"
  },
  {
    "path": "cpp/samples/png_readwrite.cpp",
    "chars": 4327,
    "preview": "#include <stdio.h>\n#include <png.h>\n\nint\t// 0 on failure, 1 on success\nsave_png(const char *filename,\t// filename\n\tunsig"
  },
  {
    "path": "cpp/samples/polyline.cpp",
    "chars": 10781,
    "preview": "/*config.h is generated by fltk in your system\n * this file is used with fltk 1.3 with gl enabled.\n * compile by: fltk-c"
  },
  {
    "path": "cpp/samples/readme.txt",
    "chars": 79,
    "preview": "FLTK 1.3.x is required http://www.fltk.org/\nlibpng is required for png support\n"
  },
  {
    "path": "cpp/samples/samples.cpp",
    "chars": 4500,
    "preview": "/*config.h is generated by fltk in your system\n * this file is used with fltk 1.3 with gl enabled.\n * compile by: fltk-c"
  },
  {
    "path": "cpp/samples/segment.cpp",
    "chars": 5301,
    "preview": "/*config.h is generated by fltk in your system\n * this file is used with fltk 1.3 with gl enabled.\n * compile by: fltk-c"
  },
  {
    "path": "cpp/samples/test1_base.cpp",
    "chars": 2222,
    "preview": "#include \"config.h\"\n#include <FL/Fl.H>\n#include <FL/gl.h>\n#include <FL/Fl_Window.H>\n#include <FL/Fl_Gl_Window.H>\n\nclass "
  },
  {
    "path": "cpp/vaser/agg_curve4.cpp",
    "chars": 9217,
    "preview": "//-----------------------------------------------------------------------\n// The Anti-Grain Geometry Project\n// A high q"
  },
  {
    "path": "cpp/vaser/backend.h",
    "chars": 191,
    "preview": "class backend\n{\npublic:\n\tstatic void vah_draw(vertex_array_holder& vah);\n\tstatic void polyline( const Vec2*, Color, doub"
  },
  {
    "path": "cpp/vaser/color.h",
    "chars": 2784,
    "preview": "float& Color_get( Color& C, int index)\n{\n\tswitch (index)\n\t{\n\t\tcase 0: return C.r;\n\t\tcase 1: return C.g;\n\t\tcase 2: return"
  },
  {
    "path": "cpp/vaser/curve.cpp",
    "chars": 3426,
    "preview": "namespace VASErin\n{\t//VASEr internal namespace\n\nclass polyline_buffer\n{\npublic:\n\tstd::vector<Vec2>   P;\n\tstd::vector<Col"
  },
  {
    "path": "cpp/vaser/gradient.cpp",
    "chars": 2332,
    "preview": "namespace VASErin\n{\t//VASEr internal namespace\n\ndouble grad_getstep(double A,double B,double t,double T)\n{\n\treturn ((T-t"
  },
  {
    "path": "cpp/vaser/opengl.cpp",
    "chars": 2546,
    "preview": "/* OpenGL 1.1 renderer and backend */\n\nnamespace VASErin\n{\t//VASEr internal namespace\n\nvoid backend::vah_draw(vertex_arr"
  },
  {
    "path": "cpp/vaser/point.h",
    "chars": 5637,
    "preview": "class Point : public Vec2\n{\npublic:\n\tPoint() { clear();}\n\tPoint(const Vec2& P) { set(P.x,P.y);}\n\tPoint(const Point& P) {"
  },
  {
    "path": "cpp/vaser/polyline.cpp",
    "chars": 53207,
    "preview": "namespace VASErin\n{\t//VASEr internal namespace\n\n/*visual testes:\n * A. points (geometry test)\n *  1. arbitrary polyline "
  },
  {
    "path": "cpp/vaser/vaser.cpp",
    "chars": 687,
    "preview": "#ifndef VASER_CPP\n#define VASER_CPP\n\n#include \"vaser.h\"\n\n#ifdef VASER_DEBUG\n\t#define DEBUG printf\n#else\n\t#define DEBUG ;"
  },
  {
    "path": "cpp/vaser/vaser.h",
    "chars": 3118,
    "preview": "#ifndef VASER_H\n#define VASER_H\n/* Vase Renderer first draft, version 0.3 */\n\n/* Basic usage\n* You should provide these "
  },
  {
    "path": "cpp/vaser/vertex_array_holder.h",
    "chars": 4825,
    "preview": "#ifndef VASER_VERTEX_ARRAY_HOLDER_H\n#define VASER_VERTEX_ARRAY_HOLDER_H\n\nclass vertex_array_holder\n{\npublic:\n\tint count;"
  },
  {
    "path": "cpp/workbench/base.html",
    "chars": 1079,
    "preview": "<body>\n<script src='raphael-min.js'></script>\n<script src='dragger.js'></script>\n<script src='drawer.js'></script>\n<link"
  },
  {
    "path": "cpp/workbench/dragger.js",
    "chars": 2071,
    "preview": "function dragger(TP,div_id)\n{\n\tthis.drag=-1;\n\tthis.TP=TP;\n\n\tthis.onMouseDown = function(e)\n\t{\n\t\tif (e.token=='in') {\n\t\t\t"
  },
  {
    "path": "cpp/workbench/drawer.js",
    "chars": 1271,
    "preview": "function drawer()\n{\n\tvar T=this;\n\tT.canx=0; T.cany=0;\n\tT.linestring=[\"\",\"\",\"\",\"\",\"\"];\n\tT.linecolor=[\"#000\",\"#F00\",\"#0F0\""
  },
  {
    "path": "cpp/workbench/gradient_along_path.html",
    "chars": 2881,
    "preview": "<!DOCTYPE html>\n<meta charset=\"utf-8\">\n<body>\n<script src=\"http://d3js.org/d3.v3.min.js\"></script>\n<script>\n//code comes"
  },
  {
    "path": "cpp/workbench/knife_cut_test.cpp",
    "chars": 3928,
    "preview": "/*config.h is generated by fltk in your system\n * this file is used with fltk 1.3 with gl enabled.\n * compile by: fltk-c"
  },
  {
    "path": "cpp/workbench/layout.css",
    "chars": 104,
    "preview": ".main_canvas\n{\n\twidth:800px; height:600px;\n}\n\n.side_pane\n{\n\tposition:absolute;\n\tleft:810px; top:10px;\n}\n"
  },
  {
    "path": "cpp/workbench/outward_vector.html",
    "chars": 2985,
    "preview": "<body>\n<script src='raphael-min.js'></script>\n<script src='dragger.js'></script>\n<script src='drawer.js'></script>\n<link"
  },
  {
    "path": "cpp/workbench/quad_reflex.html",
    "chars": 2010,
    "preview": "<body>\n<script src='raphael-min.js'></script>\n<script src='dragger.js'></script>\n<script src='drawer.js'></script>\n<link"
  },
  {
    "path": "cpp/workbench/raphael-min.js",
    "chars": 60275,
    "preview": "/*\n * Raphael 1.5.2 - JavaScript Vector Library\n *\n * Copyright (c) 2010 Dmitry Baranovskiy (http://raphaeljs.com)\n * Li"
  },
  {
    "path": "cpp/workbench/readme.txt",
    "chars": 90,
    "preview": "this folder is for holding experiments related to development. you can safely ignor them.\n"
  },
  {
    "path": "cpp/workbench/triangle_knife_cut.js",
    "chars": 2457,
    "preview": "function triangle_knife_cut(p1,p2,p3,kn1,kn2,kn3)\n{\n\tvar poly = []; //the retained polygon\n\tvar poly_cut = []; //the cut"
  },
  {
    "path": "cpp/workbench/triangles_dual_knife_cut.html",
    "chars": 3372,
    "preview": "<body>\n<script src='raphael-min.js'></script>\n<script src='dragger.js'></script>\n<script src='drawer.js'></script>\n<scri"
  },
  {
    "path": "cpp/workbench/triangles_knife_cut.html",
    "chars": 2490,
    "preview": "<body>\n<script src='raphael-min.js'></script>\n<script src='dragger.js'></script>\n<script src='drawer.js'></script>\n<scri"
  },
  {
    "path": "cpp/workbench/vector_arc.html",
    "chars": 3942,
    "preview": "<body>\n<script src='raphael-min.js'></script>\n<script src='dragger.js'></script>\n<script src='drawer.js'></script>\n<link"
  },
  {
    "path": "csharp/Assets/Demo/Demo.cs",
    "chars": 7306,
    "preview": "using Vaser;\nusing UnityEngine;\nusing System.Collections.Generic;\n\npublic class Demo : MonoBehaviour {\n\n    private int "
  },
  {
    "path": "csharp/Assets/Demo/Demo.cs.meta",
    "chars": 243,
    "preview": "fileFormatVersion: 2\nguid: 81156b434fc508ff78146c2e146a0bb4\nMonoImporter:\n  externalObjects: {}\n  serializedVersion: 2\n "
  },
  {
    "path": "csharp/Assets/Demo/demo.unity",
    "chars": 6658,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!29 &1\nOcclusionCullingSettings:\n  m_ObjectHideFlags: 0\n  serializedVersi"
  },
  {
    "path": "csharp/Assets/Demo/demo.unity.meta",
    "chars": 155,
    "preview": "fileFormatVersion: 2\nguid: b7a312166f95c074da8312847a33dbcd\nDefaultImporter:\n  externalObjects: {}\n  userData: \n  assetB"
  },
  {
    "path": "csharp/Assets/Demo.meta",
    "chars": 172,
    "preview": "fileFormatVersion: 2\nguid: 12e8321a818ec5ccb87d276a3bb47c22\nfolderAsset: yes\nDefaultImporter:\n  externalObjects: {}\n  us"
  },
  {
    "path": "csharp/Assets/Resources/Vaser/Fade.mat",
    "chars": 2106,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!21 &2100000\nMaterial:\n  serializedVersion: 6\n  m_ObjectHideFlags: 0\n  m_"
  },
  {
    "path": "csharp/Assets/Resources/Vaser/Fade.mat.meta",
    "chars": 188,
    "preview": "fileFormatVersion: 2\nguid: 38ae69e0a7563d741bb934ad6cf21933\nNativeFormatImporter:\n  externalObjects: {}\n  mainObjectFile"
  },
  {
    "path": "csharp/Assets/Resources/Vaser/Fade.shader",
    "chars": 1229,
    "preview": "Shader \"Fade\" {\n\n    SubShader {\n        Tags { \"Queue\"=\"Transparent\" \"RenderType\"=\"Transparent\" \"IgnoreProjector\"=\"Tru"
  },
  {
    "path": "csharp/Assets/Resources/Vaser/Fade.shader.meta",
    "chars": 204,
    "preview": "fileFormatVersion: 2\nguid: 98eb10bd2379fb34092e77f682e39b23\nShaderImporter:\n  externalObjects: {}\n  defaultTextures: []\n"
  },
  {
    "path": "csharp/Assets/Resources/Vaser.meta",
    "chars": 172,
    "preview": "fileFormatVersion: 2\nguid: 12ce771d903fa393d85bff11f9ce9827\nfolderAsset: yes\nDefaultImporter:\n  externalObjects: {}\n  us"
  },
  {
    "path": "csharp/Assets/Resources.meta",
    "chars": 172,
    "preview": "fileFormatVersion: 2\nguid: 16b03c580bf442779a7db0145e2aad4c\nfolderAsset: yes\nDefaultImporter:\n  externalObjects: {}\n  us"
  },
  {
    "path": "csharp/Assets/Vaser/Gradient.cs",
    "chars": 6049,
    "preview": "using UnityEngine;\nusing System.Collections.Generic;\n\nnamespace Vaser\n{\n    public class Gradient\n    {\n        public c"
  },
  {
    "path": "csharp/Assets/Vaser/Gradient.cs.meta",
    "chars": 243,
    "preview": "fileFormatVersion: 2\nguid: cbc53ed91caad3e419e6ab5858f7e12b\nMonoImporter:\n  externalObjects: {}\n  serializedVersion: 2\n "
  },
  {
    "path": "csharp/Assets/Vaser/Polybezier.cs",
    "chars": 17117,
    "preview": "using UnityEngine;\nusing System.Collections.Generic;\n\nnamespace Vaser\n{\n    public class Polybezier\n    {\n        privat"
  },
  {
    "path": "csharp/Assets/Vaser/Polybezier.cs.meta",
    "chars": 243,
    "preview": "fileFormatVersion: 2\nguid: 5ac57cfb8ec56bd489a88a68ee553086\nMonoImporter:\n  externalObjects: {}\n  serializedVersion: 2\n "
  },
  {
    "path": "csharp/Assets/Vaser/Polyline.cs",
    "chars": 45934,
    "preview": "using UnityEngine;\nusing System.Collections.Generic;\n\nnamespace Vaser\n{\n    public class Polyline\n    {\n        public V"
  },
  {
    "path": "csharp/Assets/Vaser/Polyline.cs.meta",
    "chars": 243,
    "preview": "fileFormatVersion: 2\nguid: f3267f6b485b83291a8297963816eb95\nMonoImporter:\n  externalObjects: {}\n  serializedVersion: 2\n "
  },
  {
    "path": "csharp/Assets/Vaser/Vec2Ext.cs",
    "chars": 6667,
    "preview": "using UnityEngine;\n\nnamespace Vaser\n{\n    public static class Vec2Ext\n    {\n        public static float Length(this Vect"
  },
  {
    "path": "csharp/Assets/Vaser/Vec2Ext.cs.meta",
    "chars": 243,
    "preview": "fileFormatVersion: 2\nguid: 1169d245ab3670b4685e0ceeafcdde52\nMonoImporter:\n  externalObjects: {}\n  serializedVersion: 2\n "
  },
  {
    "path": "csharp/Assets/Vaser/VertexArrayHolder.cs",
    "chars": 11256,
    "preview": "using UnityEngine;\nusing System.Collections.Generic;\n\nnamespace Vaser\n{\n    public class VertexArrayHolder\n    {\n       "
  },
  {
    "path": "csharp/Assets/Vaser/VertexArrayHolder.cs.meta",
    "chars": 243,
    "preview": "fileFormatVersion: 2\nguid: 0321073adc558cd47964c01d4ded4443\nMonoImporter:\n  externalObjects: {}\n  serializedVersion: 2\n "
  },
  {
    "path": "csharp/Assets/Vaser.meta",
    "chars": 172,
    "preview": "fileFormatVersion: 2\nguid: 89409ccbf42597ee79d84df5e334ee24\nfolderAsset: yes\nDefaultImporter:\n  externalObjects: {}\n  us"
  },
  {
    "path": "csharp/Docs/README.md",
    "chars": 648,
    "preview": "![Screenshot](Screenshot%20at%202019-10-23%2021-04-21.png)\n![Screenshot](Screenshot%20at%202019-10-29%2015-48-55.png)\n!["
  },
  {
    "path": "csharp/Packages/manifest.json",
    "chars": 1759,
    "preview": "{\n  \"dependencies\": {\n    \"com.unity.collab-proxy\": \"1.2.16\",\n    \"com.unity.ext.nunit\": \"1.0.0\",\n    \"com.unity.ide.rid"
  },
  {
    "path": "csharp/ProjectSettings/AudioManager.asset",
    "chars": 416,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!11 &1\nAudioManager:\n  m_ObjectHideFlags: 0\n  serializedVersion: 2\n  m_Vo"
  },
  {
    "path": "csharp/ProjectSettings/ClusterInputManager.asset",
    "chars": 114,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!236 &1\nClusterInputManager:\n  m_ObjectHideFlags: 0\n  m_Inputs: []\n"
  },
  {
    "path": "csharp/ProjectSettings/DynamicsManager.asset",
    "chars": 1190,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!55 &1\nPhysicsManager:\n  m_ObjectHideFlags: 0\n  serializedVersion: 11\n  m"
  },
  {
    "path": "csharp/ProjectSettings/EditorBuildSettings.asset",
    "chars": 160,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!1045 &1\nEditorBuildSettings:\n  m_ObjectHideFlags: 0\n  serializedVersion:"
  },
  {
    "path": "csharp/ProjectSettings/EditorSettings.asset",
    "chars": 832,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!159 &1\nEditorSettings:\n  m_ObjectHideFlags: 0\n  serializedVersion: 8\n  m"
  },
  {
    "path": "csharp/ProjectSettings/GraphicsSettings.asset",
    "chars": 2183,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!30 &1\nGraphicsSettings:\n  m_ObjectHideFlags: 0\n  serializedVersion: 12\n "
  },
  {
    "path": "csharp/ProjectSettings/InputManager.asset",
    "chars": 5793,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!13 &1\nInputManager:\n  m_ObjectHideFlags: 0\n  serializedVersion: 2\n  m_Ax"
  },
  {
    "path": "csharp/ProjectSettings/NavMeshAreas.asset",
    "chars": 1308,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!126 &1\nNavMeshProjectSettings:\n  m_ObjectHideFlags: 0\n  serializedVersio"
  },
  {
    "path": "csharp/ProjectSettings/Physics2DSettings.asset",
    "chars": 2028,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!19 &1\nPhysics2DSettings:\n  m_ObjectHideFlags: 0\n  serializedVersion: 4\n "
  },
  {
    "path": "csharp/ProjectSettings/PresetManager.asset",
    "chars": 120,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!1386491679 &1\nPresetManager:\n  m_ObjectHideFlags: 0\n  m_DefaultList: []\n"
  },
  {
    "path": "csharp/ProjectSettings/ProjectSettings.asset",
    "chars": 18522,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!129 &1\nPlayerSettings:\n  m_ObjectHideFlags: 0\n  serializedVersion: 18\n  "
  },
  {
    "path": "csharp/ProjectSettings/ProjectVersion.txt",
    "chars": 85,
    "preview": "m_EditorVersion: 2019.2.14f1\nm_EditorVersionWithRevision: 2019.2.14f1 (49dd4e9fa428)\n"
  },
  {
    "path": "csharp/ProjectSettings/QualitySettings.asset",
    "chars": 6437,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!47 &1\nQualitySettings:\n  m_ObjectHideFlags: 0\n  serializedVersion: 5\n  m"
  },
  {
    "path": "csharp/ProjectSettings/TagManager.asset",
    "chars": 378,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!78 &1\nTagManager:\n  serializedVersion: 2\n  tags: []\n  layers:\n  - Defaul"
  },
  {
    "path": "csharp/ProjectSettings/TimeManager.asset",
    "chars": 202,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!5 &1\nTimeManager:\n  m_ObjectHideFlags: 0\n  Fixed Timestep: 0.02\n  Maximu"
  },
  {
    "path": "csharp/ProjectSettings/UnityConnectSettings.asset",
    "chars": 853,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!310 &1\nUnityConnectSettings:\n  m_ObjectHideFlags: 0\n  serializedVersion:"
  },
  {
    "path": "csharp/ProjectSettings/VFXManager.asset",
    "chars": 273,
    "preview": "%YAML 1.1\n%TAG !u! tag:unity3d.com,2011:\n--- !u!937362698 &1\nVFXManager:\n  m_ObjectHideFlags: 0\n  m_IndirectShader: {fil"
  },
  {
    "path": "csharp/ProjectSettings/XRSettings.asset",
    "chars": 158,
    "preview": "{\n    \"m_SettingKeys\": [\n        \"VR Device Disabled\",\n        \"VR Device User Alert\"\n    ],\n    \"m_SettingValues\": [\n  "
  },
  {
    "path": "csharp/README.md",
    "chars": 1083,
    "preview": "# Vaser Unity\n\nVaser Unity is a continuation of [VASE renderer](https://github.com/tyt2y3/vaserenderer), reimplementing "
  },
  {
    "path": "docs/API.html",
    "chars": 14942,
    "preview": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<head>\n\t<link rel"
  },
  {
    "path": "docs/getting_started.html",
    "chars": 3586,
    "preview": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<head>\n\t<link rel"
  },
  {
    "path": "docs/index.html",
    "chars": 9900,
    "preview": "<!DOCTYPE HTML PUBLIC \"-//W3C//DTD HTML 4.01 Transitional//EN\" \"http://www.w3.org/TR/html4/loose.dtd\">\n<head>\n\t<title>VA"
  },
  {
    "path": "docs/style.css",
    "chars": 2997,
    "preview": "body\n{\n\tmargin: 0 auto;\n\tpadding: 0;\n\tfont: normal normal 13.8pt Palatino, \"Palatino Linotype\", Georgia, Times, \"Times N"
  }
]

// ... and 4 more files (download for full content)

About this extraction

This page contains the full source code of the tyt2y3/vaserenderer GitHub repository, extracted and formatted as plain text for AI agents and large language models (LLMs). The extraction includes 86 files (385.6 KB), approximately 137.7k tokens, and a symbol index with 268 extracted functions, classes, methods, constants, and types. Use this with OpenClaw, Claude, ChatGPT, Cursor, Windsurf, or any other AI tool that accepts text input. You can copy the full output to your clipboard or download it as a .txt file.

Extracted by GitExtract — free GitHub repo to text converter for AI. Built by Nikandr Surkov.

Copied to clipboard!