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.

VASE renderer

About

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++.

Unconventional features

Per vertex coloring and weighting



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.

Linear gradient along curve


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.

Feathering for brush like effects


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.

Premium quality anti aliasing



From left to right: raw polygon without anti aliasing,
anti aliased with outset fade polygon,
exaggerated outsetting polygon with wireframe,
same as left without wireframe.

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.

Below are line rendering comparison between VASEr and Cairo and AGG:

VASEr

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.

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.

Articles

+ Drawing polylines by tessellation.
+ Drawing nearly perfect 2D line segments in OpenGL

Related Work

Acknowledgement

Bezier curve subdivision code is extracted from Anti-Grain Geometry V2.4 by Maxim Shemanarev (McSeem).

The open-source graphics library AGG inspired me a lot during my teenage.

================================================ 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 #include 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 #include #include #include "config.h" //config.h must always be placed before any Fl header #include #include #include #include #include #include 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; ivalue()) 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 #include #include #include "config.h" //config.h must always be placed before any Fl header #include #include #include 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", ¤t); 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 #include #include "config.h" //config.h must always be placed before any Fl header #include #include #include #include #include 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 #include #include #include 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=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 #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 P; std::vector C; std::vector W; std::vector 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; istops || !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= 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 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(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 && w0.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; a100) { 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 ( a100) { 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 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 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(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 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; ifeather && !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; itess && 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; icos_a) || (costho>cos_b) || //when the angle difference at an anchor is smaller than a critical degree, do polyline approximation (lencos_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 #include #include 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 vert; //because it holds 2d vectors std::vector 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
This is description.
================================================ 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 ================================================ 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 #include #include "../samples/config.h" //config.h must always be placed before any Fl header #include #include #include #include #include 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; iset_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; iend(); //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 ================================================
Blue vectors are outward perpendicular vectors.
Red ones are outward bisecting vectors.
This method only works for these 2 special case: outward perpendicular and outward bisecting vectors. Drag control points to observe.
================================================ FILE: cpp/workbench/quad_reflex.html ================================================
Note that we always draw the 2 blue lines correctly, even when the quad is reflexed.
This is especially useful when drawing quad by triangle strip to ensure there is no reflex.
================================================ 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";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(fb-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("");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||n1){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=aH&&(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"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);j0.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];q1?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];jb.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"));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")}}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;g1&&(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];Kr)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;oe){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;cd)return d;while(cf?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];v0 == std_sign>0; //true means this point should be cut var s2 = signed_area( kn1,kn2,p2)>0 == std_sign>0; var s3 = signed_area( kn1,kn2,p3)>0 == std_sign>0; var sums = s1+s2+s3; if ( sums == 0) { //all 3 points are retained poly.push(p1); poly.push(p2); poly.push(p3); return { poly:poly, poly_cut:poly_cut}; } else if ( sums == 3) { //all 3 are cut away poly_cut.push(p1); poly_cut.push(p2); poly_cut.push(p3); return { poly:poly, poly_cut:poly_cut}; } else { if ( sums == 2) { s1 = !s1; s2 = !s2; s3 = !s3; } // var ip1,ip2, outp; if ( s1) { //here assume one point is cut away outp= p1; ip1 = p2; ip2 = p3; } else if ( s2) { outp= p2; ip1 = p1; ip2 = p3; } else if ( s3) { outp= p3; ip1 = p1; ip2 = p2; } var interP1 = intersect( kn1,kn2, ip2,outp); var interP2 = intersect( kn1,kn2, ip1,outp); //ip2 first gives a polygon //ip1 first gives a triangle strip poly.push(ip1); poly.push(ip2); poly.push(interP1); poly.push(interP2); poly_cut.push(outp); poly_cut.push(interP1); poly_cut.push(interP2); if ( sums == 1) { return { poly:poly, poly_cut:poly_cut}; } else if ( sums == 2) { return { poly:poly_cut, poly_cut:poly}; //^ we swap the result! } } } 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 intersect( P1,P2, //line 1 P3,P4) //line 2 { //Determine the intersection point of two line segments var mua,mub; var 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); function negligible(M) { return -0.00000001 < M && M < 0.00000001; } if ( negligible(numera) && negligible(numerb) && negligible(denom)) { //meaning the lines coincide return { x: (P1.x + P2.x) * 0.5, y: (P1.y + P2.y) * 0.5 }; } if ( negligible(denom)) { //meaning lines are parallel return { x:0, y:0}; } mua = numera / denom; mub = numerb / denom; return { x: P1.x + mua * (P2.x - P1.x), y: P1.y + mua * (P2.y - P1.y) }; //http://paulbourke.net/geometry/lineline2d/ } ================================================ FILE: cpp/workbench/triangles_dual_knife_cut.html ================================================
This is the "dual knife cut" method of a triangle.
The green points define the sides to be cut away.
It can operate on multiple triangles.
The cut away part is shown in red and retained part in blue.
The 2 parts are given offsets (depend on the green points) so they look separated, just for fun.
Drag the anchors to play. You can also drag the tips of the triangles.
================================================ FILE: cpp/workbench/triangles_knife_cut.html ================================================
This is the "knife cut" method of a triangle.
The 2 black anchored points define the knife, while the green point define the side to be cut away.
The cut away part is shown in red and retained part in blue.
The 2 parts are given offsets (depend on the green point) so they look separated, just for fun.
It can operate on multiple triangles.
Drag the anchors to play. You can also drag the tips of the triangles.
================================================ FILE: cpp/workbench/vector_arc.html ================================================


