Created
August 5, 2016 22:08
-
-
Save scribblemaniac/e169bf11e122e02cfb82a508e32cb249 to your computer and use it in GitHub Desktop.
Creates an animated Koch Snowflake in VPaint's .vec format
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#include <iostream> | |
#include <fstream> | |
#include <sstream> | |
#include <vector> | |
#include <array> | |
#include <utility> | |
#include <map> | |
#include <cmath> | |
using namespace std; | |
#define OUTPUT_PATH "VPaintProjects/Result.vec" | |
// Radius and padding are not guaranteed to be accurate | |
#define RADIUS 400 | |
#define PADDING 200 | |
// Ignored for this version of the code | |
// If you want a uniform line width, replace all occurrences of 5*step+5 to LINE_WIDTH and use this macro | |
#define LINE_WIDTH 200 | |
#define LINE_COLOR "rgba(255,255,255,0.3)" | |
// A triangle has a subdivision of 0 | |
#define SUBDIVISIONS 4 | |
#define FPS 30 | |
#define DURATION 1 | |
const float offset = RADIUS + PADDING; | |
const float canvasLength = 2 * offset; | |
int main() { | |
if(SUBDIVISIONS < 1) { | |
cerr << "Not enough subdivisions" << endl; | |
return -1; | |
} | |
ofstream output(OUTPUT_PATH); | |
// vertexOutput is a workaround for a current VPaint bug where the edges need to be before the vertices | |
stringstream vertexOutput; | |
output << "<?xml version=\"1.0\" encoding=\"UTF-8\"?><vec version=\"1.6\"><playback framerange=\"0 " << (FPS * DURATION * SUBDIVISIONS) << "\" fps=\"" << FPS << "\" subframeinbetweening=\"off\" playmode=\"normal\"/><canvas position=\"0 0\" size=\"" << canvasLength << " " << canvasLength << "\"/><layer><background color=\"rgba(135,206,250,1)\" image=\"\" position=\"0 0\" size=\"cover\" repeat=\"norepeat\" opacity=\"1\" hold=\"yes\"/><objects>"; | |
map<int,pair<float,float> > fixedPoints; | |
fixedPoints[0] = {offset + RADIUS * cos(M_PI / 2), offset + RADIUS * sin(M_PI / 2)}; | |
fixedPoints[1 * std::pow(4, SUBDIVISIONS)] = {offset + RADIUS * cos(7 * M_PI / 6), offset + RADIUS * sin(7 * M_PI / 6)}; | |
fixedPoints[2 * std::pow(4, SUBDIVISIONS)] = {offset + RADIUS * cos(11 * M_PI / 6), offset + RADIUS * sin(11 * M_PI / 6)}; | |
const int totalPoints = 3 * pow(4, SUBDIVISIONS); | |
array<pair<float,float>, totalPoints> pointPositions; | |
int edgeStart = (SUBDIVISIONS + 1) * totalPoints; | |
int step; | |
for(int i = 0; i <= SUBDIVISIONS; i++) { | |
// Update inbetween vertices' position if necessary | |
for(int j = 0; j < totalPoints; j++) { | |
int lowerFixed = j; | |
while(!fixedPoints.count(lowerFixed)) --lowerFixed; | |
int upperFixed = j; | |
while(!fixedPoints.count(upperFixed)) upperFixed = (upperFixed + 1) % totalPoints; | |
step = ((upperFixed == 0 ? totalPoints : upperFixed) - lowerFixed) / 4.0; | |
// Determine where the point should be at for this iteration | |
float calcX, calcY; | |
if(j == lowerFixed || j == upperFixed) { | |
calcX = fixedPoints[j].first; | |
calcY = fixedPoints[j].second; | |
} | |
else if(lowerFixed+step == j && upperFixed != (j+step) % totalPoints) { | |
calcX = fixedPoints[lowerFixed].first + (fixedPoints[upperFixed].first - fixedPoints[lowerFixed].first) / 3; | |
calcY = fixedPoints[lowerFixed].second + (fixedPoints[upperFixed].second - fixedPoints[lowerFixed].second) / 3; | |
} | |
else if(lowerFixed+step != j && upperFixed == (j+step) % totalPoints) { | |
calcX = fixedPoints[lowerFixed].first + 2 * (fixedPoints[upperFixed].first - fixedPoints[lowerFixed].first) / 3; | |
calcY = fixedPoints[lowerFixed].second + 2 * (fixedPoints[upperFixed].second - fixedPoints[lowerFixed].second) / 3; | |
} | |
else { | |
calcX = 0.5 * (fixedPoints[upperFixed].first + fixedPoints[lowerFixed].first); | |
calcY = 0.5 * (fixedPoints[upperFixed].second + fixedPoints[lowerFixed].second); | |
} | |
vertexOutput << "<vertex id=\"" << ((i * totalPoints) + j) << "\" frame=\"" << (i * FPS * DURATION) << "\" position=\"" << calcX << " " << calcY << "\"/>"; | |
pointPositions[j].first = calcX; | |
pointPositions[j].second = calcY; | |
if(j > 0) { | |
output << "<edge id=\"" << (edgeStart + i * totalPoints + j) << "\" frame=\"" << (i * FPS * DURATION) << "\" startvertex=\"" << ((i * totalPoints) + j - 1) << "\" endvertex=\"" << ((i * totalPoints) + j) << "\" curve=\"xywdense(5 " << pointPositions[j-1].first << "," << pointPositions[j-1].second << "," << 5*step+5 << " " << calcX << "," << calcY << "," << 5*step+5 << ")\" color=\"" << LINE_COLOR << "\"/>"; | |
} | |
} | |
// Close the shape | |
output << "<edge id=\"" << (edgeStart + i * totalPoints) << "\" frame=\"" << (i * FPS * DURATION) << "\" startvertex=\"" << ((i + 1) * totalPoints - 1) << "\" endvertex=\"" << (i * totalPoints) << "\" curve=\"xywdense(5 " << pointPositions[totalPoints - 1].first << "," << pointPositions[totalPoints - 1].second << "," << 5*step+5 << " " << pointPositions[0].first << "," << pointPositions[0].second << "," << 5*step+5 << ")\" color=\"" << LINE_COLOR << "\"/>"; | |
// We don't need to worry about the next part if this is the last iteration | |
if(i == SUBDIVISIONS) break; | |
// Fix this iterations verticies | |
const map<int,pair<float,float> > prevFixedPoints(fixedPoints); | |
for(auto iter = prevFixedPoints.begin(); iter != prevFixedPoints.end(); iter++) { | |
// Getting the closest fixed points in either direction | |
pair<const int, pair<float, float> > begin = *iter; | |
auto endIter = next(iter); | |
if(endIter == prevFixedPoints.end()) endIter = prevFixedPoints.begin(); | |
auto end = *endIter; | |
// The step is the number of points between each fixed point after this iteration | |
int step = ((end.first == 0 ? totalPoints : end.first) - begin.first) / 4.0; | |
float jumpLength = sqrt(pow(begin.second.first - end.second.first, 2) + pow(begin.second.second - end.second.second, 2)) / 3; | |
float angle = atan2(end.second.second - begin.second.second, end.second.first - begin.second.first) - M_PI / 2; | |
// Fix new points, the three points of a newly formed equilateral triangle | |
fixedPoints[begin.first+step] = pointPositions[begin.first+step]; | |
fixedPoints[begin.first+2*step] = {pointPositions[begin.first+2*step].first + cos(angle) * jumpLength, pointPositions[begin.first+2*step].second + sin(angle) * jumpLength}; | |
fixedPoints[begin.first+3*step] = pointPositions[begin.first+3*step]; | |
} | |
} | |
output << vertexOutput.str(); | |
// Add inbetween vertices/edges | |
int inbetweenVStart = 2 * edgeStart; | |
int inbetweenEStart = inbetweenVStart + (edgeStart - totalPoints); | |
for(int i = 0; i < SUBDIVISIONS; i++) { | |
for(int j = 0; j < totalPoints; j++) { | |
output << "<inbetweenvertex id=\"" << (inbetweenVStart + i * totalPoints + j) << "\" beforevertex=\"" << (i * totalPoints + j) << "\" aftervertex=\"" << ((i + 1) * totalPoints + j) << "\" color=\"rgba(0,0,0,1)\"/>"; | |
output << "<inbetweenedge id=\"" << (inbetweenEStart + i * totalPoints + j) << "\" beforepath=\"[" << (edgeStart + i * totalPoints + j) << "+]\" afterpath=\"[" << (edgeStart + (i + 1) * totalPoints + j) << "+]\" startanimatedvertex=\"[" << (inbetweenVStart + i * totalPoints + j) << "]\" endanimatedvertex=\"[" << (inbetweenVStart + i * totalPoints + (j + 1) % totalPoints) << "]\" color=\"" << LINE_COLOR << "\"/>"; | |
} | |
} | |
output << "</objects></layer></vec>" << endl; | |
output.close(); | |
return 0; | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment