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