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