#define BALLS_NUM           7
#define SPRINGS_NUM         3

#define STRETCH             500.0
#define GRAVITY             50.0 //not really gravity
#define BALL_RADIUS         5
#define BASE_SIZE           10

CMass   balls[BALLS_NUM];
CSpring springs[SPRINGS_NUM];
F64     collision_t;

U0 DrawIt(CTask *task, CDC *dc)
{
    I64  i, cx = task->pix_width >> 1, cy = task->pix_height >> 1;
    Bool sound_on = FALSE;

    dc->color = BLACK;
    GrPrint(dc, 0, 0, "Protect your base.");
    GrRect(dc, cx - BASE_SIZE, cy - BASE_SIZE, BASE_SIZE * 2, BASE_SIZE * 2);
    dc->color = CYAN;
    GrRect(dc, cx - BASE_SIZE + 2, cy - BASE_SIZE + 2, BASE_SIZE * 2 - 4, BASE_SIZE * 2 - 4);
    dc->color = YELLOW;
    GrLine(dc, balls[0].x, balls[0].y, 
            mouse.pos.x - task->pix_left - task->scroll_x, 
            mouse.pos.y - task->pix_top  - task->scroll_y);
    for (i = 0; i < SPRINGS_NUM; i++)
        GrLine(dc, springs[i].end1->x, springs[i].end1->y, springs[i].end2->x, springs[i].end2->y);

    dc->color = LTCYAN;
    GrCircle(dc, balls[0].x, balls[0].y, BALL_RADIUS);
    GrFloodFill(dc, balls[0].x, balls[0].y, TRUE);
    dc->color = BLACK;
    GrCircle(dc, balls[0].x, balls[0].y, BALL_RADIUS);

    for (i = 1; i < BALLS_NUM; i++)
    {
        dc->color = LTPURPLE;
        GrCircle(dc, balls[i].x, balls[i].y, BALL_RADIUS);
        GrFloodFill(dc, balls[i].x, balls[i].y, TRUE);
        if (    cx - BASE_SIZE - BALL_RADIUS <= balls[i].x <= cx + BASE_SIZE + BALL_RADIUS &&
                cy - BASE_SIZE - BALL_RADIUS <= balls[i].y <= cy + BASE_SIZE + BALL_RADIUS)
            sound_on = TRUE;
        dc->color = BLACK;
        GrCircle(dc, balls[i].x, balls[i].y, BALL_RADIUS);
    }
    if (sound_on)
        Sound(74);
    else
        Sound;
}

U0 MyDerivative(CMathODE *ode, F64 t, COrder2D3 *, COrder2D3 *)
{
    I64     i, j;
    F64     d, dd;
    CD3     p, p2;
    CTask  *task = ode->win_task;

    D3SubEqu(D3Equ(&p2, 
             mouse.pos.x - task->pix_left - task->scroll_x, 
             mouse.pos.y - task->pix_top  - task->scroll_y, 0), &balls[0].state->x);
    D3AddEqu(&balls[0].DstateDt->DxDt, D3MulEqu(&p2, STRETCH));

    D3Equ(&p2, task->pix_width >> 1, task->pix_height >> 1, 0);
    for (i = 1; i < BALLS_NUM; i++) {
        D3Sub(&p, &p2, &balls[i].state->x);
        if (d = D3Norm(&p))
        {
            //Gravity would be /(d*d*d), but that's too exponential.
            D3MulEqu(&p, GRAVITY/d);
            D3AddEqu(&balls[i].DstateDt->DxDt, &p);
        }
    }

    for (i = 0; i < BALLS_NUM; i++)
        for (j = i + 1; j < BALLS_NUM; j++)
        {
            D3Sub(&p, &balls[j].state->x, &balls[i].state->x);
            dd = D3NormSqr(&p);
            if (dd <= (2 * BALL_RADIUS) * (2 * BALL_RADIUS))
            {
                if (t-collision_t > 0.05)
                {
                    Noise(50, 102, 105);
                    collision_t = t;
                }
                d = Sqrt(dd) + 0.0001;
                dd = 10.0 * Sqr(Sqr((2 * BALL_RADIUS) * (2 * BALL_RADIUS) - dd));
                D3MulEqu(&p, dd / d);
                D3AddEqu(&balls[j].DstateDt->DxDt, &p);
                D3SubEqu(&balls[i].DstateDt->DxDt, &p);
            }
        }

    d = balls[0].state->x;
    if (d - BALL_RADIUS < 0)
        balls[0].DstateDt->DxDt += Sqr(Sqr(Sqr(d - BALL_RADIUS)));
    if (d + BALL_RADIUS > task->pix_width)
        balls[0].DstateDt->DxDt -= Sqr(Sqr(Sqr((d + BALL_RADIUS) - task->pix_width)));

    d = balls[0].state->y;
    if (d - BALL_RADIUS < 0)
        balls[0].DstateDt->DyDt += Sqr(Sqr(Sqr(d - BALL_RADIUS)));
    if (d + BALL_RADIUS > task->pix_height)
        balls[0].DstateDt->DyDt -= Sqr(Sqr(Sqr((d + BALL_RADIUS) - task->pix_height)));
}

