#define MT_HOSE         1
#define MT_DROPLET      2
class MyMass:CMass
{
    I64 type;
    F64 radius;
};

#define ST_HOSE                 1
class MySpring:CSpring
{
    I64 type;
};



                <1>/* Graphics Not Rendered in HTML */

                <2>/* Graphics Not Rendered in HTML */

                <3>/* Graphics Not Rendered in HTML */















/* Graphics Not Rendered in HTML */

#define HOSE_RADIUS         3
#define LINK_SIZE           6
#define NOZZLE_START_Y      (GR_HEIGHT - 15 * FONT_HEIGHT)
#define NOZZLE_LEN          18
#define FAUCET_X            (5 * HOSE_RADIUS)
#define FAUCET_Y            (GR_HEIGHT - 12 * FONT_HEIGHT)
#define GROUND_Y            (GR_HEIGHT - 3  * FONT_HEIGHT)
MyMass *faucet, *nozzle;
F64     nozzle_theta;

CMathODE *ode = NULL;
F64       start_up_timeout;

U0 DrawIt(CTask *, CDC *dc)
{
    Bool      first;
    F64       dx, dy, d;
    I64       x1b, y1b, x2b, y2b, x1a, y1a, x2a, y2a;
    MyMass   *tmpm, *tmpm1;
    MySpring *tmps;
    CD3I32    poly[4];

    Sprite3(dc, 0, GROUND_Y, 0, <4>);
    if (start_up_timeout > tS)
    {
        ode->drag_v2 = 0.01; //Let hose settle during start-up
        ode->drag_v3 = 0.0001;
        dc->color = RED;
        GrPrint(dc, (GR_WIDTH - FONT_WIDTH * 6) >> 1, GR_HEIGHT >> 1, "Squirt");
        return;
    }
    else
    {
        ode->drag_v2 = 0.0005;
        ode->drag_v3 = 0.0000025;
    }

    tmpm = faucet;
    dc->color = BLACK;
    GrRect(dc, tmpm->x + 8, tmpm->y, 8, GROUND_Y - FAUCET_Y);
    Sprite3(dc, tmpm->x, tmpm->y, 0, <1>);
    dc->color = BLACK;
    GrCircle(dc, tmpm->x, tmpm->y, tmpm->radius);
    dc->color = GREEN;
    GrFloodFill(dc, tmpm->x, tmpm->y);

    tmpm = nozzle;
    tmpm1 = nozzle->last;
    dx = tmpm->x - tmpm1->x;
    dy = tmpm->y - tmpm1->y;
    nozzle_theta = Wrap(Arg(dx, dy));
    Sprite3ZB(dc, tmpm->x, tmpm->y, 0, <2>, nozzle_theta);
    dc->color = BLACK;
    GrCircle(dc, tmpm->x, tmpm->y, tmpm->radius);
    dc->color = GREEN;
    GrFloodFill(dc, tmpm->x, tmpm->y);

    first = TRUE;
    tmpm = ode->next_mass;
    while (tmpm != &ode->next_mass)
    {
        if (tmpm->type == MT_HOSE)
        {
            tmpm1 = tmpm->last;
            dx = tmpm->x - tmpm1->x;
            dy = tmpm->y - tmpm1->y;
            d = HOSE_RADIUS / Max(Sqrt(dx * dx + dy * dy), 0.001);
            dx *= d;
            dy *= d;
            x2a = tmpm->x - dy;
            y2a = tmpm->y + dx;
            x2b = tmpm->x + dy;
            y2b = tmpm->y - dx;

            if (first)
                first = FALSE;
            else
            {
                dc->color = GREEN;
                poly[0].x = x1a;
                poly[0].y = y1a;
                poly[0].z = 0;
                poly[1].x = x2a;
                poly[1].y = y2a;
                poly[1].z = 0;
                poly[2].x = x2b;
                poly[2].y = y2b;
                poly[2].z = 0;
                poly[3].x = x1b;
                poly[3].y = y1b;
                poly[3].z = 0;
                GrFillPoly3(dc, 4, poly);
            }

            //Fill gaps
            GrLine(dc, x2a, y2a, x2b, y2b);

            x1a = x2a;
            y1a = y2a;
            x1b = x2b;
            y1b = y2b;
        }
        else if (tmpm->type == MT_DROPLET)
            Sprite3(dc, tmpm->x, tmpm->y, 0, <3>);
        tmpm = tmpm->next;
    }

    tmps = ode->next_spring;
    while (tmps != &ode->next_spring)
    {
        if (tmps->type == ST_HOSE)
        {
            dx = tmps->end1->x - tmps->end2->x;
            dy = tmps->end1->y - tmps->end2->y;
            d = HOSE_RADIUS / Max(Sqrt(dx * dx + dy * dy), 0.001);
            dx *= d;
            dy *= d;
            dc->color = BLACK;
            GrLine(dc, tmps->end1->x - dy, tmps->end1->y + dx, tmps->end2->x - dy, tmps->end2->y + dx);
            GrLine(dc, tmps->end1->x + dy, tmps->end1->y - dx, tmps->end2->x + dy, tmps->end2->y - dx);
        }
        tmps = tmps->next;
    }
}

