F64 SpanTime()
{
    if (run_bttn.state)
        return a.elapsed_t + tS - a.start_wall_t;
    else
        return a.elapsed_t;
}

F64 Cost(CMathODE *ode)
{
    MyMass   *tmpm;
    MySpring *tmps;
    F64       res = 0;

    tmpm=ode->next_mass;
    while (tmpm != &ode->next_mass)
    {
        res += tmpm->cost;
        tmpm = tmpm->next;
    }
    tmps = ode->next_spring;
    while (tmps != &ode->next_spring)
    {
        res += tmps->cost;
        tmps = tmps->next;
    }

    return res;
}

U0 DrawIt(CTask *task, CDC *dc)
{
    MyMass   *tmpm;
    MySpring *tmps;

    tmps = ode->next_spring;
    while (tmps != &ode->next_spring)
    {
        if (!(tmps->flags & SSF_INACTIVE))
        {
            dc->color = tmps->color;
            dc->thick = tmps->thick;
            GrLine3(dc, tmps->end1->x, tmps->end1->y, 0, tmps->end2->x, tmps->end2->y, 0);
        }
        tmps = tmps->next;
    }

    if (cursor_mass)
    {
        dc->color = RED;
        dc->thick = 2;
        GrLine3(dc, mouse.pos.x - task->pix_left - task->scroll_x, 
                    mouse.pos.y - task->pix_top  - task->scroll_y, 0, 
                    cursor_mass->x, cursor_mass->y, 0);
    }

    tmpm=ode->next_mass;
    while (tmpm != &ode->next_mass)
    {
        if (!(tmpm->flags&MSF_INACTIVE))
        {
            dc->color = BLACK;
            GrCircle(dc, tmpm->x, tmpm->y, tmpm->radius);
            GrFloodFill(dc, tmpm->x, tmpm->y, TRUE);
            dc->color = tmpm->color;
            GrCircle(dc, tmpm->x, tmpm->y, tmpm->radius);
            GrFloodFill(dc, tmpm->x, tmpm->y, TRUE);
            dc->color = BLACK;
            GrCircle(dc, tmpm->x, tmpm->y, tmpm->radius);
        }
        tmpm = tmpm->next;
    }

    dc->color = BLACK;
    GrPrint(dc, 90, 0, "Cost:%12.2,f", Cost(ode));
    GrPrint(dc, 90, FONT_HEIGHT, "Time:%12.2f", SpanTime);
}

MyMass *PlaceMass(I64 x,  I64 y)
{
    MyMass *tmpm = CAlloc(sizeof(MyMass));

    tmpm->drag_profile_factor   = 1.0;
    tmpm->x                     = x;
    tmpm->y                     = y;
    tmpm->mass                  = MASS_MASS;
    tmpm->radius                = MASS_RADIUS;
    tmpm->cost                  = 25.0 * COST_SCALE;
    tmpm->color                 = YELLOW;
    QueueInsert(tmpm, ode->last_mass);

    return tmpm;
}

U0 NullSpring(MySpring *tmps, F64 scale)
{
    F64 d = D3Dist(&tmps->end1->x, &tmps->end2->x);

    tmps->rest_len              = d * scale;
    tmps->compression_strength  = tmps->base_compression_strength   / (tmps->rest_len + 1.0);
    tmps->tensile_strength      = tmps->base_tensile_strength       / (tmps->rest_len + 1.0);
    tmps->const                 = tmps->base_const                  / (tmps->rest_len + 1.0);
    tmps->cost                  = tmps->base_cost                   *  tmps->rest_len;
}

U0 MoveMass(MyMass *tmpm, I64 x,  I64 y)
{
    MySpring *tmps;

    tmpm->x     = x;
    tmpm->y     = y;
    tmpm->DxDt  = 0;
    tmpm->DyDt  = 0;
    tmps = ode->next_spring;
    while (tmps != &ode->next_spring)
    {
        if (tmps->end1 == tmpm || tmps->end2 == tmpm)
        {
            if (tmps->flags & SSF_NO_COMPRESSION)
                NullSpring(tmps, WIRE_PERCENT);
            else
                NullSpring(tmps, 1.0);
        }
        tmps = tmps->next;
    }
}

