#include "stdafx.h" #include "token.h" #include "moon.h" #include "eye.h" #include "cosmos.h" #include "ringforce.h" using namespace std; Cosmos::Cosmos() { _eye = new Eye(); Clear(); } Cosmos::~Cosmos() { delete _eye; _eye = NULL; } void Cosmos::Clear() { for (int iMoon = _moon.size(); iMoon--; ) { delete _moon[iMoon]; } _moon.clear(); _displayMoon.clear(); _firstMass = 0; _backgroundColor = 0x000000; _work = 20; _increment = 1.0; _inc = _increment / _work; _sleep = 50; _trail = false; _eye->Init(Point(), 0, 0, 0, 100.0); _eye->SetWidth(0); _eye->SetHeight(0); _points = 10; _stepFunction = &Moon::Step10; _energy = false; _stop = false; _autocenter = true; _length = 0.0; _scaleMass = 1.0; _follow = -1; _followMoon = NULL; _noPerspective = false; _t = 0.0; _stopped = false; } // already parsed "", then params, then finally "". void Cosmos::ParseApplet(Tokenizer* t) { Clear(); // finish parsing Skip(">")) { string tag = t->Text(); if (t->Skip("width")) { t->Expect("="); int width; if (1 != sscanf_s(t->Text(), "%d", &width)) { t->Throw("expected a number for applet width"); } _eye->SetWidth(width); } else if (t->Skip("height")) { t->Expect("="); int height; if (1 != sscanf_s(t->Text(), "%d", &height)) { t->Throw("expected a number for applet height"); } _eye->SetHeight(height); } else { t->Next(); t->Expect("="); } if (!t->Next()) { t->Throw("did not close \" map nameValue; for (;;) { if (t->Skip("<")) { if (t->Skip("/")) { t->Expect("applet"); t->Expect(">"); // successfully parsed the whole applet, this routine is done break; } else if (t->Skip("param")) { t->Expect("name"); t->Expect("="); string name = t->Text(); for (u4 i = 0; i < name.length(); ++i) { name[i] = tolower(name[i]); } t->Next(); t->Expect("value"); t->Expect("="); string value = t->Text(); t->Next(); t->Expect(">"); nameValue[name] = value; } else { ASSERT(t->Next()); } } else { ASSERT(t->Next()); } } // for every possible config value, see if it was given map::iterator iter; iter = nameValue.find("increment"); if (iter != nameValue.end()) { ASSERT(sscanf_s(iter->second.c_str(), "%lg", &_increment) == 1); } iter = nameValue.find("work"); if (iter != nameValue.end()) { ASSERT(sscanf_s(iter->second.c_str(), "%d", &_work) == 1); } // the background color is always quoted, so we do not have to worry about it having been split into many tokens iter = nameValue.find("background"); if (iter != nameValue.end()) { ASSERT(sscanf_s(iter->second.c_str(), "%x", &_backgroundColor) == 1); _backgroundColor ^= ((_backgroundColor & 0xff) << 16); _backgroundColor ^= (_backgroundColor >> 16); _backgroundColor ^= ((_backgroundColor & 0xff) << 16); } iter = nameValue.find("sleep"); if (iter != nameValue.end()) { ASSERT(sscanf_s(iter->second.c_str(), "%d", &_sleep) == 1); } iter = nameValue.find("trail"); if (iter != nameValue.end()) { _trail = ((iter->second[0] == 'y') || (iter->second[0] == 't')); } iter = nameValue.find("energy"); if (iter != nameValue.end()) { _energy = ((iter->second[0] == 'y') || (iter->second[0] == 't')); } iter = nameValue.find("stop"); if (iter != nameValue.end()) { _stop = ((iter->second[0] == 'y') || (iter->second[0] == 't')); } iter = nameValue.find("length"); if (iter != nameValue.end()) { ASSERT(sscanf_s(iter->second.c_str(), "%lg", &_length) == 1); } iter = nameValue.find("autocenter"); if (iter != nameValue.end()) { _autocenter = ((iter->second[0] == 'y') || (iter->second[0] == 't')); } iter = nameValue.find("points"); if (iter != nameValue.end()) { ASSERT(sscanf_s(iter->second.c_str(), "%d", &_points) == 1); switch (_points) { case 3: _stepFunction = &Moon::Leapfrog; break; case 4: _stepFunction = &Moon::Step4; break; case 5: _stepFunction = &Moon::Step5; break; case 6: _stepFunction = &Moon::Step6; break; case 7: _stepFunction = &Moon::Step7; break; case 8: _stepFunction = &Moon::Step8; break; case 9: _stepFunction = &Moon::Step9; break; case 10: _stepFunction = &Moon::Step10; break; case 11: _stepFunction = &Moon::Step11; break; case 12: _stepFunction = &Moon::Step12; break; case 13: _stepFunction = &Moon::Step13; break; case 14: _stepFunction = &Moon::Step14; break; default: ASSERT(false, "\"points\" must be between 3 and 14"); } } iter = nameValue.find("scalemass"); if (iter != nameValue.end()) { ASSERT(sscanf_s(iter->second.c_str(), "%lg", &_scaleMass) == 1); } iter = nameValue.find("ring"); if (iter != nameValue.end()) { _ring = ((iter->second[0] == 'y') || (iter->second[0] == 't')); } iter = nameValue.find("follow"); if (iter != nameValue.end()) { ASSERT(sscanf_s(iter->second.c_str(), "%d", &_follow) == 1); } iter = nameValue.find("noperspective"); if (iter != nameValue.end()) { _noPerspective = ((iter->second[0] == 'y') || (iter->second[0] == 't')); } iter = nameValue.find("eye"); if (iter != nameValue.end()) { _eye->ReadConfig(iter->second.c_str(), _ring); } int moons; iter = nameValue.find("moons"); if (iter != nameValue.end()) { ASSERT(sscanf_s(iter->second.c_str(), "%d", &moons) == 1); } ASSERT(_moon.size() == 0); _moon.reserve(moons); _displayMoon.reserve(moons); char mooni[] = "moon123456789"; for (int i = 0; i <= moons; ++i) { sprintf_s(mooni, "moon%d", i); iter = nameValue.find(mooni); if (iter != nameValue.end()) { Moon *moon = new Moon(); moon->_id = i; moon->ReadConfig(iter->second.c_str()); _moon.push_back(moon); _displayMoon.push_back(moon); if (i == _follow) { _followMoon = moon; } } } _inc = _increment / _work; // adjust mass and velocity to pre-account for _inc for (u4 iMoon = 0; iMoon < _moon.size(); ++iMoon) { Moon *moon = _moon[iMoon]; moon->_m *= (_scaleMass * _inc * _inc); moon->_v *= _inc; } // sort the moons from least mass to most mass for (u4 i = _moon.size(); --i > 0; ) { for (u4 j = i + 1; --j > 0; ) { if (_moon[j]->_m < _moon[j - 1]->_m) { Moon *moon = _moon[j]; _moon[j] = _moon[j - 1]; _moon[j - 1] = moon; } } } // remember which is the first moon with mass for (_firstMass = 0; _firstMass < _moon.size() && _moon[_firstMass]->_m == 0.0; ++_firstMass) ; if (_autocenter) { // for moons, set the central position and total momentum to zero double mass = 0.0; Point zeroV; Point zeroP; for (u4 iMoon = 0; iMoon < _moon.size(); ++iMoon) { Point x; Moon* moon = _moon[iMoon]; x = moon->_v; x *= moon->_m; zeroV += x; x = moon->_p; x *= moon->_m; zeroP += x; mass += moon->_m; } zeroV *= 1.0 / mass; zeroP *= 1.0 / mass; if (_ring) { zeroV._x = 0; zeroV._z = 0; zeroP._x = 0; zeroP._z = 0; } for (u4 iMoon = 0; iMoon < _moon.size(); ++iMoon) { Moon* moon = _moon[iMoon]; moon->_v -= zeroV; moon->_p -= zeroP; } } for (u4 iMoon = 0; iMoon < _moon.size(); ++iMoon) { Moon* moon = _moon[iMoon]; moon->_points = _points; moon->InitArrays(); } printf("successfully parsed applet\n"); } void Cosmos::Load(const char* path) { Point::UnitTest(); Moon::UnitTest(); Clear(); Tokenizer t; ASSERT(t.Init(path), "could not initialize from %s", path); // if you find the start of an applet, then call ParseApplet, otherwise skip everything for (;;) { if (t.Skip("<") && t.Skip("applet")) { ParseApplet(&t); } else if (!t.Next()) { break; } } } // O(mn), find accelerations by attracting every moon to every other moon // n is number of moons, m is number of moons with mass void Cosmos::MoonAcceleration() { int nMoons = _moon.size(); // start with no acceleration for (int iMoon = 0; iMoon < nMoons; iMoon++) _moon[iMoon]->ZeroAcceleration(); // moons without mass do not interact with each other for (u4 iMoon = 0; iMoon < _firstMass; iMoon++) { Moon* moon = _moon[iMoon]; for (int iOther = _firstMass; iOther < nMoons; iOther++) { moon->Attract(_moon[iOther]); } } // moons with mass do attract each other, equally for (int iMoon = _firstMass; iMoon < nMoons; iMoon++) { Moon* moon = _moon[iMoon]; for (int iOther = iMoon + 1; iOther < nMoons; iOther++) { moon->Attract(_moon[iOther]); } } } // unlike points, the attraction of rings is not antisymmetric void Cosmos::RingAcceleration() { u4 nMoons = _moon.size(); // start with no acceleration for (u4 iMoon = 0; iMoon < nMoons; iMoon++) _moon[iMoon]->ZeroAcceleration(); // rings with mass attract each other, but the attraction is not antisymmetric, you have to calculate it both ways for (u4 iMoon = 0; iMoon < nMoons; iMoon++) { Moon* moon = _moon[iMoon]; for (u4 iRing = _firstMass; iRing < nMoons; iRing++) { Moon* ring = _moon[iRing]; moon->_a.PlusCP(ring->_m, RingAttraction(ring->_p, moon->_p)); } } // fixed rings do not get accelerated for (u4 iMoon = 0; iMoon < nMoons; iMoon++) { Moon* moon = _moon[iMoon]; if (moon->_fixed) { moon->_a._x = 0; moon->_a._z = 0; } } } void Cosmos::MeasureAccelerations() { if (_ring) { RingAcceleration(); } else { MoonAcceleration(); } } void Cosmos::MoveToNewPositions() { if (_ring) { RingAcceleration(); MoveRings(); } else { MoonAcceleration(); MoveMoons(); } } void Cosmos::MoveMoons() { if (_followMoon) { _eye->Center(_followMoon->_p); } for (u4 i = 0; i < _moon.size(); ++i) { Moon* moon = _moon[i]; moon->RecordStep(); moon->_peye = _eye->Map(moon->_p); } // sort displayMoon by distance for (u4 i = 1; i < _displayMoon.size(); ++i) { for (u4 j = i + 1; --j > 0 && _displayMoon[j]->_peye._z > _displayMoon[j - 1]->_peye._z; ) { Moon *moon = _displayMoon[j]; _displayMoon[j] = _displayMoon[j - 1]; _displayMoon[j - 1] = moon; } } ++_counter; } void Cosmos::MoveRings() { if (_followMoon) { _eye->Center(_followMoon->_p); } for (u4 i = 0; i < _moon.size(); ++i) { Moon* moon = _moon[i]; moon->RecordStep(); moon->_peye = _eye->MapRing(moon->_p); } // no need to sort rings by distance, we always display all rings ++_counter; } void Cosmos::Multistep() { for (int iMoon = _moon.size(); iMoon--; ) { (_moon[iMoon]->*_stepFunction)(); } MoveToNewPositions(); } bool Cosmos::Move() { if (_length > 0 && _t > _length) { // simulation has completed return false; } for (int i = 0; i<_work; ++i) { _t += _inc; if (_length <= 0 || _t < _length) { Multistep(); } } return true; } // prepare to run: generate the history that the step function needs // Suppose iter=0 and _points=3 (using leapfrog). Let P, V, A be reverse direction and p, v, a be forward. // The goal is to start at the start, but have a history going back before the start: // v_0 = v_given - (1/2)a_0 (v_0 for leapfrog is really p_0 - p_-1, thus the -(1/2)a_0) // p_0 = p_-1 + v_0 = p_given // To do that, run leapfrog in reverse, starting with // A_0 = a_0 = (derived from p_given) // V_0 = -v_given - (1/2)a_0 // P_0 = p_0 = p_given // and run leapfrog once // V_1 = V_0 + A_0 = (-v_given - (1/2)a_0) + a_0 = -v_given + (1/2)a_0 // P_1 = P_0 + V_0 // then repair the positions so we go forward in time again // a_0 = A_0 // v_0 = -V_1 = v_given - (1/2)a_0 // p_0 = p_given void Cosmos::Prepare() { double big = 1.0; int iters = 0; for (int iIter = 0; iIter < iters; ++iIter) { big *= 2.0; } // reduce timestep 2^^iters times, and reverse time by reversing velocity for (u4 iMoon = 0; iMoon < _moon.size(); ++iMoon) { Moon *moon = _moon[iMoon]; moon->_m /= (big*big); moon->_v *= -1.0 / (big); } MeasureAccelerations(); // adjust velocity to really represent (position - prevPosition), according to Leapfrog for (u4 iMoon = 0; iMoon < _moon.size(); ++iMoon) { Moon *moon = _moon[iMoon]; Point a = moon->_a; a *= -0.5; moon->_v += a; } MoveToNewPositions(); // Take _points-2 steps backwards with the inaccurate Leapfrog method, so _points-1 points total now for (int iStep = 2; iStep < _points; ++iStep) { for (u4 iMoon = 0; iMoon < _moon.size(); ++iMoon) { _moon[iMoon]->Leapfrog(); } MoveToNewPositions(); } // Use the accurate multistep method to produce _points-1 more points, for 2*_points-2 total points, then take every other point, // and double the timestep. Do that iter times to scale up to the correct time increment. for (int iIter = 0; iIter < iters; ++iIter) { for (int iStep = 1; iStep < _points; ++iStep) { Multistep(); } for (u4 iMoon = 0; iMoon < _moon.size(); ++iMoon) { Moon* moon = _moon[iMoon]; int head = moon->_head; for (int iHist = 0; iHist < _points-1; ++iHist) { int iNew = head - iHist; int iOld = head - iHist - iHist; *moon->_ov[iNew] = *moon->_ov[iOld]; *moon->_ov[iNew] += *moon->_ov[iOld - 1]; *moon->_oa[iNew] = *moon->_oa[iOld]; *moon->_oa[iNew] *= 4.0; } moon->_m *= 4.0; moon->_v = *moon->_ov[head]; moon->_a = *moon->_oa[head]; } } // Now reverse time, leaving us at the original starting point with _points-1 of history and the correct increment size for (u4 iMoon = 0; iMoon < _moon.size(); ++iMoon) { Moon* moon = _moon[iMoon]; int head = moon->_head; for (int iHist = 0; iHist < (_points-2) / 2; ++iHist) { Point x; x = *moon->_ov[head - iHist]; *moon->_ov[head - iHist] = *moon->_ov[head - _points + 3 + iHist]; *moon->_ov[head - _points + 3 + iHist] = x; } for (int iHist = 0; iHist < (_points-1) / 2; ++iHist) { Point x; x = *moon->_oa[head - iHist]; *moon->_oa[head - iHist] = *moon->_oa[head - _points + 2 + iHist]; *moon->_oa[head - _points + 2 + iHist] = x; } // adjust the position, and reverse the velocities for (int iHist = 0; iHist < _points-2; ++iHist) { *moon->_ov[head - iHist] *= -1.0; moon->_p += *moon->_ov[head - iHist]; } moon->_v = *moon->_ov[head]; moon->_a = *moon->_oa[head]; if (_ring) { moon->_peye = _eye->MapRing(moon->_p); } else { moon->_peye = _eye->Map(moon->_p); } } } void Cosmos::RotateRightAndDown(int right, int down) { double bigger = sqrt(sqrt(right*right + down*down)); Eye* eye = GetEye(); eye->RotateRight(right * bigger / eye->Width()); eye->RotateDown(down * bigger / eye->Width()); } void Cosmos::ShiftRightAndDown(int right, int down) { Eye* eye = GetEye(); eye->ShiftRight(right); eye->ShiftDown(down); } void Cosmos::RightAndDown(int right, int down) { if (_ring) { ShiftRightAndDown(right, down); } else { RotateRightAndDown(right, down); } } void Cosmos::ClearDisplay(HDC& memoryDC) { SelectObject(memoryDC, GetStockObject(DC_BRUSH)); SetDCBrushColor(memoryDC, _backgroundColor); Rectangle(memoryDC, 0, 0, _eye->Width(), _eye->Height()); } // draw the current state of the cosmos void Cosmos::Display(HDC& memoryDC) { static int iter = 0; if (!_trail || iter == 0) { ClearDisplay(memoryDC); } for (u4 iMoon = 0; iMoon < _displayMoon.size(); ++iMoon) { Moon* moon = _displayMoon[iMoon]; Point& peye = moon->_peye; if (peye._z > 0.0) { int x = _eye->MapX(peye); int y = _eye->MapY(peye); double radius = _eye->MapRadius(moon->_size, peye); if (radius < 0.5) { SetPixel(memoryDC, x, y, moon->_color); } else if (radius < 1.0) { SetPixel(memoryDC, x, y, moon->_color); SetPixel(memoryDC, x, y + 1, moon->_color); } else { int radiusFloor = (int)radius; int radiusCeil = (int)ceil(radius); SelectObject(memoryDC, GetStockObject(DC_BRUSH)); SetDCBrushColor(memoryDC, moon->_color); SelectObject(memoryDC, GetStockObject(DC_PEN)); SetDCPenColor(memoryDC, moon->_color); Ellipse(memoryDC, x - radiusFloor, y - radiusFloor, x + radiusCeil, y + radiusCeil); } } } ++iter; }