diff --git a/data/gource.1 b/data/gource.1 index e46b22aa..633e4b91 100644 --- a/data/gource.1 +++ b/data/gource.1 @@ -105,6 +105,24 @@ Time in seconds files remain idle before they are removed or 0 for no limit. \fB\-\-file\-idle\-time\-at\-end SECONDS\fR Time in seconds files remain idle at the end before they are removed. .TP +\fB\-S, \-\-scale-by-file-size\fR +Scale file nodes by file size. +.TP +\fB\-\-file-scale FACTOR\fR +Scale factor for file nodes (default: 1.0). +.TP +\fB\-\-dir-spacing FACTOR\fR +Spacing for directory nodes (default: 1.0). +.TP +\fB\-\-file-gravity FACTOR\fR +Gravity for file nodes (default: 0.000001). +.TP +\fB\-\-file-repulsion FACTOR\fR +Repulsion for file nodes (default: 1000.0). +.TP +\fB\-\-show-file-size-on-hover\fR +Show file size on hover. +.TP \fB\-e, \-\-elasticity FLOAT\fR Elasticity of nodes. .TP @@ -378,6 +396,8 @@ type - Single character for the update type - (A)dded, (M)odified or (D)ele file - Path of the file updated. .ti 10 colour - A colour for the file in hex (FFFFFF) format. Optional. +.ti 10 +file_size - The size of the file in bytes. Optional. .SS Caption Log Format diff --git a/src/dirnode.cpp b/src/dirnode.cpp index 6d23f385..9b2f0c22 100644 --- a/src/dirnode.cpp +++ b/src/dirnode.cpp @@ -16,11 +16,11 @@ */ #include "dirnode.h" +#include "gource_settings.h" float gGourceMinDirSize = 15.0; float gGourceForceGravity = 10.0; -float gGourceDirPadding = 1.5; bool gGourceNodeDebug = false; bool gGourceGravity = true; @@ -108,7 +108,8 @@ void RDirNode::nodeUpdated(bool userInitiated) { if(userInitiated) since_last_node_change = 0.0; calcRadius(); - updateFilePositions(); + if (!gGourceSettings.scale_by_file_size) + updateFilePositions(); if(visible && noDirs() && noFiles()) visible = false; if(parent !=0) parent->nodeUpdated(true); } @@ -408,6 +409,11 @@ bool RDirNode::addFile(RFile* f) { //debugLog("addFile %s to %s\n", f->fullpath.c_str(), abspath.c_str()); files.push_back(f); + if (parent == 0) { + float rx = (rand() % 200 - 100) / 100.0f; + float ry = (rand() % 200 - 100) / 100.0f; + f->setPos(f->getPos() + vec2(rx, ry)); + } if(!f->isHidden()) visible_count++; f->setDir(this); @@ -573,7 +579,17 @@ float RDirNode::getArea() const{ void RDirNode::calcRadius() { - float total_file_area = file_area * visible_count; + float total_file_area = 0; + if (gGourceSettings.scale_by_file_size) { + for (RFile* f : files) { + if (!f->isHidden()) { + float r = f->getSize() / 2.0f; + total_file_area += r * r * PI; + } + } + } else { + total_file_area = file_area * visible_count; + } dir_area = total_file_area; @@ -586,11 +602,11 @@ void RDirNode::calcRadius() { // parent_circ += node->getRadiusSqrt(); } - this->dir_radius = std::max(1.0f, (float)sqrt(dir_area)) * gGourceDirPadding; + this->dir_radius = std::max(1.0f, (float)sqrt(dir_area)) * gGourceSettings.dir_spacing; //this->dir_radius_sqrt = sqrt(dir_radius); //dir_radius_sqrt is not used // this->parent_radius = std::max(1.0, parent_circ / PI); - this->parent_radius = std::max(1.0f, (float) sqrt(total_file_area) * gGourceDirPadding); + this->parent_radius = std::max(1.0f, (float) sqrt(total_file_area)) * gGourceSettings.dir_spacing; } float RDirNode::distanceToParent() const{ @@ -892,6 +908,53 @@ void RDirNode::calcEdges() { } } +void RDirNode::applyFilePhysics(float dt) { + if (files.empty() || !gGourceSettings.scale_by_file_size) return; + + const int iterations = 2; + for (int i = 0; i < iterations; ++i) { + // Apply forces + for (auto it1 = files.begin(); it1 != files.end(); ++it1) { + RFile* f1 = *it1; + if (f1->isHidden()) continue; + + // Gravity + vec2 dir_to_center = pos - f1->getPos(); + float dist_to_center = glm::length(dir_to_center); + f1->accel += normalise(dir_to_center) * dist_to_center * gGourceSettings.file_gravity; + + // Repulsion from other files + for (auto it2 = std::next(it1); it2 != files.end(); ++it2) { + RFile* f2 = *it2; + if (f2->isHidden()) continue; + + vec2 dir = f2->getPos() - f1->getPos(); + float dist2 = glm::length2(dir); + float r1 = f1->getSize() / 2.0f; + float r2 = f2->getSize() / 2.0f; + float r_sum = r1 + r2; + + if (dist2 < r_sum * r_sum) { + float dist = sqrt(dist2); + float overlap = r_sum - dist; + vec2 force = normalise(dir) * overlap * gGourceSettings.file_repulsion; + f1->accel -= force; + f2->accel += force; + } + } + } + + // Update positions + for (RFile* f : files) { + if (f->isHidden()) continue; + f->vel += f->accel * dt; + f->setPos(f->getPos() + f->vel * dt); + f->vel *= 0.9f; // Damping + f->accel = vec2(0.0f, 0.0f); + } + } +} + void RDirNode::logic(float dt) { //move @@ -904,11 +967,14 @@ void RDirNode::logic(float dt) { } //update files - for(std::list::iterator it = files.begin(); it!=files.end(); it++) { - RFile* f = *it; - - f->logic(dt); - } + if (gGourceSettings.scale_by_file_size) { + applyFilePhysics(dt); + } + for(std::list::iterator it = files.begin(); it!=files.end(); it++) { + RFile* f = *it; + + f->logic(dt); + } //update child nodes for(std::list::iterator it = children.begin(); it != children.end(); it++) { diff --git a/src/dirnode.h b/src/dirnode.h index 8a78782b..0465ec56 100644 --- a/src/dirnode.h +++ b/src/dirnode.h @@ -93,8 +93,9 @@ class RDirNode : public QuadItem { void updateSplinePoint(float dt); void move(float dt); - vec2 calcFileDest(int layer_no, int file_no); + vec2 calcFileDest(int max_files, int file_no); void updateFilePositions(); + void applyFilePhysics(float dt); void adjustDepth(); void adjustPath(); diff --git a/src/file.cpp b/src/file.cpp index 9f81f291..9f81ae06 100644 --- a/src/file.cpp +++ b/src/file.cpp @@ -16,6 +16,9 @@ */ #include "file.h" +#include "gource_settings.h" +#include +#include float gGourceFileDiameter = 8.0; @@ -28,6 +31,7 @@ RFile::RFile(const std::string & name, const vec3 & colour, const vec2 & pos, in hidden = true; size = gGourceFileDiameter * 1.05; radius = size * 0.5; + file_size = 0; setGraphic(gGourceSettings.file_graphic); @@ -73,6 +77,24 @@ RFile::RFile(const std::string & name, const vec3 & colour, const vec2 & pos, in RFile::~RFile() { } +void RFile::setFileSize(unsigned int new_file_size) { + file_size = new_file_size; + + if (gGourceSettings.scale_by_file_size) { + if (file_size > 0) { + radius = gGourceSettings.file_scale * log((float)file_size + 1.0f); + } else { + radius = gGourceSettings.file_scale; + } + size = radius * 2.0f; + setGraphic(gGourceSettings.file_graphic); + } +} + +unsigned int RFile::getFileSize() const { + return file_size; +} + void RFile::remove(time_t removed_timestamp) { last_action = elapsed; fade_start = elapsed; @@ -283,11 +305,13 @@ void RFile::drawNameText(float alpha) { float name_alpha = selected ? 1.0 : alpha; + std::string label = gGourceSettings.file_extensions ? ext : name; + if(selected) { - file_selected_font.draw(screenpos.x, screenpos.y, name); + file_selected_font.draw(screenpos.x, screenpos.y, label.c_str()); } else { file_font.setAlpha(name_alpha); - file_font.draw(screenpos.x, screenpos.y, gGourceSettings.file_extensions ? ext : name); + file_font.draw(screenpos.x, screenpos.y, label.c_str()); } } diff --git a/src/file.h b/src/file.h index e8b75e87..25f7f27e 100644 --- a/src/file.h +++ b/src/file.h @@ -40,6 +40,7 @@ class RFile : public Pawn { float last_action; float radius; + unsigned int file_size; vec2 dest; float distance; @@ -75,6 +76,8 @@ class RFile : public Pawn { void setDest(const vec2 & dest){ this->dest = dest; } void setDistance(float distance){ this->distance = distance; } + void setFileSize(unsigned int file_size); + unsigned int getFileSize() const; void calcScreenPos(GLint* viewport, GLdouble* modelview, GLdouble* projection); diff --git a/src/formats/commitlog.cpp b/src/formats/commitlog.cpp index 67f5b51b..163ca79b 100644 --- a/src/formats/commitlog.cpp +++ b/src/formats/commitlog.cpp @@ -287,7 +287,7 @@ bool RCommitLog::createTempFile(std::string& temp_file) { // RCommitFile -RCommitFile::RCommitFile(const std::string& filename, const std::string& action, vec3 colour) { +RCommitFile::RCommitFile(const std::string& filename, const std::string& action, vec3 colour, unsigned int file_size) { this->filename = RCommitLog::filter_utf8(filename); @@ -298,6 +298,7 @@ RCommitFile::RCommitFile(const std::string& filename, const std::string& action, this->action = action; this->colour = colour; + this->file_size = file_size; } RCommit::RCommit() { @@ -318,11 +319,11 @@ vec3 RCommit::fileColour(const std::string& filename) { } } -void RCommit::addFile(const std::string& filename, const std::string& action) { - addFile(filename, action, fileColour(filename)); +void RCommit::addFile(const std::string& filename, const std::string& action, unsigned int file_size) { + addFile(filename, action, fileColour(filename), file_size); } -void RCommit::addFile(const std::string& filename, const std::string& action, const vec3& colour) { +void RCommit::addFile(const std::string& filename, const std::string& action, const vec3& colour, unsigned int file_size) { //check filename against filters if(!gGourceSettings.file_filters.empty()) { @@ -347,7 +348,7 @@ void RCommit::addFile(const std::string& filename, const std::string& action, c } } - files.push_back(RCommitFile(filename, action, colour)); + files.push_back(RCommitFile(filename, action, colour, file_size)); } void RCommit::postprocess() { diff --git a/src/formats/commitlog.h b/src/formats/commitlog.h index b95f0cd7..67dbcaa0 100644 --- a/src/formats/commitlog.h +++ b/src/formats/commitlog.h @@ -35,8 +35,9 @@ class RCommitFile { std::string filename; std::string action; vec3 colour; + unsigned int file_size; - RCommitFile(const std::string& filename, const std::string& action, vec3 colour); + RCommitFile(const std::string& filename, const std::string& action, vec3 colour, unsigned int file_size); }; class RCommit { @@ -44,14 +45,15 @@ class RCommit { public: time_t timestamp; std::string username; + std::string commit_hash; std::list files; void postprocess(); bool isValid(); - void addFile(const std::string& filename, const std::string& action); - void addFile(const std::string& filename, const std::string& action, const vec3& colour); + void addFile(const std::string& filename, const std::string& action, unsigned int file_size = 0); + void addFile(const std::string& filename, const std::string& action, const vec3& colour, unsigned int file_size = 0); RCommit(); void debug(); diff --git a/src/formats/custom.cpp b/src/formats/custom.cpp index f8335a4b..464e6180 100644 --- a/src/formats/custom.cpp +++ b/src/formats/custom.cpp @@ -17,8 +17,7 @@ #include "custom.h" #include "../gource_settings.h" - -Regex custom_regex("^(?:\\xEF\\xBB\\xBF)?([^|]+)\\|([^|]*)\\|([ADM]?)\\|([^|]+)(?:\\|#?([a-fA-F0-9]{6}))?"); +#include CustomLog::CustomLog(const std::string& logfile) : RCommitLog(logfile) { } @@ -48,27 +47,25 @@ bool CustomLog::parseCommit(RCommit& commit) { bool CustomLog::parseCommitEntry(RCommit& commit) { std::string line; - std::vector entries; - if(!getNextLine(line)) return false; - //custom line - if(!custom_regex.match(line, &entries)) return false; + std::vector parts; + boost::split(parts, line, boost::is_any_of("|")); - time_t timestamp; + if (parts.size() < 4) return false; - // Allow timestamp to be a string - if(entries[0].size() > 1 && entries[0].find("-", 1) != std::string::npos) { - if(!SDLAppSettings::parseDateTime(entries[0], timestamp)) + time_t timestamp; + if(parts[0].size() > 1 && parts[0].find("-", 1) != std::string::npos) { + if(!SDLAppSettings::parseDateTime(parts[0], timestamp)) return false; } else { - timestamp = (time_t) atoll(entries[0].c_str()); - if(!timestamp && entries[0] != "0") + timestamp = (time_t) atoll(parts[0].c_str()); + if(!timestamp && parts[0] != "0") return false; } - std::string username = (entries[1].size()>0) ? entries[1] : "Unknown"; - std::string action = (entries[2].size()>0) ? entries[2] : "A"; + std::string username = (parts[1].size()>0) ? parts[1] : "Unknown"; + std::string action = (parts[2].size()>0) ? parts[2] : "A"; //if this file is for the same person and timestamp //we add to the commit, else we save the lastline @@ -85,16 +82,21 @@ bool CustomLog::parseCommitEntry(RCommit& commit) { bool has_colour = false; vec3 colour; - - if(entries.size()>=5 && entries[4].size()>0) { + + if (parts.size() >= 5 && parts[4].size() > 0) { has_colour = true; - colour = parseColour(entries[4]); + colour = parseColour(parts[4]); + } + + unsigned int file_size = 0; + if (parts.size() >= 6 && parts[5].size() > 0) { + file_size = std::stoul(parts[5]); } if(has_colour) { - commit.addFile(entries[3], action, colour); + commit.addFile(parts[3], action, colour, file_size); } else { - commit.addFile(entries[3], action); + commit.addFile(parts[3], action, file_size); } return true; diff --git a/src/formats/git.cpp b/src/formats/git.cpp index ef3a482b..756e5486 100644 --- a/src/formats/git.cpp +++ b/src/formats/git.cpp @@ -17,6 +17,7 @@ #include "git.h" #include "../gource_settings.h" +#include #ifndef _MSC_VER #include @@ -104,9 +105,9 @@ std::string GitCommitLog::logCommand() { } if(gGourceSettings.author_time) { - log_command += " --pretty=format:user:%aN%n%at"; + log_command += " --pretty=format:user:%aN%n%at%n%H"; } else { - log_command += " --pretty=format:user:%aN%n%ct"; + log_command += " --pretty=format:user:%aN%n%ct%n%H"; } if(!gGourceSettings.start_date.empty()) { @@ -127,7 +128,7 @@ std::string GitCommitLog::logCommand() { return log_command; } -GitCommitLog::GitCommitLog(const std::string& logfile) : RCommitLog(logfile, 'u') { +GitCommitLog::GitCommitLog(const std::string& logfile) : RCommitLog(logfile, 'u'), m_repository_path(logfile) { log_command = logCommand(); @@ -215,30 +216,63 @@ bool GitCommitLog::parseCommit(RCommit& commit) { //this isnt a commit we are parsing, abort if(commit.timestamp == 0) return false; + if(!logf->getNextLine(line)) return false; + commit.commit_hash = line; + continue; } //should see username before files if(commit.username.empty()) return false; - size_t tab = line.find('\t'); - - //incorrect log format - if(tab == std::string::npos || tab == 0 || tab == line.size()-1) continue; - - std::string status = line.substr(tab - 1, 1); - std::string file = line.substr(tab + 1); - - if(file.empty()) continue; - - //check for and remove double quotes - if(file.find('"') == 0 && file.rfind('"') == file.size()-1) { - if(file.size()<=2) continue; - - file = file.substr(1,file.size()-2); + if (line[0] == ':') { + std::vector parts; + boost::split(parts, line, boost::is_any_of(" \t")); + + if (parts.size() >= 6) { + std::string status = parts[4]; + std::string filename = parts[5]; + std::string dst_blob = parts[3]; + unsigned int file_size = 0; + + if (gGourceSettings.scale_by_file_size && status != "D") { + char cmd_buff[2048]; + int written = snprintf(cmd_buff, 2048, "git --git-dir=%s/.git --work-tree=%s cat-file -s %s", m_repository_path.c_str(), m_repository_path.c_str(), dst_blob.c_str()); + + if(written > 0 && written < 2048) { + FILE* pipe = popen(cmd_buff, "r"); + if (pipe) { + char buffer[128]; + if (fgets(buffer, 128, pipe) != NULL) { + file_size = atol(buffer); + } + pclose(pipe); + } + } + } + + commit.addFile(filename, status, file_size); + } + } else { + size_t tab = line.find('\t'); + + //incorrect log format + if(tab == std::string::npos || tab == 0 || tab == line.size()-1) continue; + + std::string status = line.substr(tab - 1, 1); + std::string file = line.substr(tab + 1); + + if(file.empty()) continue; + + //check for and remove double quotes + if(file.find('"') == 0 && file.rfind('"') == file.size()-1) { + if(file.size()<=2) continue; + + file = file.substr(1,file.size()-2); + } + + commit.addFile(file, status); } - - commit.addFile(file, status); } //check we at least got a username diff --git a/src/formats/git.h b/src/formats/git.h index cc5d1d90..f775391c 100644 --- a/src/formats/git.h +++ b/src/formats/git.h @@ -21,6 +21,8 @@ #include "commitlog.h" class GitCommitLog : public RCommitLog { +private: + std::string m_repository_path; protected: bool parseCommit(RCommit& commit); BaseLog* generateLog(const std::string& dir); diff --git a/src/gource.cpp b/src/gource.cpp index cf86c4f9..76a80f66 100644 --- a/src/gource.cpp +++ b/src/gource.cpp @@ -1003,6 +1003,7 @@ RFile* Gource::addFile(const RCommitFile& cf) { int tagid = tag_seq++; RFile* file = new RFile(cf.filename, cf.colour, vec2(0.0,0.0), tagid); + file->setFileSize(cf.file_size); files[cf.filename] = file; @@ -1229,7 +1230,12 @@ void Gource::processCommit(const RCommit& commit, float t) { } std::map::iterator seen_file = files.find(cf.filename); - if(seen_file != files.end()) file = seen_file->second; + if(seen_file != files.end()) { + file = seen_file->second; + if (cf.action == "M") { + file->setFileSize(cf.file_size); + } + } if(file == 0) { file = addFile(cf); @@ -2678,6 +2684,9 @@ void Gource::draw(float t, float dt) { textbox.setText(hoverFile->getName()); if(display_path.size()) textbox.addLine(display_path); + if (gGourceSettings.show_file_size_on_hover) { + textbox.addLine(std::to_string(hoverFile->getFileSize()) + " bytes"); + } textbox.setColour(hoverFile->getColour()); textbox.setPos(mousepos, true); diff --git a/src/gource_settings.cpp b/src/gource_settings.cpp index 76ec9479..6a5d1870 100644 --- a/src/gource_settings.cpp +++ b/src/gource_settings.cpp @@ -80,7 +80,9 @@ void GourceSettings::help(bool extended_help) { printf(" --author-time Use the timestamp of the author instead of\n"); printf(" the timestamp of the committer\n"); printf(" -c, --time-scale SCALE Change simulation time scale (default: 1.0)\n"); - printf(" -e, --elasticity FLOAT Elasticity of nodes (default: 0.0)\n\n"); + printf(" -e, --elasticity FLOAT Elasticity of nodes (default: 0.0)\n"); + printf(" -S, --scale-by-file-size Scale file nodes by file size.\n\n"); + printf(" \n"); printf(" --key Show file extension key\n\n"); @@ -187,6 +189,12 @@ if(extended_help) { printf(" --hash-seed SEED Change the seed of hash function.\n\n"); + printf(" --file-scale FACTOR Scale factor for file nodes (default: 1.0).\n"); + printf(" --dir-spacing FACTOR Spacing for directory nodes (default: 1.0).\n"); + printf(" --file-gravity FACTOR Gravity for file nodes (default: 0.000001).\n"); + printf(" --file-repulsion FACTOR Repulsion for file nodes (default: 1000.0).\n"); + printf(" --show-file-size-on-hover Show file size on hover.\n\n"); + printf(" --path PATH\n\n"); } @@ -230,6 +238,7 @@ GourceSettings::GourceSettings() { arg_aliases["H"] = "extended-help"; arg_aliases["b"] = "background-colour"; arg_aliases["c"] = "time-scale"; + arg_aliases["S"] = "scale-by-file-size"; arg_aliases["background"] = "background-colour"; arg_aliases["disable-bloom"] = "hide-bloom"; arg_aliases["disable-progress"] = "hide-progress"; @@ -363,6 +372,13 @@ GourceSettings::GourceSettings() { arg_types["filename-time"] = "float"; arg_types["dir-name-depth"] = "int"; + + arg_types["scale-by-file-size"] = "bool"; + arg_types["file-scale"] = "float"; + arg_types["dir-spacing"] = "float"; + arg_types["file-gravity"] = "float"; + arg_types["file-repulsion"] = "float"; + arg_types["show-file-size-on-hover"] = "bool"; } void GourceSettings::setGourceDefaults() { @@ -472,6 +488,14 @@ void GourceSettings::setGourceDefaults() { user_friction = 1.0f; user_scale = 1.0f; + scale_by_file_size = false; + file_scale = 1.0f; + + dir_spacing = 1.0f; + file_gravity = 0.000001f; + file_repulsion = 1000.0f; + show_file_size_on_hover = false; + follow_users.clear(); highlight_users.clear(); highlight_all_users = false; @@ -1452,6 +1476,58 @@ void GourceSettings::importGourceSettings(ConfFile& conffile, ConfSection* gourc highlight_dirs = true; } + if(gource_settings->getBool("scale-by-file-size")) { + scale_by_file_size = true; + } + + if((entry = gource_settings->getEntry("file-scale")) != 0) { + + if(!entry->hasValue()) conffile.entryException(entry, "specify file-scale (float)"); + + file_scale = entry->getFloat(); + + if(file_scale <= 0.0f) { + conffile.invalidValueException(entry); + } + } + + if((entry = gource_settings->getEntry("dir-spacing")) != 0) { + + if(!entry->hasValue()) conffile.entryException(entry, "specify dir-spacing (float)"); + + dir_spacing = entry->getFloat(); + + if(dir_spacing <= 0.0f) { + conffile.invalidValueException(entry); + } + } + + if((entry = gource_settings->getEntry("file-gravity")) != 0) { + + if(!entry->hasValue()) conffile.entryException(entry, "specify file-gravity (float)"); + + file_gravity = entry->getFloat(); + + if(file_gravity <= 0.0f) { + conffile.invalidValueException(entry); + } + } + + if((entry = gource_settings->getEntry("file-repulsion")) != 0) { + + if(!entry->hasValue()) conffile.entryException(entry, "specify file-repulsion (float)"); + + file_repulsion = entry->getFloat(); + + if(file_repulsion <= 0.0f) { + conffile.invalidValueException(entry); + } + } + + if(gource_settings->getBool("show-file-size-on-hover")) { + show_file_size_on_hover = true; + } + if((entry = gource_settings->getEntry("camera-mode")) != 0) { if(!entry->hasValue()) conffile.entryException(entry, "specify camera-mode (overview,track)"); diff --git a/src/gource_settings.h b/src/gource_settings.h index 1975ec59..70d37ffa 100644 --- a/src/gource_settings.h +++ b/src/gource_settings.h @@ -136,6 +136,13 @@ class GourceSettings : public SDLAppSettings { float user_scale; float time_scale; + bool scale_by_file_size; + float file_scale; + float dir_spacing; + float file_gravity; + float file_repulsion; + bool show_file_size_on_hover; + bool highlight_dirs; bool highlight_all_users; diff --git a/src/pawn.cpp b/src/pawn.cpp index 34646f73..8f3769b6 100644 --- a/src/pawn.cpp +++ b/src/pawn.cpp @@ -25,6 +25,8 @@ Pawn::Pawn(const std::string& name, vec2 pos, int tagid) { this->tagid = tagid; this->hidden = false; this->speed = 1.0; + this->vel = vec2(0.0f, 0.0f); + this->accel = vec2(0.0f, 0.0f); selected = false; mouseover = false; diff --git a/src/pawn.h b/src/pawn.h index 4c3389fb..a2467241 100644 --- a/src/pawn.h +++ b/src/pawn.h @@ -34,7 +34,9 @@ class Pawn : public QuadItem { std::string name; float namewidth; +public: vec2 accel; + vec2 vel; float speed; float elapsed;