Note that we always draw the arc from red to blue.
Drag control points to observe.
Source code in contained in this HTML file.
================================================ FILE: csharp/Assets/Demo/Demo.cs ================================================ using Vaser; using UnityEngine; using System.Collections.Generic; public class Demo : MonoBehaviour { private int mode = 1; private Polyline polyline = null; private Polyline.Opt opt = new Polyline.Opt(); private GameObject myGameObject = null; private void Start() { opt.feather = false; opt.feathering = 15f; opt.joint = Polyline.Opt.PLJround; opt.cap = Polyline.Opt.PLCround; opt.triangulation = false; { Camera cam = Camera.main; Vector3 a = cam.WorldToScreenPoint(new Vector3(0,0,0)); Vector3 b = cam.WorldToScreenPoint(new Vector3(1,1,0)); opt.worldToScreenRatio = Vector3.Distance(a,b) / new Vector3(1,1,0).magnitude; } VertexArrayHolder holder = null; if (mode == 0) { holder = new VertexArrayHolder(); holder.Push3( new Vector2(-2, 0.5f), new Vector2(-1, 0.5f), new Vector2(-2, -0.5f), Color.red, Color.green, Color.blue, 5, 0, 5 ); holder.Push3( new Vector2(-2, -0.5f), new Vector2(-1, -0.5f), new Vector2(-2, -0.75f), Color.red, Color.green, Color.blue, 1.25f, 0, 5 ); holder.Push3( new Vector2(-0.6f, 0.5f), new Vector2(-0.1f, 0.5f), new Vector2(-0.6f, -0.5f), Color.red, Color.green, Color.blue, 5, 0, 2.5f ); holder.Push3( new Vector2(-0.6f, -0.5f), new Vector2(-0.1f, -0.5f), new Vector2(-0.6f, -0.75f), Color.red, Color.green, Color.blue, 1.25f, 0, 2.5f ); holder.Push3( new Vector2(0.25f, 0.5f), new Vector2(2.25f, 0.5f), new Vector2(0.25f, -0.5f), Color.red, Color.green, Color.blue, 5, 0, 10 ); holder.Push3( new Vector2(0.25f, -0.5f), new Vector2(2f, -0.5f), new Vector2(0.25f, -0.75f), Color.red, Color.green, Color.blue, 1.25f, 0, 10 ); } else if (mode == 1) { polyline = new Polyline( new List { new Vector2(0, .75f), new Vector2(-.75f, -.1f), new Vector2(.75f, .1f), new Vector2(0, -.75f), }, new List { Color.red, Color.green, Color.blue, Color.red, }, new List { 0.05f, 0.15f, 0.15f, 0.05f, }, opt ); } else if (mode == 2) { Polybezier polybezier = new Polybezier( new List { new Vector2(0, .75f), new Vector2(-.75f, -.1f), new Vector2(.75f, .1f), new Vector2(0, -.75f), }, new Vaser.Gradient ( new List { new Vaser.Gradient.Stop(0.0f, Color.red), new Vaser.Gradient.Stop(0.0f, 0.01f), new Vaser.Gradient.Stop(0.25f, Color.green), new Vaser.Gradient.Stop(0.25f, 0.15f), new Vaser.Gradient.Stop(0.5f, Color.blue), new Vaser.Gradient.Stop(0.5f, 0.15f), new Vaser.Gradient.Stop(1.0f, Color.red), new Vaser.Gradient.Stop(1.0f, 0.01f), } ), new Polybezier.Opt { worldToScreenRatio = opt.worldToScreenRatio } ); polyline = polybezier.Render(opt); } myGameObject = new GameObject("Mesh", typeof(MeshFilter), typeof(MeshRenderer)); myGameObject.transform.localScale = new Vector3(1,1,1); myGameObject.GetComponent().material = Resources.Load("Vaser/Fade"); if (polyline != null) { Mesh mesh = polyline.GetMesh(); myGameObject.GetComponent().mesh = mesh; } if (mode == 0) { Mesh mesh = new Mesh(); mesh.SetVertices(holder.GetVertices()); mesh.SetUVs(0, holder.GetUVs()); mesh.SetColors(holder.GetColors()); mesh.SetTriangles(holder.GetTriangles(), 0); myGameObject.GetComponent().mesh = mesh; } } void Update() { float time = Time.fixedTime; if (mode == 3) { polyline = new Polyline(); polyline.holder.Push4( new Vector2(-2.3f, 1), new Vector2( 2.3f, 1), new Vector2(-2.3f, -1), new Vector2( 2.3f, -1), new Color(0.365f, 0.808f, 0.910f, 1), new Color(0.769f, 0.380f, 0.639f, 1), new Color(0.365f, 0.808f, 0.910f, 1), new Color(0.5f, 0.305f, 0.773f, 1), 0, 0, 0, 0 ); // pink new Color(0.769f, 0.380f, 0.639f, 1) // cyan new Color(0.180f, 0.5f, 0.941f, 1) for (int k=0; k<10; k++) { Polybezier.Buffer buffer = new Polybezier.Buffer(); buffer.AddPoints(ComputeSineCurve(time, 0.5f+0.035f*k)); buffer.Gradient( new Vaser.Gradient ( new List { new Vaser.Gradient.Stop(0.0f, new Color(0.365f, 0.808f, 0.910f, 1)), new Vaser.Gradient.Stop(0.5f, new Color(0.914f, 0.914f, 0.369f, 1)), new Vaser.Gradient.Stop(1.0f, new Color(0.5f, 0.305f, 0.773f, 1)), new Vaser.Gradient.Stop(0.0f, 0.01f), new Vaser.Gradient.Stop(1.0f, 0.01f), } ) ); polyline.Append(buffer.Render(opt)); } Mesh mesh = polyline.GetMesh(); myGameObject.GetComponent().mesh = mesh; } } private List ComputeSineCurve(float time, float amplitude) { List curve = new List (); for (float t=-1; t<1; t+=0.02f) { float value = 0; List harmonics = new List { 0.5f, 0.25f, 0.25f }; for (int j=0; j stops; //array must be sorted in ascending order of t public const char GD_ratio = (char) 0; public const char GD_length = (char) 1; public const char GS_none = (char) 0; public const char GS_rgba = (char) 1; public const char GS_rgb = (char) 2; public const char GS_alpha = (char) 3; public const char GS_weight = (char) 4; public Gradient() { stops = new List (); } public Gradient(List stopss) { stops = stopss; } public Gradient(Color cc, float ww) { stops = new List (); stops.Add(new Stop(0, cc)); stops.Add(new Stop(0, ww)); } public struct Stop { public float t; //position public char type; public Color color; public float weight; public Stop(float tt, Color cc) { t = tt; type = GS_rgba; color = cc; weight = 0; } public Stop(float tt, float ww) { t = tt; type = GS_weight; color = new Color(0,0,0,0); weight = ww; } } public void Apply(List C, List W, List L, int limit, float pathLength) { if (stops.Count == 0) { return; } //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 float lengthAlong = 0.0f; if (stops.Count <= 1) { return; } for (int i = 0; i < limit; i += 1) { lengthAlong += L[i]; float p = 0.0f; if (unit == GD_ratio) { p = lengthAlong / pathLength; } else if (unit == GD_length) { p = lengthAlong; } else { break; } //lookup for cur for (nex_w = cur_w; nex_w < stops.Count; nex_w += 1) { if (stops[nex_w].type == GS_weight && p <= stops[nex_w].t) { cur_w = nex_w; break; } } for (nex_c = cur_c; nex_c < stops.Count; nex_c += 1) { if ((stops[nex_c].type == GS_rgba || stops[nex_c].type == GS_rgb) && p <= stops[nex_c].t) { cur_c = nex_c; break; } } for (nex_a = cur_a; nex_a < stops.Count; nex_a += 1) { if ((stops[nex_a].type == GS_rgba || stops[nex_a].type == GS_alpha) && p <= stops[nex_a].t) { cur_a = nex_a; break; } } //look for las for (nex_w = cur_w; nex_w >= 0; nex_w -= 1) { if (stops[nex_w].type == GS_weight && p >= stops[nex_w].t) { las_w = nex_w; break; } } for (nex_c = cur_c; nex_c >= 0; nex_c -= 1) { if ((stops[nex_c].type == GS_rgba || stops[nex_c].type == GS_rgb) && p >= stops[nex_c].t) { las_c = nex_c; break; } } for (nex_a = cur_a; nex_a >= 0; nex_a -= 1) { if ((stops[nex_a].type == GS_rgba || stops[nex_a].type == GS_alpha) && p >= stops[nex_a].t) { las_a = nex_a; break; } } if (cur_c == las_c) { C[i] = Sc(cur_c); } else { C[i] = ColorBetween(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] = GetStep(Sw(las_w), Sw(cur_w), p - St(las_w), St(cur_w) - St(las_w)); } if (cur_a == las_a) { C[i] = SaC(i, cur_a); } else { C[i] = GetStepColor(i, Sa(las_a), Sa(cur_a), p - St(las_a), St(cur_a) - St(las_a)); } } Color Sc(int x) { return stops[x].color; } float Sw(int x) { return stops[x].weight; } float Sa(int x) { return stops[x].color.a; } float St(int x) { return stops[x].t; } Color SaC(int i, int x) { Color c = new Color(C[i].r, C[i].g, C[i].b, C[i].a); c.a = stops[x].color.a; return c; } float GetStep(float A, float B, float t, float T) { return ((T - t) * A + t * B) / T; } Color GetStepColor(int i, float A, float B, float t, float T) { Color c = new Color(C[i].r, C[i].g, C[i].b, C[i].a); c.a = GetStep(A, B, t, T); return c; } } public static Color ColorBetween(Color A, Color B, float t) { if (t < 0.0f) t = 0.0f; if (t > 1.0f) t = 1.0f; float kt = 1.0f - t; return new Color( A.r * kt + B.r * t, A.g * kt + B.g * t, A.b * kt + B.b * t, A.a * kt + B.a * t ); } } } ================================================ FILE: csharp/Assets/Vaser/Gradient.cs.meta ================================================ fileFormatVersion: 2 guid: cbc53ed91caad3e419e6ab5858f7e12b MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: csharp/Assets/Vaser/Polybezier.cs ================================================ using UnityEngine; using System.Collections.Generic; namespace Vaser { public class Polybezier { private Buffer buffer; public class Opt { public float worldToScreenRatio = 1.0f; } public Polybezier(List P, Color cc, float ww, Opt opt) : this(P, new Gradient(cc, ww), opt) { // empty } public Polybezier(List P, Gradient grad, Opt opt) { buffer = new Buffer(); if (opt == null) { opt = new Opt(); } double default_approximation_scale = (grad.stops.Count > 2 ? 2 : 1) * opt.worldToScreenRatio; const double default_angle_tolerance = (15 / 180 * System.Math.PI); const double default_cusp_limit = 5.0; for (int i=0; i P; public List C; public List W; public List L; //length along polyline public float pathLength; //total segment length public Buffer() { P = new List(); C = new List(); W = new List(); L = new List(); pathLength = 0.0f; L.Add(0.0f); } public void Gradient(Gradient grad) { grad.Apply(C, W, L, C.Count, pathLength); } public Polyline Render(Polyline.Opt opt) { if (P.Count == 0) { return new Polyline(); } Polyline.Inopt inopt = new Polyline.Inopt(); inopt.segmentLength = L; return new Polyline(P, C, W, opt, inopt); } public void AddPoint(float x, float y) { AddPoint(new Vector2(x, y)); } public void AddPoint(double x, double y) { AddPoint(new Vector2((float) x, (float) y)); } public void AddPoint(Vector2 V) { AddVertex(V, new Color(0,0,0,1), 1); } public void AddPoints(List points) { for (int i=0; i 0 && P[N-1].x == V.x && P[N-1].y == V.y) { return false; //duplicate } else { //point P.Add(V); if (N > 0) { float len = (V - P[N-1]).Length(); pathLength += len; L.Add(len); } C.Add(cc); W.Add(ww); return true; } } } //----------------------------------------------------------------------- // 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 //----------------------------------------------------------------------- private void RecursiveBezier( double x1, double y1, double x2, double y2, double x3, double y3, double x4, double y4, int level, double m_angle_tolerance, double m_cusp_limit, double m_distance_tolerance_square, Buffer obj) { const double M_PI = System.Math.PI; //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 = System.Math.Abs(((x2 - x4) * dy - (y2 - y4) * dx)); double d3 = System.Math.Abs(((x3 - x4) * dy - (y3 - y4) * dx)); double da1, da2, k; switch (((d2 > curve_collinearity_epsilon ? 1 : 0) << 1) + (d3 > curve_collinearity_epsilon ? 1 : 0)) { 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) { obj.AddPoint(x2, y2); return; } } else { if (d3 < m_distance_tolerance_square) { obj.AddPoint(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) { obj.AddPoint(x23, y23); return; } // Angle Condition //---------------------- da1 = System.Math.Abs(System.Math.Atan2(y4 - y3, x4 - x3) - System.Math.Atan2(y3 - y2, x3 - x2)); if (da1 >= M_PI) da1 = 2*M_PI - da1; if (da1 < m_angle_tolerance) { obj.AddPoint(x2, y2); obj.AddPoint(x3, y3); return; } if (m_cusp_limit != 0.0) { if (da1 > m_cusp_limit) { obj.AddPoint(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) { obj.AddPoint(x23, y23); return; } // Angle Condition //---------------------- da1 = System.Math.Abs(System.Math.Atan2(y3 - y2, x3 - x2) - System.Math.Atan2(y2 - y1, x2 - x1)); if (da1 >= M_PI) { da1 = 2*M_PI - da1; } if (da1 < m_angle_tolerance) { obj.AddPoint(x2, y2); obj.AddPoint(x3, y3); return; } if (m_cusp_limit != 0.0) { if (da1 > m_cusp_limit) { obj.AddPoint(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) { obj.AddPoint(x23, y23); return; } // Angle & Cusp Condition //---------------------- k = System.Math.Atan2(y3 - y2, x3 - x2); da1 = System.Math.Abs(k - System.Math.Atan2(y2 - y1, x2 - x1)); da2 = System.Math.Abs(System.Math.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 //---------------------- obj.AddPoint(x23, y23); return; } if (m_cusp_limit != 0.0) { if (da1 > m_cusp_limit) { obj.AddPoint(x2, y2); return; } if (da2 > m_cusp_limit) { obj.AddPoint(x3, y3); return; } } } break; } // Continue subdivision //---------------------- RecursiveBezier(x1, y1, x12, y12, x123, y123, x1234, y1234, level + 1, m_angle_tolerance, m_cusp_limit, m_distance_tolerance_square, obj); RecursiveBezier(x1234, y1234, x234, y234, x34, y34, x4, y4, level + 1, m_angle_tolerance, m_cusp_limit, m_distance_tolerance_square, obj); double calc_sq_distance(double xx1, double yy1, double xx2, double yy2) { double ddx = xx2-xx1; double ddy = yy2-yy1; return ddx * ddx + ddy * ddy; } } private void Curve4Div( 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, Buffer obj) { double m_distance_tolerance_square = 0.5 / m_approximation_scale; m_distance_tolerance_square *= m_distance_tolerance_square; obj.AddPoint(x1, y1); RecursiveBezier( x1, y1, x2, y2, x3, y3, x4, y4, 0, m_angle_tolerance, m_cusp_limit, m_distance_tolerance_square, obj); obj.AddPoint(x4, y4); } // end of AGG //----------------------------------------------------------------------- } } ================================================ FILE: csharp/Assets/Vaser/Polybezier.cs.meta ================================================ fileFormatVersion: 2 guid: 5ac57cfb8ec56bd489a88a68ee553086 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: csharp/Assets/Vaser/Polyline.cs ================================================ using UnityEngine; using System.Collections.Generic; namespace Vaser { public class Polyline { public VertexArrayHolder holder; public class Opt { public char joint = PLJmiter; public char cap = PLCbutt; public bool feather = false; public float feathering = 0.0f; public bool noFeatherAtCap = false; public bool noFeatherAtCore = false; public float worldToScreenRatio = 1.0f; public bool triangulation = false; //for Opt.joint public const char PLJmiter = (char) 0; //default public const char PLJbevel = (char) 1; public const char PLJround = (char) 2; //for Opt.cap public const char PLCbutt = (char) 0; //default public const char PLCround = (char) 1; public const char PLCsquare = (char) 2; public const char PLCrect = (char) 3; public const char PLCboth = (char) 0; //default public const char PLCfirst = (char) 10; public const char PLClast = (char) 20; public const char PLCnone = (char) 30; } public class Inopt { public bool constColor = false; public bool constWeight = false; public bool noCapFirst = false; public bool noCapLast = false; public bool joinFirst = false; public bool joinLast = false; public List segmentLength = null; // length of each segment; optional public VertexArrayHolder holder = new VertexArrayHolder(); public Inopt ShallowCopy() { return (Inopt) this.MemberwiseClone(); } } public Polyline() { holder = new VertexArrayHolder(); } public Polyline( List P, List C, List W, Opt opt) : this(P, C, W, opt, null) { // empty } public Polyline( List P, Color C, float W, Opt opt) : this(P, new List { C }, new List { W }, opt, new Inopt { constColor = true, constWeight = true, }) { // empty } public Polyline( List P, List C, List W, Opt opt, Inopt inopt) { int length = P.Count; if (opt == null) { opt = new Opt(); } if (inopt == null) { inopt = new Inopt(); } if (opt.cap >= 10) { char dec = (char)((int) opt.cap - ((int) opt.cap % 10)); if (dec == Opt.PLCfirst || dec == Opt.PLCnone) { inopt.noCapLast = true; } if (dec == Opt.PLClast || dec == Opt.PLCnone) { inopt.noCapFirst = true; } opt.cap -= dec; } int A = 0, B = 0; bool on = false; for (int i = 1; i < length - 1; i++) { Vector2 V1 = P[i] - P[i - 1]; Vector2 V2 = P[i + 1] - P[i]; float len = 0; if (inopt.segmentLength != null) { V1 *= 1 / inopt.segmentLength[i]; V2 *= 1 / inopt.segmentLength[i + 1]; len += (inopt.segmentLength[i] + inopt.segmentLength[i + 1]) * 0.5f; } else { len += Vec2Ext.Normalize(ref V1) * 0.5f; len += Vec2Ext.Normalize(ref V2) * 0.5f; } float costho = V1.x * V2.x + V1.y * V2.y; const float m_pi = (float) System.Math.PI; //float angle = acos(costho)*180/m_pi; float cos_a = (float) System.Math.Cos(15 * m_pi / 180); float cos_b = (float) System.Math.Cos(10 * m_pi / 180); float cos_c = (float) System.Math.Cos(25 * m_pi / 180); float weight = W[inopt.constWeight ? 0 : i]; bool approx = false; if ((weight * opt.worldToScreenRatio < 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; on = true; if (A == 1) { A = 0; } if (A > 1) { PolylineRange(P, C, W, opt, inopt, B, A, false); } } else if (!approx && on) { B = i; on = false; PolylineRange(P, C, W, opt, inopt, A, B, true); } } if (on && B < length - 1) { B = length - 1; PolylineRange(P, C, W, opt, inopt, A, B, true); } else if (!on && A < length - 1) { A = length - 1; PolylineRange(P, C, W, opt, inopt, B, A, false); } holder = inopt.holder; inopt.holder = null; } public Mesh GetMesh() { Mesh mesh = new Mesh(); mesh.SetVertices(holder.GetVertices()); mesh.SetUVs(0, holder.GetUVs()); mesh.SetColors(holder.GetColors()); mesh.SetTriangles(holder.GetTriangles(), 0); return mesh; } public void Append(Polyline polyline) { holder.Push(polyline.holder); } private struct StPolyline // to hold info for AnchorLate() to perform triangulation { //for all joints public Vector2 vP; //vector to intersection point; outward //for djoint==PLJbevel public Vector2 T; //core thickness of a line public Vector2 R; //fading edge of a line public Vector2 bR; //out stepping vector, same direction as cap public Vector2 T1; //alternate vectors, same direction as T21 // all T,R,T1 are outward //for djoint==PLJround public float t,r; //for degeneration case public bool degenT; //core degenerated public bool degenR; //fade degenerated public bool preFull; //draw the preceding segment in full public Vector2 PT; public char djoint; //determined joint // e.g. originally a joint is PLJmiter. but it is smaller than critical angle, should then set djoint to PLJbevel } private class StAnchor // to hold memory for the working of anchor() { public Vector2[] P = new Vector2[3]; //point public Color[] C = new Color[3]; //color public float[] W = new float[3]; //weight public StPolyline[] SL = new StPolyline[3]; public VertexArrayHolder vah = new VertexArrayHolder(); } private static void PolyPointInter( List P, List C, List W, Inopt inopt, ref Vector2 p, ref Color c, ref float w, int at, float t) { Color color(int I) { return C[inopt != null && inopt.constColor ? 0 : I]; } float weight(int I) { return W[inopt != null && inopt.constWeight ? 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 = Gradient.ColorBetween(color(at), color(at + 1), t); w = (weight(at) + weight(at + 1)) * t; } } private static void PolylineApprox( List P, List C, List W, Opt opt, Inopt inopt, int from, int to) { if (to - from + 1 < 2) { return; } bool capFirst = !(inopt != null && inopt.noCapFirst); bool capLast = !(inopt != null && inopt.noCapLast); bool joinFirst = inopt != null && inopt.joinFirst; bool joinLast = inopt != null && inopt.joinLast; VertexArrayHolder vcore = new VertexArrayHolder(); //curve core vcore.SetGlDrawMode(VertexArrayHolder.GL_TRIANGLE_STRIP); Color color(int I) { return C[inopt != null && inopt.constColor ? 0: I]; } float weight(int I) { return W[inopt != null && inopt.constWeight ? 0: I]; } void poly_step(int i, Vector2 pp, float ww, Color cc) { float t = 0, r = 0; DetermineTr(ww, ref t, ref r, opt.worldToScreenRatio); if (opt.feather && !opt.noFeatherAtCore) { r *= opt.feathering; } float rr = (t + r) / r; Vector2 V = P[i] - P[i - 1]; Vec2Ext.Perpen(ref V); Vec2Ext.Normalize(ref V); V *= (t + r); vcore.Push(pp - V, cc, rr); vcore.Push(pp + V, cc, 0); } for (int i = from + 1; i < to; i++) { poly_step(i, P[i], weight(i), color(i)); } Vector2 P_las = new Vector2(), P_fir = new Vector2(); Color C_las = new Color(), C_fir = new Color(); float W_las = 0, W_fir = 0; PolyPointInter(P, C, W, inopt, ref P_las, ref C_las, ref W_las, to - 1, 0.5f); poly_step(to, P_las, W_las, C_las); StAnchor SA = new StAnchor(); { PolyPointInter(P, C, W, inopt, ref P_fir, ref C_fir, ref W_fir, from, joinFirst ? 0.5f : 0.0f); SA.P[0] = P_fir; SA.P[1] = P[from + 1]; SA.C[0] = C_fir; SA.C[1] = color(from + 1); SA.W[0] = W_fir; SA.W[1] = weight(from + 1); Segment(SA, opt, capFirst, false, true); } if (!joinLast) { SA.P[0] = P_las; SA.P[1] = P[to]; SA.C[0] = C_las; SA.C[1] = color(to); SA.W[0] = W_las; SA.W[1] = weight(to); Segment(SA, opt, false, capLast, true); } inopt.holder.Push(vcore); inopt.holder.Push(SA.vah); if (opt.triangulation) { DrawTriangles(vcore, inopt.holder, opt.worldToScreenRatio); DrawTriangles(SA.vah, inopt.holder, opt.worldToScreenRatio); } } private static void PolylineExact( List P, List C, List W, Opt opt, Inopt inopt, int from, int to) { bool capFirst = !(inopt != null && inopt.noCapFirst); bool capLast = !(inopt != null && inopt.noCapLast); bool joinFirst = inopt != null && inopt.joinFirst; bool joinLast = inopt != null && inopt.joinLast; Color color(int I) { return C[inopt != null && inopt.constColor ? 0: I]; } float weight(int I) { return W[inopt != null && inopt.constWeight ? 0: I]; } Vector2 mid_l = new Vector2(), mid_n = new Vector2(); //the last and the next mid point Color c_l = new Color(), c_n = new Color(); float w_l = 0, w_n = 0; //init for the first anchor PolyPointInter(P, C, W, inopt, ref mid_l, ref c_l, ref w_l, from, joinFirst ? 0.5f : 0); StAnchor SA = new StAnchor(); if (to - from + 1 == 2) { SA.P[0] = P[from]; SA.P[1] = P[from + 1]; SA.C[0] = color(from); SA.C[1] = color(from + 1); SA.W[0] = weight(from); SA.W[1] = weight(from + 1); Segment(SA, opt, capFirst, capLast, true); } else { for (int i = from + 1; i < to; i++) { if (i == to - 1 && !joinLast) { PolyPointInter(P, C, W, inopt, ref mid_n, ref c_n, ref w_n, i, 1.0f); } else { PolyPointInter(P, C, W, inopt, ref mid_n, ref c_n, ref w_n, i, 0.5f); } SA.P[0] = mid_l; SA.C[0] = c_l; SA.W[0] = w_l; SA.P[2] = mid_n; 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) && capFirst, (i == to - 1) && capLast); mid_l = mid_n; c_l = c_n; w_l = w_n; } } inopt.holder.Push(SA.vah); if (opt.triangulation) { DrawTriangles(SA.vah, inopt.holder, opt.worldToScreenRatio); } } private static void PolylineRange( List P, List C, List W, Opt opt, Inopt ininopt, int from, int to, bool approx) { Inopt inopt; if (ininopt == null) { inopt = new Inopt(); } else { inopt = ininopt.ShallowCopy(); } if (from > 0) { from -= 1; } inopt.joinFirst = from != 0; inopt.joinLast = to != (P.Count - 1); inopt.noCapFirst = ininopt.noCapFirst || inopt.joinFirst; inopt.noCapLast = ininopt.noCapLast || inopt.joinLast; if (approx) { PolylineApprox(P, C, W, opt, inopt, from, to); } else { PolylineExact(P, C, W, opt, inopt, from, to); } } private static void DrawTriangles(VertexArrayHolder triangles, VertexArrayHolder holder, float scale) { Opt opt = new Opt(); opt.cap = Opt.PLCnone; opt.joint = Opt.PLJbevel; opt.worldToScreenRatio = scale; if (triangles.glmode == VertexArrayHolder.GL_TRIANGLES) { for (int i = 0; i < triangles.GetCount(); i++) { List P = new List (); P.Add(triangles.Get(i)); i += 1; P.Add(triangles.Get(i)); i += 1; P.Add(triangles.Get(i)); P.Add(P[0]); Polyline polyline = new Polyline(P, Color.red, 1 / scale, opt); holder.Push(polyline.holder); } } else if (triangles.glmode == VertexArrayHolder.GL_TRIANGLE_STRIP) { for (int i = 2; i < triangles.GetCount(); i++) { List P = new List (); P.Add(triangles.Get(i - 2)); P.Add(triangles.Get(i)); P.Add(triangles.Get(i - 1)); Polyline polyline = new Polyline(P, Color.red, 1 / scale, opt); holder.Push(polyline.holder); } } } private static void DrawDigit(VertexArrayHolder holder, Vector2 pp, float scale, int digit) { Opt opt = new Opt(); opt.cap = Opt.PLCnone; opt.joint = Opt.PLJbevel; opt.worldToScreenRatio = scale; List P; float size = 5 / scale; float halfSize = size / 2; switch (digit) { case 0: P = new List { new Vector2(halfSize, size) + pp, new Vector2(halfSize, -size) + pp, new Vector2(-halfSize, -size) + pp, new Vector2(-halfSize, size) + pp, new Vector2(halfSize, size) + pp, new Vector2(-halfSize, -size) + pp, }; break; case 1: P = new List { new Vector2(-halfSize, 0) + pp, new Vector2(0, size) + pp, new Vector2(0, -size) + pp, }; break; case 2: P = new List { new Vector2(-halfSize, size) + pp, new Vector2(halfSize, size) + pp, new Vector2(halfSize, 0) + pp, new Vector2(-halfSize, 0) + pp, new Vector2(-halfSize, -size) + pp, new Vector2(halfSize, -size) + pp, }; break; case 3: P = new List { new Vector2(-halfSize, size) + pp, new Vector2(halfSize, size) + pp, new Vector2(halfSize, 0) + pp, new Vector2(-halfSize, 0) + pp, new Vector2(halfSize, 0) + pp, new Vector2(halfSize, -size) + pp, new Vector2(-halfSize, -size) + pp, }; break; case 4: P = new List { new Vector2(halfSize, -size) + pp, new Vector2(halfSize, size) + pp, new Vector2(-halfSize, 0) + pp, new Vector2(halfSize, 0) + pp, }; break; default: return; } Polyline polyline = new Polyline(P, Color.red, 1 / scale, opt); holder.Push(polyline.holder); } private static void DetermineTr(float w, ref float t, ref float R, float scale) { //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 w *= scale; float f = w - (float) System.Math.Floor(w); // resolution dependent if (w >= 0.0 && w < 1.0) { t = 0.05f; R = 0.768f; } else if (w >= 1.0 && w < 2.0) { t = 0.05f + f * 0.33f; R = 0.768f + 0.312f * f; } else if (w >= 2.0 && w < 3.0) { t = 0.38f + f * 0.58f; R = 1.08f; } else if (w >= 3.0 && w < 4.0) { t = 0.96f + f * 0.48f; R = 1.08f; } else if (w >= 4.0 && w < 5.0) { t = 1.44f + f * 0.46f; R = 1.08f; } else if (w >= 5.0 && w < 6.0) { t = 1.9f + f * 0.6f; R = 1.08f; } else if (w >= 6.0) { t = 2.5f + (w - 6.0f) * 0.50f; R = 1.08f; } t /= scale; R /= scale; } private static void MakeTrc( ref Vector2 P1, ref Vector2 P2, ref Vector2 T, ref Vector2 R, ref Vector2 C, float w, Opt opt, ref float rr, ref float tt, ref float dist, bool anchorMode) { float t = 1.0f, r = 0.0f; //calculate t,r DetermineTr(w, ref t, ref r, opt.worldToScreenRatio); if (opt.feather && !opt.noFeatherAtCore) { r *= opt.feathering; } if (anchorMode) { t += r; } //output tt = t; rr = r; Vector2 DP = P2 - P1; dist = Vec2Ext.Normalize(ref DP); C = DP * (1 / opt.worldToScreenRatio); Vec2Ext.Perpen(ref DP); T = DP * t; R = DP * r; } private static void Segment(StAnchor SA, Opt opt, bool capFirst, bool capLast, bool core) { float[] weight = SA.W; Vector2[] P = new Vector2[2]; P[0] = SA.P[0]; P[1] = SA.P[1]; Color[] C = new Color[3]; C[0] = SA.C[0]; C[1] = SA.C[1]; Vector2 T2 = new Vector2(); Vector2 R2 = new Vector2(); Vector2 bR = new Vector2(); float t = 0, r = 0; bool varying_weight = weight[0] != weight[1]; Vector2 capStart = new Vector2(), capEnd = new Vector2(); StPolyline[] SL = new StPolyline[2]; /*for (int i = 0; i < 2; i++) { //lower the transparency for weight < 1.0 float actualWeight = weight[i] * opt.worldToScreenRatio; if (actualWeight >= 0.0 && actualWeight < 1.0) { C[i].a *= actualWeight; } }*/ { int i = 0; float dd = 0f; MakeTrc(ref P[i], ref P[i + 1], ref T2, ref R2, ref bR, weight[i], opt, ref r, ref t, ref dd, false); if (capFirst) { if (opt.cap == Opt.PLCsquare) { P[0] -= bR * (t + r); } capStart = bR; Vec2Ext.Opposite(ref capStart); if (opt.feather && !opt.noFeatherAtCap) { capStart *= 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; SL[i].degenT = false; SL[i].degenR = false; } { int i = 1; float dd = 0f; if (varying_weight) { MakeTrc(ref P[i - 1], ref P[i], ref T2, ref R2, ref bR, weight[i], opt, ref r, ref t, ref dd, false); } if (capLast) { if (opt.cap == Opt.PLCsquare) { P[1] += bR * (t + r); } capEnd = bR; if (opt.feather && !opt.noFeatherAtCap) { capEnd *= 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; SL[i].degenT = false; SL[i].degenR = false; } SegmentLate(opt, P, C, SL, SA.vah, capStart, capEnd, core); } private static void SegmentLate( Opt opt, Vector2[] P, Color[] C, StPolyline[] SL, VertexArrayHolder tris, Vector2 cap1, Vector2 cap2, bool core) { tris.SetGlDrawMode(VertexArrayHolder.GL_TRIANGLES); Vector2 P_0, P_1; P_0 = P[0]; P_1 = P[1]; Vector2 P1, P2, P3, P4; //core Vector2 P1c, P2c, P3c, P4c; //cap Vector2 P1r, P2r, P3r, P4r; //fade float s0 = 1, s1 = 1; if (SL[0].t < SL[1].t) { s0 = (SL[0].t + SL[0].r) / (SL[1].t + SL[1].r); } if (SL[1].t < SL[0].t) { s1 = (SL[1].t + SL[1].r) / (SL[0].t + SL[0].r); } P1 = P_0 + SL[0].T + SL[0].R; P1r = P1 - SL[0].R * s0; P1c = P1 + cap1 * s0; P2 = P_0 - SL[0].T - SL[0].R; P2r = P2 + SL[0].R * s0; P2c = P2 + cap1 * s0; P3 = P_1 + SL[1].T + SL[1].R; P3r = P3 - SL[1].R * s1; P3c = P3 + cap2 * s1; P4 = P_1 - SL[1].T - SL[1].R; P4r = P4 + SL[1].R * s1; P4c = P4 + cap2 * s1; float rr = System.Math.Max((SL[0].t + SL[0].r) / SL[0].r, (SL[1].t + SL[1].r) / SL[1].r); float rc = (P_1 - P_0).Length() / System.Math.Max(SL[0].t + SL[0].r, SL[1].t + SL[1].r); //core if (core) { float rc0 = 0, rc1 = 0; if (SL[0].djoint == Opt.PLCbutt && !cap1.IsZero()) { rc0 = rc; } if (SL[1].djoint == Opt.PLCbutt && !cap2.IsZero()) { rc1 = rc; } tris.Push3(P1, P3, P2, C[0], C[1], C[0], rr, 0, rc0); tris.Push3(P2, P3, P4, C[0], C[1], C[1], 0, rc1, rr); } //caps for (int j = 0; j < 2; j++) { VertexArrayHolder cap = new VertexArrayHolder(); cap.SetGlDrawMode(VertexArrayHolder.GL_TRIANGLE_STRIP); Vector2 cur_cap = j == 0 ? cap1 : cap2; if (cur_cap.IsZero()) { continue; } if (SL[j].djoint == Opt.PLCround) { //round cap Vector2 O = P[j]; float dangle = GetPljRoundDangle(SL[j].t, SL[j].r, opt.worldToScreenRatio); VectorsToArc( cap, O, C[j], C[j], SL[j].T + SL[j].R, -SL[j].T - SL[j].R, dangle, SL[j].t + SL[j].r, 0.0f, false, O, j == 0 ? cap1 : cap2, rr, false); //cap.Push(O-SL[j].T-SL[j].R, C[j]); //cap.Push(O, C[j]); tris.Push(cap); } else if (SL[j].djoint == Opt.PLCrect || SL[j].djoint == Opt.PLCsquare) { //rectangular cap Vector2 Pj, Pjr, Pjc, Pk, Pkr, Pkc; if (j == 0) { Pj = P1; Pjr = P1r; Pjc = P1c; Pk = P2; Pkr = P2r; Pkc = P2c; } else { Pj = P4; Pjr = P4r; Pjc = P4c; Pk = P3; Pkr = P3r; Pkc = P3c; } tris.PushF(Pk, C[j]); tris.PushF(Pkc, C[j]); tris.Push(Pkr, C[j]); tris.Push(Pkr, C[j]); tris.PushF(Pkc, C[j]); tris.Push(Pjr, C[j]); tris.Push(Pjr, C[j]); tris.PushF(Pkc, C[j]); tris.PushF(Pjc, C[j]); tris.Push(Pjr, C[j]); tris.PushF(Pjc, C[j]); tris.PushF(Pj, C[j]); } } } private static void Anchor(StAnchor SA, Opt opt, bool capFirst, bool capLast) { Vector2[] P = SA.P; Color[] C = SA.C; float[] weight = SA.W; StPolyline[] SL = SA.SL; if (Vec2Ext.SignedArea(P[0], P[1], P[2]) > 0) { // rectify clockwise Vector2 P0 = P[0]; P[0] = P[2]; P[2] = P0; Color C0 = C[0]; C[0] = C[2]; C[2] = C0; float weight0 = weight[0]; weight[0] = weight[2]; weight[2] = weight0; bool capCap = capFirst; capFirst = capLast; capLast = capCap; } //const float 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 float cos_cri_angle = 0.979386f; //cos(critical_angle) bool varying_weight = !(weight[0] == weight[1] & weight[1] == weight[2]); Vector2 T1 = new Vector2(), T2 = new Vector2(), T21 = new Vector2(), T31 = new Vector2(), RR = new Vector2(); /*for (int i = 0; i < 3; i++) { //lower the transparency for weight < 1.0 float actualWeight = weight[i] * opt.worldToScreenRatio; if (actualWeight >= 0.0 && actualWeight < 1.0) { C[i].a *= actualWeight; } }*/ { int i = 0; Vector2 cap0 = new Vector2(), cap1 = new Vector2(); float r = 0f, t = 0f, d = 0f; MakeTrc(ref P[i], ref P[i + 1], ref T2, ref RR, ref cap1, weight[i], opt, ref r, ref t, ref d, true); if (varying_weight) { MakeTrc(ref P[i], ref P[i + 1], ref T31, ref RR, ref cap0, weight[i + 1], opt, ref d, ref d, ref d, true); } else { T31 = T2; } SL[i].djoint = opt.cap; SL[i].T = T2; SL[i].t = t; SL[i].r = r; SL[i].degenT = false; SL[i + 1].T1 = T31; } { int i = 1; float r = 0f, t = 0f; Vector2 P_cur = P[i]; //current point Vector2 P_nxt = P[i + 1]; //next point Vector2 P_las = P[i - 1]; //last point { Vector2 cap0 = new Vector2(), bR = new Vector2(); float length_cur = 0f, length_nxt = 0f, d = 0f; MakeTrc(ref P_las, ref P_cur, ref T1, ref RR, ref cap0, weight[i - 1], opt, ref d, ref d, ref length_cur, true); if (varying_weight) { MakeTrc(ref P_las, ref P_cur, ref T21, ref RR, ref cap0, weight[i], opt, ref d, ref d, ref d, true); } else { T21 = T1; } MakeTrc(ref P_cur, ref P_nxt, ref T2, ref RR, ref bR, weight[i], opt, ref r, ref t, ref length_nxt, true); if (varying_weight) { MakeTrc(ref P_cur, ref P_nxt, ref T31, ref RR, ref cap0, weight[i + 1], opt, ref d, ref d, ref d, true); } else { T31 = T2; } SL[i].T = T2; SL[i].bR = bR; SL[i].t = t; SL[i].r = r; SL[i].degenT = false; SL[i + 1].T1 = T31; } { //2nd point //find the angle between the 2 line segments Vector2 ln1 = new Vector2(), ln2 = new Vector2(), V = new Vector2(); ln1 = P_cur - P_las; ln2 = P_nxt - P_cur; Vec2Ext.Normalize(ref ln1); Vec2Ext.Normalize(ref ln2); Vec2Ext.Dot(ln1, ln2, ref V); float cos_tho = V.x + V.y; bool zero_degree = System.Math.Abs(cos_tho - 1.0f) < 0.0000001f; bool d180_degree = cos_tho < -1.0f + 0.0001f; bool intersection_fail = false; int result3 = 1; SL[i].djoint = opt.joint; if (SL[i].djoint == Opt.PLJmiter) { if (System.Math.Abs(cos_tho) >= cos_cri_angle) { SL[i].djoint = Opt.PLJbevel; } } Vec2Ext.AnchorOutward(ref T1, P_cur, P_nxt); Vec2Ext.AnchorOutward(ref T21, P_cur, P_nxt); Vec2Ext.FollowSigns(ref SL[i].T1, T21); Vec2Ext.AnchorOutward(ref T2, P_cur, P_las); Vec2Ext.FollowSigns(ref SL[i].T, T2); Vec2Ext.AnchorOutward(ref T31, P_cur, P_las); { //must do intersection Vector2 interP = new Vector2(), vP = new Vector2(); float[] pts = new float[2]; result3 = Vec2Ext.Intersect( P_las + T1, P_cur + T21, P_nxt + T31, P_cur + T2, ref interP, pts); if (result3 != 0) { vP = interP - P_cur; SL[i].vP = vP; if (SL[i].djoint == Opt.PLJmiter) { if (pts[0] > 2 || pts[1] > 2 || vP.Length() > 2 * SL[i].t) { SL[i].djoint = Opt.PLJbevel; } } } else { intersection_fail = true; //Debug.Log(System.String.Format("intersection failed: cos(angle)={0}, angle={1} (degree)", cos_tho, System.Math.Acos(cos_tho) * 180 / 3.14159)); } } if (zero_degree || intersection_fail) { Segment(SA, opt, capFirst, false, true); 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, capLast, true); return; } Vec2Ext.Opposite(ref T1); Vec2Ext.Opposite(ref T21); Vec2Ext.Opposite(ref T2); Vec2Ext.Opposite(ref T31); //make intersections Vector2 PT1 = new Vector2(), PT2 = new Vector2(); int result1t, result2t; { result1t = Vec2Ext.Intersect( P_nxt - T31, P_nxt + T31, P_las + T1, P_cur + T21, //knife1_a ref PT1); //core result2t = Vec2Ext.Intersect( P_las - T1, P_las + T1, P_nxt + T31, P_cur + T2, //knife2_a ref PT2); } bool is_result1t = result1t == 1; bool is_result2t = result2t == 1; if (is_result1t | is_result2t) { //core degeneration SL[i].degenT = true; SL[i].preFull = is_result1t; SL[i].PT = is_result1t ? PT1: PT2; } else { int result4; result4 = Vec2Ext.Intersect( P_nxt - T31, P_nxt + T31, P_las, P_cur, ref PT1); if (result4 == 1) { SL[i].degenT = true; SL[i].preFull = true; SL[i].PT = PT1; } else { result4 = Vec2Ext.Intersect( P_las - T1, P_las + T1, P_cur, P_nxt, ref PT2); if (result4 == 1) { SL[i].degenT = true; SL[i].preFull = false; SL[i].PT = PT2; } } } if (d180_degree | result3 == 0) { //to solve visual bugs 3 and 1.1 SL[i].vP = SL[i].T; Vec2Ext.FollowSigns(ref SL[i].T1, SL[i].T); SL[i].djoint = Opt.PLJmiter; } } } { int i = 2; Vector2 cap0 = new Vector2(); float r = 0f, t = 0f, d = 0f; MakeTrc(ref P[i - 1], ref P[i], ref T2, ref RR, ref cap0, weight[i], opt, ref r, ref t, ref d, true); SL[i].djoint = opt.cap; SL[i].T = T2; SL[i].t = t; SL[i].r = r; SL[i].degenT = false; } AnchorLate(opt, P, C, SA.SL, SA.vah, capFirst, capLast); if (capFirst && SL[0].djoint != Opt.PLCbutt && SL[0].djoint != Opt.PLCnone) { Segment(SA, opt, true, false, false); } if (capLast && SL[1].djoint != Opt.PLCbutt && SL[1].djoint != Opt.PLCnone) { 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, true, false); } } //Anchor private static void AnchorLate( Opt opt, Vector2[] P, Color[] C, StPolyline[] SL, VertexArrayHolder tris, bool capFirst, bool capLast) { Vector2 P_0 = P[0], P_1 = P[1], P_2 = P[2]; Vector2 P0, P1, P2, P3, P4, P5, P6, P7; P0 = P_1 + SL[1].vP; P1 = P_1 - SL[1].vP; P2 = P_1 + SL[1].T1; P3 = P_0 + SL[0].T; P4 = P_0 - SL[0].T; P5 = P_1 + SL[1].T; P6 = P_2 + SL[2].T; P7 = P_2 - SL[2].T; int normal_line_core_joint = 1; //0:dont draw, 1:draw, 2:outer only float rr = SL[1].t / SL[1].r; float rc1 = 0, rc2 = 0; if (capFirst && SL[0].djoint == Opt.PLCbutt) { rc1 = (P_1 - P_0).Length() / (SL[0].r) / 2; } if (capLast && SL[2].djoint == Opt.PLCbutt) { rc2 = (P_2 - P_1).Length() / (SL[2].r) / 2; } if (SL[1].degenT) { P1 = SL[1].PT; tris.Push3(P1, P3, P2, C[1], C[0], C[1], 0, rr, 0); //fir seg tris.Push3(P1, P5, P6, C[1], C[1], C[2], 0, rr, 0); //las seg if (SL[1].preFull) { tris.Push3(P3, P1, P4, C[0], C[1], C[0], 0, rr, 0); } else { tris.Push3(P1, P6, P7, C[1], C[2], C[2], 0, 0, rr); } } else { // normal first segment tris.Push3(P2, P4, P3, C[1], C[0], C[0], 0, rc1, rr); tris.Push3(P4, P2, P1, C[0], C[1], C[1], 0, 0, rr); // normal last segment tris.Push3(P5, P7, P1, C[1], C[2], C[1], 0, rr, 0); tris.Push3(P7, P5, P6, C[2], C[1], C[2], 0, rr, rc2); } if (normal_line_core_joint != 0) { switch (SL[1].djoint) { case Opt.PLJmiter: tris.Push3(P2, P0, P1, C[1], C[1], C[1], rr, 0, 0); tris.Push3(P1, P0, P5, C[1], C[1], C[1], 0, rr, 0); break; case Opt.PLJbevel: if (normal_line_core_joint == 1) tris.Push3(P2, P5, P1, C[1], C[1], C[1], rr, 0, 0); break; case Opt.PLJround: { VertexArrayHolder strip = new VertexArrayHolder(); strip.SetGlDrawMode(VertexArrayHolder.GL_TRIANGLE_STRIP); if (normal_line_core_joint == 1) { VectorsToArc(strip, P_1, C[1], C[1], SL[1].T1, SL[1].T, GetPljRoundDangle(SL[1].t, SL[1].r, opt.worldToScreenRatio), SL[1].t, 0.0f, false, P1, new Vector2(), rr, true); } else if (normal_line_core_joint == 2) { VectorsToArc(strip, P_1, C[1], C[1], SL[1].T1, SL[1].T, GetPljRoundDangle(SL[1].t, SL[1].r, opt.worldToScreenRatio), SL[1].t, 0.0f, false, P5, new Vector2(), rr, true); } tris.Push(strip); } break; } } } //AnchorLate private static void VectorsToArc( VertexArrayHolder hold, Vector2 P, Color C, Color C2, Vector2 PA, Vector2 PB, float dangle, float r, float r2, bool ignorEnds, Vector2 apparentP, Vector2 hint, float rr, bool innerFade) { // triangulate an inner arc between vectors A and B, // A and B are position vectors relative to P const float m_pi = (float) System.Math.PI; Vector2 A = PA * (1 / r); Vector2 B = PB * (1 / r); float rrr; if (innerFade) { rrr = 0; } else { rrr = -1; } float angle1 = (float) System.Math.Acos(A.x); float angle2 = (float) System.Math.Acos(B.x); if (A.y > 0) { angle1 = 2 * m_pi - angle1; } if (B.y > 0) { angle2 = 2 * m_pi - angle2; } if (!hint.IsZero()) { // special case when angle1 == angle2, // have to determine which side by hint if (hint.x > 0 && hint.y == 0) { angle1 -= (angle1 < angle2 ? 1 : -1) * 0.00001f; } else if (hint.x == 0 && hint.y > 0) { angle1 -= (angle1 < angle2 ? 1 : -1) * 0.00001f; } else if (hint.x > 0 && hint.y > 0) { angle1 -= (angle1 < angle2 ? 1 : -1) * 0.00001f; } else if (hint.x > 0 && hint.y < 0) { angle1 -= (angle1 < angle2 ? 1 : -1) * 0.00001f; } else if (hint.x < 0 && hint.y > 0) { angle1 += (angle1 < angle2 ? 1 : -1) * 0.00001f; } else if (hint.x < 0 && hint.y < 0) { angle1 += (angle1 < angle2 ? 1 : -1) * 0.00001f; } } // (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 ignorEnds=false, the two edges of the arc lie exactly on angle1 // and angle2. when ignorEnds=true, the two edges of the arc do not touch // angle1 or angle2. // -P is the mathematical center of the arc. // -when use_apparent_P is true, r2 is ignored, // apparentP 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 if (angle2 > angle1) { if (angle2 - angle1 > m_pi) { angle2 = angle2 - 2 * m_pi; } } else { if (angle1 - angle2 > m_pi) { angle1 = angle1 - 2 * m_pi; } } bool incremental = true; if (angle1 > angle2) { incremental = false; } if (incremental) { if (!ignorEnds) { hold.Push(new Vector2(P.x + PB.x, P.y + PB.y), C, rr); hold.Push(apparentP, C2, rrr); } for (float a = angle2 - dangle; a > angle1; a -= dangle) { inner_arc_push(System.Math.Cos(a), System.Math.Sin(a)); } if (!ignorEnds) { hold.Push(new Vector2(P.x + PA.x, P.y + PA.y), C, rr); hold.Push(apparentP, C2, rrr); } } else //decremental { if (!ignorEnds) { hold.Push(apparentP, C2, rr); hold.Push(new Vector2(P.x + PB.x, P.y + PB.y), C, rrr); } for (float a = angle2 + dangle; a < angle1; a += dangle) { inner_arc_push(System.Math.Cos(a), System.Math.Sin(a), true); } if (!ignorEnds) { hold.Push(apparentP, C2, rr); hold.Push(new Vector2(P.x + PA.x, P.y + PA.y), C, rrr); } } void inner_arc_push(double x, double y, bool reverse = false) { Vector2 PP = new Vector2(P.x + (float) x * r, P.y - (float) y * r); //hold.Dot(PP, 0.05f); return; if (!reverse) { hold.Push(PP, C, rr); hold.Push(apparentP, C2, rrr); } else { hold.Push(apparentP, C2, rr); hold.Push(PP, C, rrr); } } } private static float GetPljRoundDangle(float t, float r, float scale) { float dangle; float sum = (t + r) * scale; if (sum <= 1.44f + 1.08f) { //w<=4.0, feathering=1.0 dangle = 0.6f / sum; } else if (sum <= 3.25f + 1.08f) { //w<=6.5, feathering=1.0 dangle = 2.8f / sum; } else { dangle = 4.2f / sum; } return dangle; } } } ================================================ FILE: csharp/Assets/Vaser/Polyline.cs.meta ================================================ fileFormatVersion: 2 guid: f3267f6b485b83291a8297963816eb95 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: csharp/Assets/Vaser/Vec2Ext.cs ================================================ using UnityEngine; namespace Vaser { public static class Vec2Ext { public static float Length(this Vector2 a) { return (float) System.Math.Sqrt(a.x*a.x+a.y*a.y); } public static float SignedArea(Vector2 P1, Vector2 P2, Vector2 P3) { return (P2.x-P1.x)*(P3.y-P1.y) - (P3.x-P1.x)*(P2.y-P1.y); } public static void Dot(Vector2 a, Vector2 b, ref Vector2 o) //dot product: o = a dot b { o.x = a.x * b.x; o.y = a.y * b.y; } public static void Opposite(ref Vector2 a) { a.x = -a.x; a.y = -a.y; } public static float Normalize(ref Vector2 a) { float L = a.Length(); if (L > 0.0000001f) { a.x /= L; a.y /= L; } return L; } public static void Perpen(ref Vector2 a) //perpendicular: anti-clockwise 90 degrees { float y_value=a.y; a.y=a.x; a.x=-y_value; } public static void FollowSigns(ref Vector2 a, Vector2 b) { if ((a.x>0) != (b.x>0)) a.x = -a.x; if ((a.y>0) != (b.y>0)) a.y = -a.y; } //judgements public static bool Negligible(float M) { const float vaser_min_alw = 0.000000001f; return -vaser_min_alw < M && M < vaser_min_alw; } public static bool Negligible(double M) { const double vaser_min_alw = 0.0000000001; return -vaser_min_alw < M && M < vaser_min_alw; } public static bool Negligible(this Vector2 a) { return Negligible(a.x) && Negligible(a.y); } public static bool IsZero(this Vector2 a) { return a.x==0.0 && a.y==0.0; } public static bool Intersecting(Vector2 A, Vector2 B, Vector2 C, Vector2 D) { //return true if AB intersects CD return SignedArea(A,B,C)>0 != SignedArea(A,B,D)>0; } public static bool OppositeQuadrant(Vector2 P1, Vector2 P2) { int P1x = P1.x>0? 1:(P1.x<0?-1:0); int P1y = P1.y>0? 1:(P1.y<0?-1:0); int P2x = P2.x>0? 1:(P2.x<0?-1:0); int 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; } public static bool AnchorOutward(ref Vector2 V, Vector2 b, Vector2 c, bool reverse=false) { //put the correct outward vector at V, with V placed on b, comparing distances from c bool determinant = (b.x*V.x - c.x*V.x + b.y*V.y - c.y*V.y) > 0; 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 } } public static int Intersect( Vector2 P1, Vector2 P2, //line 1 Vector2 P3, Vector2 P4, //line 2 ref Vector2 Pout, //the output point float[] u_out = null) { //Determine the intersection point of two line segments float 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.5f; Pout.y = (P1.y + P2.y) * 0.5f; return 2; //meaning the lines coincide } if (Negligible(denom)) { Pout.x = 0; Pout.y = 0; return 0; //meaning lines are parallel } mua = (float) (numera / denom); mub = (float) (numerb / denom); if (u_out != null) { u_out[0] = mua; u_out[1] = 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/ } public static int Octant(this Vector2 a) { if (a.x == 0 && a.y == 0) { return 0; } if (a.x == 0) { return a.y > 0 ? 3 : 7; } if (a.y == 0) { return a.x > 0 ? 1 : 5; } if (a.x > 0 && a.y > 0) { if (a.y/a.x < 0.5f) { return 1; } else if (a.y/a.x > 2f) { return 3; } else { return 2; } } else if (a.x < 0 && a.y > 0) { if (a.y/a.x < -2f) { return 3; } else if (a.y/a.x > -0.5f) { return 5; } else { return 4; } } else if (a.x < 0 && a.y < 0) { if (a.y/a.x < 0.5f) { return 5; } else if (a.y/a.x > 2f) { return 7; } else { return 6; } } else if (a.x > 0 && a.y < 0) { if (a.y/a.x < -2f) { return 7; } else if (a.y/a.x > -0.5f) { return 1; } else { return 8; } } return 0; } } } ================================================ FILE: csharp/Assets/Vaser/Vec2Ext.cs.meta ================================================ fileFormatVersion: 2 guid: 1169d245ab3670b4685e0ceeafcdde52 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: csharp/Assets/Vaser/VertexArrayHolder.cs ================================================ using UnityEngine; using System.Collections.Generic; namespace Vaser { public class VertexArrayHolder { public const int GL_POINTS = 1; public const int GL_LINES = 2; public const int GL_TRIANGLES = 3; public const int GL_LINE_STRIP = 4; public const int GL_LINE_LOOP = 5; public const int GL_TRIANGLE_STRIP = 6; public const int GL_TRIANGLE_FAN = 7; public int glmode = GL_TRIANGLES; //drawing mode in opengl private bool jumping = false; private List vert = new List(); private List color = new List(); private List fade = new List(); public int GetCount() { return vert.Count; } public void SetGlDrawMode(int gl_draw_mode) { glmode = gl_draw_mode; } public int Push(Vector2 P, Color cc, float fade0=0) { int cur = vert.Count; vert.Add(P); // A Vector2 can be implicitly converted into a Vector3. (The z is set to zero in the result). color.Add(cc); fade.Add(fade0); if (jumping) { jumping = false; RepeatLastPush(); } return cur; } public int PushF(Vector2 P, Color C) { C.a = 0; return Push(P, C); } public void Push3( Vector2 P1, Vector2 P2, Vector2 P3, Color C1, Color C2, Color C3, float fade1=0, float fade2=0, float fade3=0) { Push(P1, C1, fade1); Push(P2, C2, fade2); Push(P3, C3, fade3); } public void Push4( Vector2 P1, Vector2 P2, Vector2 P3, Vector2 P4, Color C1, Color C2, Color C3, Color C4, float r1, float r2, float r3, float r4) { //interpret P0 to P3 as triangle strip Push3(P1, P2, P3, C1, C2, C3, r1, r2, r3); Push3(P3, P2, P4, C3, C2, C4, r3, r2, r4); } public void Dot(Vector2 P, float size) { size /= 2; if (glmode == GL_TRIANGLES) { } else if (glmode == GL_TRIANGLE_STRIP) { Push(new Vector2(P.x-size, P.y), Color.red); Push(new Vector2(P.x, P.y+size), Color.red); Push(new Vector2(P.x, P.y-size), Color.red); Push(new Vector2(P.x+size, P.y), Color.red); Jump(); } } public void Push(VertexArrayHolder hold) { if (glmode == hold.glmode) { vert.AddRange(hold.vert); color.AddRange(hold.color); fade.AddRange(hold.fade); } else if (glmode == GL_TRIANGLES && hold.glmode == GL_TRIANGLE_STRIP) { for (int b=3; b < hold.vert.Count; b+=2) { vert.Add(hold.vert[b-3]); color.Add(hold.color[b-3]); fade.Add(hold.fade[b-0]); vert.Add(hold.vert[b-2]); color.Add(hold.color[b-2]); fade.Add(hold.fade[b-0]); vert.Add(hold.vert[b-1]); color.Add(hold.color[b-1]); fade.Add(hold.fade[b-1]); vert.Add(hold.vert[b-1]); color.Add(hold.color[b-1]); fade.Add(hold.fade[b-0]); vert.Add(hold.vert[b-2]); color.Add(hold.color[b-2]); fade.Add(hold.fade[b-1]); vert.Add(hold.vert[b-0]); color.Add(hold.color[b-0]); fade.Add(hold.fade[b-0]); } } } public Vector2 Get(int i) { Vector2 P = new Vector2(); P.x = vert[i].x; P.y = vert[i].y; return P; } public Color GetColor(int b) { return color[b]; } public Vector2 GetRelativeEnd(int di = -1) { //di=-1 is the last one int count = vert.Count; int i = count+di; if (i<0) i=0; if (i>=count) i=count-1; return Get(i); } public void RepeatLastPush() { int i = vert.Count-1; Push(Get(i), GetColor(i)); } public void Jump() //to make a jump in triangle strip by degenerated triangles { if (glmode == GL_TRIANGLE_STRIP) { RepeatLastPush(); jumping = true; } } public List GetVertices() { return vert; } public List GetTriangles() { List indices = new List(); if (glmode == GL_TRIANGLES) { for (int i=0; i < vert.Count; i++) { indices.Add(i); } } return indices; } public List GetUVs() { List uvs = new List(); Vector4[] UVS = new Vector4[] { new Vector4(0, 0, 0, 0), new Vector4(1, 0, 0, 0), new Vector4(1, 1, 0, 0), new Vector4(0, 1, 0, 0), new Vector4(-1, 1, 0, 0), new Vector4(-1, 0, 0, 0), new Vector4(-1, -1, 0, 0), new Vector4(0, -1, 0, 0), new Vector4(1, -1, 0, 0), new Vector4(-1, 0, 0, 0), }; if (glmode == GL_TRIANGLES) { for (int i=2; i < fade.Count; i+=3) { int count = 0; if (fade[i-2] > 0) count++; if (fade[i-1] > 0) count++; if (fade[i-0] > 0) count++; if (count == 0) { addUV(0, 1, 1); addUV(0, 1, 1); addUV(0, 1, 1); } else if (count == 3) { addUV(1, fade[i-2], fade[i-2]); addUV(5, fade[i-1], fade[i-1]); addUV(3, fade[i-0], fade[i-0]); } else if (count == 2) { float fadeX = 0, fadeY = 0; for (int j=0; j<3; j++) { float fadeU = 0, fadeV = 0; if (j == 0) { fadeU = fade[i-2]; fadeV = fade[i-0]; } else if (j == 1) { fadeU = fade[i-1]; fadeV = fade[i-2]; } else if (j == 2) { fadeU = fade[i-0]; fadeV = fade[i-1]; } if (fadeU > 0 && fadeV > 0) { fadeX = fadeV; fadeY = fadeU; } } for (int j=0; j<3; j++) { float fadeU = 0, fadeV = 0; if (j == 0) { fadeU = fade[i-2]; fadeV = fade[i-0]; } else if (j == 1) { fadeU = fade[i-1]; fadeV = fade[i-2]; } else if (j == 2) { fadeU = fade[i-0]; fadeV = fade[i-1]; } if (fadeU > 0 && fadeV > 0) { //Debug.Log("fade both"); addUV(8, fadeX, fadeY); } else if (fadeU > 0) { //Debug.Log("fadeU"); addUV(2, fadeX, fadeY); } else if (fadeV > 0) { //Debug.Log("fadeV"); addUV(6, fadeX, fadeY); } } } else if (count == 1 && fade[i-2] >= 0 && fade[i-1] >= 0 && fade[i-0] >= 0) { float fadeU = 0; float fadeV = 0; for (int j=0; j<3; j++) { if (j == 0) { fadeU = fade[i-2]; fadeV = fade[i-0]; } else if (j == 1) { fadeU = fade[i-1]; fadeV = fade[i-2]; } else if (j == 2) { fadeV = fade[i-1]; fadeU = fade[i-0]; } if (fadeU > 0 || fadeV > 0) { //Debug.Log("fade side"); float fader = fadeU > 0 ? fadeU : fadeV; addUV(1, fader, fader); } else { //Debug.Log("corner fade"); float fader = fade[i-2] > 0 ? fade[i-2] : fade[i-1] > 0 ? fade[i-1] : fade[i-0]; addUV(5, fader, fader); } } } else if (count == 1) { // may be fade is < 0 float fadeU = 0; float fadeV = 0; for (int j=0; j<3; j++) { if (j == 0) { fadeU = fade[i-2]; fadeV = fade[i-0]; } else if (j == 1) { fadeU = fade[i-1]; fadeV = fade[i-2]; } else if (j == 2) { fadeV = fade[i-1]; fadeU = fade[i-0]; } if (fadeU > 0 || fadeV > 0) { //Debug.Log("fan fade"); float fader = fadeU > 0 ? fadeU : fadeV; addUV(9, fader, fader); } else { //Debug.Log("zero fade"); float fader = fade[i-2] > 0 ? fade[i-2] : fade[i-1] > 0 ? fade[i-1] : fade[i-0]; addUV(0, fader, fader); } } } } } return uvs; void addUV(int i, float rx, float ry) { uvs.Add(new Vector4(UVS[i].x, UVS[i].y, rx, ry)); } } public List GetColors() { return color; } } } ================================================ FILE: csharp/Assets/Vaser/VertexArrayHolder.cs.meta ================================================ fileFormatVersion: 2 guid: 0321073adc558cd47964c01d4ded4443 MonoImporter: externalObjects: {} serializedVersion: 2 defaultReferences: [] executionOrder: 0 icon: {instanceID: 0} userData: assetBundleName: assetBundleVariant: ================================================ FILE: csharp/Assets/Vaser.meta ================================================ fileFormatVersion: 2 guid: 89409ccbf42597ee79d84df5e334ee24 folderAsset: yes DefaultImporter: externalObjects: {} userData: assetBundleName: assetBundleVariant: ================================================ FILE: csharp/Docs/README.md ================================================ ![Screenshot](Screenshot%20at%202019-10-23%2021-04-21.png) ![Screenshot](Screenshot%20at%202019-10-29%2015-48-55.png) ![Screenshot](Screenshot%20at%202019-10-30%2000-13-05.png) ![Screenshot](Screenshot%20at%202019-11-10%2002-48-59.png) ![Screenshot](Screenshot%20at%202019-12-02%2000-44-00.png) ![Screenshot](Screenshot%20at%202019-12-02%2004-27-00.png) ![Screenshot](Screenshot%20at%202019-12-07%2023-56-00.png) ![Screenshot](Screenshot%20at%202019-12-08%2022-44-00.png) ![Screenshot](Screenshot%20at%202019-12-11%2001-11-10.png) ![Screenshot](Screenshot%20at%202019-12-15%2003-31-00.png) ![Screenshot](Screenshot%20at%202019-12-15%2003-57-00.png) ================================================ FILE: csharp/Packages/manifest.json ================================================ { "dependencies": { "com.unity.collab-proxy": "1.2.16", "com.unity.ext.nunit": "1.0.0", "com.unity.ide.rider": "1.1.0", "com.unity.ide.vscode": "1.1.2", "com.unity.package-manager-ui": "2.2.0", "com.unity.test-framework": "1.0.13", "com.unity.textmeshpro": "2.0.1", "com.unity.timeline": "1.1.0", "com.unity.ugui": "1.0.0", "com.unity.modules.ai": "1.0.0", "com.unity.modules.androidjni": "1.0.0", "com.unity.modules.animation": "1.0.0", "com.unity.modules.assetbundle": "1.0.0", "com.unity.modules.audio": "1.0.0", "com.unity.modules.cloth": "1.0.0", "com.unity.modules.director": "1.0.0", "com.unity.modules.imageconversion": "1.0.0", "com.unity.modules.imgui": "1.0.0", "com.unity.modules.jsonserialize": "1.0.0", "com.unity.modules.particlesystem": "1.0.0", "com.unity.modules.physics": "1.0.0", "com.unity.modules.physics2d": "1.0.0", "com.unity.modules.screencapture": "1.0.0", "com.unity.modules.terrain": "1.0.0", "com.unity.modules.terrainphysics": "1.0.0", "com.unity.modules.tilemap": "1.0.0", "com.unity.modules.ui": "1.0.0", "com.unity.modules.uielements": "1.0.0", "com.unity.modules.umbra": "1.0.0", "com.unity.modules.unityanalytics": "1.0.0", "com.unity.modules.unitywebrequest": "1.0.0", "com.unity.modules.unitywebrequestassetbundle": "1.0.0", "com.unity.modules.unitywebrequestaudio": "1.0.0", "com.unity.modules.unitywebrequesttexture": "1.0.0", "com.unity.modules.unitywebrequestwww": "1.0.0", "com.unity.modules.vehicles": "1.0.0", "com.unity.modules.video": "1.0.0", "com.unity.modules.vr": "1.0.0", "com.unity.modules.wind": "1.0.0", "com.unity.modules.xr": "1.0.0" } } ================================================ FILE: csharp/ProjectSettings/AudioManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!11 &1 AudioManager: m_ObjectHideFlags: 0 serializedVersion: 2 m_Volume: 1 Rolloff Scale: 1 Doppler Factor: 1 Default Speaker Mode: 2 m_SampleRate: 0 m_DSPBufferSize: 1024 m_VirtualVoiceCount: 512 m_RealVoiceCount: 32 m_SpatializerPlugin: m_AmbisonicDecoderPlugin: m_DisableAudio: 0 m_VirtualizeEffects: 1 m_RequestedDSPBufferSize: 1024 ================================================ FILE: csharp/ProjectSettings/ClusterInputManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!236 &1 ClusterInputManager: m_ObjectHideFlags: 0 m_Inputs: [] ================================================ FILE: csharp/ProjectSettings/DynamicsManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!55 &1 PhysicsManager: m_ObjectHideFlags: 0 serializedVersion: 11 m_Gravity: {x: 0, y: -9.81, z: 0} m_DefaultMaterial: {fileID: 0} m_BounceThreshold: 2 m_SleepThreshold: 0.005 m_DefaultContactOffset: 0.01 m_DefaultSolverIterations: 6 m_DefaultSolverVelocityIterations: 1 m_QueriesHitBackfaces: 0 m_QueriesHitTriggers: 1 m_EnableAdaptiveForce: 0 m_ClothInterCollisionDistance: 0 m_ClothInterCollisionStiffness: 0 m_ContactsGeneration: 1 m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff m_AutoSimulation: 1 m_AutoSyncTransforms: 0 m_ReuseCollisionCallbacks: 1 m_ClothInterCollisionSettingsToggle: 0 m_ContactPairsMode: 0 m_BroadphaseType: 0 m_WorldBounds: m_Center: {x: 0, y: 0, z: 0} m_Extent: {x: 250, y: 250, z: 250} m_WorldSubdivisions: 8 m_FrictionType: 0 m_EnableEnhancedDeterminism: 0 m_EnableUnifiedHeightmaps: 1 m_DefaultMaxAngluarSpeed: 7 ================================================ FILE: csharp/ProjectSettings/EditorBuildSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!1045 &1 EditorBuildSettings: m_ObjectHideFlags: 0 serializedVersion: 2 m_Scenes: [] m_configObjects: {} ================================================ FILE: csharp/ProjectSettings/EditorSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!159 &1 EditorSettings: m_ObjectHideFlags: 0 serializedVersion: 8 m_ExternalVersionControlSupport: Hidden Meta Files m_SerializationMode: 2 m_LineEndingsForNewScripts: 2 m_DefaultBehaviorMode: 0 m_PrefabRegularEnvironment: {fileID: 0} m_PrefabUIEnvironment: {fileID: 0} m_SpritePackerMode: 0 m_SpritePackerPaddingPower: 1 m_EtcTextureCompressorBehavior: 1 m_EtcTextureFastCompressor: 1 m_EtcTextureNormalCompressor: 2 m_EtcTextureBestCompressor: 4 m_ProjectGenerationIncludedExtensions: txt;xml;fnt;cd;asmdef;rsp;asmref m_ProjectGenerationRootNamespace: m_CollabEditorSettings: inProgressEnabled: 1 m_EnableTextureStreamingInEditMode: 1 m_EnableTextureStreamingInPlayMode: 1 m_AsyncShaderCompilation: 1 m_ShowLightmapResolutionOverlay: 1 ================================================ FILE: csharp/ProjectSettings/GraphicsSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!30 &1 GraphicsSettings: m_ObjectHideFlags: 0 serializedVersion: 12 m_Deferred: m_Mode: 1 m_Shader: {fileID: 69, guid: 0000000000000000f000000000000000, type: 0} m_DeferredReflections: m_Mode: 1 m_Shader: {fileID: 74, guid: 0000000000000000f000000000000000, type: 0} m_ScreenSpaceShadows: m_Mode: 1 m_Shader: {fileID: 64, guid: 0000000000000000f000000000000000, type: 0} m_LegacyDeferred: m_Mode: 1 m_Shader: {fileID: 63, guid: 0000000000000000f000000000000000, type: 0} m_DepthNormals: m_Mode: 1 m_Shader: {fileID: 62, guid: 0000000000000000f000000000000000, type: 0} m_MotionVectors: m_Mode: 1 m_Shader: {fileID: 75, guid: 0000000000000000f000000000000000, type: 0} m_LightHalo: m_Mode: 1 m_Shader: {fileID: 105, guid: 0000000000000000f000000000000000, type: 0} m_LensFlare: m_Mode: 1 m_Shader: {fileID: 102, guid: 0000000000000000f000000000000000, type: 0} m_AlwaysIncludedShaders: - {fileID: 7, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 15104, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 15105, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 15106, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 10753, guid: 0000000000000000f000000000000000, type: 0} - {fileID: 10770, guid: 0000000000000000f000000000000000, type: 0} m_PreloadedShaders: [] m_SpritesDefaultMaterial: {fileID: 10754, guid: 0000000000000000f000000000000000, type: 0} m_CustomRenderPipeline: {fileID: 0} m_TransparencySortMode: 0 m_TransparencySortAxis: {x: 0, y: 0, z: 1} m_DefaultRenderingPath: 1 m_DefaultMobileRenderingPath: 1 m_TierSettings: [] m_LightmapStripping: 0 m_FogStripping: 0 m_InstancingStripping: 0 m_LightmapKeepPlain: 1 m_LightmapKeepDirCombined: 1 m_LightmapKeepDynamicPlain: 1 m_LightmapKeepDynamicDirCombined: 1 m_LightmapKeepShadowMask: 1 m_LightmapKeepSubtractive: 1 m_FogKeepLinear: 1 m_FogKeepExp: 1 m_FogKeepExp2: 1 m_AlbedoSwatchInfos: [] m_LightsUseLinearIntensity: 0 m_LightsUseColorTemperature: 0 m_LogWhenShaderIsCompiled: 0 ================================================ FILE: csharp/ProjectSettings/InputManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!13 &1 InputManager: m_ObjectHideFlags: 0 serializedVersion: 2 m_Axes: - serializedVersion: 3 m_Name: Horizontal descriptiveName: descriptiveNegativeName: negativeButton: left positiveButton: right altNegativeButton: a altPositiveButton: d gravity: 3 dead: 0.001 sensitivity: 3 snap: 1 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Vertical descriptiveName: descriptiveNegativeName: negativeButton: down positiveButton: up altNegativeButton: s altPositiveButton: w gravity: 3 dead: 0.001 sensitivity: 3 snap: 1 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire1 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: left ctrl altNegativeButton: altPositiveButton: mouse 0 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire2 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: left alt altNegativeButton: altPositiveButton: mouse 1 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire3 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: left shift altNegativeButton: altPositiveButton: mouse 2 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Jump descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: space altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Mouse X descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0 sensitivity: 0.1 snap: 0 invert: 0 type: 1 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Mouse Y descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0 sensitivity: 0.1 snap: 0 invert: 0 type: 1 axis: 1 joyNum: 0 - serializedVersion: 3 m_Name: Mouse ScrollWheel descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0 sensitivity: 0.1 snap: 0 invert: 0 type: 1 axis: 2 joyNum: 0 - serializedVersion: 3 m_Name: Horizontal descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0.19 sensitivity: 1 snap: 0 invert: 0 type: 2 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Vertical descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: altNegativeButton: altPositiveButton: gravity: 0 dead: 0.19 sensitivity: 1 snap: 0 invert: 1 type: 2 axis: 1 joyNum: 0 - serializedVersion: 3 m_Name: Fire1 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 0 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire2 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 1 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Fire3 descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 2 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Jump descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: joystick button 3 altNegativeButton: altPositiveButton: gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Submit descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: return altNegativeButton: altPositiveButton: joystick button 0 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Submit descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: enter altNegativeButton: altPositiveButton: space gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 - serializedVersion: 3 m_Name: Cancel descriptiveName: descriptiveNegativeName: negativeButton: positiveButton: escape altNegativeButton: altPositiveButton: joystick button 1 gravity: 1000 dead: 0.001 sensitivity: 1000 snap: 0 invert: 0 type: 0 axis: 0 joyNum: 0 ================================================ FILE: csharp/ProjectSettings/NavMeshAreas.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!126 &1 NavMeshProjectSettings: m_ObjectHideFlags: 0 serializedVersion: 2 areas: - name: Walkable cost: 1 - name: Not Walkable cost: 1 - name: Jump cost: 2 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 - name: cost: 1 m_LastAgentTypeID: -887442657 m_Settings: - serializedVersion: 2 agentTypeID: 0 agentRadius: 0.5 agentHeight: 2 agentSlope: 45 agentClimb: 0.75 ledgeDropHeight: 0 maxJumpAcrossDistance: 0 minRegionArea: 2 manualCellSize: 0 cellSize: 0.16666667 manualTileSize: 0 tileSize: 256 accuratePlacement: 0 debug: m_Flags: 0 m_SettingNames: - Humanoid ================================================ FILE: csharp/ProjectSettings/Physics2DSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!19 &1 Physics2DSettings: m_ObjectHideFlags: 0 serializedVersion: 4 m_Gravity: {x: 0, y: -9.81} m_DefaultMaterial: {fileID: 0} m_VelocityIterations: 8 m_PositionIterations: 3 m_VelocityThreshold: 1 m_MaxLinearCorrection: 0.2 m_MaxAngularCorrection: 8 m_MaxTranslationSpeed: 100 m_MaxRotationSpeed: 360 m_BaumgarteScale: 0.2 m_BaumgarteTimeOfImpactScale: 0.75 m_TimeToSleep: 0.5 m_LinearSleepTolerance: 0.01 m_AngularSleepTolerance: 2 m_DefaultContactOffset: 0.01 m_JobOptions: serializedVersion: 2 useMultithreading: 0 useConsistencySorting: 0 m_InterpolationPosesPerJob: 100 m_NewContactsPerJob: 30 m_CollideContactsPerJob: 100 m_ClearFlagsPerJob: 200 m_ClearBodyForcesPerJob: 200 m_SyncDiscreteFixturesPerJob: 50 m_SyncContinuousFixturesPerJob: 50 m_FindNearestContactsPerJob: 100 m_UpdateTriggerContactsPerJob: 100 m_IslandSolverCostThreshold: 100 m_IslandSolverBodyCostScale: 1 m_IslandSolverContactCostScale: 10 m_IslandSolverJointCostScale: 10 m_IslandSolverBodiesPerJob: 50 m_IslandSolverContactsPerJob: 50 m_AutoSimulation: 1 m_QueriesHitTriggers: 1 m_QueriesStartInColliders: 1 m_CallbacksOnDisable: 1 m_ReuseCollisionCallbacks: 1 m_AutoSyncTransforms: 0 m_AlwaysShowColliders: 0 m_ShowColliderSleep: 1 m_ShowColliderContacts: 0 m_ShowColliderAABB: 0 m_ContactArrowScale: 0.2 m_ColliderAwakeColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.7529412} m_ColliderAsleepColor: {r: 0.5686275, g: 0.95686275, b: 0.54509807, a: 0.36078432} m_ColliderContactColor: {r: 1, g: 0, b: 1, a: 0.6862745} m_ColliderAABBColor: {r: 1, g: 1, b: 0, a: 0.2509804} m_LayerCollisionMatrix: ffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffffff ================================================ FILE: csharp/ProjectSettings/PresetManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!1386491679 &1 PresetManager: m_ObjectHideFlags: 0 m_DefaultList: [] ================================================ FILE: csharp/ProjectSettings/ProjectSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!129 &1 PlayerSettings: m_ObjectHideFlags: 0 serializedVersion: 18 productGUID: 91caf70474c988447b5a55aeda9bd15b AndroidProfiler: 0 AndroidFilterTouchesWhenObscured: 0 AndroidEnableSustainedPerformanceMode: 0 defaultScreenOrientation: 4 targetDevice: 2 useOnDemandResources: 0 accelerometerFrequency: 60 companyName: DefaultCompany productName: vaser-unity defaultCursor: {fileID: 0} cursorHotspot: {x: 0, y: 0} m_SplashScreenBackgroundColor: {r: 0.13725491, g: 0.12156863, b: 0.1254902, a: 1} m_ShowUnitySplashScreen: 1 m_ShowUnitySplashLogo: 1 m_SplashScreenOverlayOpacity: 1 m_SplashScreenAnimation: 1 m_SplashScreenLogoStyle: 1 m_SplashScreenDrawMode: 0 m_SplashScreenBackgroundAnimationZoom: 1 m_SplashScreenLogoAnimationZoom: 1 m_SplashScreenBackgroundLandscapeAspect: 1 m_SplashScreenBackgroundPortraitAspect: 1 m_SplashScreenBackgroundLandscapeUvs: serializedVersion: 2 x: 0 y: 0 width: 1 height: 1 m_SplashScreenBackgroundPortraitUvs: serializedVersion: 2 x: 0 y: 0 width: 1 height: 1 m_SplashScreenLogos: [] m_VirtualRealitySplashScreen: {fileID: 0} m_HolographicTrackingLossScreen: {fileID: 0} defaultScreenWidth: 1024 defaultScreenHeight: 768 defaultScreenWidthWeb: 960 defaultScreenHeightWeb: 600 m_StereoRenderingPath: 0 m_ActiveColorSpace: 0 m_MTRendering: 1 m_StackTraceTypes: 010000000100000001000000010000000100000001000000 iosShowActivityIndicatorOnLoading: -1 androidShowActivityIndicatorOnLoading: -1 displayResolutionDialog: 0 iosUseCustomAppBackgroundBehavior: 0 iosAllowHTTPDownload: 1 allowedAutorotateToPortrait: 1 allowedAutorotateToPortraitUpsideDown: 1 allowedAutorotateToLandscapeRight: 1 allowedAutorotateToLandscapeLeft: 1 useOSAutorotation: 1 use32BitDisplayBuffer: 1 preserveFramebufferAlpha: 0 disableDepthAndStencilBuffers: 0 androidStartInFullscreen: 1 androidRenderOutsideSafeArea: 1 androidUseSwappy: 0 androidBlitType: 0 defaultIsNativeResolution: 1 macRetinaSupport: 1 runInBackground: 1 captureSingleScreen: 0 muteOtherAudioSources: 0 Prepare IOS For Recording: 0 Force IOS Speakers When Recording: 0 deferSystemGesturesMode: 0 hideHomeButton: 0 submitAnalytics: 1 usePlayerLog: 1 bakeCollisionMeshes: 0 forceSingleInstance: 0 useFlipModelSwapchain: 1 resizableWindow: 0 useMacAppStoreValidation: 0 macAppStoreCategory: public.app-category.games gpuSkinning: 1 graphicsJobs: 0 xboxPIXTextureCapture: 0 xboxEnableAvatar: 0 xboxEnableKinect: 0 xboxEnableKinectAutoTracking: 0 xboxEnableFitness: 0 visibleInBackground: 1 allowFullscreenSwitch: 1 graphicsJobMode: 0 fullscreenMode: 1 xboxSpeechDB: 0 xboxEnableHeadOrientation: 0 xboxEnableGuest: 0 xboxEnablePIXSampling: 0 metalFramebufferOnly: 0 xboxOneResolution: 0 xboxOneSResolution: 0 xboxOneXResolution: 3 xboxOneMonoLoggingLevel: 0 xboxOneLoggingLevel: 1 xboxOneDisableEsram: 0 xboxOnePresentImmediateThreshold: 0 switchQueueCommandMemory: 0 switchQueueControlMemory: 16384 switchQueueComputeMemory: 262144 switchNVNShaderPoolsGranularity: 33554432 switchNVNDefaultPoolsGranularity: 16777216 switchNVNOtherPoolsGranularity: 16777216 vulkanEnableSetSRGBWrite: 0 m_SupportedAspectRatios: 4:3: 1 5:4: 1 16:10: 1 16:9: 1 Others: 1 bundleVersion: 0.1 preloadedAssets: [] metroInputSource: 0 wsaTransparentSwapchain: 0 m_HolographicPauseOnTrackingLoss: 1 xboxOneDisableKinectGpuReservation: 1 xboxOneEnable7thCore: 1 vrSettings: cardboard: depthFormat: 0 enableTransitionView: 0 daydream: depthFormat: 0 useSustainedPerformanceMode: 0 enableVideoLayer: 0 useProtectedVideoMemory: 0 minimumSupportedHeadTracking: 0 maximumSupportedHeadTracking: 1 hololens: depthFormat: 1 depthBufferSharingEnabled: 1 lumin: depthFormat: 0 frameTiming: 2 enableGLCache: 0 glCacheMaxBlobSize: 524288 glCacheMaxFileSize: 8388608 oculus: sharedDepthBuffer: 1 dashSupport: 1 lowOverheadMode: 0 protectedContext: 0 v2Signing: 0 enable360StereoCapture: 0 isWsaHolographicRemotingEnabled: 0 protectGraphicsMemory: 0 enableFrameTimingStats: 0 useHDRDisplay: 0 m_ColorGamuts: 00000000 targetPixelDensity: 30 resolutionScalingMode: 0 androidSupportedAspectRatio: 1 androidMaxAspectRatio: 2.1 applicationIdentifier: {} buildNumber: {} AndroidBundleVersionCode: 1 AndroidMinSdkVersion: 16 AndroidTargetSdkVersion: 0 AndroidPreferredInstallLocation: 1 aotOptions: stripEngineCode: 1 iPhoneStrippingLevel: 0 iPhoneScriptCallOptimization: 0 ForceInternetPermission: 0 ForceSDCardPermission: 0 CreateWallpaper: 0 APKExpansionFiles: 0 keepLoadedShadersAlive: 0 StripUnusedMeshComponents: 1 VertexChannelCompressionMask: 4054 iPhoneSdkVersion: 988 iOSTargetOSVersionString: 9.0 tvOSSdkVersion: 0 tvOSRequireExtendedGameController: 0 tvOSTargetOSVersionString: 9.0 uIPrerenderedIcon: 0 uIRequiresPersistentWiFi: 0 uIRequiresFullScreen: 1 uIStatusBarHidden: 1 uIExitOnSuspend: 0 uIStatusBarStyle: 0 iPhoneSplashScreen: {fileID: 0} iPhoneHighResSplashScreen: {fileID: 0} iPhoneTallHighResSplashScreen: {fileID: 0} iPhone47inSplashScreen: {fileID: 0} iPhone55inPortraitSplashScreen: {fileID: 0} iPhone55inLandscapeSplashScreen: {fileID: 0} iPhone58inPortraitSplashScreen: {fileID: 0} iPhone58inLandscapeSplashScreen: {fileID: 0} iPadPortraitSplashScreen: {fileID: 0} iPadHighResPortraitSplashScreen: {fileID: 0} iPadLandscapeSplashScreen: {fileID: 0} iPadHighResLandscapeSplashScreen: {fileID: 0} iPhone65inPortraitSplashScreen: {fileID: 0} iPhone65inLandscapeSplashScreen: {fileID: 0} iPhone61inPortraitSplashScreen: {fileID: 0} iPhone61inLandscapeSplashScreen: {fileID: 0} appleTVSplashScreen: {fileID: 0} appleTVSplashScreen2x: {fileID: 0} tvOSSmallIconLayers: [] tvOSSmallIconLayers2x: [] tvOSLargeIconLayers: [] tvOSLargeIconLayers2x: [] tvOSTopShelfImageLayers: [] tvOSTopShelfImageLayers2x: [] tvOSTopShelfImageWideLayers: [] tvOSTopShelfImageWideLayers2x: [] iOSLaunchScreenType: 0 iOSLaunchScreenPortrait: {fileID: 0} iOSLaunchScreenLandscape: {fileID: 0} iOSLaunchScreenBackgroundColor: serializedVersion: 2 rgba: 0 iOSLaunchScreenFillPct: 100 iOSLaunchScreenSize: 100 iOSLaunchScreenCustomXibPath: iOSLaunchScreeniPadType: 0 iOSLaunchScreeniPadImage: {fileID: 0} iOSLaunchScreeniPadBackgroundColor: serializedVersion: 2 rgba: 0 iOSLaunchScreeniPadFillPct: 100 iOSLaunchScreeniPadSize: 100 iOSLaunchScreeniPadCustomXibPath: iOSUseLaunchScreenStoryboard: 0 iOSLaunchScreenCustomStoryboardPath: iOSDeviceRequirements: [] iOSURLSchemes: [] iOSBackgroundModes: 0 iOSMetalForceHardShadows: 0 metalEditorSupport: 1 metalAPIValidation: 1 iOSRenderExtraFrameOnPause: 0 appleDeveloperTeamID: iOSManualSigningProvisioningProfileID: tvOSManualSigningProvisioningProfileID: iOSManualSigningProvisioningProfileType: 0 tvOSManualSigningProvisioningProfileType: 0 appleEnableAutomaticSigning: 0 iOSRequireARKit: 0 iOSAutomaticallyDetectAndAddCapabilities: 1 appleEnableProMotion: 0 clonedFromGUID: c0afd0d1d80e3634a9dac47e8a0426ea templatePackageId: com.unity.template.3d@3.1.2 templateDefaultScene: Assets/Scenes/SampleScene.unity AndroidTargetArchitectures: 1 AndroidSplashScreenScale: 0 androidSplashScreen: {fileID: 0} AndroidKeystoreName: '{inproject}: ' AndroidKeyaliasName: AndroidBuildApkPerCpuArchitecture: 0 AndroidTVCompatibility: 0 AndroidIsGame: 1 AndroidEnableTango: 0 androidEnableBanner: 1 androidUseLowAccuracyLocation: 0 androidUseCustomKeystore: 0 m_AndroidBanners: - width: 320 height: 180 banner: {fileID: 0} androidGamepadSupportLevel: 0 AndroidValidateAppBundleSize: 1 AndroidAppBundleSizeToValidate: 150 resolutionDialogBanner: {fileID: 0} m_BuildTargetIcons: [] m_BuildTargetPlatformIcons: [] m_BuildTargetBatching: - m_BuildTarget: Standalone m_StaticBatching: 1 m_DynamicBatching: 0 - m_BuildTarget: tvOS m_StaticBatching: 1 m_DynamicBatching: 0 - m_BuildTarget: Android m_StaticBatching: 1 m_DynamicBatching: 0 - m_BuildTarget: iPhone m_StaticBatching: 1 m_DynamicBatching: 0 - m_BuildTarget: WebGL m_StaticBatching: 0 m_DynamicBatching: 0 m_BuildTargetGraphicsAPIs: - m_BuildTarget: AndroidPlayer m_APIs: 150000000b000000 m_Automatic: 0 - m_BuildTarget: iOSSupport m_APIs: 10000000 m_Automatic: 1 - m_BuildTarget: AppleTVSupport m_APIs: 10000000 m_Automatic: 0 - m_BuildTarget: WebGLSupport m_APIs: 0b000000 m_Automatic: 1 m_BuildTargetVRSettings: - m_BuildTarget: Standalone m_Enabled: 0 m_Devices: - Oculus - OpenVR openGLRequireES31: 0 openGLRequireES31AEP: 0 openGLRequireES32: 0 vuforiaEnabled: 0 m_TemplateCustomTags: {} mobileMTRendering: Android: 1 iPhone: 1 tvOS: 1 m_BuildTargetGroupLightmapEncodingQuality: [] m_BuildTargetGroupLightmapSettings: [] playModeTestRunnerEnabled: 0 runPlayModeTestAsEditModeTest: 0 actionOnDotNetUnhandledException: 1 enableInternalProfiler: 0 logObjCUncaughtExceptions: 1 enableCrashReportAPI: 0 cameraUsageDescription: locationUsageDescription: microphoneUsageDescription: switchNetLibKey: switchSocketMemoryPoolSize: 6144 switchSocketAllocatorPoolSize: 128 switchSocketConcurrencyLimit: 14 switchScreenResolutionBehavior: 2 switchUseCPUProfiler: 0 switchApplicationID: 0x01004b9000490000 switchNSODependencies: switchTitleNames_0: switchTitleNames_1: switchTitleNames_2: switchTitleNames_3: switchTitleNames_4: switchTitleNames_5: switchTitleNames_6: switchTitleNames_7: switchTitleNames_8: switchTitleNames_9: switchTitleNames_10: switchTitleNames_11: switchTitleNames_12: switchTitleNames_13: switchTitleNames_14: switchPublisherNames_0: switchPublisherNames_1: switchPublisherNames_2: switchPublisherNames_3: switchPublisherNames_4: switchPublisherNames_5: switchPublisherNames_6: switchPublisherNames_7: switchPublisherNames_8: switchPublisherNames_9: switchPublisherNames_10: switchPublisherNames_11: switchPublisherNames_12: switchPublisherNames_13: switchPublisherNames_14: switchIcons_0: {fileID: 0} switchIcons_1: {fileID: 0} switchIcons_2: {fileID: 0} switchIcons_3: {fileID: 0} switchIcons_4: {fileID: 0} switchIcons_5: {fileID: 0} switchIcons_6: {fileID: 0} switchIcons_7: {fileID: 0} switchIcons_8: {fileID: 0} switchIcons_9: {fileID: 0} switchIcons_10: {fileID: 0} switchIcons_11: {fileID: 0} switchIcons_12: {fileID: 0} switchIcons_13: {fileID: 0} switchIcons_14: {fileID: 0} switchSmallIcons_0: {fileID: 0} switchSmallIcons_1: {fileID: 0} switchSmallIcons_2: {fileID: 0} switchSmallIcons_3: {fileID: 0} switchSmallIcons_4: {fileID: 0} switchSmallIcons_5: {fileID: 0} switchSmallIcons_6: {fileID: 0} switchSmallIcons_7: {fileID: 0} switchSmallIcons_8: {fileID: 0} switchSmallIcons_9: {fileID: 0} switchSmallIcons_10: {fileID: 0} switchSmallIcons_11: {fileID: 0} switchSmallIcons_12: {fileID: 0} switchSmallIcons_13: {fileID: 0} switchSmallIcons_14: {fileID: 0} switchManualHTML: switchAccessibleURLs: switchLegalInformation: switchMainThreadStackSize: 1048576 switchPresenceGroupId: switchLogoHandling: 0 switchReleaseVersion: 0 switchDisplayVersion: 1.0.0 switchStartupUserAccount: 0 switchTouchScreenUsage: 0 switchSupportedLanguagesMask: 0 switchLogoType: 0 switchApplicationErrorCodeCategory: switchUserAccountSaveDataSize: 0 switchUserAccountSaveDataJournalSize: 0 switchApplicationAttribute: 0 switchCardSpecSize: -1 switchCardSpecClock: -1 switchRatingsMask: 0 switchRatingsInt_0: 0 switchRatingsInt_1: 0 switchRatingsInt_2: 0 switchRatingsInt_3: 0 switchRatingsInt_4: 0 switchRatingsInt_5: 0 switchRatingsInt_6: 0 switchRatingsInt_7: 0 switchRatingsInt_8: 0 switchRatingsInt_9: 0 switchRatingsInt_10: 0 switchRatingsInt_11: 0 switchLocalCommunicationIds_0: switchLocalCommunicationIds_1: switchLocalCommunicationIds_2: switchLocalCommunicationIds_3: switchLocalCommunicationIds_4: switchLocalCommunicationIds_5: switchLocalCommunicationIds_6: switchLocalCommunicationIds_7: switchParentalControl: 0 switchAllowsScreenshot: 1 switchAllowsVideoCapturing: 1 switchAllowsRuntimeAddOnContentInstall: 0 switchDataLossConfirmation: 0 switchUserAccountLockEnabled: 0 switchSystemResourceMemory: 16777216 switchSupportedNpadStyles: 22 switchNativeFsCacheSize: 32 switchIsHoldTypeHorizontal: 0 switchSupportedNpadCount: 8 switchSocketConfigEnabled: 0 switchTcpInitialSendBufferSize: 32 switchTcpInitialReceiveBufferSize: 64 switchTcpAutoSendBufferSizeMax: 256 switchTcpAutoReceiveBufferSizeMax: 256 switchUdpSendBufferSize: 9 switchUdpReceiveBufferSize: 42 switchSocketBufferEfficiency: 4 switchSocketInitializeEnabled: 1 switchNetworkInterfaceManagerInitializeEnabled: 1 switchPlayerConnectionEnabled: 1 ps4NPAgeRating: 12 ps4NPTitleSecret: ps4NPTrophyPackPath: ps4ParentalLevel: 11 ps4ContentID: ED1633-NPXX51362_00-0000000000000000 ps4Category: 0 ps4MasterVersion: 01.00 ps4AppVersion: 01.00 ps4AppType: 0 ps4ParamSfxPath: ps4VideoOutPixelFormat: 0 ps4VideoOutInitialWidth: 1920 ps4VideoOutBaseModeInitialWidth: 1920 ps4VideoOutReprojectionRate: 60 ps4PronunciationXMLPath: ps4PronunciationSIGPath: ps4BackgroundImagePath: ps4StartupImagePath: ps4StartupImagesFolder: ps4IconImagesFolder: ps4SaveDataImagePath: ps4SdkOverride: ps4BGMPath: ps4ShareFilePath: ps4ShareOverlayImagePath: ps4PrivacyGuardImagePath: ps4NPtitleDatPath: ps4RemotePlayKeyAssignment: -1 ps4RemotePlayKeyMappingDir: ps4PlayTogetherPlayerCount: 0 ps4EnterButtonAssignment: 1 ps4ApplicationParam1: 0 ps4ApplicationParam2: 0 ps4ApplicationParam3: 0 ps4ApplicationParam4: 0 ps4DownloadDataSize: 0 ps4GarlicHeapSize: 2048 ps4ProGarlicHeapSize: 2560 playerPrefsMaxSize: 32768 ps4Passcode: frAQBc8Wsa1xVPfvJcrgRYwTiizs2trQ ps4pnSessions: 1 ps4pnPresence: 1 ps4pnFriends: 1 ps4pnGameCustomData: 1 playerPrefsSupport: 0 enableApplicationExit: 0 resetTempFolder: 1 restrictedAudioUsageRights: 0 ps4UseResolutionFallback: 0 ps4ReprojectionSupport: 0 ps4UseAudio3dBackend: 0 ps4SocialScreenEnabled: 0 ps4ScriptOptimizationLevel: 0 ps4Audio3dVirtualSpeakerCount: 14 ps4attribCpuUsage: 0 ps4PatchPkgPath: ps4PatchLatestPkgPath: ps4PatchChangeinfoPath: ps4PatchDayOne: 0 ps4attribUserManagement: 0 ps4attribMoveSupport: 0 ps4attrib3DSupport: 0 ps4attribShareSupport: 0 ps4attribExclusiveVR: 0 ps4disableAutoHideSplash: 0 ps4videoRecordingFeaturesUsed: 0 ps4contentSearchFeaturesUsed: 0 ps4attribEyeToEyeDistanceSettingVR: 0 ps4IncludedModules: [] monoEnv: splashScreenBackgroundSourceLandscape: {fileID: 0} splashScreenBackgroundSourcePortrait: {fileID: 0} blurSplashScreenBackground: 1 spritePackerPolicy: webGLMemorySize: 16 webGLExceptionSupport: 1 webGLNameFilesAsHashes: 0 webGLDataCaching: 1 webGLDebugSymbols: 0 webGLEmscriptenArgs: webGLModulesDirectory: webGLTemplate: APPLICATION:Default webGLAnalyzeBuildSize: 0 webGLUseEmbeddedResources: 0 webGLCompressionFormat: 1 webGLLinkerTarget: 1 webGLThreadsSupport: 0 webGLWasmStreaming: 0 scriptingDefineSymbols: {} platformArchitecture: {} scriptingBackend: {} il2cppCompilerConfiguration: {} managedStrippingLevel: {} incrementalIl2cppBuild: {} allowUnsafeCode: 0 additionalIl2CppArgs: scriptingRuntimeVersion: 1 gcIncremental: 0 gcWBarrierValidation: 0 apiCompatibilityLevelPerPlatform: {} m_RenderingPath: 1 m_MobileRenderingPath: 1 metroPackageName: Template_3D metroPackageVersion: metroCertificatePath: metroCertificatePassword: metroCertificateSubject: metroCertificateIssuer: metroCertificateNotAfter: 0000000000000000 metroApplicationDescription: Template_3D wsaImages: {} metroTileShortName: metroTileShowName: 0 metroMediumTileShowName: 0 metroLargeTileShowName: 0 metroWideTileShowName: 0 metroSupportStreamingInstall: 0 metroLastRequiredScene: 0 metroDefaultTileSize: 1 metroTileForegroundText: 2 metroTileBackgroundColor: {r: 0.13333334, g: 0.17254902, b: 0.21568628, a: 0} metroSplashScreenBackgroundColor: {r: 0.12941177, g: 0.17254902, b: 0.21568628, a: 1} metroSplashScreenUseBackgroundColor: 0 platformCapabilities: {} metroTargetDeviceFamilies: {} metroFTAName: metroFTAFileTypes: [] metroProtocolName: XboxOneProductId: XboxOneUpdateKey: XboxOneSandboxId: XboxOneContentId: XboxOneTitleId: XboxOneSCId: XboxOneGameOsOverridePath: XboxOnePackagingOverridePath: XboxOneAppManifestOverridePath: XboxOneVersion: 1.0.0.0 XboxOnePackageEncryption: 0 XboxOnePackageUpdateGranularity: 2 XboxOneDescription: XboxOneLanguage: - enus XboxOneCapability: [] XboxOneGameRating: {} XboxOneIsContentPackage: 0 XboxOneEnableGPUVariability: 1 XboxOneSockets: {} XboxOneSplashScreen: {fileID: 0} XboxOneAllowedProductIds: [] XboxOnePersistentLocalStorageSize: 0 XboxOneXTitleMemory: 8 xboxOneScriptCompiler: 1 XboxOneOverrideIdentityName: vrEditorSettings: daydream: daydreamIconForeground: {fileID: 0} daydreamIconBackground: {fileID: 0} cloudServicesEnabled: UNet: 1 luminIcon: m_Name: m_ModelFolderPath: m_PortalFolderPath: luminCert: m_CertPath: m_SignPackage: 1 luminIsChannelApp: 0 luminVersion: m_VersionCode: 1 m_VersionName: facebookSdkVersion: 7.9.4 facebookAppId: facebookCookies: 1 facebookLogging: 1 facebookStatus: 1 facebookXfbml: 0 facebookFrictionlessRequests: 1 apiCompatibilityLevel: 6 cloudProjectId: framebufferDepthMemorylessMode: 0 projectName: organizationId: cloudEnabled: 0 enableNativePlatformBackendsForNewInputSystem: 0 disableOldInputManagerSupport: 0 legacyClampBlendShapeWeights: 0 ================================================ FILE: csharp/ProjectSettings/ProjectVersion.txt ================================================ m_EditorVersion: 2019.2.14f1 m_EditorVersionWithRevision: 2019.2.14f1 (49dd4e9fa428) ================================================ FILE: csharp/ProjectSettings/QualitySettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!47 &1 QualitySettings: m_ObjectHideFlags: 0 serializedVersion: 5 m_CurrentQuality: 5 m_QualitySettings: - serializedVersion: 2 name: Very Low pixelLightCount: 0 shadows: 0 shadowResolution: 0 shadowProjection: 1 shadowCascades: 1 shadowDistance: 15 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 0 blendWeights: 1 textureQuality: 1 anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 0 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 vSyncCount: 0 lodBias: 0.3 maximumLODLevel: 0 streamingMipmapsActive: 0 streamingMipmapsAddAllCameras: 1 streamingMipmapsMemoryBudget: 512 streamingMipmapsRenderersPerFrame: 512 streamingMipmapsMaxLevelReduction: 2 streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 4 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 asyncUploadPersistentBuffer: 1 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - serializedVersion: 2 name: Low pixelLightCount: 0 shadows: 0 shadowResolution: 0 shadowProjection: 1 shadowCascades: 1 shadowDistance: 20 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 0 blendWeights: 2 textureQuality: 0 anisotropicTextures: 0 antiAliasing: 0 softParticles: 0 softVegetation: 0 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 vSyncCount: 0 lodBias: 0.4 maximumLODLevel: 0 streamingMipmapsActive: 0 streamingMipmapsAddAllCameras: 1 streamingMipmapsMemoryBudget: 512 streamingMipmapsRenderersPerFrame: 512 streamingMipmapsMaxLevelReduction: 2 streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 16 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 asyncUploadPersistentBuffer: 1 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - serializedVersion: 2 name: Medium pixelLightCount: 1 shadows: 1 shadowResolution: 0 shadowProjection: 1 shadowCascades: 1 shadowDistance: 20 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 0 blendWeights: 2 textureQuality: 0 anisotropicTextures: 1 antiAliasing: 0 softParticles: 0 softVegetation: 0 realtimeReflectionProbes: 0 billboardsFaceCameraPosition: 0 vSyncCount: 1 lodBias: 0.7 maximumLODLevel: 0 streamingMipmapsActive: 0 streamingMipmapsAddAllCameras: 1 streamingMipmapsMemoryBudget: 512 streamingMipmapsRenderersPerFrame: 512 streamingMipmapsMaxLevelReduction: 2 streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 64 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 asyncUploadPersistentBuffer: 1 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - serializedVersion: 2 name: High pixelLightCount: 2 shadows: 2 shadowResolution: 1 shadowProjection: 1 shadowCascades: 2 shadowDistance: 40 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 1 blendWeights: 2 textureQuality: 0 anisotropicTextures: 1 antiAliasing: 0 softParticles: 0 softVegetation: 1 realtimeReflectionProbes: 1 billboardsFaceCameraPosition: 1 vSyncCount: 1 lodBias: 1 maximumLODLevel: 0 streamingMipmapsActive: 0 streamingMipmapsAddAllCameras: 1 streamingMipmapsMemoryBudget: 512 streamingMipmapsRenderersPerFrame: 512 streamingMipmapsMaxLevelReduction: 2 streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 256 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 asyncUploadPersistentBuffer: 1 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - serializedVersion: 2 name: Very High pixelLightCount: 3 shadows: 2 shadowResolution: 2 shadowProjection: 1 shadowCascades: 2 shadowDistance: 70 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 1 blendWeights: 4 textureQuality: 0 anisotropicTextures: 2 antiAliasing: 2 softParticles: 1 softVegetation: 1 realtimeReflectionProbes: 1 billboardsFaceCameraPosition: 1 vSyncCount: 1 lodBias: 1.5 maximumLODLevel: 0 streamingMipmapsActive: 0 streamingMipmapsAddAllCameras: 1 streamingMipmapsMemoryBudget: 512 streamingMipmapsRenderersPerFrame: 512 streamingMipmapsMaxLevelReduction: 2 streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 1024 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 asyncUploadPersistentBuffer: 1 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] - serializedVersion: 2 name: Ultra pixelLightCount: 4 shadows: 2 shadowResolution: 2 shadowProjection: 1 shadowCascades: 4 shadowDistance: 150 shadowNearPlaneOffset: 3 shadowCascade2Split: 0.33333334 shadowCascade4Split: {x: 0.06666667, y: 0.2, z: 0.46666667} shadowmaskMode: 1 blendWeights: 4 textureQuality: 0 anisotropicTextures: 2 antiAliasing: 2 softParticles: 1 softVegetation: 1 realtimeReflectionProbes: 1 billboardsFaceCameraPosition: 1 vSyncCount: 1 lodBias: 2 maximumLODLevel: 0 streamingMipmapsActive: 0 streamingMipmapsAddAllCameras: 1 streamingMipmapsMemoryBudget: 512 streamingMipmapsRenderersPerFrame: 512 streamingMipmapsMaxLevelReduction: 2 streamingMipmapsMaxFileIORequests: 1024 particleRaycastBudget: 4096 asyncUploadTimeSlice: 2 asyncUploadBufferSize: 16 asyncUploadPersistentBuffer: 1 resolutionScalingFixedDPIFactor: 1 excludedTargetPlatforms: [] m_PerPlatformDefaultQuality: Android: 2 Lumin: 5 Nintendo 3DS: 5 Nintendo Switch: 5 PS4: 5 PSP2: 2 Standalone: 5 WebGL: 3 Windows Store Apps: 5 XboxOne: 5 iPhone: 2 tvOS: 2 ================================================ FILE: csharp/ProjectSettings/TagManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!78 &1 TagManager: serializedVersion: 2 tags: [] layers: - Default - TransparentFX - Ignore Raycast - - Water - UI - - - - - - - - - - - - - - - - - - - - - - - - - - m_SortingLayers: - name: Default uniqueID: 0 locked: 0 ================================================ FILE: csharp/ProjectSettings/TimeManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!5 &1 TimeManager: m_ObjectHideFlags: 0 Fixed Timestep: 0.02 Maximum Allowed Timestep: 0.33333334 m_TimeScale: 1 Maximum Particle Timestep: 0.03 ================================================ FILE: csharp/ProjectSettings/UnityConnectSettings.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!310 &1 UnityConnectSettings: m_ObjectHideFlags: 0 serializedVersion: 1 m_Enabled: 0 m_TestMode: 0 m_EventOldUrl: https://api.uca.cloud.unity3d.com/v1/events m_EventUrl: https://cdp.cloud.unity3d.com/v1/events m_ConfigUrl: https://config.uca.cloud.unity3d.com m_TestInitMode: 0 CrashReportingSettings: m_EventUrl: https://perf-events.cloud.unity3d.com m_Enabled: 0 m_LogBufferSize: 10 m_CaptureEditorExceptions: 1 UnityPurchasingSettings: m_Enabled: 0 m_TestMode: 0 UnityAnalyticsSettings: m_Enabled: 0 m_TestMode: 0 m_InitializeOnStartup: 1 UnityAdsSettings: m_Enabled: 0 m_InitializeOnStartup: 1 m_TestMode: 0 m_IosGameId: m_AndroidGameId: m_GameIds: {} m_GameId: PerformanceReportingSettings: m_Enabled: 0 ================================================ FILE: csharp/ProjectSettings/VFXManager.asset ================================================ %YAML 1.1 %TAG !u! tag:unity3d.com,2011: --- !u!937362698 &1 VFXManager: m_ObjectHideFlags: 0 m_IndirectShader: {fileID: 0} m_CopyBufferShader: {fileID: 0} m_SortShader: {fileID: 0} m_RenderPipeSettingsPath: m_FixedTimeStep: 0.016666668 m_MaxDeltaTime: 0.05 ================================================ FILE: csharp/ProjectSettings/XRSettings.asset ================================================ { "m_SettingKeys": [ "VR Device Disabled", "VR Device User Alert" ], "m_SettingValues": [ "False", "False" ] } ================================================ FILE: csharp/README.md ================================================ # Vaser Unity Vaser Unity is a continuation of [VASE renderer](https://github.com/tyt2y3/vaserenderer), reimplementing the original C++ algorithm in C# while taking advantage of fragment shader to reduce triangle count. A major difference is that "feathering" no longer requires extra vertices. So the following patch requires only 4 vertices (instead of 8 in the C++ version): ![Screenshort](Docs/Screenshot%20at%202019-12-02%2000-44-00.png) More screenshots: ![Screenshot](Docs/Screenshot%20at%202019-12-11%2001-11-10.png) ![Screenshot](Docs/Screenshot%20at%202019-12-15%2003-31-00.png) ![Screenshot](Docs/Screenshot%20at%202019-12-30%2017-36-00.png) ## Features - Per vertex coloring and weighting - Linear gradient along curve - Brush-like feathering effects - Premium quality anti-aliasing - Various caps and joints More screenshots can be found under [Docs](Docs). ## Status Current implementation is still experimental, may be buggy and the API is subject to change. While this library can be used standalone, it may eventually be integrated into a larger framework. ================================================ FILE: docs/API.html ================================================
VASEr: better stroke rendering

VASEr API reference

Overview

API design

VASEr functions have no state, all stateful operations are put into the class renderer. Thus you need to pass many parameters on each VASEr call, and these parameters are stored in structures. Normally if you do not know what options to set, just put 0 at the parameter polyline_opt*. VASEr ensures empty parameter polyline_opt* options=0 and empty structure polyline_opt opt={0}; are default options and will not cause any error.

Index

Details

class renderer

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

As stated, all stateful operations are put into the class renderer. init() is intended to set up shaders etc. When calling init(), the open gl context is assumed to be set up by the user already. Call renderer::before() and renderer::after() before and after rendering. For more information, look at getting started.

get_image() captures an image of the current render context, in rgba 32 bit format. the (x,y) pixel (r,g,b,a) is at (image.buf[y*w*b+x*b],image.buf[y*w*b+x*b+1],image.buf[y*w*b+x*b+2],image.buf[y*w*b+x*b+3]), where b=4 and w=image.width.

struct gradient

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;

A gradient is an array of stops that specify properties at different positions in a one dimensional space. The properties can be color or weight, and the space parameter (unit) can be the length along a curve. The default parameter is ratio, that frankly speaking t=0.5 is midway along the curve.

Be aware of the union, which means each stop can only hold one type of information, which must be explicitly specified by gradient_stop.type. If the type is unspecified, the stop is ignored.

Stops of each type must be sorted in ascending order of t, but it is okay for say stops of weight come after stops of color. If there is only one stop of a type, then it is considered constant everywhere. If there is no stop of a type, then no change will be applied.

For a concrete example, look at polybezier.

struct Image

struct Image
{
	unsigned char* buf; //must **free** buffer manually
	short width;
	short height;
};
the unsigned char* buf must be freed manually as in malloc/free

struct tessellator_opt

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;

a polyline_opt contains a pointer to tessellator_opt which can be used to control tessellating behavior. in this version, parts is unimplemented. if you don't need this control,, setting polyline_opt.tess to zero pointer is okay.

polyline()

void polyline(
	Vec2* P,       //array of point of a polyline
	Color* C,      //array of color
	double* weight,//array of weight
	int length,    //length of the array
	polyline_opt* options) //extra options
All arrays must be of the same size otherwise memory error will occur.
There are 3 other variants if you do not need varying color or weight.
//constant color and weight
void polyline( const Vec2*, Color, double W, int length, const polyline_opt*);
//constant weight
void polyline( const Vec2*, const Color*, double W, int length, const polyline_opt*);
//constant color
void polyline( const Vec2*, Color, const double* W, int length, const polyline_opt*);

Options

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;
opt.joint
PLJ_miter
PLJ_bevel
PLJ_round

opt.cap
PLC_butt
PLC_round
PLC_square
PLC_rect
PLC_rect is related to feathering. PLC_rect puts the fade polygon out of the end points of a polyline.
When feather=false, PLC_rect looks very close to PLC_butt. The difference is only obvious at high value of feathering.
PLC_rect
PLC_butt
There are modifiers and can be numerically added together to the value to control rendering of cap parts. it is necessary if you need to join segments yourself.
PLC_butt+PLC_both
PLC_butt+PLC_first
PLC_butt+PLC_none

opt.feather, opt.feathering, opt.no_feather_at_cap, opt.no_feather_at_core
feathering is a multiplier to the magnitude of the outset-fade polygon. do not set it to lower than 1.0 .
feathering is unique to VASEr. A feathered polyline with round cap and round joint can mimic the feel of an air brush stroke.
opt.feather = false;
opt.feather = true;
opt.feathering = 8.0;
opt.feather = true;
opt.feathering = 8.0;
opt.no_feather_at_cap = true;
opt.no_feather_at_core = false;
opt.feather = true;
opt.feathering = 8.0;
opt.no_feather_at_cap = false;
opt.no_feather_at_core = true;
remarks: no_feather_at_cap only affects cap type PLC_butt, PLC_square and PLC_rect .

Usage


void sample_polyline1()
{
	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 sample_polyline2()
{
	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_opt opt={0};
	polyline( AP, AC, AW, size_of_AP, &opt);
}
example program is at samples/samples.cpp under VASEr package.

Notes

Varying color is stable but will cause overdraw at degenerated cases.
Varying weight is stable most of the time but will go wild when extreme case occur.
This version of polyline is improved to render dense vertices for curves.

Further work

After solving the above mentioned problems, can provide the choice between color blending profiles, possibly 'hard' and 'soft'. and besides gradient, provide texture along path.

segment()

void segment( Vec2,  Vec2,
              Color, Color,
              double, double, //weight
              const polyline_opt*); //same use as polyline
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
segment() is merely a wrapper over polyline(). There are also three variants.

Usage

void sample_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 sample_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;
		
		segment(P1,P2, C1,C2, W1,W2, 0);
	}
}
example program is at samples/samples.cpp< under VASEr package. In the below spectrums, each segment is 0.3 pixel heavier than the previous segment, demonstrating sub pixel accuracy and anti aliasing quality of VASEr.

polybezier()

void polybezier( const Vec2* P, const gradient*, int length, const polybezier_opt*);
void polybezier( const Vec2* P, Color, double W, int length, const polybezier_opt*);
	
The P is an array of control points. The evaluated curve is continuous, with adjacent bezier sections sharing a common control point. so n cubic bezier sections require 3n+1 points. A pointer to gradient or constant color and width can be passed in.

Options

struct polybezier_opt
{
	//set the whole structure to 0 will give default options
	const polyline_opt* poly;
};
Under the hood, bezier curves will be evaluated, and the set of points will be sent to polyline() for rendering, using const polyline_opt* poly as options if specified.

Usage


void sample_bezier1()
{
	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 sample_bezier2()
{
	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);
}
Again, example program is at samples/samples.cpp.

================================================ FILE: docs/getting_started.html ================================================
VASEr: better stroke rendering

VASEr getting started

The role of a renderer

To use VASEr properly, you need to understand what role does VASEr play in the rendering pipeline. Suppose your application has a 2D rendering pipeline like:

transformation
clipping
primitives generation
glDrawArrays(); or glBegin(); glEnd();
composition

VASEr merely takes care the primitives generation part (redlighted). Call renderer::before() and renderer::after() before and after rendering. VASEr API is namespaced VASEr.

Example: hide
How to correctly set gl states for VASEr..

Suppose you have a helloworld application that only renders a line segment in draw():

void draw()
{
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
		glLoadIdentity();
		glOrtho( 0,context_width,context_height,0,0.0f,100.0f);
		glLineWidth(2.0);
		glBegin(GL_LINES);
			glColor4f(1,0,0.5, 1);
			glVertex2f(10,100);
			glColor4f(0.5,0,1, 1);
			glVertex2f(100,300);
		glEnd();
		//other drawings
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
}
then change to:
void draw()
{
	using namespace VASEr;
	glMatrixMode(GL_PROJECTION);
	glPushMatrix();
		glLoadIdentity();
		glOrtho( 0,context_width,context_height,0,0.0f,100.0f);
		renderer::before();
			{	Vec2  P1 = {10,100};
				Vec2  P2 = {100,300};
				Color C1 = {1,0,0.5, 1};
				Color C2 = {0.5,0,1, 1};
				double W1= 2.0;
				double W2= W1;
				segment(P1,P2, C1,C2, W1,W2, 0);
			}
			//other VASEr renderings
		renderer::after();
	glMatrixMode(GL_PROJECTION);
	glPopMatrix();
}

include and compile

Provide these structs to VASEr before include by:

namespace VASEr
{
	struct Vec2 { double x,y;};
	struct Color { float r,g,b,a;};
} 
or
namespace VASEr
{
	typedef your_vec2 Vec2;
	typedef your_color Color;
} 

then #include "vaser.h". There is only one file to compile, vaser.cpp. It is a unity build; no make file, no hassle. Alternatively, #include "vaser.cpp" just works, because implementation is namespaced in VASErin to prevent collision.

defining a Vec2 with float precision may work but may lead to undesirable precision lost, because internally VASEr use double for floating point operations.

================================================ FILE: docs/index.html ================================================ VASE renderer: better stroke rendering
VASEr: better stroke rendering

VASE renderer

About

VASE renderer version 0.42 (VASEr 0.42) is a tessellating library for rendering high quality 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++.

Unconventional features

Per vertex coloring and weighting



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.

Linear gradient along curve


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 expressive freedom.

Feathering for brush like effects


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.

Premium quality anti aliasing



From left to right: raw polygon without anti aliasing,
anti aliased with outset fade polygon,
exaggerated outsetting polygon with wireframe,
same as left without wireframe.

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.

Below are line rendering comparison between VASEr and Cairo, click flip to compare:

VASEr

between VASEr and AGG:

VASEr

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.

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.

Performant

VASEr is for real time rendering. Tessellation is done on CPU and the triangles are then sent off to GPU for rasterization. VASEr is fast regardless of the rendering resolution.

Can tessellation be accelerated by GPU? Probably, but I have no plan to do so. The performance enhancement strategy of VASEr is adaptation, that means doing expensive joint processing only when necessary and unperceivablely switch to approximation for thin lines and curves. The decision requires some global information and result in lots of branching, which makes it harder to write GPU programs. But after all, correctness is the first priority and performance is the last.

"Next generation" 2d graphics?

Q: Who needs "Per vertex coloring and weighting", "Linear gradient along curve" or "Feathering for brush like effects" in vector graphics? A: Same answer as "who needs typesetting and beautiful fonts on their computers?".

Q: These effects can be achieved by vector graphics package XXX using tool AAA then effect BBB, so who needs VASE renderer? A: Arguably true. But the fundamental change is making vertex a three dimensional entity (position, color, weight), and to supercharge the intrinsic properties of strokes. The idea is, it should be natural to apply colors and thickness to a stroke.

Status

VASEr is production ready for conventional use. The unconventional features are largely usable for normal circumstances, but may have defects at extreme cases. In particular, varying thickness is undesirable at acute angles and when weight difference is large. So it is not bullet proof yet. The tessellation code is tediously written for each point for each triangle for each circumstance, because a general outsetting and tessellating algorithm is doomed to be slow.

Currently VASEr is only a programming library and is not used in any vector graphics tool. I do have a plan (a plan only) to develop associated tools.

VASEr is not (yet) a general purpose graphics library like Cairo, its feature is still very limited. It has been developed slowly over the years. I always wanted to develop VASEr into a general purpose graphics library, but just do not have the time. It takes more effort than you may think to develop a rendering routine. Any comment, bug report or patch is greatly appreciated. In any case, sending me any form of encouragement (e.g. great work, thank you, donation) will help.

Documentation

Source code

Package with documentation, source code, sample images and sample programs is on github. Comments, discussions, and contributions are greatly appreciated. You can reach me via email or on github.

License

VASE renderer version 0.42 (VASEr 0.42) is licensed under The 3-Clause BSD License.

Copyright (2011-2016) Tsang Hao Fung (Chris Tsang) tyt2y3@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.

Bezier curve subdivision code is extracted from Anti-Grain Geometry V2.4 Copyright 2002-2005 Maxim Shemanarev (McSeem) [Modified BSD License]

================================================ FILE: docs/style.css ================================================ body { margin: 0 auto; padding: 0; font: normal normal 13.8pt Palatino, "Palatino Linotype", Georgia, Times, "Times New Roman", serif; line-height: 18pt; color: #555; background: #AAA; } .vaser_wrap { width: 900px; margin: 0 auto; padding: 0; border-style: solid; border-color: #FAFAFA; border-width: 10px; background: #FFFFFF; } h1, h2, h3, h4, h5, h6 { font-family: Palatino, "Palatino Linotype", Georgia, Times, "Times New Roman", serif; font-weight: normal; color: #777; margin: 0; } h1 { font-size: 26pt; padding: 20px 0 10px 0;} h2 { font-size: 24pt; padding: 20px 0 10px 0;} h3 { font-size: 22pt; padding: 20px 0 10px 20px;} h4 { font-size: 20pt; padding: 15px 0 10px 100px;} h5 { font-size: 14pt; padding: 15px 0 10px 120px;} h6 { font-size: 12pt; font-weight: bold; color: #999; padding: 0 0 0 160px;} a { color: #ba954f; text-decoration: none; outline: 0; } a:hover { color: #dabc83; } a img { border: 0; } blockquote, q { quotes: none; } blockquote:before, blockquote:after, q:before, q:after { content: ''; content: none; } /*vaser_wrap*/ .article_name { padding: 0px 0 0 0; text-align: center; } .vaser_wrap .bar { background: #fe7f7f; /* Old browsers */ background: -moz-linear-gradient(left, #fe7f7f 0%, #e9d47f 33%, #807ffc 66%, #fc7f81 100%); /* FF3.6+ */ background: -webkit-gradient(linear, left top, right top, color-stop(0%,#fe7f7f), color-stop(33%,#e9d47f), color-stop(66%,#807ffc), color-stop(100%,#fc7f81)); /* Chrome,Safari4+ */ background: -webkit-linear-gradient(left, #fe7f7f 0%,#e9d47f 33%,#807ffc 66%,#fc7f81 100%); /* Chrome10+,Safari5.1+ */ background: -o-linear-gradient(left, #fe7f7f 0%,#e9d47f 33%,#807ffc 66%,#fc7f81 100%); /* Opera 11.10+ */ background: -ms-linear-gradient(left, #fe7f7f 0%,#e9d47f 33%,#807ffc 66%,#fc7f81 100%); /* IE10+ */ background: linear-gradient(to right, #fe7f7f 0%,#e9d47f 33%,#807ffc 66%,#fc7f81 100%); /* W3C */ filter: progid:DXImageTransform.Microsoft.gradient( startColorstr='#fe7f7f', endColorstr='#fc7f81',GradientType=1 ); /* IE6-9 */ text-shadow: #000 1px 1px 8px; color: #FFF; width: 100%; height: 30px; text-align:center; top: 0; font: normal normal 15px Liberation mono, Ubuntu mono, monospace; line-height: 30px; } .vaser_wrap pre, code { font: normal normal 11pt Courier New, Courier, monospace; margin: 5px; } .vaser_wrap table { border-collapse: collapse; border-spacing: 0; } .vaser_wrap td { border: 1px solid #AAAAAA; } .vaser_wrap img { border: 0; } .vaser_wrap p { line-height: 18pt; margin: 5px; } .vaser_wrap ul { margin: 0; } .vaser_wrap .textblock { margin-left:50px; } .api_table table { /*child tables*/ width:600px; } .api_table tr:nth-child(even) {} .api_table tr:nth-child(odd) {} .api_table table td { text-align: center; } .api_table caption { font-size:130%; } .para_tips { font-size:11pt; } .box_tips { border: 1px solid #AAA; margin: 0 0 0 80px; overflow: hidden; } .box_redlight { background: #FFE0E0; } /*end vaser_wrap*/