U0 Whap()
{
    I64       i;
    CMathODE *ode = ODENew(0, 1e-2, ODEF_HAS_MASSES);

    SettingsPush; //See SettingsPush
    AutoComplete;
    WinBorder;
    WinMax;

    MenuPush(   "File {"
                "  Abort(,CH_SHIFT_ESC);"
                "  Exit(,CH_ESC);"
                "}"
                );
    ode->derive             = &MyDerivative;
    ode->drag_v2            = 0.002;
    ode->drag_v3            = 0.00001;
    ode->acceleration_limit = 5e3;
    MemSet(balls, 0, BALLS_NUM * sizeof(CMass));
    D3Equ(&balls[0].x, 100, 100, 0);
    for (i = 1; i < BALLS_NUM; i++)
        D3Equ(&balls[i].x, RandI16 % 500 + Fs->pix_width >> 1, RandI16 % 500 + Fs->pix_height >> 1, 0);
    balls[0].x = mouse.pos.x - Fs->pix_left - Fs->scroll_x;
    balls[0].y = mouse.pos.y - Fs->pix_top  - Fs->scroll_y;
    for (i = 0; i < BALLS_NUM; i++)
    {
        balls[i].mass                = 1.0;
        balls[i].drag_profile_factor = 1.0;
        QueueInsert(&balls[i], ode->last_mass);
    }
    balls[2].x = balls[1].x + 15;
    balls[2].y = balls[1].y;
    balls[3].x = balls[1].x;
    balls[3].y = balls[1].y + 15;
    MemSet(springs, 0, SPRINGS_NUM * sizeof(CSpring));
    springs[0].end1     = &balls[1];
    springs[0].end2     = &balls[2];
    springs[0].rest_len = 15;
    springs[0].const    = 10000;
    QueueInsert(&springs[0], ode->last_spring);
    springs[1].end1     = &balls[1];
    springs[1].end2     = &balls[3];
    springs[1].rest_len = 15;
    springs[1].const    = 10000;
    QueueInsert(&springs[1], ode->last_spring);
    springs[2].end1     = &balls[2];
    springs[2].end2     = &balls[3];
    springs[2].rest_len = sqrt2 * 15;
    springs[2].const    = 10000;
    QueueInsert(&springs[2], ode->last_spring);

    collision_t = 0;
    QueueInsert(ode, Fs->last_ode);

    DocCursor;
    DocClear;
    Fs->draw_it = &DrawIt;
    CharGet;
    SettingsPop;
    QueueRemove(ode);
    ODEDel(ode);
    MenuPop;
}

Whap;