#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;