U0 MyDerivative(CMathODE *ode, F64 t, COrder2D3 *state, COrder2D3 *DstateDt)
{//The forces due to springs and drag are
//automatically handled by the
    //ode code.  We can add new forces
    //here.
    no_warn t, state, DstateDt;
    MyMass *tmpm1 = ode->next_mass;

    while (tmpm1 != &ode->next_mass)
    {
        if (tmpm1->type == MT_HOSE)
        {
            if (tmpm1->state->y + tmpm1->radius > GROUND_Y)
                tmpm1->DstateDt->DyDt -= Sqr(Sqr(tmpm1->state->y + tmpm1->radius - GROUND_Y)) * tmpm1->mass;
            else
                tmpm1->DstateDt->DyDt += 500 * tmpm1->mass;
            if (tmpm1 == nozzle || tmpm1 == faucet)
            {
                tmpm1->DstateDt->DxDt = 0;
                tmpm1->DstateDt->DyDt = 0;
            }
        }
        else if (tmpm1->type == MT_DROPLET)
            tmpm1->DstateDt->DyDt = 500 * tmpm1->mass;
        tmpm1 = tmpm1->next;
    }
}

MyMass *PlaceMass(I64 type, I64 x,  I64 y, F64 r, F64 dx, F64 dy, F64 mass, CTask *mem_task)
{
    MyMass *tmpm = CAlloc(sizeof(MyMass), mem_task);

    tmpm->type      = type;
    tmpm->mass      = mass;
    tmpm->drag_profile_factor = 250.0;
    tmpm->x         = x;
    tmpm->y         = y;
    tmpm->DxDt      = dx;
    tmpm->DyDt      = dy;
    tmpm->radius    = r;
    QueueInsert(tmpm, ode->last_mass);

    return tmpm;
}

MySpring PlaceSpring(MyMass *tmpm1, MyMass *tmpm2)
{
    MySpring *tmps = CAlloc(sizeof(MySpring));

    tmps->end1  = tmpm1;
    tmps->end2  = tmpm2;
    tmps->const = 20000;
    QueueInsert(tmps, ode->last_spring);

    return tmps;
}

U0 HoseNew()
{
    I64          i;
    MyMass      *tmpm1 = NULL, *tmpm;
    MySpring    *tmps;

    for (i = FAUCET_X; i < GR_WIDTH; i += LINK_SIZE)
    {
        tmpm = PlaceMass(MT_HOSE, i / 2, GROUND_Y - HOSE_RADIUS, HOSE_RADIUS, 0, 0, 1.0, Fs);
        if (tmpm1)
        {
            tmps = PlaceSpring(tmpm, tmpm1);
            tmps->rest_len  = LINK_SIZE;
            tmps->type      = ST_HOSE;
            nozzle = tmpm;
        }
        else
            faucet = tmpm;
        tmpm1 = tmpm;
    }
    faucet->y = FAUCET_Y;
    nozzle->y = NOZZLE_START_Y;
    nozzle_theta = 0;
}

U0 AnimateTask(I64)
{
    MyMass *tmpm, *tmpm1;
    F64     dx, dy;

    while (TRUE)
    {
        dx = Cos(nozzle_theta);
        dy = Sin(nozzle_theta);
        PlaceMass(MT_DROPLET, nozzle->x + NOZZLE_LEN * dx,
                              nozzle->y + NOZZLE_LEN * dy, HOSE_RADIUS, 500 * dx, 500 * dy, 100.0, Fs->parent_task);
        if (Rand < 0.05) //faucet drip
            PlaceMass(MT_DROPLET, faucet->x, faucet->y, HOSE_RADIUS, 0, 0, 100.0, Fs->parent_task);

        tmpm = ode->next_mass;
        while (tmpm != &ode->next_mass)
        {
            tmpm1 = tmpm->next;
            if (tmpm->type == MT_DROPLET && tmpm->y + tmpm->radius > GROUND_Y)
            {
                QueueRemove(tmpm);
                Free(tmpm);
            }
            tmpm = tmpm1;
        }
        Refresh;
    }
}

