Skip to content

Instantly share code, notes, and snippets.

@scribblemaniac
Created August 5, 2016 22:08
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save scribblemaniac/e169bf11e122e02cfb82a508e32cb249 to your computer and use it in GitHub Desktop.
Save scribblemaniac/e169bf11e122e02cfb82a508e32cb249 to your computer and use it in GitHub Desktop.
Creates an animated Koch Snowflake in VPaint's .vec format
#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