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 reg 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;
}