U0 DelSpring(MySpring *tmps)
{
    QueueRemove(tmps);
    Free(tmps);
}

U0 DelMass(MyMass *tmpm)
{
    MySpring *tmps, *tmps1;

    tmps = ode->next_spring;
    while (tmps != &ode->next_spring)
    {
        tmps1 = tmps->next;
        if (tmps->end1 == tmpm || tmps->end2 == tmpm)
            DelSpring(tmps);
        tmps = tmps1;
    }
    QueueRemove(tmpm);
    Free(tmpm);
}

U0 DrawSpring(CDC *dc, MyMass *tmpm, I64 x, I64 y)
{
    switch (mode_bttn.state)
    {
        case MD_CONCRETE:
            dc->color = LTGRAY;
            dc->thick = 2;
            break;

        case MD_STEEL:
            dc->color = DKGRAY;
            dc->thick = 2;
            break;

        case MD_WIRE:
            dc->color = RED;
            dc->thick = 1;
            break;
    }
    GrLine3(dc, tmpm->x, tmpm->y, 0, x, y, 0);
}

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

    tmps->end1 = tmpm1;
    tmps->end2 = tmpm2;
    switch (mode_bttn.state)
    {
        case MD_CONCRETE:
            tmps->base_const                = 3.00  * SPRING_SCALE;
            tmps->base_compression_strength = 10.00 * STRENGTH_SCALE;
            tmps->base_tensile_strength     = 0.35  * STRENGTH_SCALE;
            tmps->base_cost                 = 0.30  * COST_SCALE;
            NullSpring(tmps, 1.0);
            tmps->color                     = LTGRAY;
            tmps->thick                     = 2;
            break;

        case MD_STEEL:
            tmps->base_const                = 1.00 * SPRING_SCALE;
            tmps->base_compression_strength = 1.00 * STRENGTH_SCALE;
            tmps->base_tensile_strength     = 1.00 * STRENGTH_SCALE;
            tmps->base_cost                 = 1.00 * COST_SCALE;
            NullSpring(tmps, 1.0);
            tmps->color                     = DKGRAY;
            tmps->thick                     = 2;
            break;

        case MD_WIRE:
            tmps->base_const                = 0.25 * SPRING_SCALE;
            tmps->base_compression_strength = 0.00;
            tmps->base_tensile_strength     = 0.50 * STRENGTH_SCALE;
            tmps->base_cost                 = 0.10 * COST_SCALE;
            NullSpring(tmps, WIRE_PERCENT);
            tmps->color                     = RED;
            tmps->thick                     = 1;
            tmps->flags |= SSF_NO_COMPRESSION;
            break;
    }
    QueueInsert(tmps, ode->last_spring);
}

U0 AnimateTask(SpanAnimateStruct *a)
{
    MySpring    *tmps, *tmps1;
    Bool         old_run = FALSE;
    F64          f;

    while (TRUE)
    {
        tmps = ode->next_spring;
        while (tmps != &ode->next_spring)
        {
            tmps1 = tmps->next;
            f = tmps->f;
            if (    f > 0 && f > tmps->compression_strength && !(tmps->flags & SSF_NO_COMPRESSION) ||
                    f < 0 && -f>tmps->tensile_strength && !(tmps->flags & SSF_NO_TENSION))
                tmps->flags |= SSF_INACTIVE;
            tmps = tmps1;
        }
        AdjustLoads(ode);
        Refresh; //CMathODE updated once per refresh.
        if (old_run != run_bttn.state)
        {
            if (run_bttn.state)
            {
                if (!a->elapsed_t || !a->saved_ode)
                {
                    Free(a->saved_ode);
                    a->saved_ode = SpanSave(ode);
                }
                a->start_wall_t = tS;
                ODEPause(ode, OFF);
            }
            else
            {
                ODEPause(ode);
                a->elapsed_t += tS - a->start_wall_t;
            }
            old_run = run_bttn.state;
        }
    }
}