#define NOZZLE_MOVE_STEPS   5
#define NOZZLE_MOVE         15.0
U0 MoveNozzleTaskX(I64 sign)
{
    I64 i;

    for (i = 0; i < NOZZLE_MOVE_STEPS; i++)
    {
        nozzle->x = Clamp(nozzle->x + sign * NOZZLE_MOVE / NOZZLE_MOVE_STEPS, HOSE_RADIUS * 3, GR_WIDTH - HOSE_RADIUS * 3);
        Refresh;
    }
}

U0 MoveNozzleTaskY(I64 sign)
{
    I64 i;

    for (i = 0; i < NOZZLE_MOVE_STEPS; i++)
    {
        nozzle->y = Clamp(nozzle->y + sign * NOZZLE_MOVE / NOZZLE_MOVE_STEPS, HOSE_RADIUS * 3, GROUND_Y);
        Refresh;
    }
}

U0 Init()
{
    DocClear;
    "$BG, LTCYAN$%h*c", ToI64(GROUND_Y / FONT_HEIGHT), '\n';

    //Allow hose to settle.
    start_up_timeout = tS + 0.5;

    ode = ODENew(0, 5e-2, ODEF_HAS_MASSES);
    ode->derive             = &MyDerivative;
    ode->acceleration_limit = 5e3;

    HoseNew;
    QueueInsert(ode, Fs->last_ode);
}

U0 CleanUp()
{
    Refresh(NOZZLE_MOVE_STEPS); //Let nozzle move tasks die
    QueueRemove(ode);
    QueueDel(&ode->next_mass, TRUE);
    QueueDel(&ode->next_spring, TRUE);
    ODEDel(ode);
    DocClear;
}

U0 SongTask(I64)
{
    Fs->task_end_cb = &SoundTaskEndCB;
    MusicSettingsReset;
    while (TRUE)
    {
        Play("5sDCDC4qA5DetDFFeDG4etA5EF4qG5eFC");
        Play("5sDCDC4qA5DetDFFeDG4etA5EF4qG5eFC");
        Play("5DCsG4A5G4AqBeBA5qEE4B5eC4B");
        Play("5DCsG4A5G4AqBeBA5qEE4B5eC4B");
    }
}

U0 Squirt()
{
    I64 sc;

    SettingsPush; //See SettingsPush
    Fs->text_attr = YELLOW << 4 + BLUE;
    Fs->song_task = Spawn(&SongTask, NULL, "Song",, Fs);
    AutoComplete;
    WinBorder;
    WinMax;
    DocCursor;

    MenuPush(   "File {"
                "  Abort(,CH_SHIFT_ESC);"
                "  Exit(,CH_ESC);"
                "}"
                "Play {"
                "  Restart(,'\n');"
                "  Left(,,SC_CURSOR_LEFT);"
                "  Right(,,SC_CURSOR_RIGHT);"
                "  Up(,,SC_CURSOR_UP);"
                "  Down(,,SC_CURSOR_DOWN);"
                "}"
                );

    Init;
    Fs->animate_task = Spawn(&AnimateTask, NULL, "Animate",, Fs);
    Fs->draw_it      = &DrawIt;

    try
    {
        while (TRUE)
        {
            switch (KeyGet(&sc))
            {
                case 0:
                    switch (sc.u8[0])
                    {
                        case SC_CURSOR_LEFT:
                            Spawn(&MoveNozzleTaskX, -1, "Move Nozzle",, Fs);
                            break;

                        case SC_CURSOR_RIGHT:
                            Spawn(&MoveNozzleTaskX, 1, "Move Nozzle",, Fs);
                            break;

                        case SC_CURSOR_UP:
                            Spawn(&MoveNozzleTaskY, -1, "Move Nozzle",, Fs);
                            break;

                        case SC_CURSOR_DOWN:
                            Spawn(&MoveNozzleTaskY, 1, "Move Nozzle",, Fs);
                            break;
                    }
                    break;

                case '\n':
                    CleanUp;
                    Init;
                    break;

                case CH_SHIFT_ESC:
                case CH_ESC:
                    goto sq_done;
            }
        }
sq_done: //Don't goto out of try
    }
    catch
        PutExcept;
    SettingsPop;
    CleanUp;
    MenuPop;
}

Squirt;