38 Commits

Author SHA1 Message Date
Endert0m 5af236ad44 Update README.md 2026-06-23 13:13:22 +02:00
Endertom 7fadd4a9ef Modifica versione in cmakelists 2026-06-23 13:11:25 +02:00
Endertom 5588ccee31 Aggiunta finestra di controllo visibilità 2026-06-23 12:52:19 +02:00
Endertom c48d0505fc Refactoring collezioni 2026-06-23 11:39:36 +02:00
Endertom 2dfd574814 Preparazione controllo visibilità collezioni 2026-06-23 11:31:19 +02:00
Endertom f74155e9f7 Rimosso demo imGui 2026-06-22 19:42:39 +02:00
Endertom 68b9d89c12 Refactoring main e update di readme 2026-06-22 19:31:38 +02:00
Endertom 1dd7ce812a Aggiunte texture pavimento 2026-06-22 19:20:31 +02:00
Endertom 276e00aee1 Aggiustata direzione texture su YZ 2026-06-22 19:01:14 +02:00
Endertom d2a0256a3e Aggiunta pavimento 2026-06-22 18:57:07 +02:00
Endertom 4e763fc2af Aggiustato cambio direzione texture 2026-06-22 18:37:19 +02:00
Endertom 818677f831 Aggiunta texture 2026-06-22 17:12:46 +02:00
Endertom 37619774f2 Refactoring per inserimento texture 2026-06-22 16:39:59 +02:00
Endertom 821dd67b5b Fix dei warning e posizione texture 2026-06-22 16:08:39 +02:00
Endertom 4ac25779bd Preparazione aggiunta texture 2026-06-22 16:00:08 +02:00
Endertom 729c634854 Modifica README 2026-06-22 12:56:54 +02:00
Endertom 217635d871 Link del tempo all'aggiornamento dei dati 2026-06-22 12:40:36 +02:00
Endertom 539045bd80 Aggiunta finestra selezione moltiplicatore tempo 2026-06-22 12:23:23 +02:00
Endertom c6094c6a44 Modifica di update() per gestire moltiplicatore tempo 2026-06-22 12:16:32 +02:00
Endertom f05db110ff Aggiornato README 2026-06-22 11:32:26 +02:00
Endertom f131849292 Applicazione correzione rotazione bacino 2026-06-22 10:38:24 +02:00
Endertom 7c2897aae1 fix rotazione 2026-06-21 23:43:17 +02:00
Endertom 1349de403c Aggiunto clock per i pezzi (aggiusta calcolo veocità) 2026-06-21 12:43:21 +02:00
Endertom e1965aaf1e Cambio sistema per rotazione bacino (non funziona) 2026-06-21 11:58:12 +02:00
Endertom 7fee7b1e13 Modifica per aggiornare la rotazione (non Funziona) 2026-06-20 20:39:48 +02:00
Endertom a1441cc28e Aggiunta calcolo rotazione da accTan in rb 2026-06-20 20:21:29 +02:00
Endertom ef2e09ef0f Preparazione per controllo accelerazione tangenziale 2026-06-20 17:40:06 +02:00
Endertom 51c620ef4d Aggiunta restituzione Z_acc su gamba 2026-06-20 17:26:05 +02:00
Endertom bf10647b70 Modifica sensore per restituire accelerazione su z 2026-06-20 17:06:47 +02:00
Endertom be1678beaf Preparazione alla versione 0.8 2026-06-20 16:18:48 +02:00
Endertom 14c143ff56 Modifica di readme 2026-06-20 13:06:32 +02:00
Endertom 4ef359d64c Ridimensionamento bacino 2026-06-20 12:59:17 +02:00
Endertom 4d8d1316e0 Aggiustata direzione gambe su piano YZ 2026-06-20 10:51:27 +02:00
Endertom e440779338 Aggiunto ctrl transp su lower_body in base al piano 2026-06-20 10:20:56 +02:00
Endertom fc5eba1279 Aggiuto controllo trasparenza nelle collezioni 2026-06-20 09:57:53 +02:00
Endertom 83f132a1c4 Aggiunta selezione trasparenza pezzi 2026-06-20 09:42:32 +02:00
Endertom a2eee90e10 Modifica nome eseguibile 2026-06-19 23:44:35 +02:00
Endertom fa6d6cf76e Aggiunto cambio direzione per corretta visualizzaione dx/sx 2026-06-19 19:04:28 +02:00
46 changed files with 66630 additions and 218 deletions
+2 -2
View File
@@ -53,9 +53,9 @@ target_compile_options(common INTERFACE
set(METHODS_PATH "./src/*/methods/*.cpp")
set(VERSION "V6")
set(VERSION "V11")
file(GLOB_RECURSE METHODS_SRC "${METHODS_PATH}")
add_executable(main${VERSION} ./src/testMain.cpp ${METHODS_SRC} )
add_executable(main${VERSION} ./src/Main.cpp ${METHODS_SRC} )
target_link_libraries(main${VERSION} PRIVATE SFML::Graphics ImGui-SFML::ImGui-SFML common glm)
target_compile_definitions(main${VERSION} PRIVATE $<$<CONFIG:Debug>:DEBUG_MODE>)
+26 -1
View File
@@ -43,10 +43,35 @@ Per spostare l'intera scena si tiene premuto il tasto centrale del mouse.
- Modifica di sfml_util per gestire le collezioni
- Aggiunta modalità debug
## Nella versione v0.7
- Aggiunta impostazione di trasparenza dei pezzi
- Aggiustato cambio direzione della gamba (sulla visualizzazione dei piani XZ e -XZ)
- Aggiunto controllo trasparenza delle collezioni
- Modificato lower_body per gestire la trasparenza della gamba più lontana
- Ridimensionato bacino per migiore visualizzazione
## Nella versione v0.8
- Aggiunta oscillazione bacino
- Aggiustato calcolo posizione con clock dedicato
## Nella versione v0.9
- Modificata la funzione update di pezzi e collezioni per implementare controllo sul tempo
- Aggiunta finestra con slider per selezione moltiplicatore del tempo
## Nella versione v1.0
- Aggiunta controllo texture
- Aggiunta pavimento
- Refactoring generale
- Definizione main finale (non più di test)
## Nella versione v1.1
- Aggiunta controllo visibilità collezioni su GUI
- Refactoring collezioni
# Per compilare:
cmake --build
# Per lanciare:
./build/bin/mainV6
./build/bin/mainV11
+10 -1
View File
@@ -3,8 +3,9 @@ Pos=60,60
Size=400,400
[Window][Dear ImGui Demo]
Pos=781,47
Pos=487,44
Size=455,873
Collapsed=1
[Window][Hello, world!]
Pos=11,12
@@ -42,3 +43,11 @@ Size=353,1005
Pos=400,0
Size=400,30
[Window][Set time multiplier]
Pos=400,639
Size=400,30
[Window][Set overlap]
Pos=500,31
Size=300,70
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
Binary file not shown.
BIN
View File
Binary file not shown.
Binary file not shown.
Binary file not shown.
File diff suppressed because it is too large Load Diff
File diff suppressed because it is too large Load Diff
+16516
View File
File diff suppressed because it is too large Load Diff
+16515
View File
File diff suppressed because it is too large Load Diff
+77
View File
@@ -0,0 +1,77 @@
#include "include.hpp"
int main() {
CSVProcessor processor;
// inizializzo variabili per gestire l'intervallo di visualizzazione
unsigned int min = 0;
unsigned int pos = 0;
unsigned int maj = 100;
//Costruisco la GUI
State gs(800, 700, "Visualizzatore passo",&maj,&min,&pos);
gs.window.setFramerateLimit(70);
printf("Costruisco gli oggetti\n");
try{
processor.readCSVFile (DATA_PATH + "coscia_filt.csv"); //utilizzo questo file per definire la dimensione dei dati
const auto& coscia = processor.getData();
gs.setIntervall(coscia.size());
//provo ad aggiungere una collection
//gs.collections.push_back(new Gamba({220,0,220},&pos,"coscia_filt.csv","caviglia_filt.csv"));
std::vector<gamba_data> data;
gamba_data d;
d.dataPos = &pos;
d.cavigliaData = "caviglia_dx.csv";
d.cosciaData = "coscia_dx.csv";
gamba_data s;
s.dataPos = &pos;
s.cavigliaData = "caviglia_sx.csv";
s.cosciaData = "coscia_sx.csv";
data.push_back(d);
data.push_back(s);
gs.collections.push_back(new Lower_Body(rb::Vector3{200,200,100},data));
//aggiungo il pavimento
gs.pieces.push_back(new Pavimento({200,200,550},_Float16(0.2) ));
gs.pieces[0]->body.setRot({0,-0.03,0});
printf("Ho costruito tutto!\n");
}
catch(char* e){
printf("%s\n",e);
}
printf("Avvio l'interfaccia grafica\n");
unsigned int curTime = 0;
unsigned int freq = 50; //frequenza campionamento sensori
const unsigned int T = 1000/freq; //i sensori hanno una freq di campionamento di 50hz
//Avvio il loop della GUI
gs.clock.start();
gs.updateCollections();
sf::Clock mainClock;
while (gs.window.isOpen())
{
curTime += mainClock.restart().asMilliseconds() *(*gs.tMul) ;
if (curTime > T){
if (gs.play && pos+curTime/T < maj) pos += curTime / T;
curTime = 0;
}
// Show update
gs.update();
doGraphics(gs);
}
return 0;
}
@@ -11,8 +11,27 @@ struct collection{
class CollectionInterface{
protected:
float transparency = 1.0;
bool isVisible = true;
public:
virtual collection create(ReferencePlane plane) = 0;
virtual void update(sf::Clock cl, float multiplier) = 0;
virtual bool setTransparency(float alpha) {
if (alpha < 0 || alpha > 1) return false;
transparency = alpha;
return true;
};
virtual bool getVisibility(){
return isVisible;
}
virtual float getTransparency() {
return transparency;
}
virtual void setVisibility(bool c) {
isVisible = c;
}
virtual ~CollectionInterface(){};
};
+4 -1
View File
@@ -6,13 +6,16 @@
class Gamba : public CollectionInterface {
protected:
std::vector<PieceInterface*> sensori;
std::vector<Sensore*> sensori;
std::vector<PieceInterface*> pezzi;
std::vector<JointInterface*> joints;
public:
Gamba(rb::Vector3 pos, unsigned int* dataPos, std::string cosciaData, std::string cavigliaData);
collection create(ReferencePlane plane) override;
PieceInterface* getJointPiece();
void setDirection(Direction dir);
void update(sf::Clock cl, float multiplier)override {};
float getZ_Acc();
};
#endif
+8 -3
View File
@@ -12,6 +12,13 @@ struct gamba_data{
class Lower_Body : public CollectionInterface{
private:
int64_t prevT = 0;
float velD = 0;
float velS = 0;
float posS = 0;
float posD = 0;
protected:
Gamba* sx;
Gamba* dx;
@@ -26,9 +33,7 @@ protected:
public:
Lower_Body(rb::Vector3 pos, std::vector<gamba_data> data);
~Lower_Body();
void setVisibility(bool c);
void setTransparency(float t);
void update(sf::Clock cl, float multiplier) override;
collection create(ReferencePlane plane) override;
};
+21 -2
View File
@@ -15,8 +15,8 @@ Gamba::Gamba(rb::Vector3 pos, unsigned int* dataPos, std::string cosciaData, std
sensori.push_back(new Sensore (rb::Vector3{pos[0],pos[1],pos[2]+200},_Float16( 0.2 ),dataPos,caviglia));
// modifico la rotazione relativa della gamba
sensori[0]->body.setRot({_Float16 (1.3),_Float16 (1.7),0});
sensori[1]->body.setRot({_Float16 (1.8),_Float16 (1.7),0});
sensori[0]->body.setRot({_Float16 (1.5708),_Float16 (1.5708),0});
sensori[1]->body.setRot({_Float16 (1.5708),_Float16 (1.5708),0});
joints.push_back(new RigidJoint(sensori[0], {pezzi[0]}));
joints.push_back(new PivotJoint(sensori[0], {sensori[1]}, rb::Vector3{0,0,100}));
@@ -32,9 +32,11 @@ Gamba::Gamba(rb::Vector3 pos, unsigned int* dataPos, std::string cosciaData, std
collection Gamba::create(ReferencePlane plane){
collection coll;
for(auto e : pezzi){
e->setTransparency(transparency);
coll.pieces.push_back(e);
}
for(auto e : sensori){
e->setTransparency(transparency);
coll.pieces.push_back(e);
}
@@ -48,3 +50,20 @@ collection Gamba::create(ReferencePlane plane){
PieceInterface* Gamba::getJointPiece(){
return sensori[0];
}
void Gamba::setDirection(Direction dir){
for (auto i : pezzi){
i->setDirection(dir);
}
for (auto i : sensori){
i->setDirection(dir);
}
}
float Gamba::getZ_Acc(){
float totZ_Acc = 0;
totZ_Acc = sensori[0]->getZ_Acc() + sensori[1]->getZ_Acc();
return totZ_Acc;
}
+56 -11
View File
@@ -3,28 +3,48 @@
Lower_Body::Lower_Body(rb::Vector3 pos,std::vector<gamba_data> data){
if (data.size() != 2) throw "Lower_Body_Error: data vector size must be 2";
sx = new Gamba({pos[0],pos[1],pos[2]+150},data[0].dataPos,data[0].cosciaData,data[0].cavigliaData);
dx = new Gamba({pos[0],pos[1]+200,pos[2]+150},data[1].dataPos,data[1].cosciaData,data[1].cavigliaData);
t = new Torso({pos[0],pos[1]+100,pos[2]},_Float16(3.0));
sx = new Gamba({pos[0],pos[1]-60,pos[2]+150},data[0].dataPos,data[0].cosciaData,data[0].cavigliaData);
dx = new Gamba({pos[0],pos[1]+60,pos[2]+150},data[1].dataPos,data[1].cosciaData,data[1].cavigliaData);
t = new Torso({pos[0],pos[1],pos[2]},_Float16(3.0));
PieceInterface* psx = sx->getJointPiece();
PieceInterface* pdx = dx->getJointPiece();
jsx = new PivotJoint(t, {psx}, rb::Vector3{0,-100,50});
jdx = new PivotJoint(t, {pdx}, rb::Vector3{0,100,50});
jsx = new PivotJoint(t, {psx}, rb::Vector3{0,-60,50});
jdx = new PivotJoint(t, {pdx}, rb::Vector3{0,60,50});
}
collection Lower_Body::create(ReferencePlane plane){
collection coll;
sx->setTransparency(transparency);
dx->setTransparency(transparency);
t->setTransparency(transparency);
if (isVisible){
coll.joints.push_back(jsx);
coll.joints.push_back(jdx);
switch (plane)
{
case ReferencePlane::XZ: case ReferencePlane::XZN:
case ReferencePlane::XZN:
dx->setTransparency(0.5 * transparency);
dx->setDirection(Direction::L);
sx->setDirection(Direction::R);
coll = coll + dx->create(plane);
coll = coll + sx->create(plane);
break;
case ReferencePlane::XZ:
sx->setTransparency(0.5 * transparency);
dx->setDirection(Direction::R);
sx->setDirection(Direction::L);
coll = coll + sx->create(plane);
coll = coll + dx->create(plane);
break;
case ReferencePlane::YZ:
sx->setDirection(Direction::R);
dx->setDirection(Direction::L);
coll = coll + dx->create(plane);
coll = coll + sx->create(plane);
break;
@@ -32,11 +52,9 @@ collection Lower_Body::create(ReferencePlane plane){
default:
break;
}
coll.pieces.push_back(t);
coll.joints.push_back(jsx);
coll.joints.push_back(jdx);
}
return coll;
}
@@ -48,6 +66,33 @@ Lower_Body::~Lower_Body(){
delete jsx;
}
void Lower_Body::setVisibility(bool c){
void Lower_Body::update(sf::Clock cl, float multiplier){
float sxAcc = sx->getZ_Acc() ;
float dxAcc = dx->getZ_Acc() ;
int64_t Dtime = cl.getElapsedTime().asMicroseconds();
if (prevT == 0) prevT = Dtime;
float dt = (float(Dtime) / 1000000.0) - (float(prevT) / 1000000.0);
prevT = Dtime;
float tmpVelS = sxAcc*dt;
float tmpVelD = dxAcc*dt;
float tmpPosD = tmpVelD *dt *500 + velD * 500 * dt;
float tmpPosS = tmpVelS * 500 *dt + velS * 500 * dt;
velD += tmpVelD;
velS += tmpVelS;
// PosD + PosS + Z = 0
float alpha = atan(tmpPosD/60.0 - tmpPosS/60); //il 60 è il raggio (dimesione del bacino)
//applico smoothing e ritorno a zero
velD -= velD * fabs(alpha);
velS -= velS * fabs(alpha) ;
t->body.setRot({alpha,0,0});
auto tPos = t->body.getPos();
}
+1
View File
@@ -5,6 +5,7 @@
#include "pieces/headers/caviglia.hpp"
#include "pieces/headers/sensore.hpp"
#include "pieces/headers/torso.hpp"
#include "pieces/headers/pavimento.hpp"
#include "joints/headers/rigid_joint.hpp"
#include "joints/headers/pivot_joint.hpp"
#include "collections/headers/gamba.hpp"
+5 -2
View File
@@ -3,18 +3,21 @@
#ifndef CAVIGLIA_H
#define CAVIGLIA_H
class Caviglia : public PieceInterface{
private:
const sf::Vector3f caviglia_Dim = {60, 200, 60};
const sf::Color caviglia_Col = sf::Color(230,160,11,255);
const std::string TEXTURE_F = std::string("cavigliaF.png");
const std::string TEXTURE_L = std::string("cavigliaL.png");
public:
Caviglia(rb::Vector3 coords, _Float16 mass);
~Caviglia();
void update(sf::Clock cl) override;
void update(sf::Clock cl, float multiplier) override;
sf::Shape* draw(ReferencePlane plane) override;
};
+5 -1
View File
@@ -5,17 +5,21 @@
class Coscia : public PieceInterface{
private:
const sf::Vector3f coscia_Dim = {80, 200, 80};
const sf::Color coscia_Col = sf::Color::Yellow;
const std::string TEXTURE_F = std::string("cosciaF.png");
const std::string TEXTURE_L = std::string("cosciaL.png");
public:
Coscia(rb::Vector3 coords, _Float16 mass);
~Coscia();
void update(sf::Clock cl) override;
void update(sf::Clock cl, float multiplier) override;
sf::Shape* draw(ReferencePlane plane) override;
};
-1
View File
@@ -1 +0,0 @@
#include "piece_interface.hpp"
+25
View File
@@ -0,0 +1,25 @@
#include "piece_interface.hpp"
#ifndef PAVIMENTO_H
#define PAVIMENTO_H
class Pavimento : public PieceInterface{
private:
const sf::Vector3f pavimento_Dim = {600, 40, 600};
const sf::Color pavimento_Col = sf::Color(255,255,255,255);
const std::string TEXTURE_F = std::string("pavimentoF.png");
const std::string TEXTURE_L = std::string("pavimentoL.png");
public:
Pavimento(rb::Vector3 coords, _Float16 mass);
~Pavimento();
void update(sf::Clock cl, float multiplier) override {};
sf::Shape* draw(ReferencePlane plane) override;
};
#endif
+32 -1
View File
@@ -2,6 +2,7 @@
#include <math.h>
#include "../../rigidbody/headers/rb.hpp"
#define TEXTUREPATH std::string("./../../textures/")
#ifndef PIECE_INTERFACE_H
#define PIECE_INTERFACE_H
@@ -13,6 +14,11 @@ enum class ReferencePlane {
XZN
};
enum class Direction {
L,
R
};
//classi
class PieceInterface{
protected:
@@ -25,16 +31,41 @@ class PieceInterface{
shapeXZ->setFillColor(color);
shapeYZ->setFillColor(color);
}
Direction direction = Direction::L;
sf::Texture TextureF ;
sf::Texture TextureL ;
void setTextures (std::string F, std::string L){
try{
TextureF = sf::Texture(TEXTUREPATH + F);
TextureL = sf::Texture(TEXTUREPATH + L);
shapeXZ->setTexture(&TextureL);
shapeYZ->setTexture(&TextureF);
}catch(...){
throw "Errore nel caricamento texture.";
}
}
public:
sf::Shape* shapeXZ, *shapeYZ;
rb::Vector3 globalPos;
rb::rigidbody body;
sf::Color color;
float transparency = 1.0; //canale alpha del pezzo
virtual void update(sf::Clock cl) = 0;
virtual void update(sf::Clock cl, float multiplier) = 0;
virtual sf::Shape* draw(ReferencePlane plane) = 0;
virtual ~PieceInterface(){}
virtual void setDirection(Direction dir){
direction = dir;
}
virtual bool setTransparency(float alpha){
if (alpha < 0 || alpha > 1) return false;
transparency = alpha;
return true;
}
};
+6 -4
View File
@@ -7,7 +7,7 @@
class Sensore : public PieceInterface{
private:
const sf::Vector3f sensore_Dim = {30, 60, 30};
const sf::Vector3f sensore_Dim = {15, 20, 15};
const sf::Color sensore_Col = sf::Color::Red;
std::vector<std::vector<float>> accData;
@@ -15,6 +15,8 @@ class Sensore : public PieceInterface{
std::vector<std::vector<float>> rotData;
std::vector<float> timeData;
float gModule;
//in che punto sto controllando il segnale
unsigned int* dataPos;
@@ -27,13 +29,13 @@ class Sensore : public PieceInterface{
Sensore(rb::Vector3 coords, _Float16 mass, unsigned int* st, std::vector<std::vector<float>> data);
~Sensore();
void update(sf::Clock cl) override;
void update(sf::Clock cl,float multiplier) override;
sf::Shape* draw(ReferencePlane plane) override;
//funzioni specifiche
void initCSV(std::vector<std::vector<float>> data);
void setIntervall(int min, int max);
void setPos(int &pos);
float getZ_Acc();
};
#endif
+6 -4
View File
@@ -4,17 +4,19 @@
#define TORSO_H
class Torso : public PieceInterface{
private:
const sf::Vector3f torso_Dim = {100, 100, 250};
const sf::Vector3f torso_Dim = {100, 100, 150};
const sf::Color torso_Col = sf::Color::Red;
const std::string TEXTURE_F = std::string("bacinoF.png");
const std::string TEXTURE_L = std::string("bacinoL.png");
public:
Torso(rb::Vector3 coords, _Float16 mass);
~Torso();
void update(sf::Clock cl) override;
void update(sf::Clock cl, float multiplier) override;
sf::Shape* draw(ReferencePlane plane) override;
};
+15 -5
View File
@@ -2,11 +2,18 @@
Caviglia::Caviglia(rb::Vector3 coords, _Float16 mass){
rb::Vector3 com = {caviglia_Dim.x/2,caviglia_Dim.x/2, caviglia_Dim.y/2};
body = rb::rigidbody(coords, com, mass);
body = rb::rigidbody(coords, com, mass, caviglia_Dim.x/2);
color = caviglia_Col;
globalPos = {0,0,0};
initialize_shapes(caviglia_Dim);
try{
setTextures(TEXTURE_F,TEXTURE_L);
}
catch (const char* &e ){
printf("Caviglia: %s\n", e);
}
}
@@ -15,7 +22,7 @@ Caviglia::~Caviglia(){
delete shapeYZ;
}
void Caviglia::update(sf::Clock cl){
void Caviglia::update(sf::Clock cl,float multiplier){
//body.step(cl);
}
@@ -26,12 +33,14 @@ sf::Shape* Caviglia::draw(ReferencePlane plane){
switch (plane)
{
case ReferencePlane::XZ:
case ReferencePlane::XZ : case ReferencePlane::XZN:
{
sf::Shape* shape = shapeXZ;
shape->setRotation(sf::Angle(sf::radians(tmpRot[1])));
shape->setPosition({tmpPos[0]+globalPos[0],tmpPos[2]+globalPos[2]});
shape->setScale({1,cos(float(tmpRot[0]))});
shape->setScale({plane == ReferencePlane::XZ ? float(1.0) : float(-1.0),cos(float(tmpRot[0]))});
shape->setFillColor(color*sf::Color(255,255,255,transparency*255));
return shape;}
break;
@@ -40,7 +49,8 @@ sf::Shape* Caviglia::draw(ReferencePlane plane){
sf::Shape* shape = shapeYZ;
shape->setRotation(sf::Angle(sf::radians(tmpRot[0])));
shape->setPosition({tmpPos[1]+globalPos[1],tmpPos[2]+globalPos[2]});
shape->setScale({1,cos(float(tmpRot[1]))});
shape->setScale({direction == Direction::R ? float(1.0) : float(-1.0),cos(float(tmpRot[1]))});
shape->setFillColor(color*sf::Color(255,255,255,transparency*255));
return shape;}
break;
+14 -13
View File
@@ -2,10 +2,17 @@
Coscia::Coscia(rb::Vector3 coords, _Float16 mass){
rb::Vector3 com = {coscia_Dim.x/2,coscia_Dim.z/2,coscia_Dim.y/2};
body = rb::rigidbody(coords, com, mass);
body = rb::rigidbody(coords, com, mass, coscia_Dim.z/2);
color = coscia_Col;
globalPos = {0,0,0};
initialize_shapes(coscia_Dim);
try{
setTextures(TEXTURE_F,TEXTURE_L);
}
catch (const char* &e ){
printf("Coscia: %s\n", e);
}
}
Coscia::~Coscia(){
@@ -13,7 +20,7 @@ Coscia::~Coscia(){
delete shapeYZ;
}
void Coscia::update(sf::Clock cl){
void Coscia::update(sf::Clock cl, float multiplier){
//body.step(cl);
}
@@ -25,17 +32,13 @@ sf::Shape* Coscia::draw(ReferencePlane plane){
switch (plane)
{
case ReferencePlane::XZ:
case ReferencePlane::XZ : case ReferencePlane::XZN:
{
sf::Shape* shape = shapeXZ;
shape->setRotation(sf::Angle(sf::radians(tmpRot[1])));
shape->setPosition({tmpPos[0]+globalPos[0],tmpPos[2]+globalPos[2]});
//calcolo ridimensionamento dato da cos(x)-> questo per definire l'ancoraggio corretto del pivot
shape->setScale({1,cos(float(tmpRot[0]))});
//shape->setScale({1,(0.5* cos(float(tmpRot[0]*2)))+0.5});
shape->setScale({plane == ReferencePlane::XZ ? float(1.0) : float(-1.0),cos(float(tmpRot[0]))});
shape->setFillColor(color*sf::Color(255,255,255,transparency*255));
return shape;}
break;
@@ -44,10 +47,8 @@ sf::Shape* Coscia::draw(ReferencePlane plane){
sf::Shape* shape = shapeYZ;
shape->setRotation(sf::Angle(sf::radians(tmpRot[0])));
shape->setPosition({tmpPos[1]+globalPos[1],tmpPos[2]+globalPos[2]});
//calcolo ridimensionamento dato da cos(x) -> questo per definire l'ancoraggio corretto del pivot
shape->setScale({1,cos(float(tmpRot[1]))});
//shape->setScale({1,(0.5* cos(float(tmpRot[1]*2)))+0.5});
shape->setFillColor(color*sf::Color(255,255,255,transparency*255));
shape->setScale({direction == Direction::R ? float(1.0) : float(-1.0),cos(float(tmpRot[1]))});
return shape;}
break;
+57
View File
@@ -0,0 +1,57 @@
#include "../headers/pavimento.hpp"
Pavimento::Pavimento(rb::Vector3 coords, _Float16 mass){
rb::Vector3 com = {pavimento_Dim.x/2,pavimento_Dim.z/2,pavimento_Dim.y/2};
body = rb::rigidbody(coords, com, mass, pavimento_Dim.z/2);
color = pavimento_Col;
globalPos = {0,0,0};
initialize_shapes(pavimento_Dim);
try{
setTextures(TEXTURE_F,TEXTURE_L);
}
catch (const char* &e ){
printf("Pavimento: %s\n", e);
}
}
Pavimento::~Pavimento(){
delete shapeXZ;
delete shapeYZ;
}
sf::Shape* Pavimento::draw(ReferencePlane plane){
rb::Vector3 tmpPos = body.getPos();
rb::Vector3 tmpRot = body.getRot();
switch (plane)
{
case ReferencePlane::XZ : case ReferencePlane::XZN:
{
sf::Shape* shape = shapeXZ;
shape->setRotation(sf::Angle(sf::radians(tmpRot[1])));
shape->setPosition({tmpPos[0]+globalPos[0],tmpPos[2]+globalPos[2]});
shape->setScale({plane == ReferencePlane::XZ ? float(1.0) : float(-1.0),cos(float(tmpRot[0]))});
shape->setFillColor(color*sf::Color(255,255,255,transparency*255));
return shape;}
break;
case ReferencePlane::YZ:
{
sf::Shape* shape = shapeYZ;
shape->setRotation(sf::Angle(sf::radians(tmpRot[0])));
shape->setPosition({tmpPos[1]+globalPos[1],tmpPos[2]+globalPos[2]});
shape->setFillColor(color*sf::Color(255,255,255,transparency*255));
shape->setScale({1,cos(float(tmpRot[1]))});
return shape;}
break;
default:
break;
}
return nullptr;
}
+40 -12
View File
@@ -3,7 +3,7 @@
Sensore::Sensore(rb::Vector3 coords, _Float16 mass){
rb::Vector3 com = {sensore_Dim.x/2,sensore_Dim.z/2, sensore_Dim.y/2};
body = rb::rigidbody(coords, com, mass);
body = rb::rigidbody(coords, com, mass, sensore_Dim.z/2);
color = sensore_Col;
globalPos = {0,0,0};
initialize_shapes(sensore_Dim);
@@ -22,7 +22,7 @@ Sensore::~Sensore(){
void Sensore::initCSV(std::vector<std::vector<float>> data){
//timestamp_ns, wx, wy, wz, ax, ay, az, gx, gy, gz
if (data.size() < 1) throw "Sensor data empty";
if (data.size() < 10) throw "Sensor data empty";
float stTime = int64_t( data[0][0] ) ;
for (std::vector<float> row : data){
@@ -37,10 +37,19 @@ void Sensore::initCSV(std::vector<std::vector<float>> data){
accData.push_back(tmpA);
gData.push_back(tmpG);
}
//trovo il modulo di g facendo la media del modulo nei primi 1000 campioni
gModule = 0;
int nCampioni = int(data.size())>1000 ? 1000 : 10;
for(int i = 0; i<nCampioni ;i++) {
gModule += sqrt(pow(gData[i][0],2)+pow(gData[i][1],2)+pow(gData[i][2],2));
}
gModule = gModule / 1000;
}
void Sensore::update(sf::Clock cl){
void Sensore::update(sf::Clock cl, float multiplier){
//calcolo la posizione e velocità
if (*dataPos >= gData.size()) *dataPos = gData.size()-1;
@@ -49,7 +58,7 @@ void Sensore::update(sf::Clock cl){
body.setAcc(rb::Vector3{accData[*dataPos]});
body.step(cl);
body.step(cl, multiplier);
}
@@ -60,11 +69,14 @@ sf::Shape* Sensore::draw(ReferencePlane plane){
switch (plane)
{
case ReferencePlane::XZ:
case ReferencePlane::XZ : case ReferencePlane::XZN:
{
sf::Shape* shape = shapeXZ;
shape->setRotation(sf::Angle(sf::radians(tmpRot[1])));
shape->setPosition({tmpPos[0]+globalPos[0],tmpPos[2]+globalPos[2]});
shape->setFillColor(color*sf::Color(255,255,255,transparency*255));
shape->setScale({plane == ReferencePlane::XZ ? float(1.0) : float(-1.0),1});
return shape;}
break;
@@ -73,6 +85,8 @@ sf::Shape* Sensore::draw(ReferencePlane plane){
sf::Shape* shape = shapeYZ;
shape->setRotation(sf::Angle(sf::radians(tmpRot[0])));
shape->setPosition({tmpPos[1]+globalPos[1],tmpPos[2]+globalPos[2]});
shape->setFillColor(color*sf::Color(255,255,255,transparency*255));
shape->setScale({direction == Direction::R ? float(1.0) : float(-1.0),1});
return shape;}
break;
@@ -85,23 +99,37 @@ sf::Shape* Sensore::draw(ReferencePlane plane){
void Sensore::calcRotWithG(unsigned int index){ // calcolo rotazione con valori della gravità
int dir = direction == Direction::R ? -1 : 1;
std::vector<float> grav = gData[index];
float modG = sqrt(pow(grav[0],2)+pow(grav[1],2)+pow(grav[2],2));
//x = mod * cosX -> mod = x/cosx -> cosx = x/mod
float tmpSinX = -grav[0] / modG;
float tmpSinY = -grav[1] / modG;
float tmpSinZ = -grav[2] / modG;
float tmpSinX = -grav[0] / gModule;
float tmpSinY = -grav[1] / gModule;
float tmpSinZ = -grav[2] / gModule;
float tmpAX = acos(tmpSinX);
float tmpAY = acos(tmpSinY);
float tmpAX = acos(dir*tmpSinX);
float tmpAY = acos(dir*tmpSinY);
float tmpAZ = acos(tmpSinZ);
body.setRot(rb::Vector3{tmpAY, tmpAX, tmpAZ });
}
float Sensore::getZ_Acc(){
//int id = *dataPos;
float tmpAcc = 0;
rb::Vector3 acc = body.getAcc();
rb::Vector3 rot = body.getRot();
float modAcc = sqrt(pow(acc[0],2)+pow(acc[1],2)+pow(acc[2],2));
float zAcc = cos(rot[2]) * modAcc;
//dipende se il sensore conta la gravità nell'accelerazione sugli assi
tmpAcc = zAcc - gModule;
//tmpAcc = gModule - sqrt(pow(gData[id][0],2)+pow(gData[id][1],2)+pow(gData[id][2],2));
return tmpAcc;
}
/////////////// cinematica inversa
+13 -3
View File
@@ -2,11 +2,18 @@
Torso::Torso(rb::Vector3 coords, _Float16 mass){
rb::Vector3 com = {torso_Dim.x/2, torso_Dim.y/2, torso_Dim.z/2};
body = rb::rigidbody(coords,com, mass);
body = rb::rigidbody(coords,com, mass, torso_Dim.y/2);
color = torso_Col;
globalPos = {0,0,0};
initialize_shapes(torso_Dim);
try{
setTextures(TEXTURE_F,TEXTURE_L);
}
catch (const char* &e){
printf("Caviglia: %s\n", e);
}
}
Torso::~Torso(){
@@ -14,7 +21,7 @@ Torso::~Torso(){
delete shapeYZ;
}
void Torso::update(sf::Clock cl){
void Torso::update(sf::Clock cl,float multiplier){
}
@@ -26,11 +33,13 @@ sf::Shape* Torso::draw(ReferencePlane plane){
switch (plane)
{
case ReferencePlane::XZ:
case ReferencePlane::XZ: case ReferencePlane::XZN:
{
sf::Shape* shape = shapeXZ;
shape->setRotation(sf::Angle(sf::radians(tmpRot[1])));
shape->setPosition({tmpPos[0]+globalPos[0],tmpPos[2]+globalPos[2]});
shape->setScale({plane == ReferencePlane::XZ ? float(1.0) : float(-1.0),cos(float(tmpRot[0]))});
shape->setFillColor(color*sf::Color(255,255,255,transparency*255));
return shape;}
break;
@@ -39,6 +48,7 @@ sf::Shape* Torso::draw(ReferencePlane plane){
sf::Shape* shape = shapeYZ;
shape->setRotation(sf::Angle(sf::radians(tmpRot[0])));
shape->setPosition({tmpPos[1]+globalPos[1],tmpPos[2]+globalPos[2]});
shape->setFillColor(color*sf::Color(255,255,255,transparency*255));
return shape;}
break;
+8 -4
View File
@@ -16,7 +16,9 @@
Vector3 acc = {0,0,0};
Vector3 rot = {0,0,0};
Vector3 tanAcc = {0,0,0};
Vector3 tanVel = {0,0,0};
float R = 1;
_Float16 mass = 1;
Vector3 coords = {0,0,0};
@@ -26,7 +28,7 @@
//funzioni
void calcVel(const float Dtime);
void calcRot(const time_t Dtime);
void calcRot(const float Dtime);
void calcAcc(const Vector3 Dacc);
void calcTanAcc(const Vector3 Dacc);
void calcPos(const float Dtime);
@@ -34,18 +36,20 @@
public:
rigidbody(){}
rigidbody(Vector3 coords, Vector3 centerOfMass, _Float16 mass);
rigidbody(){ }
rigidbody(Vector3 coords, Vector3 centerOfMass, _Float16 mass, float radius);
~rigidbody();
Vector3 getPos();
Vector3 getRot();
Vector3 getAcc();
void setPos(const Vector3 Npos);
void setRot(const Vector3 Nrot);
void setVel(const Vector3 Nacc);
void setAcc(const Vector3 Nvel);
void step(const sf::Clock time);
void setTanAcc(const Vector3 Dacc);
void step(const sf::Clock time, float multiplier);
//complesso, deve definire accelerazione e accelerazione tangenziale
void appForce(Vector3 f, Vector3 pos);
+44 -5
View File
@@ -3,7 +3,7 @@
using namespace rb ;
rigidbody::rigidbody(Vector3 coords, Vector3 centerOfMass, _Float16 mass)
rigidbody::rigidbody(Vector3 coords, Vector3 centerOfMass, _Float16 mass, float radius)
{
if (coords.size() != 3) throw "Coords must be 3";
if (centerOfMass.size() != 3) throw "COM coords must be 3";
@@ -11,7 +11,7 @@ rigidbody::rigidbody(Vector3 coords, Vector3 centerOfMass, _Float16 mass)
this->coords = coords;
this->centerOfMass = centerOfMass;
this->mass = mass;
this->R = radius;
}
rigidbody::~rigidbody()
@@ -37,7 +37,7 @@ void rigidbody::setPos(Vector3 Npos){
}
void rigidbody::setAcc(const Vector3 Nacc){
if (Nacc.size() != 3) throw "Vel vector must be 3 in lenght!";
if (Nacc.size() != 3) throw "Acc vector must be 3 in lenght!";
int i = 0;
for (float axis : Nacc){
@@ -85,16 +85,55 @@ void rigidbody::calcPos(const float Dtime){
}
}
void rigidbody::step(const sf::Clock time){
void rigidbody::step(const sf::Clock time, float multiplier){
int64_t Dtime = time.getElapsedTime().asMicroseconds();
if (prevT == 0) prevT = Dtime;
float dt = (float(Dtime) / 1000000.0) - (float(prevT) / 1000000.0);
float dt = ((float(Dtime) / 1000000.0) - (float(prevT) / 1000000.0)) * multiplier;
prevT = Dtime;
calcRot(dt);
calcPos(dt);
calcVel(dt);
}
void rigidbody::setTanAcc(const Vector3 Dacc){
if (Dacc.size() != 3) throw "Vel vector must be 3 in lenght!";
int i = 0;
for (float a : Dacc ){
tanAcc[i] = a;
i++;
}
}
void rigidbody::calcRot(const float Dtime){
// Ds = wt +1/2*at^2 -> l'accelerazione angolare la trovo ac = v^2/R
Vector3 tmpVel;
for (float a : tanAcc){
tmpVel.push_back( a*Dtime );
}
int i = 0;
for (float nv : tmpVel){
tanVel[i++] += nv;
}
Vector3 tmpTanAcc;
for (int i = 0; i<3; i++){
tmpTanAcc.push_back( pow(tanVel[i],2) / R );
}
i=0;
for (auto axes : rot){
rot[i] = axes + (0.5 * tmpTanAcc[i] * pow(Dtime,2));
i++;
}
}
rb::Vector3 rigidbody::getAcc(){
return Vector3(acc);
}
+59 -6
View File
@@ -5,6 +5,7 @@
#include "collections/headers/collection_interface.hpp"
#include <imgui.h>
#include <imgui-SFML.h>
#include <format>
template <typename T1, typename T2>
double dist(sf::Vector2<T1> p1, sf::Vector2<T2> p2)
@@ -29,10 +30,11 @@ struct State
sf::Vector2f cameraOffset = {0.,0.};
sf::Clock clock;
float* tMul;
sf::Clock PieceClock;
ReferencePlane selectedPlane = ReferencePlane::XZ;
PieceInterface* selected = nullptr;
bool rot_Piece = false;
bool drag_Piece = false;
bool drag = false;
@@ -50,6 +52,8 @@ struct State
window = sf::RenderWindow(sf::VideoMode({w, h}), title);
if (ImGui::SFML::Init(window)); // L'if è solo per togliere il warning, va aggiustato gestendo le eccezioni
clock.restart();
tMul = new float(1.0);
PieceClock.restart();
intervalMajLimit = maj;
intervalMinLimit = min;
this->pos = pos;
@@ -66,6 +70,9 @@ struct State
createdColl.push_back(c->create(selectedPlane));
}
}
~State(){
delete tMul;
}
};
///
@@ -80,11 +87,16 @@ void State::update(){
std::vector<PieceInterface*> collPieces;
std::vector<JointInterface*> collJoints;
*/
if (play){
for (auto i : collections){
i->update(PieceClock, *tMul);
}
}
for (auto i : createdColl){
if (play){
for (auto j : i.pieces){
j->update(clock);
j->update(PieceClock, *tMul);
}
}
for (auto j : i.joints){
@@ -94,7 +106,7 @@ void State::update(){
if (play){
for(PieceInterface* p : pieces){
p->update(clock);
p->update(PieceClock, *tMul);
}
}
for(JointInterface* j : joints){
@@ -276,14 +288,12 @@ void handle_resize(const sf::Event::Resized &resized, State &gs)
/// Graphics
void doGUI(State &gs)
{
// TODO: here code to display the menus
//Bottoni
sf::Time elapsed = gs.clock.restart();
unsigned int zero = 0;
ImGui::SFML::Update(gs.window, elapsed);
ImGui::ShowDemoWindow();
//Finestra gestione posizione nei dati
ImGuiWindowFlags sdp_flags = ImGuiWindowFlags_NoMove|
@@ -292,6 +302,8 @@ void doGUI(State &gs)
ImGuiWindowFlags_NoCollapse|
ImGuiWindowFlags_NoTitleBar;
// Finestra gestione posizione dati
ImGui::Begin("Set data position", 0,sdp_flags);
ImGui::SetNextItemWidth(ImGui::GetContentRegionAvail().x * 0.3);
ImGui::SliderScalar("Start", ImGuiDataType_U32 ,gs.intervalMinLimit,&zero,gs.intervalMajLimit);
@@ -320,8 +332,44 @@ void doGUI(State &gs)
ImGui::Begin("Set visualization plane",0,sdp_flags);
const char* MyEnumNames[] = { "XZ", "YZ", "-XZ" };
int currentPlane = (int)gs.selectedPlane;
if (ImGui::SliderInt("Selected Plane", &currentPlane,0,2,MyEnumNames[currentPlane])) gs.updateCollections();
if (ImGui::SliderInt("Selected Plane", &currentPlane,0,2,MyEnumNames[currentPlane])){
gs.selectedPlane = (ReferencePlane)currentPlane;
gs.updateCollections();
}
ImGui::End();
//Finestra gestione velocità di riproduzione
ImGui::Begin("Set time multiplier", 0,sdp_flags);
const float TimeMul[] = {0.5, 0.75, 1, 1.25, 1.5};
const char* TimeMulChar[] = {"0.5", "0.75", "1", "1.25", "1.5"};
static int Timeid = 2;
ImGui::SliderInt("Time", &Timeid,0,4,TimeMulChar[Timeid]);
*gs.tMul = TimeMul[Timeid];
ImGui::End();
//Finestra controllo sovrapposizione (solo su collezioni)
ImGui::Begin("Set overlap", 0,sdp_flags);
int c = 0;
for (auto i : gs.collections){
std::stringstream s;
s <<"Collezione :" << c;
ImGui::Text(s.str().c_str());
float tmpTr = i->getTransparency();
if (ImGui::SliderFloat("Transparency ", &tmpTr,0.0,1.0)) {
gs.updateCollections();
i->setTransparency(tmpTr);
}
bool tmpVs = i->getVisibility();
if (ImGui::Checkbox("is visible", &tmpVs)) {
i->setVisibility(tmpVs);
gs.updateCollections();
}
c++;
ImGui::Separator();
}
ImGui::End();
@@ -330,6 +378,11 @@ void doGUI(State &gs)
ImGui::SetWindowSize("Set data position",ImVec2(wsize.x,30));
ImGui::SetWindowPos("Set visualization plane",ImVec2(wsize.x-400,0));
ImGui::SetWindowSize("Set visualization plane",ImVec2(400,30));
ImGui::SetWindowPos("Set time multiplier",ImVec2(wsize.x-400,wsize.y - 61));
ImGui::SetWindowSize("Set time multiplier",ImVec2(400,30));
ImGui::SetWindowPos("Set overlap",ImVec2(wsize.x-300,31));
ImGui::SetWindowSize("Set overlap",ImVec2(300,70*gs.collections.size()));
ImGui::SFML::Render(gs.window);
}
-121
View File
@@ -1,121 +0,0 @@
#include "include.hpp"
int main() {
CSVProcessor processor;
try {
processor.readCSVFile("data.csv");
// Access headers
const auto& headers = processor.getHeaders();
for (const auto& header : headers) {
std::cout << header << "\t";
}
std::cout << std::endl;
// Access data
int n = 0;
const auto& data = processor.getData();
for (const auto& row : data) {
if (n++ >40) break;
for (float value : row) {
std::cout << value << "\t";
}
std::cout << std::endl;
}
} catch (const std::exception& e) {
std::cerr << "Error: " << e.what() << std::endl;
}
// inizializzo variabili per gestire l'intervallo di visualizzazione
unsigned int min = 0;
unsigned int pos = 0;
unsigned int maj = 100;
//Costruisco la GUI
State gs(800, 700, "Visualizzatore passo",&maj,&min,&pos);
gs.window.setFramerateLimit(70);
printf("Costruisco gli oggetti\n");
try{
processor.readCSVFile (DATA_PATH + "coscia_filt.csv");
const auto& coscia = processor.getData();
gs.setIntervall(coscia.size());
/*
gs.pieces.push_back(new Coscia (rb::Vector3{300,300,300},2));
gs.pieces.push_back(new Sensore (rb::Vector3{300,300,300},_Float16( 0.2 ),&pos,coscia));
gs.pieces.push_back(new Caviglia (rb::Vector3{300,300,500},1));
gs.pieces[1]->body.setRot({0,0,0});
processor.readCSVFile(DATA_PATH + "caviglia_filt.csv");
const auto& caviglia = processor.getData();
gs.pieces.push_back(new Sensore (rb::Vector3{300,300,500},_Float16( 0.2 ),&pos,caviglia));
gs.pieces.push_back(new Torso(rb::Vector3{300,400,150},2));
// modifico la rotazione relativa della gamba
gs.pieces[1]->body.setRot({_Float16 (1.3),_Float16 (1.7),0});
gs.pieces[3]->body.setRot({_Float16 (1.8),_Float16 (1.7),0});
// aggiungo i joint
gs.joints.push_back(new PivotJoint(gs.pieces[4], {gs.pieces[1]}, rb::Vector3{0,-100,50}));
gs.joints.push_back(new RigidJoint(gs.pieces[1], {gs.pieces[0]}));
gs.joints.push_back(new PivotJoint(gs.pieces[1], {gs.pieces[3]}, rb::Vector3{0,0,100}));
gs.joints.push_back(new RigidJoint(gs.pieces[3], {gs.pieces[2]}));
*/
//provo ad aggiungere una collection
//gs.collections.push_back(new Gamba({220,0,220},&pos,"coscia_filt.csv","caviglia_filt.csv"));
std::vector<gamba_data> data;
gamba_data d;
d.dataPos = &pos;
d.cavigliaData = "caviglia_dx.csv";
d.cosciaData = "coscia_dx.csv";
gamba_data s;
s.dataPos = &pos;
s.cavigliaData = "caviglia_sx.csv";
s.cosciaData = "coscia_sx.csv";
data.push_back(d);
data.push_back(s);
gs.collections.push_back(new Lower_Body(rb::Vector3{200,200,100},data));
printf("Ho costruito tutto!\n");
}
catch(char* e){
printf("%s\n",e);
}
printf("Avvio l'interfaccia grafica\n");
unsigned int curTime = 0;
unsigned int freq = 50;
const unsigned int T = 1000/freq; //i sensori hanno una freq di campionamento di 50hz
//Avvio il loop della GUI
gs.clock.start();
gs.updateCollections();
sf::Clock mainClock;
while (gs.window.isOpen())
{
curTime += mainClock.restart().asMilliseconds();
if (curTime > T){
if (gs.play && pos+curTime/T < maj) pos += curTime / T;
curTime = 0;
}
// Show update
gs.update();
doGraphics(gs);
}
return 0;
}
Binary file not shown.
Binary file not shown.

After

Width:  |  Height:  |  Size: 5.5 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 13 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.4 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 6.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 11 KiB