U0 Init(SpanAnimateStruct *a)
{
    SpanDel(ode);
    ode = SpanNew;

    run_bttn.state = 0;
    Refresh(2); //Allow stop to be registered in animate task.

    if (a->saved_ode)
        SpanLoad(ode, a->saved_ode);
    else
        SpanBridge1Init(ode);
    a->elapsed_t = 0;
    cursor_mass = NULL;
}

U0 SongTask(I64)
{//Song by Terry A. Davis
    Fs->task_end_cb = &SoundTaskEndCB;
    MusicSettingsReset;
    music.tempo             = 3.636;
    music.stacatto_factor   = 0.902;
    while (TRUE)
    {
        Play("5q.EeDqED4G5DhE");
        Play("5q.EeDqED4G5DhE");
        Play("5q.FeEFEqF4G5EhF");
        Play("5q.FeEFEqF4G5EhF");
    }
}

U0 Span()
{
    I64          message_code, arg1, arg2;
    MyMass      *tmpm1 = NULL, *tmpm2 = NULL;
    MySpring    *tmps;
    CCtrl       *bt_run, *bt_mode;
    U8          *src;
    CDC         *dc = DCAlias;

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

    bt_run =CtrlBttnNew(0, 0, 80,, 2, "Stopped\0Running\0", run_colors, &run_bttn);
    bt_mode=CtrlBttnNew(0, 3.0 * FONT_HEIGHT, 80,, MD_MODES_NUM, Define("ST_SPAN_MODES"), mode_colors, &mode_bttn);
    a.saved_ode = NULL;

    Fs->win_inhibit |= WIG_DBL_CLICK;

    MenuPush(   "File {"
                "  New(,CH_CTRLN);"
                "  Open(,CH_CTRLO);"
                "  SaveAs(,CH_CTRLA);"
                "  Abort(,CH_SHIFT_ESC);"
                "  Exit(,CH_ESC);"
                "}"
                "Play {"
                "  Restart(,'\n');"
                "  RunStop(,CH_SPACE);"
                "  Mass(,'m');"
                "  Concrete(,'c');"
                "  Steel(,'s');"
                "  Wire(,'w');"
                "  Move(,'v');"
                "  Delete(,'d');"
                "}"
                );

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

    PopUpOk(    "Build a bridge to hold-up the\n"
                "red masses.  Test your design\n"
                "by pressing run/stop.\n\n"
                "The lowest cost bridge that\n"
                "stays standing wins.\n\n"
                "For a variation, try without\n"
                "using the center base point.\n"
                "\n"
                "Use\n"
                "\t$GREEN$'m'$FG$ass\n"
                "\t$GREEN$'c'$FG$oncrete\n"
                "\t$GREEN$'s'$FG$teel\n"
                "\t$GREEN$'w'$FG$ire\n"
                "\nto sel materials.\n");

    try
    {
        while (TRUE)
        {
            message_code = MessageGet(&arg1, &arg2, 1 << MESSAGE_MS_L_DOWN | 1 << MESSAGE_MS_R_DOWN |
                                                    1 << MESSAGE_MS_L_UP | 1 << MESSAGE_KEY_DOWN | 1 << MESSAGE_MS_MOVE);
            DCFill(dc);
            switch (message_code)
            {
                case MESSAGE_MS_L_DOWN:
                    cursor_mass = tmpm1 = tmpm2 = NULL;
                    switch (mode_bttn.state)
                    {
                        case MD_MASS:
                            PlaceMass(arg1, arg2);
                            break;

                        case MD_CONCRETE:
                        case MD_STEEL:
                        case MD_WIRE:
                            tmpm1 = MassFind(ode, arg1, arg2);
                            break;

                        case MD_MOVE:
                            if (run_bttn.state)
                                cursor_mass = MassFind(ode, arg1, arg2);
                            else
                                if (tmpm1 = MassFind(ode, arg1, arg2))
                                    MoveMass(tmpm1, arg1, arg2);
                            break;

                        case MD_DELETE:
                            MassOrSpringFind(ode, &tmpm1, &tmps, arg1, arg2);
                            if (tmpm1)
                                DelMass(tmpm1);
                            if (tmps)
                                DelSpring(tmps);
                            break;
                    }
                    break;

                case MESSAGE_MS_L_UP:
                    switch (mode_bttn.state)
                    {
                        case MD_CONCRETE:
                        case MD_STEEL:
                        case MD_WIRE:
                            if (tmpm1 && (tmpm2 = MassFind(ode, arg1, arg2)) && tmpm1 != tmpm2)
                                PlaceSpring(tmpm1, tmpm2);
                            break;

                        case MD_MOVE:
                            if (!run_bttn.state && tmpm1)
                                MoveMass(tmpm1, arg1, arg2);
                            break;
                    }
                    cursor_mass = tmpm1 = tmpm2 = NULL;
                    break;

                case MESSAGE_MS_MOVE:
                    switch (mode_bttn.state)
                    {
                        case MD_MOVE:
                            if (!run_bttn.state && tmpm1)
                                MoveMass(tmpm1, arg1, arg2);
                            break;

                        case MD_CONCRETE:
                        case MD_STEEL:
                        case MD_WIRE:
                            if (tmpm1)
                            {
                                DrawSpring(dc, tmpm1, arg1, arg2);
                            }
                            break;
                    }
                    break;

                case MESSAGE_MS_R_DOWN:
                    mode_bttn.state++;
                    if (mode_bttn.state >= MD_MODES_NUM)
                        mode_bttn.state = 0;
                    cursor_mass = tmpm1 = tmpm2 = NULL;
                    break;

                case MESSAGE_KEY_DOWN:
                    switch (arg1)
                    {
                        case '\n':
                            if (!SpanTime || !a.saved_ode)
                            {
                                Free(a.saved_ode);
                                a.saved_ode = SpanSave(ode);
                            }
                            Init(&a);
                            break;

                        case CH_CTRLN:
                            Free(a.saved_ode);
                            a.saved_ode = NULL;
                            Init(&a);
                            break;

                        case CH_CTRLO:
                            if (src = SpanRead)
                            {
                                Free(a.saved_ode);
                                a.saved_ode = src;
                                Init(&a);
                            }
                            break;

                        case CH_CTRLA:
                            if (!SpanTime || !a.saved_ode)
                            {
                                Free(a.saved_ode);
                                a.saved_ode = SpanSave(ode);
                            }
                            Init(&a);
                            SpanWrite(ode);
                            break;

                        case CH_SPACE:
                            run_bttn.state = !run_bttn.state;
                            break;

                        case 'c':
                            mode_bttn.state = MD_CONCRETE;
                            break;

                        case 's':
                            mode_bttn.state = MD_STEEL;
                            break;

                        case 'w':
                            mode_bttn.state = MD_WIRE;
                            break;

                        case 'm':
                            mode_bttn.state = MD_MASS;
                            break;

                        case 'v':
                            mode_bttn.state = MD_MOVE;
                            break;

                        case 'd':
                            mode_bttn.state = MD_DELETE;
                            break;

                        case CH_ESC:
                            if (!SpanTime || !a.saved_ode)
                            {
                                Free(a.saved_ode);
                                a.saved_ode = SpanSave(ode);
                            }
                            Init(&a);
                            SpanWrite(ode);
                        case CH_SHIFT_ESC:
                            goto span_done;
                    }
                    break;
            }
        }
span_done: //Don't goto out of try
        MessageGet(,, 1 << MESSAGE_KEY_UP);
    }
    catch
        PutExcept;
    DocClear;
    SettingsPop;
    CtrlBttnDel(bt_run);
    CtrlBttnDel(bt_mode);
    SpanDel(ode);
    DCFill(dc);
    DCDel(dc);
    MenuPop;
}