1

I am trying to make a simulation of bouncing object with set borders and angles. I used the JS library P5.js.

My problem is, when the object touches the wall, it does bounce, but it also moves it to wrong position (mainly more to the bottom right).

I created object "Wall", which is made of two point (A, B) and those points are used to create a linear function, and I use it to determine, whether a point is "behind it".

f1: y = tg * x + Ax - Ay * tg; tg = (By - Ay) / (Bx - Ax)

f2: y = tn * x + Ox - Ay * tn; tn = Vy / Vx

Then I created an "Object":

class Obj {
    constructor(x, y) {
        this.pos = createVector(x, y); // Reference position of object
        this.vel = createVector(0, 0); // Velocity of object
        this.acc = createVector(0, 0); // Acceleration
        this.ang = PI / 3;             // Angle
        this.mass = 10;                // *
        this.bouncyness = 0.8;         // *
        this.friction = 0.01;          // *
                                       // Position of points relative to main position (this.pos)
        this.points = [
            { x: -25, y: -25 },
            { x: 0, y: -25 },
            { x: 25, y: -25 },
            { x: 25, y: 0 },
            { x: 25, y: 25 },
            { x: 0, y: 25 },
            { x: -25, y: 25 },
            { x: -25, y: 0 },
        ];
    }


    // ***

    // Updating object properties
    update() {
        for (let point of this.points) {
            const dx = point.x * cos(this.ang) - point.y * sin(this.ang);   // X distance between point and center on screen (counting with angle)
            const dy = point.y * cos(this.ang) + point.x * sin(this.ang);   // Y distance between point and center on screen (counting with angle)
                                      
            for (let wall of walls) {                                       // Looping through walls
                                                                            // Checking if the point is behind the wall
                if (wall.isPointBehind(this.pos.x + dx, this.pos.y + dy)) {
                                                                            // Checking, if the walls are diagonal or vertical
                    if (wall.getDX() == 0) {
                        this.pos.x = wall.A.x - dx;
                    } else if (wall.getDY() == 0) {
                        this.pos.y = wall.A.y - dy;
                    } else {
                        const { x: Ox, y: Oy } = this.pos;      // Position of object
                        const { x: Vx, y: Vy } = this.pos;      // Velocity of object
                        const { x: Ax, y: Ay } = wall.A;        // Position of the first point of the wall
                        const tg = wall.getTG();                // Tan of the angle of the wall: (By - Ay) / (Bx - Ax)
                        const tn = Vy / Vx;                     // Tan of angle of speed

                        // I found the intersection of these 2 linear function by setting f1 = f2 and I got this equasion for x (XX)
                        const XX = (Oy - Ox * tn - Ay + Ax * tg) / (tg - tn);   // Value of x, where the linear functions intersect
                        const YY = XX * tg + Ay - Ax * tg;                      // Y ...

                        // In the end I tried almost everything- calculating it through the point, by difference in positions and many others  but it usually works the same, and this seems to be the closest.
                        const RX = XX - dx;                                     [enter image description here][1]
                        const RY = YY - dy;

                        this.pos = createVector(RX, RY);
                    }
                }
            }
        }

        // ***
    }
}

In the end I tried almost everything- calculating it through the point, by difference in positions and many others but it usually works the same, and this seems to be the closest.

(Sorry if I made some grammatical errors)

Object almost touching the wall

After it touched the wall, it moved more to the right bottomň

Geometrical representation (not very good)

Simpler geometrical representation of ball (circle) object without speed, using just a line perpendicular to the wall

Calculations

Whole code: https://editor.p5js.org/MatoXsvk/sketches/ulrioFh7N

Two ways, how the object can be moved

