curvedraw

changeset 15:37ab3a4c02f8

merged
author John Tsiombikas <nuclear@member.fsf.org>
date Sun, 20 Dec 2015 09:06:04 +0200
parents b625f0575d66 4da693339d99
children 7f795f7fecd6
files Makefile src/app.cc src/curvefile.cc src/curvefile.h src/main.cc
diffstat 12 files changed, 834 insertions(+), 112 deletions(-) [+]
line diff
     1.1 --- a/Makefile	Thu Dec 17 16:41:42 2015 +0200
     1.2 +++ b/Makefile	Sun Dec 20 09:06:04 2015 +0200
     1.3 @@ -5,17 +5,17 @@
     1.4  dep = $(obj:.o=.d)
     1.5  bin = curvedraw
     1.6  
     1.7 -CXXFLAGS = -pedantic -Wall -g
     1.8 +CXXFLAGS = -std=c++11 -pedantic -Wall -g
     1.9  LDFLAGS = $(libgl) -lvmath -ldrawtext -lm
    1.10  
    1.11  sys := $(shell uname -s | sed 's/MINGW.*/win32/')
    1.12  
    1.13  ifeq ($(sys), Darwin)
    1.14 -	libgl = -framework OpenGL -framework GLUT
    1.15 +	libgl = -framework OpenGL -framework GLUT -lGLEW
    1.16  else ifeq ($(sys), win32)
    1.17 -	libgl = -lopengl32 -lglut32
    1.18 +	libgl = -lopengl32 -lglut32 -lglew32
    1.19  else
    1.20 -	libgl = -lGL -lglut
    1.21 +	libgl = -lGL -lglut -lGLEW
    1.22  endif
    1.23  
    1.24  $(bin): $(obj)
     2.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     2.2 +++ b/curvedraw.sln	Sun Dec 20 09:06:04 2015 +0200
     2.3 @@ -0,0 +1,22 @@
     2.4 +
     2.5 +Microsoft Visual Studio Solution File, Format Version 12.00
     2.6 +# Visual Studio 2013
     2.7 +VisualStudioVersion = 12.0.31101.0
     2.8 +MinimumVisualStudioVersion = 10.0.40219.1
     2.9 +Project("{8BC9CEB8-8B4A-11D0-8D11-00A0C91BC942}") = "curvedraw", "curvedraw.vcxproj", "{04CA7CB1-80DB-4E89-8FD3-54715CDA43CE}"
    2.10 +EndProject
    2.11 +Global
    2.12 +	GlobalSection(SolutionConfigurationPlatforms) = preSolution
    2.13 +		Debug|Win32 = Debug|Win32
    2.14 +		Release|Win32 = Release|Win32
    2.15 +	EndGlobalSection
    2.16 +	GlobalSection(ProjectConfigurationPlatforms) = postSolution
    2.17 +		{04CA7CB1-80DB-4E89-8FD3-54715CDA43CE}.Debug|Win32.ActiveCfg = Debug|Win32
    2.18 +		{04CA7CB1-80DB-4E89-8FD3-54715CDA43CE}.Debug|Win32.Build.0 = Debug|Win32
    2.19 +		{04CA7CB1-80DB-4E89-8FD3-54715CDA43CE}.Release|Win32.ActiveCfg = Release|Win32
    2.20 +		{04CA7CB1-80DB-4E89-8FD3-54715CDA43CE}.Release|Win32.Build.0 = Release|Win32
    2.21 +	EndGlobalSection
    2.22 +	GlobalSection(SolutionProperties) = preSolution
    2.23 +		HideSolutionNode = FALSE
    2.24 +	EndGlobalSection
    2.25 +EndGlobal
     3.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     3.2 +++ b/curvedraw.vcxproj	Sun Dec 20 09:06:04 2015 +0200
     3.3 @@ -0,0 +1,99 @@
     3.4 +<?xml version="1.0" encoding="utf-8"?>
     3.5 +<Project DefaultTargets="Build" ToolsVersion="12.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
     3.6 +  <ItemGroup Label="ProjectConfigurations">
     3.7 +    <ProjectConfiguration Include="Debug|Win32">
     3.8 +      <Configuration>Debug</Configuration>
     3.9 +      <Platform>Win32</Platform>
    3.10 +    </ProjectConfiguration>
    3.11 +    <ProjectConfiguration Include="Release|Win32">
    3.12 +      <Configuration>Release</Configuration>
    3.13 +      <Platform>Win32</Platform>
    3.14 +    </ProjectConfiguration>
    3.15 +  </ItemGroup>
    3.16 +  <PropertyGroup Label="Globals">
    3.17 +    <ProjectGuid>{04CA7CB1-80DB-4E89-8FD3-54715CDA43CE}</ProjectGuid>
    3.18 +    <Keyword>Win32Proj</Keyword>
    3.19 +    <RootNamespace>curvedraw</RootNamespace>
    3.20 +  </PropertyGroup>
    3.21 +  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.Default.props" />
    3.22 +  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'" Label="Configuration">
    3.23 +    <ConfigurationType>Application</ConfigurationType>
    3.24 +    <UseDebugLibraries>true</UseDebugLibraries>
    3.25 +    <PlatformToolset>v120</PlatformToolset>
    3.26 +    <CharacterSet>MultiByte</CharacterSet>
    3.27 +  </PropertyGroup>
    3.28 +  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'" Label="Configuration">
    3.29 +    <ConfigurationType>Application</ConfigurationType>
    3.30 +    <UseDebugLibraries>false</UseDebugLibraries>
    3.31 +    <PlatformToolset>v120</PlatformToolset>
    3.32 +    <WholeProgramOptimization>false</WholeProgramOptimization>
    3.33 +    <CharacterSet>MultiByte</CharacterSet>
    3.34 +  </PropertyGroup>
    3.35 +  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.props" />
    3.36 +  <ImportGroup Label="ExtensionSettings">
    3.37 +  </ImportGroup>
    3.38 +  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    3.39 +    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
    3.40 +  </ImportGroup>
    3.41 +  <ImportGroup Label="PropertySheets" Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    3.42 +    <Import Project="$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props" Condition="exists('$(UserRootDir)\Microsoft.Cpp.$(Platform).user.props')" Label="LocalAppDataPlatform" />
    3.43 +  </ImportGroup>
    3.44 +  <PropertyGroup Label="UserMacros" />
    3.45 +  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    3.46 +    <LinkIncremental>true</LinkIncremental>
    3.47 +  </PropertyGroup>
    3.48 +  <PropertyGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    3.49 +    <LinkIncremental>false</LinkIncremental>
    3.50 +  </PropertyGroup>
    3.51 +  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Debug|Win32'">
    3.52 +    <ClCompile>
    3.53 +      <PrecompiledHeader>
    3.54 +      </PrecompiledHeader>
    3.55 +      <WarningLevel>Level3</WarningLevel>
    3.56 +      <Optimization>Disabled</Optimization>
    3.57 +      <PreprocessorDefinitions>WIN32;FREEGLUT_LIB_PRAGMAS=0;_DEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
    3.58 +      <DisableSpecificWarnings>4244;4996;4305</DisableSpecificWarnings>
    3.59 +    </ClCompile>
    3.60 +    <Link>
    3.61 +      <SubSystem>Console</SubSystem>
    3.62 +      <GenerateDebugInformation>true</GenerateDebugInformation>
    3.63 +      <AdditionalDependencies>freeglut.lib;libdrawtext.lib;opengl32.lib;glu32.lib;glew32.lib;libvmath-dbg.lib;%(AdditionalDependencies)</AdditionalDependencies>
    3.64 +    </Link>
    3.65 +  </ItemDefinitionGroup>
    3.66 +  <ItemDefinitionGroup Condition="'$(Configuration)|$(Platform)'=='Release|Win32'">
    3.67 +    <ClCompile>
    3.68 +      <WarningLevel>Level3</WarningLevel>
    3.69 +      <PrecompiledHeader>
    3.70 +      </PrecompiledHeader>
    3.71 +      <Optimization>MaxSpeed</Optimization>
    3.72 +      <FunctionLevelLinking>true</FunctionLevelLinking>
    3.73 +      <IntrinsicFunctions>true</IntrinsicFunctions>
    3.74 +      <PreprocessorDefinitions>WIN32;FREEGLUT_LIB_PRAGMAS=0;NDEBUG;_CONSOLE;_LIB;%(PreprocessorDefinitions)</PreprocessorDefinitions>
    3.75 +      <DisableSpecificWarnings>4244;4996;4305</DisableSpecificWarnings>
    3.76 +    </ClCompile>
    3.77 +    <Link>
    3.78 +      <SubSystem>Console</SubSystem>
    3.79 +      <GenerateDebugInformation>true</GenerateDebugInformation>
    3.80 +      <EnableCOMDATFolding>true</EnableCOMDATFolding>
    3.81 +      <OptimizeReferences>true</OptimizeReferences>
    3.82 +      <AdditionalDependencies>freeglut.lib;libdrawtext.lib;opengl32.lib;glu32.lib;glew32.lib;libvmath.lib;%(AdditionalDependencies)</AdditionalDependencies>
    3.83 +    </Link>
    3.84 +  </ItemDefinitionGroup>
    3.85 +  <ItemGroup>
    3.86 +    <ClCompile Include="src\app.cc" />
    3.87 +    <ClCompile Include="src\curve.cc" />
    3.88 +    <ClCompile Include="src\curvefile.cc" />
    3.89 +    <ClCompile Include="src\main.cc" />
    3.90 +    <ClCompile Include="src\widgets.cc" />
    3.91 +  </ItemGroup>
    3.92 +  <ItemGroup>
    3.93 +    <ClInclude Include="src\app.h" />
    3.94 +    <ClInclude Include="src\curve.h" />
    3.95 +    <ClInclude Include="src\curvefile.h" />
    3.96 +    <ClInclude Include="src\opengl.h" />
    3.97 +    <ClInclude Include="src\widgets.h" />
    3.98 +  </ItemGroup>
    3.99 +  <Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />
   3.100 +  <ImportGroup Label="ExtensionTargets">
   3.101 +  </ImportGroup>
   3.102 +</Project>
   3.103 \ No newline at end of file
     4.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     4.2 +++ b/curvedraw.vcxproj.filters	Sun Dec 20 09:06:04 2015 +0200
     4.3 @@ -0,0 +1,43 @@
     4.4 +<?xml version="1.0" encoding="utf-8"?>
     4.5 +<Project ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
     4.6 +  <ItemGroup>
     4.7 +    <Filter Include="src">
     4.8 +      <UniqueIdentifier>{4FC737F1-C7A5-4376-A066-2A32D752A2FF}</UniqueIdentifier>
     4.9 +      <Extensions>cpp;c;cc;cxx;def;odl;idl;hpj;bat;asm;asmx;h</Extensions>
    4.10 +    </Filter>
    4.11 +  </ItemGroup>
    4.12 +  <ItemGroup>
    4.13 +    <ClCompile Include="src\app.cc">
    4.14 +      <Filter>src</Filter>
    4.15 +    </ClCompile>
    4.16 +    <ClCompile Include="src\curve.cc">
    4.17 +      <Filter>src</Filter>
    4.18 +    </ClCompile>
    4.19 +    <ClCompile Include="src\main.cc">
    4.20 +      <Filter>src</Filter>
    4.21 +    </ClCompile>
    4.22 +    <ClCompile Include="src\widgets.cc">
    4.23 +      <Filter>src</Filter>
    4.24 +    </ClCompile>
    4.25 +    <ClCompile Include="src\curvefile.cc">
    4.26 +      <Filter>src</Filter>
    4.27 +    </ClCompile>
    4.28 +  </ItemGroup>
    4.29 +  <ItemGroup>
    4.30 +    <ClInclude Include="src\app.h">
    4.31 +      <Filter>src</Filter>
    4.32 +    </ClInclude>
    4.33 +    <ClInclude Include="src\curve.h">
    4.34 +      <Filter>src</Filter>
    4.35 +    </ClInclude>
    4.36 +    <ClInclude Include="src\opengl.h">
    4.37 +      <Filter>src</Filter>
    4.38 +    </ClInclude>
    4.39 +    <ClInclude Include="src\widgets.h">
    4.40 +      <Filter>src</Filter>
    4.41 +    </ClInclude>
    4.42 +    <ClInclude Include="src\curvefile.h">
    4.43 +      <Filter>src</Filter>
    4.44 +    </ClInclude>
    4.45 +  </ItemGroup>
    4.46 +</Project>
    4.47 \ No newline at end of file
     5.1 --- a/src/app.cc	Thu Dec 17 16:41:42 2015 +0200
     5.2 +++ b/src/app.cc	Sun Dec 20 09:06:04 2015 +0200
     5.3 @@ -7,6 +7,7 @@
     5.4  #include "app.h"
     5.5  #include "curve.h"
     5.6  #include "widgets.h"
     5.7 +#include "curvefile.h"
     5.8  
     5.9  enum SnapMode {
    5.10  	SNAP_NONE,
    5.11 @@ -29,21 +30,24 @@
    5.12  static float grid_size = 1.0;
    5.13  static SnapMode snap_mode;
    5.14  
    5.15 +static bool show_bounds;
    5.16 +
    5.17  static std::vector<Curve*> curves;
    5.18  static Curve *sel_curve;	// selected curve being edited
    5.19  static Curve *new_curve;	// new curve being entered
    5.20  static Curve *hover_curve;	// curve the mouse is hovering over (click to select)
    5.21 -static int sel_pidx = -1;	// selected point of the selected or hovered-over curve
    5.22 +static int sel_pidx = -1;	// selected point of the selected curve
    5.23 +static int hover_pidx = -1;	// hovered over point
    5.24  
    5.25  static Label *weight_label;	// floating label for the cp weight
    5.26  
    5.27 -#ifdef DRAW_MOUSE_POINTER
    5.28  static Vector2 mouse_pointer;
    5.29 -#endif
    5.30  
    5.31  
    5.32  bool app_init(int argc, char **argv)
    5.33  {
    5.34 +	glewInit();
    5.35 +
    5.36  	glEnable(GL_MULTISAMPLE);
    5.37  	glEnable(GL_CULL_FACE);
    5.38  
    5.39 @@ -148,6 +152,20 @@
    5.40  	int numpt = curve->size();
    5.41  	int segm = numpt * 16;
    5.42  
    5.43 +	if(show_bounds) {
    5.44 +		Vector3 bmin, bmax;
    5.45 +		curve->get_bbox(&bmin, &bmax);
    5.46 +
    5.47 +		glLineWidth(1.0);
    5.48 +		glColor3f(0, 1, 0);
    5.49 +		glBegin(GL_LINE_LOOP);
    5.50 +		glVertex2f(bmin.x, bmin.y);
    5.51 +		glVertex2f(bmax.x, bmin.y);
    5.52 +		glVertex2f(bmax.x, bmax.y);
    5.53 +		glVertex2f(bmin.x, bmax.y);
    5.54 +		glEnd();
    5.55 +	}
    5.56 +
    5.57  	glLineWidth(curve == hover_curve ? 4.0 : 2.0);
    5.58  	if(curve == sel_curve) {
    5.59  		glColor3f(0.3, 0.4, 1.0);
    5.60 @@ -159,7 +177,7 @@
    5.61  	glBegin(GL_LINE_STRIP);
    5.62  	for(int i=0; i<segm; i++) {
    5.63  		float t = (float)i / (float)(segm - 1);
    5.64 -		Vector2 v = curve->interpolate(t);
    5.65 +		Vector3 v = curve->interpolate(t);
    5.66  		glVertex2f(v.x, v.y);
    5.67  	}
    5.68  	glEnd();
    5.69 @@ -180,10 +198,23 @@
    5.70  				glColor3f(0.2, 1.0, 0.2);
    5.71  			}
    5.72  		}
    5.73 -		Vector2 pt = curve->get_point(i);
    5.74 +		Vector2 pt = curve->get_point2(i);
    5.75  		glVertex2f(pt.x, pt.y);
    5.76  	}
    5.77  	glEnd();
    5.78 +
    5.79 +	// draw the projected mouse point on the selected curve
    5.80 +	/*
    5.81 +	if(curve == sel_curve) {
    5.82 +		Vector3 pp = curve->proj_point(Vector3(mouse_pointer.x, mouse_pointer.y, 0.0));
    5.83 +
    5.84 +		glPointSize(5.0);
    5.85 +		glBegin(GL_POINTS);
    5.86 +		glColor3f(1, 0.8, 0.2);
    5.87 +		glVertex2f(pp.x, pp.y);
    5.88 +		glEnd();
    5.89 +	}
    5.90 +	*/
    5.91  	glPointSize(1.0);
    5.92  }
    5.93  
    5.94 @@ -215,40 +246,64 @@
    5.95  			}
    5.96  			break;
    5.97  
    5.98 -		case 'l':
    5.99 -		case 'L':
   5.100 +		case '1':
   5.101 +		case '2':
   5.102 +		case '3':
   5.103  			if(sel_curve) {
   5.104 -				sel_curve->set_type(CURVE_LINEAR);
   5.105 +				sel_curve->set_type((CurveType)((int)CURVE_LINEAR + key - '1'));
   5.106  				post_redisplay();
   5.107  			}
   5.108  			if(new_curve) {
   5.109 -				new_curve->set_type(CURVE_LINEAR);
   5.110 +				new_curve->set_type((CurveType)((int)CURVE_LINEAR + key - '1'));
   5.111  				post_redisplay();
   5.112  			}
   5.113  			break;
   5.114  
   5.115  		case 'b':
   5.116  		case 'B':
   5.117 +			show_bounds = !show_bounds;
   5.118 +			post_redisplay();
   5.119 +			break;
   5.120 +
   5.121 +		case 'n':
   5.122 +		case 'N':
   5.123  			if(sel_curve) {
   5.124 -				sel_curve->set_type(CURVE_BSPLINE);
   5.125 -				post_redisplay();
   5.126 -			}
   5.127 -			if(new_curve) {
   5.128 -				new_curve->set_type(CURVE_BSPLINE);
   5.129 +				sel_curve->normalize();
   5.130  				post_redisplay();
   5.131  			}
   5.132  			break;
   5.133  
   5.134 -		case 'h':
   5.135 -		case 'H':
   5.136 -			if(sel_curve) {
   5.137 -				sel_curve->set_type(CURVE_HERMITE);
   5.138 -				post_redisplay();
   5.139 +		case 'e':
   5.140 +		case 'E':
   5.141 +			// TODO: GUI for filename at least
   5.142 +			if(!save_curves("test.curves", &curves[0], (int)curves.size())) {
   5.143 +				fprintf(stderr, "failed to export curves\n");
   5.144  			}
   5.145 -			if(new_curve) {
   5.146 -				new_curve->set_type(CURVE_HERMITE);
   5.147 -				post_redisplay();
   5.148 +			printf("exported %d curves\n", (int)curves.size());
   5.149 +			break;
   5.150 +
   5.151 +		case 'l':
   5.152 +		case 'L':
   5.153 +			{
   5.154 +				std::list<Curve*> clist = load_curves("test.curves");
   5.155 +				if(clist.empty()) {
   5.156 +					fprintf(stderr, "failed to import curves\n");
   5.157 +				}
   5.158 +
   5.159 +				for(size_t i=0; i<curves.size(); i++) {
   5.160 +					delete curves[i];
   5.161 +				}
   5.162 +				curves.clear();
   5.163 +
   5.164 +				int num = 0;
   5.165 +				std::list<Curve*>::iterator it = clist.begin();
   5.166 +				while(it != clist.end()) {
   5.167 +					curves.push_back(*it++);
   5.168 +					++num;
   5.169 +				}
   5.170 +				printf("imported %d curves\n", num);
   5.171  			}
   5.172 +			post_redisplay();
   5.173  			break;
   5.174  		}
   5.175  	}
   5.176 @@ -335,7 +390,7 @@
   5.177  		int pidx = curves[i]->nearest_point(pos);
   5.178  		if(pidx == -1) continue;
   5.179  
   5.180 -		Vector2 cp = curves[i]->get_point(pidx);
   5.181 +		Vector2 cp = curves[i]->get_point2(pidx);
   5.182  		if((cp - pos).length_sq() < thres * thres) {
   5.183  			*curveret = curves[i];
   5.184  			*pidxret = pidx;
   5.185 @@ -347,6 +402,28 @@
   5.186  	return false;
   5.187  }
   5.188  
   5.189 +static bool hit_test(const Vector2 &pos, Curve **curveret, int *pidxret)
   5.190 +{
   5.191 +	float thres = 0.02 / view_scale;
   5.192 +
   5.193 +	if(point_hit_test(pos, curveret, pidxret)) {
   5.194 +		return true;
   5.195 +	}
   5.196 +
   5.197 +	Vector3 pos3 = Vector3(pos.x, pos.y, 0.0f);
   5.198 +	for(size_t i=0; i<curves.size(); i++) {
   5.199 +		float x;
   5.200 +		if((x = curves[i]->distance_sq(pos3)) < thres * thres) {
   5.201 +			*curveret = curves[i];
   5.202 +			*pidxret = -1;
   5.203 +			return true;
   5.204 +		}
   5.205 +	}
   5.206 +	*curveret = 0;
   5.207 +	*pidxret = -1;
   5.208 +	return false;
   5.209 +}
   5.210 +
   5.211  static Vector2 snap(const Vector2 &p)
   5.212  {
   5.213  	switch(snap_mode) {
   5.214 @@ -407,10 +484,8 @@
   5.215  	if(!dx && !dy) return;
   5.216  
   5.217  	Vector2 uv = pixel_to_uv(x, y);
   5.218 -#ifdef DRAW_MOUSE_POINTER
   5.219  	mouse_pointer = uv;
   5.220 -	post_redisplay();
   5.221 -#endif
   5.222 +	//post_redisplay();
   5.223  
   5.224  	/* when entering a new curve, have the last (extra) point following
   5.225  	 * the mouse until it's entered by a click (see on_click).
   5.226 @@ -422,7 +497,10 @@
   5.227  
   5.228  	if(!new_curve && !bnstate) {
   5.229  		// not dragging, highlight curve under mouse
   5.230 -		point_hit_test(uv, &hover_curve, &sel_pidx);
   5.231 +		hit_test(uv, &hover_curve, &hover_pidx);
   5.232 +		if(hover_curve == sel_curve) {
   5.233 +			sel_pidx = hover_pidx;
   5.234 +		}
   5.235  		post_redisplay();
   5.236  
   5.237  	} else {
   5.238 @@ -482,6 +560,7 @@
   5.239  		if(hover_curve) {
   5.240  			// if we're hovering: click selects
   5.241  			sel_curve = hover_curve;
   5.242 +			sel_pidx = hover_pidx;
   5.243  			hover_curve = 0;
   5.244  		} else if(sel_curve) {
   5.245  			// if we have a selected curve: click adds point (enter new_curve mode)
   5.246 @@ -520,9 +599,11 @@
   5.247  			// in selected curve mode: delete control point or unselect
   5.248  			Curve *hit_curve;
   5.249  			int hit_pidx;
   5.250 -			if(point_hit_test(uv, &hit_curve, &hit_pidx) && hit_curve == sel_curve) {
   5.251 -				hit_curve->remove_point(hit_pidx);
   5.252 -				sel_pidx = -1;
   5.253 +			if(hit_test(uv, &hit_curve, &hit_pidx) && hit_curve == sel_curve) {
   5.254 +				if(hit_pidx != -1) {
   5.255 +					hit_curve->remove_point(hit_pidx);
   5.256 +					sel_pidx = -1;
   5.257 +				}
   5.258  			} else {
   5.259  				sel_curve = 0;
   5.260  				sel_pidx = -1;
     6.1 --- a/src/curve.cc	Thu Dec 17 16:41:42 2015 +0200
     6.2 +++ b/src/curve.cc	Sun Dec 20 09:06:04 2015 +0200
     6.3 @@ -1,9 +1,39 @@
     6.4  #include <float.h>
     6.5 +#include <assert.h>
     6.6 +#include <algorithm>
     6.7  #include "curve.h"
     6.8  
     6.9  Curve::Curve(CurveType type)
    6.10  {
    6.11  	this->type = type;
    6.12 +	bbvalid = true;
    6.13 +}
    6.14 +
    6.15 +Curve::Curve(const Vector4 *cp, int numcp, CurveType type)
    6.16 +	: Curve(type)
    6.17 +{
    6.18 +	this->cp.resize(numcp);
    6.19 +	for(int i=0; i<numcp; i++) {
    6.20 +		this->cp[i] = cp[i];
    6.21 +	}
    6.22 +}
    6.23 +
    6.24 +Curve::Curve(const Vector3 *cp, int numcp, CurveType type)
    6.25 +	: Curve(type)
    6.26 +{
    6.27 +	this->cp.resize(numcp);
    6.28 +	for(int i=0; i<numcp; i++) {
    6.29 +		this->cp[i] = Vector4(cp[i].x, cp[i].y, cp[i].z, 1.0f);
    6.30 +	}
    6.31 +}
    6.32 +
    6.33 +Curve::Curve(const Vector2 *cp, int numcp, CurveType type)
    6.34 +	: Curve(type)
    6.35 +{
    6.36 +	this->cp.resize(numcp);
    6.37 +	for(int i=0; i<numcp; i++) {
    6.38 +		this->cp[i] = Vector4(cp[i].x, cp[i].y, 0.0f, 1.0f);
    6.39 +	}
    6.40  }
    6.41  
    6.42  void Curve::set_type(CurveType type)
    6.43 @@ -16,9 +46,20 @@
    6.44  	return type;
    6.45  }
    6.46  
    6.47 +void Curve::add_point(const Vector4 &p)
    6.48 +{
    6.49 +	cp.push_back(p);
    6.50 +	inval_bounds();
    6.51 +}
    6.52 +
    6.53 +void Curve::add_point(const Vector3 &p, float weight)
    6.54 +{
    6.55 +	add_point(Vector4(p.x, p.y, p.z, weight));
    6.56 +}
    6.57 +
    6.58  void Curve::add_point(const Vector2 &p, float weight)
    6.59  {
    6.60 -	cp.push_back(Vector3(p.x, p.y, weight));
    6.61 +	add_point(Vector4(p.x, p.y, 0.0f, weight));
    6.62  }
    6.63  
    6.64  bool Curve::remove_point(int idx)
    6.65 @@ -27,23 +68,14 @@
    6.66  		return false;
    6.67  	}
    6.68  	cp.erase(cp.begin() + idx);
    6.69 +	inval_bounds();
    6.70  	return true;
    6.71  }
    6.72  
    6.73 -int Curve::nearest_point(const Vector2 &p)
    6.74 +void Curve::clear()
    6.75  {
    6.76 -	int res = -1;
    6.77 -	float bestsq = FLT_MAX;
    6.78 -
    6.79 -	for(size_t i=0; i<cp.size(); i++) {
    6.80 -		float d = (get_point(i) - p).length_sq();
    6.81 -		if(d < bestsq) {
    6.82 -			bestsq = d;
    6.83 -			res = i;
    6.84 -		}
    6.85 -	}
    6.86 -
    6.87 -	return res;
    6.88 +	cp.clear();
    6.89 +	inval_bounds();
    6.90  }
    6.91  
    6.92  bool Curve::empty() const
    6.93 @@ -56,29 +88,45 @@
    6.94  	return (int)cp.size();
    6.95  }
    6.96  
    6.97 -Vector3 &Curve::operator [](int idx)
    6.98 +Vector4 &Curve::operator [](int idx)
    6.99 +{
   6.100 +	inval_bounds();
   6.101 +	return cp[idx];
   6.102 +}
   6.103 +
   6.104 +const Vector4 &Curve::operator [](int idx) const
   6.105  {
   6.106  	return cp[idx];
   6.107  }
   6.108  
   6.109 -const Vector3 &Curve::operator [](int idx) const
   6.110 +const Vector4 &Curve::get_point(int idx) const
   6.111  {
   6.112  	return cp[idx];
   6.113  }
   6.114  
   6.115 -const Vector3 &Curve::get_homo_point(int idx) const
   6.116 +Vector3 Curve::get_point3(int idx) const
   6.117  {
   6.118 -	return cp[idx];
   6.119 +	return Vector3(cp[idx].x, cp[idx].y, cp[idx].z);
   6.120  }
   6.121  
   6.122 -Vector2 Curve::get_point(int idx) const
   6.123 +Vector2 Curve::get_point2(int idx) const
   6.124  {
   6.125  	return Vector2(cp[idx].x, cp[idx].y);
   6.126  }
   6.127  
   6.128  float Curve::get_weight(int idx) const
   6.129  {
   6.130 -	return cp[idx].z;
   6.131 +	return cp[idx].w;
   6.132 +}
   6.133 +
   6.134 +bool Curve::set_point(int idx, const Vector3 &p, float weight)
   6.135 +{
   6.136 +	if(idx < 0 || idx >= (int)cp.size()) {
   6.137 +		return false;
   6.138 +	}
   6.139 +	cp[idx] = Vector4(p.x, p.y, p.z, weight);
   6.140 +	inval_bounds();
   6.141 +	return true;
   6.142  }
   6.143  
   6.144  bool Curve::set_point(int idx, const Vector2 &p, float weight)
   6.145 @@ -86,7 +134,8 @@
   6.146  	if(idx < 0 || idx >= (int)cp.size()) {
   6.147  		return false;
   6.148  	}
   6.149 -	cp[idx] = Vector3(p.x, p.y, weight);
   6.150 +	cp[idx] = Vector4(p.x, p.y, 0.0, weight);
   6.151 +	inval_bounds();
   6.152  	return true;
   6.153  }
   6.154  
   6.155 @@ -95,7 +144,17 @@
   6.156  	if(idx < 0 || idx >= (int)cp.size()) {
   6.157  		return false;
   6.158  	}
   6.159 -	cp[idx].z = weight;
   6.160 +	cp[idx].w = weight;
   6.161 +	return true;
   6.162 +}
   6.163 +
   6.164 +bool Curve::move_point(int idx, const Vector3 &p)
   6.165 +{
   6.166 +	if(idx < 0 || idx >= (int)cp.size()) {
   6.167 +		return false;
   6.168 +	}
   6.169 +	cp[idx] = Vector4(p.x, p.y, p.z, cp[idx].w);
   6.170 +	inval_bounds();
   6.171  	return true;
   6.172  }
   6.173  
   6.174 @@ -104,22 +163,211 @@
   6.175  	if(idx < 0 || idx >= (int)cp.size()) {
   6.176  		return false;
   6.177  	}
   6.178 -	cp[idx] = Vector3(p.x, p.y, cp[idx].z);
   6.179 +	cp[idx] = Vector4(p.x, p.y, 0.0f, cp[idx].w);
   6.180 +	inval_bounds();
   6.181  	return true;
   6.182  }
   6.183  
   6.184 -Vector2 Curve::interpolate(float t, CurveType type) const
   6.185 +
   6.186 +int Curve::nearest_point(const Vector3 &p) const
   6.187  {
   6.188 -	if(cp.empty()) {
   6.189 -		return Vector2(0, 0);
   6.190 +	int res = -1;
   6.191 +	float bestsq = FLT_MAX;
   6.192 +
   6.193 +	for(size_t i=0; i<cp.size(); i++) {
   6.194 +		float d = (get_point3(i) - p).length_sq();
   6.195 +		if(d < bestsq) {
   6.196 +			bestsq = d;
   6.197 +			res = i;
   6.198 +		}
   6.199 +	}
   6.200 +	return res;
   6.201 +}
   6.202 +
   6.203 +int Curve::nearest_point(const Vector2 &p) const
   6.204 +{
   6.205 +	int res = -1;
   6.206 +	float bestsq = FLT_MAX;
   6.207 +
   6.208 +	for(size_t i=0; i<cp.size(); i++) {
   6.209 +		float d = (get_point2(i) - p).length_sq();
   6.210 +		if(d < bestsq) {
   6.211 +			bestsq = d;
   6.212 +			res = i;
   6.213 +		}
   6.214 +	}
   6.215 +	return res;
   6.216 +}
   6.217 +
   6.218 +
   6.219 +void Curve::inval_bounds() const
   6.220 +{
   6.221 +	bbvalid = false;
   6.222 +}
   6.223 +
   6.224 +void Curve::calc_bounds() const
   6.225 +{
   6.226 +	calc_bbox(&bbmin, &bbmax);
   6.227 +	bbvalid = true;
   6.228 +}
   6.229 +
   6.230 +void Curve::get_bbox(Vector3 *bbmin, Vector3 *bbmax) const
   6.231 +{
   6.232 +	if(!bbvalid) {
   6.233 +		calc_bounds();
   6.234 +	}
   6.235 +	*bbmin = this->bbmin;
   6.236 +	*bbmax = this->bbmax;
   6.237 +}
   6.238 +
   6.239 +void Curve::calc_bbox(Vector3 *bbmin, Vector3 *bbmax) const
   6.240 +{
   6.241 +	if(empty()) {
   6.242 +		*bbmin = *bbmax = Vector3(0, 0, 0);
   6.243 +		return;
   6.244 +	}
   6.245 +
   6.246 +	Vector3 bmin = cp[0];
   6.247 +	Vector3 bmax = bmin;
   6.248 +	for(size_t i=1; i<cp.size(); i++) {
   6.249 +		const Vector4 &v = cp[i];
   6.250 +		for(int j=0; j<3; j++) {
   6.251 +			if(v[j] < bmin[j]) bmin[j] = v[j];
   6.252 +			if(v[j] > bmax[j]) bmax[j] = v[j];
   6.253 +		}
   6.254 +	}
   6.255 +	*bbmin = bmin;
   6.256 +	*bbmax = bmax;
   6.257 +}
   6.258 +
   6.259 +void Curve::normalize()
   6.260 +{
   6.261 +	if(!bbvalid) {
   6.262 +		calc_bounds();
   6.263 +	}
   6.264 +
   6.265 +	Vector3 bsize = bbmax - bbmin;
   6.266 +	Vector3 boffs = (bbmin + bbmax) * 0.5;
   6.267 +
   6.268 +	Vector3 bscale;
   6.269 +	bscale.x = bsize.x == 0.0f ? 1.0f : 1.0f / bsize.x;
   6.270 +	bscale.y = bsize.y == 0.0f ? 1.0f : 1.0f / bsize.y;
   6.271 +	bscale.z = bsize.z == 0.0f ? 1.0f : 1.0f / bsize.z;
   6.272 +
   6.273 +	for(size_t i=0; i<cp.size(); i++) {
   6.274 +		cp[i].x = (cp[i].x - boffs.x) * bscale.x;
   6.275 +		cp[i].y = (cp[i].y - boffs.y) * bscale.y;
   6.276 +		cp[i].z = (cp[i].z - boffs.z) * bscale.z;
   6.277 +	}
   6.278 +	inval_bounds();
   6.279 +}
   6.280 +
   6.281 +Vector3 Curve::proj_point(const Vector3 &p, float refine_thres) const
   6.282 +{
   6.283 +	// first step through the curve a few times and find the nearest of them
   6.284 +	int num_cp = size();
   6.285 +	int num_steps = num_cp * 5;	// arbitrary number; sounds ok
   6.286 +	float dt = 1.0f / (float)(num_steps - 1);
   6.287 +
   6.288 +	float best_distsq = FLT_MAX;
   6.289 +	float best_t = 0.0f;
   6.290 +	Vector3 best_pp;
   6.291 +
   6.292 +	float t = 0.0f;
   6.293 +	for(int i=0; i<num_steps; i++) {
   6.294 +		Vector3 pp = interpolate(t);
   6.295 +		float distsq = (pp - p).length_sq();
   6.296 +		if(distsq < best_distsq) {
   6.297 +			best_distsq = distsq;
   6.298 +			best_pp = pp;
   6.299 +			best_t = t;
   6.300 +		}
   6.301 +		t += dt;
   6.302 +	}
   6.303 +
   6.304 +	// refine by gradient descent
   6.305 +	float dist = best_distsq;
   6.306 +	t = best_t;
   6.307 +	dt *= 0.05;
   6.308 +	for(;;) {
   6.309 +		float tn = t + dt;
   6.310 +		float tp = t - dt;
   6.311 +
   6.312 +		float dn = (interpolate(tn) - p).length_sq();
   6.313 +		float dp = (interpolate(tp) - p).length_sq();
   6.314 +
   6.315 +		if(fabs(dn - dp) < refine_thres * refine_thres) {
   6.316 +			break;
   6.317 +		}
   6.318 +
   6.319 +		if(dn < dist) {
   6.320 +			t = tn;
   6.321 +			dist = dn;
   6.322 +		} else if(dp < dist) {
   6.323 +			t = tp;
   6.324 +			dist = dp;
   6.325 +		} else {
   6.326 +			break;	// found the minimum
   6.327 +		}
   6.328 +	}
   6.329 +
   6.330 +	return interpolate(t);
   6.331 +}
   6.332 +
   6.333 +float Curve::distance(const Vector3 &p) const
   6.334 +{
   6.335 +	return (proj_point(p) - p).length();
   6.336 +}
   6.337 +
   6.338 +float Curve::distance_sq(const Vector3 &p) const
   6.339 +{
   6.340 +	return fabs((proj_point(p) - p).length_sq());
   6.341 +}
   6.342 +
   6.343 +
   6.344 +Vector3 Curve::interpolate_segment(int a, int b, float t) const
   6.345 +{
   6.346 +	int num_cp = size();
   6.347 +
   6.348 +	if(t < 0.0) t = 0.0;
   6.349 +	if(t > 1.0) t = 1.0;
   6.350 +
   6.351 +	Vector4 res;
   6.352 +	if(type == CURVE_LINEAR || num_cp == 2) {
   6.353 +		res = lerp(cp[a], cp[b], t);
   6.354 +	} else {
   6.355 +		int prev = a <= 0 ? a : a - 1;
   6.356 +		int next = b >= num_cp - 1 ? b : b + 1;
   6.357 +
   6.358 +		if(type == CURVE_HERMITE) {
   6.359 +			res = catmull_rom_spline(cp[prev], cp[a], cp[b], cp[next], t);
   6.360 +		} else {
   6.361 +			res = bspline(cp[prev], cp[a], cp[b], cp[next], t);
   6.362 +			if(res.w != 0.0f) {
   6.363 +				res.x /= res.w;
   6.364 +				res.y /= res.w;
   6.365 +				res.z /= res.w;
   6.366 +			}
   6.367 +		}
   6.368 +	}
   6.369 +
   6.370 +	return Vector3(res.x, res.y, res.z);
   6.371 +}
   6.372 +
   6.373 +Vector3 Curve::interpolate(float t) const
   6.374 +{
   6.375 +	if(empty()) {
   6.376 +		return Vector3(0, 0, 0);
   6.377  	}
   6.378  
   6.379  	int num_cp = (int)cp.size();
   6.380  	if(num_cp == 1) {
   6.381 -		return Vector2(cp[0].x, cp[0].y);
   6.382 +		return Vector3(cp[0].x, cp[0].y, cp[0].z);
   6.383  	}
   6.384  
   6.385 -	Vector3 res;
   6.386 +	if(t < 0.0) t = 0.0;
   6.387 +	if(t > 1.0) t = 1.0;
   6.388 +
   6.389  	int idx0 = std::min((int)floor(t * (num_cp - 1)), num_cp - 2);
   6.390  	int idx1 = idx0 + 1;
   6.391  
   6.392 @@ -128,35 +376,17 @@
   6.393  	float t1 = (float)idx1 * dt;
   6.394  
   6.395  	t = (t - t0) / (t1 - t0);
   6.396 -	if(t < 0.0) t = 0.0;
   6.397 -	if(t > 1.0) t = 1.0;
   6.398  
   6.399 -	if(type == CURVE_LINEAR || num_cp <= 2) {
   6.400 -		res = lerp(cp[idx0], cp[idx1], t);
   6.401 -	} else {
   6.402 -		int idx_prev = idx0 <= 0 ? idx0 : idx0 - 1;
   6.403 -		int idx_next = idx1 >= num_cp - 1 ? idx1 : idx1 + 1;
   6.404 +	return interpolate_segment(idx0, idx1, t);
   6.405 +}
   6.406  
   6.407 -		if(type == CURVE_HERMITE) {
   6.408 -			res = catmull_rom_spline(cp[idx_prev], cp[idx0], cp[idx1], cp[idx_next], t);
   6.409 -		} else {
   6.410 -			res = bspline(cp[idx_prev], cp[idx0], cp[idx1], cp[idx_next], t);
   6.411 -			if(res.z != 0.0f) {
   6.412 -				res.x /= res.z;
   6.413 -				res.y /= res.z;
   6.414 -			}
   6.415 -		}
   6.416 -	}
   6.417 -
   6.418 +Vector2 Curve::interpolate2(float t) const
   6.419 +{
   6.420 +	Vector3 res = interpolate(t);
   6.421  	return Vector2(res.x, res.y);
   6.422  }
   6.423  
   6.424 -Vector2 Curve::interpolate(float t) const
   6.425 -{
   6.426 -	return interpolate(t, type);
   6.427 -}
   6.428 -
   6.429 -Vector2 Curve::operator ()(float t) const
   6.430 +Vector3 Curve::operator ()(float t) const
   6.431  {
   6.432  	return interpolate(t);
   6.433  }
     7.1 --- a/src/curve.h	Thu Dec 17 16:41:42 2015 +0200
     7.2 +++ b/src/curve.h	Sun Dec 20 09:06:04 2015 +0200
     7.3 @@ -12,37 +12,74 @@
     7.4  
     7.5  class Curve {
     7.6  private:
     7.7 -	std::vector<Vector3> cp;
     7.8 +	std::vector<Vector4> cp;
     7.9  	CurveType type;
    7.10  
    7.11 +	// bounding box
    7.12 +	mutable Vector3 bbmin, bbmax;
    7.13 +	mutable bool bbvalid;
    7.14 +
    7.15 +	void calc_bounds() const;
    7.16 +	void inval_bounds() const;
    7.17 +
    7.18  public:
    7.19  	Curve(CurveType type = CURVE_HERMITE);
    7.20 +	Curve(const Vector4 *cp, int numcp, CurveType type = CURVE_HERMITE); // homogenous
    7.21 +	Curve(const Vector3 *cp, int numcp, CurveType type = CURVE_HERMITE); // 3D points, w=1
    7.22 +	Curve(const Vector2 *cp, int numcp, CurveType type = CURVE_HERMITE); // 2D points, z=0, w=1
    7.23  
    7.24  	void set_type(CurveType type);
    7.25  	CurveType get_type() const;
    7.26  
    7.27 +	void add_point(const Vector4 &p);
    7.28 +	void add_point(const Vector3 &p, float weight = 1.0f);
    7.29  	void add_point(const Vector2 &p, float weight = 1.0f);
    7.30  	bool remove_point(int idx);
    7.31  
    7.32 -	int nearest_point(const Vector2 &p);
    7.33 +	void clear();		// remove all control points
    7.34 +	bool empty() const;	// true if 0 control points
    7.35 +	int size() const;	// returns number of control points
    7.36 +	// access operators for control points
    7.37 +	Vector4 &operator [](int idx);
    7.38 +	const Vector4 &operator [](int idx) const;
    7.39 +	const Vector4 &get_point(int idx) const;
    7.40  
    7.41 -	bool empty() const;
    7.42 -	int size() const;
    7.43 -	Vector3 &operator [](int idx);
    7.44 -	const Vector3 &operator [](int idx) const;
    7.45 -
    7.46 -	const Vector3 &get_homo_point(int idx) const;	// homogeneous point
    7.47 -	Vector2 get_point(int idx) const;
    7.48 +	Vector3 get_point3(int idx) const;
    7.49 +	Vector2 get_point2(int idx) const;
    7.50  	float get_weight(int idx) const;
    7.51  
    7.52 +	bool set_point(int idx, const Vector3 &p, float weight = 1.0f);
    7.53  	bool set_point(int idx, const Vector2 &p, float weight = 1.0f);
    7.54  	bool set_weight(int idx, float weight);
    7.55  	// move point without changing its weight
    7.56 +	bool move_point(int idx, const Vector3 &p);
    7.57  	bool move_point(int idx, const Vector2 &p);
    7.58 + 
    7.59 +	int nearest_point(const Vector3 &p) const;
    7.60 +	// nearest control point on the 2D plane z=0
    7.61 +	int nearest_point(const Vector2 &p) const;
    7.62  
    7.63 -	Vector2 interpolate(float t, CurveType type) const;
    7.64 -	Vector2 interpolate(float t) const;
    7.65 -	Vector2 operator ()(float t) const;
    7.66 +	/* get_bbox returns the axis-aligned bounding box of the curve's
    7.67 +	 * control points.
    7.68 +	 * NOTE: hermite curves can go outside of the bounding box of their control points
    7.69 +	 * NOTE: lazy calculation of bounds is performed, use calc_bbox in multithreaded programs
    7.70 +	 */
    7.71 +	void get_bbox(Vector3 *bbmin, Vector3 *bbmax) const;
    7.72 +	void calc_bbox(Vector3 *bbmin, Vector3 *bbmax) const;
    7.73 +	// normalize the curve's bounds to coincide with the unit cube
    7.74 +	void normalize();
    7.75 +
    7.76 +	// project a point to the curve (nearest point on the curve)
    7.77 +	Vector3 proj_point(const Vector3 &p, float refine_thres = 0.01) const;
    7.78 +	// equivalent to (proj_point(p) - p).length()
    7.79 +	float distance(const Vector3 &p) const;
    7.80 +	// equivalent to fabs((proj_point(p) - p).length_sq())
    7.81 +	float distance_sq(const Vector3 &p) const;
    7.82 +
    7.83 +	Vector3 interpolate_segment(int a, int b, float t) const;
    7.84 +	Vector3 interpolate(float t) const;
    7.85 +	Vector2 interpolate2(float t) const;
    7.86 +	Vector3 operator ()(float t) const;
    7.87  };
    7.88  
    7.89  #endif	// CURVE_H_
     8.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     8.2 +++ b/src/curvefile.cc	Sun Dec 20 09:06:04 2015 +0200
     8.3 @@ -0,0 +1,204 @@
     8.4 +#include <stdlib.h>
     8.5 +#include <ctype.h>
     8.6 +#include <string>
     8.7 +#include "curvefile.h"
     8.8 +
     8.9 +static bool save_curve(FILE *fp, const Curve *curve);
    8.10 +
    8.11 +bool save_curves(const char *fname, const Curve * const *curves, int count)
    8.12 +{
    8.13 +	FILE *fp = fopen(fname, "wb");
    8.14 +	if(!fp) return false;
    8.15 +
    8.16 +	bool res = save_curves(fp, curves, count);
    8.17 +	fclose(fp);
    8.18 +	return res;
    8.19 +}
    8.20 +
    8.21 +bool save_curves(FILE *fp, const Curve * const *curves, int count)
    8.22 +{
    8.23 +	fprintf(fp, "GCURVES\n");
    8.24 +
    8.25 +	for(int i=0; i<count; i++) {
    8.26 +		if(!save_curve(fp, curves[i])) {
    8.27 +			return false;
    8.28 +		}
    8.29 +	}
    8.30 +	return true;
    8.31 +}
    8.32 +
    8.33 +static const char *curve_type_str(CurveType type)
    8.34 +{
    8.35 +	switch(type) {
    8.36 +	case CURVE_LINEAR:
    8.37 +		return "polyline";
    8.38 +	case CURVE_HERMITE:
    8.39 +		return "hermite";
    8.40 +	case CURVE_BSPLINE:
    8.41 +		return "bspline";
    8.42 +	}
    8.43 +	abort();
    8.44 +}
    8.45 +
    8.46 +static bool save_curve(FILE *fp, const Curve *curve)
    8.47 +{
    8.48 +	fprintf(fp, "curve {\n");
    8.49 +	fprintf(fp, "    type %s\n", curve_type_str(curve->get_type()));
    8.50 +	fprintf(fp, "    cpcount %d\n", curve->size());
    8.51 +	for(int i=0; i<curve->size(); i++) {
    8.52 +		Vector4 cp = curve->get_point(i);
    8.53 +		fprintf(fp, "    cp %g %g %g %g\n", cp.x, cp.y, cp.z, cp.w);
    8.54 +	}
    8.55 +	fprintf(fp, "}\n");
    8.56 +	return true;
    8.57 +}
    8.58 +
    8.59 +std::list<Curve*> load_curves(const char *fname)
    8.60 +{
    8.61 +	std::list<Curve*> res;
    8.62 +	FILE *fp = fopen(fname, "r");
    8.63 +	if(!fp) return res;
    8.64 +
    8.65 +	res = load_curves(fp);
    8.66 +	fclose(fp);
    8.67 +	return res;
    8.68 +}
    8.69 +
    8.70 +static std::string next_token(FILE *fp)
    8.71 +{
    8.72 +	std::string s;
    8.73 +	int c;
    8.74 +	while((c = fgetc(fp)) != -1) {
    8.75 +		if(!isspace(c)) {
    8.76 +			ungetc(c, fp);
    8.77 +			break;
    8.78 +		}
    8.79 +	}
    8.80 +
    8.81 +	if(feof(fp)) return s;
    8.82 +	while((c = fgetc(fp)) != -1) {
    8.83 +		if(isspace(c)) {
    8.84 +			ungetc(c, fp);
    8.85 +			break;
    8.86 +		}
    8.87 +		s.push_back(c);
    8.88 +	}
    8.89 +	return s;
    8.90 +}
    8.91 +
    8.92 +static bool expect_str(FILE *fp, const char *s)
    8.93 +{
    8.94 +	std::string tok = next_token(fp);
    8.95 +	if(tok != std::string(s)) {
    8.96 +		if(tok.empty() && feof(fp)) {
    8.97 +			fprintf(stderr, "expected: %s\n", s);
    8.98 +		}
    8.99 +		return false;
   8.100 +	}
   8.101 +	return true;
   8.102 +}
   8.103 +
   8.104 +static bool expect_float(FILE *fp, float *ret)
   8.105 +{
   8.106 +	std::string tok = next_token(fp);
   8.107 +	const char *cs = tok.c_str();
   8.108 +	char *endp;
   8.109 +	*ret = strtod(cs, &endp);
   8.110 +	if(endp != cs + tok.length()) {
   8.111 +		if(!(tok.empty() && feof(fp))) {
   8.112 +			fprintf(stderr, "number expected\n");
   8.113 +		}
   8.114 +		return false;
   8.115 +	}
   8.116 +	return true;
   8.117 +}
   8.118 +
   8.119 +static bool expect_int(FILE *fp, int *ret)
   8.120 +{
   8.121 +	std::string tok = next_token(fp);
   8.122 +	const char *cs = tok.c_str();
   8.123 +	char *endp;
   8.124 +	*ret = strtol(cs, &endp, 0);
   8.125 +	if(endp != cs + tok.length()) {
   8.126 +		if(!(tok.empty() && feof(fp))) {
   8.127 +			fprintf(stderr, "integer expected\n");
   8.128 +		}
   8.129 +		return false;
   8.130 +	}
   8.131 +	return true;
   8.132 +}
   8.133 +
   8.134 +static Curve *curve_block(FILE *fp)
   8.135 +{
   8.136 +	if(!expect_str(fp, "curve") || !expect_str(fp, "{")) {
   8.137 +		return 0;
   8.138 +	}
   8.139 +
   8.140 +	Curve *curve = new Curve;
   8.141 +	int cpcount = -1;
   8.142 +	std::string tok;
   8.143 +	while(!(tok = next_token(fp)).empty() && tok != "}") {
   8.144 +		if(tok == "cpcount") {
   8.145 +			if(cpcount != -1 || !expect_int(fp, &cpcount) || cpcount <= 0) {
   8.146 +				goto err;
   8.147 +			}
   8.148 +		} else if(tok == "type") {
   8.149 +			tok = next_token(fp);
   8.150 +			if(tok == "polyline") {
   8.151 +				curve->set_type(CURVE_LINEAR);
   8.152 +			} else if(tok == "hermite") {
   8.153 +				curve->set_type(CURVE_HERMITE);
   8.154 +			} else if(tok == "bspline") {
   8.155 +				curve->set_type(CURVE_BSPLINE);
   8.156 +			} else {
   8.157 +				goto err;
   8.158 +			}
   8.159 +		} else {
   8.160 +			if(tok != "cp") {
   8.161 +				goto err;
   8.162 +			}
   8.163 +			Vector4 cp;
   8.164 +			for(int i=0; i<4; i++) {
   8.165 +				if(!expect_float(fp, &cp[i])) {
   8.166 +					goto err;
   8.167 +				}
   8.168 +			}
   8.169 +			curve->add_point(Vector3(cp.x, cp.y, cp.z), cp.w);
   8.170 +		}
   8.171 +	}
   8.172 +
   8.173 +	if(curve->size() != cpcount) {
   8.174 +		fprintf(stderr, "warning: curve cpcount was %d, but read %d control points\n", cpcount, curve->size());
   8.175 +	}
   8.176 +
   8.177 +	return curve;
   8.178 +err:
   8.179 +	fprintf(stderr, "failed to parse curve block\n");
   8.180 +	delete curve;
   8.181 +	return 0;
   8.182 +}
   8.183 +
   8.184 +std::list<Curve*> load_curves(FILE *fp)
   8.185 +{
   8.186 +	std::list<Curve*> curves;
   8.187 +	if(!expect_str(fp, "GCURVES")) {
   8.188 +		fprintf(stderr, "load_curves: failed to load, invalid file format\n");
   8.189 +		return curves;
   8.190 +	}
   8.191 +
   8.192 +	Curve *curve;
   8.193 +	while((curve = curve_block(fp))) {
   8.194 +		curves.push_back(curve);
   8.195 +	}
   8.196 +
   8.197 +	if(!feof(fp)) {
   8.198 +		std::list<Curve*>::iterator it = curves.begin();
   8.199 +		while(it != curves.end()) {
   8.200 +			delete *it++;
   8.201 +		}
   8.202 +		return curves;
   8.203 +	}
   8.204 +
   8.205 +	return curves;
   8.206 +}
   8.207 +
     9.1 --- /dev/null	Thu Jan 01 00:00:00 1970 +0000
     9.2 +++ b/src/curvefile.h	Sun Dec 20 09:06:04 2015 +0200
     9.3 @@ -0,0 +1,14 @@
     9.4 +#ifndef CURVEFILE_H_
     9.5 +#define CURVEFILE_H_
     9.6 +
     9.7 +#include <stdio.h>
     9.8 +#include <list>
     9.9 +#include "curve.h"
    9.10 +
    9.11 +bool save_curves(const char *fname, const Curve * const *curves, int count);
    9.12 +bool save_curves(FILE *fp, const Curve * const *curves, int count);
    9.13 +
    9.14 +std::list<Curve*> load_curves(const char *fname);
    9.15 +std::list<Curve*> load_curves(FILE *fp);
    9.16 +
    9.17 +#endif	// CURVEFILE_H_
    9.18 \ No newline at end of file
    10.1 --- a/src/main.cc	Thu Dec 17 16:41:42 2015 +0200
    10.2 +++ b/src/main.cc	Sun Dec 20 09:06:04 2015 +0200
    10.3 @@ -47,11 +47,6 @@
    10.4  	glutSwapBuffers();
    10.5  }
    10.6  
    10.7 -static void reshape(int x, int y)
    10.8 -{
    10.9 -	app_reshape(x, y);
   10.10 -}
   10.11 -
   10.12  static void keydown(unsigned char key, int x, int y)
   10.13  {
   10.14  	app_keyboard(key, true);
    11.1 --- a/src/opengl.h	Thu Dec 17 16:41:42 2015 +0200
    11.2 +++ b/src/opengl.h	Sun Dec 20 09:06:04 2015 +0200
    11.3 @@ -1,14 +1,6 @@
    11.4  #ifndef OPENGL_H_
    11.5  #define OPENGL_H_
    11.6  
    11.7 -#ifdef WIN32
    11.8 -#include <windows.h>
    11.9 -#endif
   11.10 -
   11.11 -#ifdef __APPLE__
   11.12 -#include <OpenGL/gl.h>
   11.13 -#else
   11.14 -#include <GL/gl.h>
   11.15 -#endif
   11.16 +#include <GL/glew.h>
   11.17  
   11.18  #endif	// OPENGL_H_
    12.1 --- a/src/widgets.cc	Thu Dec 17 16:41:42 2015 +0200
    12.2 +++ b/src/widgets.cc	Sun Dec 20 09:06:04 2015 +0200
    12.3 @@ -2,6 +2,11 @@
    12.4  #include <stdlib.h>
    12.5  #include <string.h>
    12.6  #include <stdarg.h>
    12.7 +#ifdef _MSC_VER
    12.8 +#include <malloc.h>
    12.9 +#else
   12.10 +#include <alloca.h>
   12.11 +#endif
   12.12  #include <drawtext.h>
   12.13  #include "opengl.h"
   12.14  #include "widgets.h"