17 } |
20 } |
18 |
21 |
19 void PhysicsWorld::tick () { |
22 void PhysicsWorld::tick () { |
20 // Engine::log(DEBUG, "physics.apply_force") << "*tick*"; |
23 // Engine::log(DEBUG, "physics.apply_force") << "*tick*"; |
21 |
24 |
22 for (std::vector<PhysicsObject*>::iterator i = objects.begin(); i != objects.end(); i++) { |
25 for (std::vector<PhysicsObject*>::iterator i = objects.begin(); i != objects.end(); i++) { |
23 (*i)->tick(); |
26 (*i)->tick(); |
24 } |
27 } |
25 } |
28 } |
26 |
29 |
27 PhysicsObject::PhysicsObject (PhysicsWorld &world, float mass, Vector position, Vector velocity) |
30 PhysicsObject::PhysicsObject (PhysicsWorld &world, float mass, Vector position, Vector velocity) |
28 : world(world), mass(mass), position(position), velocity(velocity) { |
31 : world(world), mass(mass), position(position), velocity(velocity) { |
29 |
32 |
30 world.addObject(this); |
33 world.addObject(this); |
31 } |
34 } |
32 |
35 |
|
36 /** |
|
37 * Updates object speed and position. This function organises force |
|
38 * integration and collision detection. |
|
39 */ |
33 void PhysicsObject::updatePosition () { |
40 void PhysicsObject::updatePosition () { |
34 |
41 // Add gravity to the force queue |
35 // Check if the player is moving on the ground |
42 forceq.push(world.gravity); |
36 /*if (this->velocity.y == 0 && (position.y >= world.dimensions.y - 3)) { |
43 |
37 position.x += 50 * velocity.x * (PHYSICS_TICK_MS / 1000.0); |
44 // Go trough every force in the queue |
38 velocity.x = 0; |
45 // TODO: It might be possible to optimize by adding forces together |
39 return; |
46 Force total; |
40 }*/ |
47 posAfterTick = position; |
41 |
48 velAfterTick = velocity; |
42 // If not moving on the ground, apply normal physics |
49 while (!forceq.empty()) { |
43 |
50 total += forceq.front(); |
44 // Calculate gravity's influence on the velocity vector |
51 forceq.pop(); |
|
52 // Engine::log(DEBUG, "PhysicsObject.updatePosition") << "Current position: " << posAfterTick; |
|
53 } |
|
54 integrate(total, PHYSICS_TICK_MS); |
|
55 |
|
56 Vector newPosition = posAfterTick /*+ (velAfterTick * PHYSICS_TICK_MS)/1000*/; |
|
57 this->velocity = velAfterTick; |
|
58 //Engine::log(DEBUG, "PhysicsObject.updatePosition") << "Nopeus: "<<this->velocity; |
|
59 /* |
45 this->velocity += world.gravity * (PHYSICS_TICK_MS / 1000.0); |
60 this->velocity += world.gravity * (PHYSICS_TICK_MS / 1000.0); |
46 |
61 |
47 Vector newPosition = position + velocity * (PHYSICS_TICK_MS / 1000.0); |
62 Vector newPosition = position + velocity * (PHYSICS_TICK_MS / 1000.0); |
|
63 */ |
48 |
64 |
49 //TODO Handle the object as a square or a polygon |
65 //TODO Handle the object as a square or a polygon |
50 |
|
51 // Engine::log(DEBUG, "physics.update_position") << "position=" << newPosition << ", velocity=" << velocity; |
|
52 |
66 |
53 bool collided = false; |
67 bool collided = false; |
54 |
68 |
55 if (newPosition.x < 0 || (newPosition.x > world.dimensions.x)) { |
69 //goes 1 unit forward every step and check if has hit anything |
56 // CRASH! |
70 Vector unitVector = (newPosition-position) / (newPosition-position).length(); |
57 this->velocity.x *= -0.5; |
71 |
58 |
72 Vector tmpVector = position; |
59 // If the velocity drops under some fixed constant we decide it is zero. |
73 Vector reached = position; |
60 // This is to prevent the object from bouncing eternally. |
74 int steps = (int) (newPosition-position).length(); |
61 if (abs(this->velocity.x) < 0.1) |
75 for(int i = 0; i < steps; i++) { |
62 this->velocity.x = 0; |
76 tmpVector += unitVector; |
63 |
77 if(world.getType(tmpVector) != EMPTY) { |
64 collided = true; |
78 //Engine::log(DEBUG, "physics.update_position") << "hit something"; |
|
79 // Then we have hit something |
|
80 reached = position + unitVector*(i-1); |
|
81 collided = true; |
|
82 break; |
|
83 } else { |
|
84 //Engine::log(DEBUG, "physics.update_position") << "didnt hit"; |
|
85 } |
|
86 } |
|
87 |
|
88 // In case of some float error |
|
89 if(!collided) { |
|
90 if(world.getType(newPosition)) { |
|
91 // There was error, and there is ground |
|
92 newPosition = tmpVector; |
|
93 } else { |
|
94 // This means everything was ok, so no need to do anything |
|
95 } |
65 } else { |
96 } else { |
66 this->position.x = newPosition.x; |
97 newPosition = reached; |
67 } |
98 this->velocity = Vector(0, 0); |
68 |
99 //TODO: it shouldn't just stop on collision |
69 if (newPosition.y <= 0 || (newPosition.y >= world.dimensions.y)) { |
100 } |
70 this->velocity.y *= -0.3; |
101 this->position = newPosition; |
71 |
102 |
72 |
103 } |
73 |
104 |
74 if (abs(this->velocity.y) < 0.1) { |
105 bool PhysicsWorld::collided (Vector oldPos, Vector newPos) { |
75 this->velocity.y = 0; |
106 int deltaX = oldPos.x - newPos.x; |
76 // Friction |
107 int deltaY = oldPos.y - newPos.y; |
77 this->velocity.x *= 0.95; |
108 double distance = sqrt(deltaX * deltaX + deltaY * deltaY); |
78 } else { |
109 double xInc = deltaX / distance; |
79 // Bigger friction |
110 double yInc = deltaY / distance; |
80 this->velocity.x *= 0.75; |
111 double currentX = oldPos.x; |
81 } |
112 double currentY = oldPos.y; |
82 |
113 |
83 collided = true; |
114 // This implementation is bit slow since it checks some squares twice. |
84 } else { |
115 for(unsigned int i = 1; i < distance; i++) { |
85 this->position.y = newPosition.y; |
116 currentX += xInc; |
86 } |
117 currentY += yInc; |
87 |
118 if(terrain[(int)currentX][(int)currentY] != EMPTY) |
88 if(!collided) { |
119 return true; |
89 this->position = newPosition; |
120 } |
90 } |
121 return false; |
91 } |
122 } |
92 |
123 |
93 void PhysicsObject::applyForce (Vector force, uint16_t dt) { |
124 /** |
94 Vector oldVelocity = velocity; |
125 * Integrates given force over time and stores new position to |
95 |
126 * posAfterTick and new velocity to velAfterTick. |
96 this->velocity += force * dt / 1000 / mass; // The last factor denotes the time. |
127 * @param force Force vector. |
97 // It should be scaled somehow. |
128 * @param dt The time the force is applied (<=PHYSICS_TICK_MS) |
98 |
129 */ |
99 // Engine::log(DEBUG, "physics.apply_force") << "force=" << force << ", velocity " << oldVelocity << " -> " << velocity; |
130 void PhysicsObject::integrate(Force force, TimeMS dt) { |
|
131 Derivative tmpd; |
|
132 Derivative k1 = evaluate(force, 0, tmpd); |
|
133 Derivative k2 = evaluate(force, 0.5f*dt, k1); |
|
134 Derivative k3 = evaluate(force, 0.5f*dt, k2); |
|
135 Derivative k4 = evaluate(force, dt, k3); |
|
136 |
|
137 |
|
138 const Vector dxdt = (k1.dx + (k2.dx + k3.dx) * 2.0f + k4.dx) * 1.0f/6.0f; |
|
139 const Vector dvdt = (k1.dv + (k2.dv + k3.dv) * 2.0f + k4.dv) * 1.0f/6.0f; |
|
140 |
|
141 // Engine::log(DEBUG, "PhysicsObject.integrate") << "Changes: "<< dxdt << " " << dvdt << " Time: " <<dt; |
|
142 posAfterTick = posAfterTick + (dxdt * dt)/1000; |
|
143 velAfterTick = velAfterTick + (dvdt * dt)/1000; |
|
144 //Engine::log(DEBUG, "PhysicsObject.integrate") << "velAfterTick: " << velAfterTick; |
|
145 } |
|
146 |
|
147 Derivative PhysicsObject::evaluate(Force force, TimeMS dt, Derivative &d) { |
|
148 Vector curPos = posAfterTick + (d.dx*dt)/1000; |
|
149 Vector curVel = velAfterTick + (d.dv*dt)/1000; |
|
150 |
|
151 Derivative out; |
|
152 out.dx = curVel; |
|
153 out.dv = acceleration(force); |
|
154 //Engine::log(DEBUG, "PhysicsObject.evaluate") << "Out.dx: " << out.dx; |
|
155 return out; |
|
156 } |
|
157 |
|
158 Vector PhysicsObject::acceleration(const Force &force) { |
|
159 return (force/mass); |
|
160 } |
|
161 |
|
162 /** |
|
163 * Adds force to the force queue. Force queue is emptied on each |
|
164 * tick. Forces that last over one tick are also handled. |
|
165 * @param force Force vector. |
|
166 * @param dt The time the force is applied. |
|
167 */ |
|
168 void PhysicsObject::applyForce (Force force, TimeMS dt) { |
|
169 // Add applied force to the queue |
|
170 forceq.push(force); |
100 } |
171 } |
101 |
172 |
102 void PhysicsObject::updatePhysics (Vector position, Vector velocity) { |
173 void PhysicsObject::updatePhysics (Vector position, Vector velocity) { |
103 this->position = position; |
174 this->position = position; |
104 this->velocity = velocity; |
175 this->velocity = velocity; |
110 |
181 |
111 void PhysicsObject::tick () { |
182 void PhysicsObject::tick () { |
112 this->updatePosition(); |
183 this->updatePosition(); |
113 } |
184 } |
114 |
185 |
|
186 /** |
|
187 * simple random map generation |
|
188 * first fills whole level with dirt |
|
189 * then randomizes circles of empty or rock |
|
190 * @param seed - seed number for random number generator |
|
191 */ |
|
192 void PhysicsWorld::generateTerrain(int seed) { |
|
193 // generating should use own random number generator, but didn't find easily how that is done |
|
194 srand(seed); |
|
195 |
|
196 // some constants to control random generation |
|
197 const int min_range = 10; |
|
198 const int max_range = 40; |
|
199 const int num = 1; |
|
200 const int rock_rarity = 4; // 1 / rock_rarity will be rock circle |
|
201 |
|
202 // loops for amount of circles |
|
203 for(int i = 0; i < num; i++) { |
|
204 // information of new circle |
|
205 int midx = rand()%(int)dimensions.x; |
|
206 int midy = rand()%(int)dimensions.y; |
|
207 int range = rand()%(max_range-min_range)+min_range; |
|
208 |
|
209 // put first circle in the middle of the cave |
|
210 // so that we have some area we can certainly spawn into |
|
211 if(i == 0) { |
|
212 midx = 60; |
|
213 midy = 60; |
|
214 range = 50; |
|
215 } |
|
216 |
|
217 TerrainType type = DIRT; |
|
218 if(rand()%rock_rarity == 0) { |
|
219 type = ROCK; |
|
220 } |
|
221 // loops for every pixel of circle |
|
222 for(int x = std::max(0, midx-range); x < std::min((int)dimensions.x, midx+range); x++) { |
|
223 for(int y = std::max(0, midy-range); y < std::min((int)dimensions.y, midy+range); y++) { |
|
224 if(x*x+y*y < range*range) { |
|
225 // and sets it to type |
|
226 terrain[x][y] = type; |
|
227 } |
|
228 } |
|
229 } |
|
230 } |
|
231 } |
|
232 |
|
233 /** |
|
234 * Returns terrainType in given tile. ROCK if tile is out of area |
|
235 * @param pos - coordinate of tile |
|
236 */ |
|
237 TerrainType PhysicsWorld::getType(Vector pos) const { |
|
238 int x = (int)(pos.x); |
|
239 int y = (int)(pos.y); |
|
240 if(x < 0 || y < 0 || x >= dimensions.x || y >= dimensions.y) { |
|
241 return ROCK; |
|
242 } |
|
243 return terrain[x][y]; |
|
244 } |