MatoX_svk
  • 11
  • 2
  • Do you have a link to the formlua you used for intersection ? I noticed your equation ```const XX = (Oy - Ox * tn - Ay + Ax * tg) / (tg - tn);``` looks fishy. It could be missing parenthesis – Gianluca Fuoco Apr 08 '22 at 23:06
  • I'm not getting a great idea of what your issue is, can you post your code on an online editor like https://editor.p5js.org/ so we can see what's happening? – Jerome Paddick Apr 09 '22 at 04:23
  • check this out [Can't flip direction of ball without messing up gravity](https://stackoverflow.com/a/53637567/2521214) – Spektre Apr 09 '22 at 07:17
  • I uploaded the whole code here: https://editor.p5js.org/MatoXsvk/sketches/ulrioFh7N (When you press the left mouse key, the object moves towards it, when the left mouse key is pressed, the object is moved to the mouse pointer) – MatoX_svk Apr 10 '22 at 13:34
  • Here are also some of visualizations I used: Using velocity: https://www.geogebra.org/m/uhnqf95v Without velocity: https://www.geogebra.org/m/brxhbbsk https://www.geogebra.org/m/npjmptbt – MatoX_svk Apr 10 '22 at 14:07
  • @MatoX_svk what are the `this.points` ? Are they your object or walls or something else? Are they defined as lines or polygon or Quad or something else? What is the desired behavior of bounce? just bounce or also rotate? Looks like you not using vector math that would most likely get rid of all goniometrics and simplify the code. The simplest would be first implement single point object ... and when working convert to set of points and apply all speed and position of hited point on all of the points (or on its transform matrix). ... – Spektre Apr 11 '22 at 06:43
  • @MatoX_svk also I would implement each wall as set of basis vectors and origin (or even [transform matrix](https://stackoverflow.com/a/28084380/2521214) in 2D it would be 3x3) for easy conversion between its local and world global coordinates so you can handle each wall as axis aligned line to simplify things, if not you can easily use reflection in vector math no goniometrics is involved... – Spektre Apr 11 '22 at 06:44
  • would it help if I post a C++ example? sorry I do not code in javascript – Spektre Apr 11 '22 at 08:16
  • @Spektre My main goal now, is to make a simulation of an object, that can detect, when one of its points is "behind" a wall, and if, it bounces the object off of it (seting the velocity and also the position, so that it isn't in the wall anymore. it doesn't need to change its rotation). The object has this.pos - position relative to the canvas, this.point - an array of points, whose position is relative to this.pos, and this.ang - angle of the obejct, which rotates the points around this.pos. – MatoX_svk Apr 11 '22 at 10:22
  • @Spektre I already created the "single point object" although I am not sure, If it is correct, because when I created it I didn't use velocity, so the object goes automatically "outside" of the wall: https://www.geogebra.org/m/brxhbbsk. In the code simulation ("https://editor.p5js.org/MatoXsvk/sketches/ulrioFh7N") you can change Obj(...) to BallObj(...) at the line 8 in sketch.js to simulate the "Ball object" / "Single point object". – MatoX_svk Apr 11 '22 at 10:32
  • @Spektre The walls are defined by the point of beginning and ending point, and all other function are only, show() and other to determine, whether the object is "behind "them and function to get the linear function easier. I learned the basics of C++ so I can try to understand it and put it to JS. – MatoX_svk Apr 11 '22 at 10:54
  • @MatoX_svk I added edit1 with better code ... – Spektre Apr 13 '22 at 08:17

1 Answers1

1

As I mentioned in the comments I would go for vector math as its simplifies things and get rid of the goniometrics...

I represent wall as a line (2 endpoints) and object as set of points. As I test only object point versus wall collision then the walls should form convex polygon (because concave tooth might hit object edge instead of vertex first).

I modified these examples:

Into this C++/VCL bouncing example:

//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#include "GLSL_math.h"
#pragma hdrstop
#include "win_main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMain *Main;
//---------------------------------------------------------------------------
vec2 acc = vec2(0,+9.81);   // global acceleration
float bouncyness = 0.95;    // global bouncyness effectivity
float friction   = 0.001;   // air friction k*vel^2 / sec
float scale      = 10.0;    // time warp
//---------------------------------------------------------------------------
class wall
    {
public:
    vec2 p0,p1;             // end points
    vec2 t,n;               // tangent, normal
    float d;                // signed |max| displacement of object in normal direction (used in update)

    wall()  { p0=vec2(0,0); p1=p0; t=p0; n=p0; }
    wall(wall& a)   { *this=a; }
    ~wall() {}
    wall* operator = (const wall *a) { *this=*a; return this; }
    //wall* operator = (const wall &a) { ...copy... return this; }

    void set(const vec2 &q0,const vec2 &q1)
        {
        p0=q0;
        p1=q1;
        t=normalize(p1-p0);
        n.x=-t.y; n.y=+t.x;
        }
    void draw(TCanvas *can)
        {
        can->MoveTo(p0.x,p0.y);
        can->LineTo(p1.x,p1.y);
        }
    };
//---------------------------------------------------------------------------
const int obj_pnts=5;       // points of object
class obj
    {
public:
    vec2 p[obj_pnts];       // vertexes relative to p0
    vec2 pos;               // center position
    vec2 vel;               // center velocity

    obj()   { set(vec2(0,0),0.0,0.0); vel=vec2(0,0); }
    obj(obj& a) { *this=a; }
    ~obj()  {}
    obj* operator = (const obj *a) { *this=*a; return this; }
    //obj* operator = (const obj &a) { ...copy... return this; }

    void set(const vec2 &q,float a,float r) // center, rotation, radius
        {
        pos=q;
        int i;
        float da=2.0*M_PI/float(obj_pnts);
        for (i=0;i<obj_pnts;i++){ p[i]=vec2(r*cos(a),r*sin(a)); a+=da; }
        }
    void draw(TCanvas *can)
        {
        int i;
        vec2 q;
        q=pos+p[obj_pnts-1]; can->MoveTo(q.x,q.y);
        for (i=0;i<obj_pnts;i++){ q=pos+p[i]; can->LineTo(q.x,q.y); }
        }
    };
//---------------------------------------------------------------------------
const int walls=4;              // number of walls
wall w[walls];                  // walls (should form a convex polygon as only vertexes of object are tested)
obj  o;                         // bouncing object
//---------------------------------------------------------------------------
void update(double dt)
    {
    int i,j,e;
    float d,dd;
    vec2 p0,p1,q0,q1,pos0,vel0;
    pos0=o.pos;                         // store last position for latter
    vel0=o.vel;                         // store last velocity for latter
    // Newton/d'Lambert simulation on objects center
    o.vel+=  acc*dt;
    o.vel-=o.vel*length(o.vel)*friction*dt; // k*vel^2 air friction
    o.pos+=o.vel*dt;
    // collision test init
    for (j=0;j<walls;j++) w[j].d=0.0;   // reset wall penetration
    // collision test
    for (e=0,i=0;i<obj_pnts;i++)        // test all points
        {
        p0= pos0+o.p[i];                // position before update
        p1=o.pos+o.p[i];                // position after update
        for (j=0;j<walls;j++)           // test all walls
            {
            q0=p0-w[j].p0;              // relative position to wall start point before update
            q1=p1-w[j].p0;              // relative position to wall start point after update
            // convert to wall local coordinates
            q0=vec2(dot(q0,w[j].t),dot(q0,w[j].n));
            q1=vec2(dot(q1,w[j].t),dot(q1,w[j].n));
            // collision?
            if (q0.y*q1.y<=0.0)
                {
                e=1;                    // flag that colliosn exist
                d=fabs(q1.y);           // perpendicular penetration of wall
                if (fabs(w[j].d)<d) w[j].d=q1.y;    // store max penetration for each wall
                }
            }
        }
    // if collison found update pos and vel of object
    if (e)
        {
        // compute displacement direction as avg of wall normals (to handle multiple walls colision)
        for (p0=vec2(0,0),j=0;j<walls;j++)
            {
            if (w[j].d>=0.0) p0+=w[j].n;
             else            p0-=w[j].n;
            }
        p0=normalize(-p0);              // normal (outward)
        p1=vec2(-p0.y,+p0.x);           // tangent
        // compute displacement so it matches penetration with new normal p0
        for (d=0.0,j=0;j<walls;j++)
            {
            dd=fabs(w[j].d)/dot(p0,w[j].n);
            if (d<dd) d=dd;
            }
        o.pos+=p0*(d+0.1);              // displace slightly more to avoid collision in next iteration
        // use displacement direction as normal to surface
        p0=normalize(-p0);              // normal
        p1=vec2(-p0.y,+p0.x);           // tangent
        // convert vel to wall relative
        o.vel=vec2(dot(o.vel,p1),dot(o.vel,p0));
        // convert back and reflect normal direction
        o.vel=(o.vel.x*p1)-(o.vel.y*bouncyness*p0);
        // full stop if slow speed to avoid accuracy problems
        if (length(vel0)<0.1*length(acc)*scale)
            {
            o.vel=vec2(0,0);
            o.pos=(o.pos+pos0)*0.5;
            }
        }
    }
//---------------------------------------------------------------------------
void TMain::draw()
    {
    if (!_redraw) return;

    // clear buffer
    bmp->Canvas->Brush->Color=clBlack;
    bmp->Canvas->FillRect(TRect(0,0,xs,ys));

    // walls
    bmp->Canvas->Pen->Color=clWhite;
    for (int i=0;i<walls;i++) w[i].draw(bmp->Canvas);

    // objects
    o.draw(bmp->Canvas);

    // render backbuffer
    Main->Canvas->Draw(0,0,bmp);
    _redraw=false;
    }
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
    {
    bmp=new Graphics::TBitmap;
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf32bit;
    pyx=NULL;
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormDestroy(TObject *Sender)
    {
    if (pyx) delete[] pyx;
    delete bmp;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormResize(TObject *Sender)
    {
    xs=ClientWidth;  xs2=xs>>1;
    ys=ClientHeight; ys2=ys>>1;
    bmp->Width=xs;
    bmp->Height=ys;
    if (pyx) delete[] pyx;
    pyx=new int*[ys];
    for (int y=0;y<ys;y++) pyx[y]=(int*) bmp->ScanLine[y];
    _redraw=true;

    // walls
    vec2 p0,p1,p2,p3;
    p0=vec2(0.1*xs,0.5*ys);
    p1=vec2(0.3*xs,0.1*ys);
    p2=vec2(0.9*xs,0.5*ys);
    p3=vec2(0.7*xs,0.9*ys);
    w[0].set(p0,p1);
    w[1].set(p1,p2);
    w[2].set(p2,p3);
    w[3].set(p3,p0);

    // objects
    o.set(vec2(0.5*xs,0.5*ys),30.0*M_PI/180.0,0.05*xs);

    update(0.0);
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormPaint(TObject *Sender)
    {
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::tim_redrawTimer(TObject *Sender)
    {
    update(0.04*scale);
    _redraw=true;
    draw();
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
    {
    // set velocity towards mouse with magnitude based on x resolution of window
    o.vel=normalize(vec2(X,Y)-o.pos)*0.2*xs;
    }
//---------------------------------------------------------------------------

This is a single form App with single timer in it firing every 40ms calling functions update and draw. On resize of app I configure the walls and object start positon and orientation. On click (mouse down) event I set the object speed so it flies towards mouse...

Now the only important stuff in here is the update(dt) function which simulate the physics (updates velocity and position of object), then it test if any point of object collide with any wall and remember for each wall how much in which side a penetration occurs (remember the biggest one).

After all this I simply compute avg normal of all collided walls in outwards direction ("go above ground") and recompute displacement so it matches the original one with actual wall normal. This complication I added because when you hit more walls at once the dispalcement on per wall basis in some cases leads to moving the object below ground...

And at last once the speed is close to zero I force the speed to zero and displace a bit to avoid accuracy problems which would lead to falling of object partially below ground.

In case you got your walls defined with strict winding rule you could use normal direction instead of these tweaks however in such case the walls would be just single sided...

Here preview of few clicks on mouse (note my global acceleration is set with gravity downwards):

preview

vector math used is with GLSL like syntax you can use any vector math you have at your disposal like GLM or rewrite the math into x,y pairs yourself. I use only basic operations like vector size, vector normalize to unit size, dot product, multiply vector by constant and +/- vectors ...

The only goniometrics used is just to generate the object points (you can change number of points with the constant obj_pnts from line,triangle,square,... to whatever circle like surface ...

All the collision testing and reflection of speed is based on normal and tangent vectors along with dot product operation. You know if you have unit vector n then dot(a,n) will give you perpendicular projection of a vector into n which can be used as penetration of wall and also to convert between local and global coordinates of wall and world without any heavy math...

[Edit1] I tweaked the code a bit to enhance the robustness

I changed the handling of stuck/slow speed problems by detecting state when more than one wall is hit at once and ignore the tangent speed in such case so the object bounce always straight upwards (relative to obstacle) with enough speed to avoid stuck and accuracy problems in next iteration. This allows to get rid of the slow speed stuck state (causing the slow sliding). Also I changed the timewarp behavior (so it does not affect constants)

Here updated code:

//$$---- Form CPP ----
//---------------------------------------------------------------------------
#include <vcl.h>
#include "GLSL_math.h"
#pragma hdrstop
#include "win_main.h"
//---------------------------------------------------------------------------
#pragma package(smart_init)
#pragma resource "*.dfm"
TMain *Main;
//---------------------------------------------------------------------------
vec2 acc = vec2(0,+9.81);   // global acceleration
float bouncyness = 0.80;    // global bouncyness effectivity
float friction   = 0.01;    // air friction k*vel^2 / sec
int twarp        = 10;      // time warp
//---------------------------------------------------------------------------
class wall
    {
public:
    vec2 p0,p1;             // end points
    vec2 t,n;               // tangent, normal
    float d;                // signed |max| displacement of object in normal direction (used in update)

    wall()  { p0=vec2(0,0); p1=p0; t=p0; n=p0; }
    wall(wall& a)   { *this=a; }
    ~wall() {}
    wall* operator = (const wall *a) { *this=*a; return this; }
    //wall* operator = (const wall &a) { ...copy... return this; }

    void set(const vec2 &q0,const vec2 &q1)
        {
        p0=q0;
        p1=q1;
        t=normalize(p1-p0);
        n.x=-t.y; n.y=+t.x;
        }
    void draw(TCanvas *can)
        {
        can->MoveTo(p0.x,p0.y);
        can->LineTo(p1.x,p1.y);
        }
    };
//---------------------------------------------------------------------------
const int obj_pnts=7;       // points of object
class obj
    {
public:
    vec2 p[obj_pnts];       // vertexes relative to p0
    vec2 pos;               // center position
    vec2 vel;               // center velocity

    obj()   { set(vec2(0,0),0.0,0.0); vel=vec2(0,0); }
    obj(obj& a) { *this=a; }
    ~obj()  {}
    obj* operator = (const obj *a) { *this=*a; return this; }
    //obj* operator = (const obj &a) { ...copy... return this; }

    void set(const vec2 &q,float a,float r) // center, rotation, radius
        {
        pos=q;
        int i;
        float da=2.0*M_PI/float(obj_pnts);
        for (i=0;i<obj_pnts;i++){ p[i]=vec2(r*cos(a),r*sin(a)); a+=da; }
        }
    void draw(TCanvas *can)
        {
        int i;
        vec2 q;
        q=pos+p[obj_pnts-1]; can->MoveTo(q.x,q.y);
        for (i=0;i<obj_pnts;i++){ q=pos+p[i]; can->LineTo(q.x,q.y); }
        }
    };
//---------------------------------------------------------------------------
const int walls=4;              // number of walls
wall w[walls];                  // walls (should form a convex polygon as only vertexes of object are tested)
obj  o;                         // bouncing object
//---------------------------------------------------------------------------
void update(double dt)
    {
    int i,j,e;
    float d,dd;
    vec2 p0,p1,q0,q1,pos0,vel0;
    pos0=o.pos;                         // store last position for latter
    vel0=o.vel;                         // store last velocity for latter
    // Newton/d'Lambert simulation on objects center
    o.vel+=  acc*dt;
    o.vel-=o.vel*length(o.vel)*friction*dt; // k*vel^2 air friction
    o.pos+=o.vel*dt;
    // collision test init
    for (j=0;j<walls;j++) w[j].d=0.0;   // reset wall penetration
    // collision test
    for (e=0,i=0;i<obj_pnts;i++)        // test all points
        {
        p0= pos0+o.p[i];                // position before update
        p1=o.pos+o.p[i];                // position after update
        for (j=0;j<walls;j++)           // test all walls
            {
            q0=p0-w[j].p0;              // relative position to wall start point before update
            q1=p1-w[j].p0;              // relative position to wall start point after update
            // convert to wall local coordinates
            q0=vec2(dot(q0,w[j].t),dot(q0,w[j].n));
            q1=vec2(dot(q1,w[j].t),dot(q1,w[j].n));
            // collision?
            if (q0.y*q1.y<=0.0)
                {
                e=1;                    // flag that colliosn exist
                d=fabs(q1.y);           // perpendicular penetration of wall
                if (fabs(w[j].d)<d) w[j].d=q1.y;    // store max penetration for each wall
                }
            }
        }
    // if collison found update pos and vel of object
    if (e)
        {
        // compute displacement direction as avg of wall normals (to handle multiple walls colision)
        // and e is number of hit walls
        for (e=0,p0=vec2(0,0),j=0;j<walls;j++)
            {
            if (fabs(w[j].d)>1e-6) e++;
            if (w[j].d>=0.0) p0+=w[j].n;
             else            p0-=w[j].n;
            }
        p0=normalize(-p0);              // normal (outward)
        p1=vec2(-p0.y,+p0.x);           // tangent
        // compute displacement so it matches penetration with new normal p0
        for (d=0.0,j=0;j<walls;j++)
            {
            dd=fabs(w[j].d)/dot(p0,w[j].n);
            if (d<dd) d=dd;
            }
        o.pos+=p0*(d+0.5);              // displace slightly more to avoid collision in next iteration
        // use displacement direction as normal to surface
        p0=normalize(-p0);              // normal
        p1=vec2(-p0.y,+p0.x);           // tangent
        // convert vel to wall relative
        o.vel=vec2(dot(o.vel,p1),dot(o.vel,p0));
        // convert back and reflect normal direction
        if (e>1)                        // if hit more walls at once
            {
            o.vel.x=0.0;                // disipate tangential speed
            d=0.5/dt;                   // enforce speed upwards
            if (o.vel.y<d) o.vel.y=d;
            }
        o.vel=(o.vel.x*p1)-(o.vel.y*bouncyness*p0);
        }
    }
//---------------------------------------------------------------------------
void TMain::draw()
    {
    if (!_redraw) return;

    // clear buffer
    bmp->Canvas->Brush->Color=clBlack;
    bmp->Canvas->FillRect(TRect(0,0,xs,ys));

    // walls
    bmp->Canvas->Pen->Color=clWhite;
    for (int i=0;i<walls;i++) w[i].draw(bmp->Canvas);

    // objects
    bmp->Canvas->Pen->Color=clAqua;
    o.draw(bmp->Canvas);

    // render backbuffer
    Main->Canvas->Draw(0,0,bmp);
    _redraw=false;
    }
//---------------------------------------------------------------------------
__fastcall TMain::TMain(TComponent* Owner) : TForm(Owner)
    {
    bmp=new Graphics::TBitmap;
    bmp->HandleType=bmDIB;
    bmp->PixelFormat=pf32bit;
    pyx=NULL;
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormDestroy(TObject *Sender)
    {
    if (pyx) delete[] pyx;
    delete bmp;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormResize(TObject *Sender)
    {
    xs=ClientWidth;  xs2=xs>>1;
    ys=ClientHeight; ys2=ys>>1;
    bmp->Width=xs;
    bmp->Height=ys;
    if (pyx) delete[] pyx;
    pyx=new int*[ys];
    for (int y=0;y<ys;y++) pyx[y]=(int*) bmp->ScanLine[y];
    _redraw=true;

    // walls
    vec2 p0,p1,p2,p3;
    p0=vec2(0.1*xs,0.5*ys);
    p1=vec2(0.3*xs,0.1*ys);
    p2=vec2(0.9*xs,0.5*ys);
    p3=vec2(0.7*xs,0.9*ys);
    w[0].set(p0,p1);
    w[1].set(p1,p2);
    w[2].set(p2,p3);
    w[3].set(p3,p0);

    // objects
    o.set(vec2(0.5*xs,0.5*ys),30.0*M_PI/180.0,0.05*xs);

    update(0.04);
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormPaint(TObject *Sender)
    {
    _redraw=true;
    }
//---------------------------------------------------------------------------
void __fastcall TMain::tim_redrawTimer(TObject *Sender)
    {
    for (int i=0;i<twarp;i++) update(0.04);
    _redraw=true;
    draw();
    }
//---------------------------------------------------------------------------
void __fastcall TMain::FormMouseDown(TObject *Sender, TMouseButton Button, TShiftState Shift, int X, int Y)
    {
    // set velocity towards mouse with magnitude based on x resolution of window
    o.vel=normalize(vec2(X,Y)-o.pos)*0.2*xs;
    }
//---------------------------------------------------------------------------

And preview:

preview

Spektre
  • 49,595
  • 11
  • 110
  • 380