35
|
1 |
|
|
2 |
#include "Physics.hh"
|
|
3 |
#include "Engine.hh"
|
|
4 |
|
|
5 |
#include <algorithm>
|
|
6 |
#include <functional>
|
41
|
7 |
#include <cmath>
|
35
|
8 |
|
|
9 |
PhysicsWorld::PhysicsWorld (Vector gravity, Vector dimensions)
|
41
|
10 |
: tick_timer(PHYSICS_TICK_MS), gravity(gravity), dimensions(dimensions), terrain(dimensions.x, std::vector<TerrainType>(dimensions.y, EMPTY)) {
|
|
11 |
|
|
12 |
generateTerrain(1337);
|
35
|
13 |
|
|
14 |
slots.connect(tick_timer.sig_timer(), this, &PhysicsWorld::tick);
|
|
15 |
tick_timer.enable();
|
|
16 |
}
|
|
17 |
|
|
18 |
void PhysicsWorld::addObject (PhysicsObject *object) {
|
|
19 |
objects.push_back(object);
|
|
20 |
}
|
|
21 |
|
|
22 |
void PhysicsWorld::tick () {
|
|
23 |
// Engine::log(DEBUG, "physics.apply_force") << "*tick*";
|
|
24 |
|
41
|
25 |
for (std::vector<PhysicsObject*>::iterator i = objects.begin(); i != objects.end(); i++) {
|
|
26 |
(*i)->tick();
|
|
27 |
}
|
35
|
28 |
}
|
|
29 |
|
|
30 |
PhysicsObject::PhysicsObject (PhysicsWorld &world, float mass, Vector position, Vector velocity)
|
|
31 |
: world(world), mass(mass), position(position), velocity(velocity) {
|
|
32 |
|
|
33 |
world.addObject(this);
|
|
34 |
}
|
|
35 |
|
41
|
36 |
/**
|
|
37 |
* Updates object speed and position. This function organises force
|
|
38 |
* integration and collision detection.
|
|
39 |
*/
|
|
40 |
void PhysicsObject::updatePosition () {
|
|
41 |
// Add gravity to the force queue
|
|
42 |
forceq.push(world.gravity);
|
|
43 |
|
|
44 |
// Go trough every force in the queue
|
|
45 |
// TODO: It might be possible to optimize by adding forces together
|
|
46 |
Force total;
|
|
47 |
posAfterTick = position;
|
|
48 |
velAfterTick = velocity;
|
|
49 |
while (!forceq.empty()) {
|
|
50 |
total += forceq.front();
|
|
51 |
forceq.pop();
|
|
52 |
// Engine::log(DEBUG, "PhysicsObject.updatePosition") << "Current position: " << posAfterTick;
|
|
53 |
}
|
|
54 |
integrate(total, PHYSICS_TICK_MS);
|
35
|
55 |
|
41
|
56 |
Vector newPosition = posAfterTick /*+ (velAfterTick * PHYSICS_TICK_MS)/1000*/;
|
|
57 |
this->velocity = velAfterTick;
|
|
58 |
//Engine::log(DEBUG, "PhysicsObject.updatePosition") << "Nopeus: "<<this->velocity;
|
|
59 |
/*
|
|
60 |
this->velocity += world.gravity * (PHYSICS_TICK_MS / 1000.0);
|
35
|
61 |
|
|
62 |
Vector newPosition = position + velocity * (PHYSICS_TICK_MS / 1000.0);
|
41
|
63 |
*/
|
35
|
64 |
|
|
65 |
//TODO Handle the object as a square or a polygon
|
|
66 |
|
|
67 |
bool collided = false;
|
|
68 |
|
41
|
69 |
//goes 1 unit forward every step and check if has hit anything
|
|
70 |
Vector unitVector = (newPosition-position) / (newPosition-position).length();
|
|
71 |
|
|
72 |
Vector tmpVector = position;
|
|
73 |
Vector reached = position;
|
|
74 |
int steps = (int) (newPosition-position).length();
|
|
75 |
for(int i = 0; i < steps; i++) {
|
|
76 |
tmpVector += unitVector;
|
|
77 |
if(world.getType(tmpVector) != EMPTY) {
|
|
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";
|
35
|
85 |
}
|
41
|
86 |
}
|
35
|
87 |
|
41
|
88 |
// In case of some float error
|
35
|
89 |
if(!collided) {
|
41
|
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 |
}
|
|
96 |
} else {
|
|
97 |
newPosition = reached;
|
|
98 |
this->velocity = Vector(0, 0);
|
|
99 |
//TODO: it shouldn't just stop on collision
|
35
|
100 |
}
|
41
|
101 |
this->position = newPosition;
|
|
102 |
|
35
|
103 |
}
|
|
104 |
|
41
|
105 |
bool PhysicsWorld::collided (Vector oldPos, Vector newPos) {
|
|
106 |
int deltaX = oldPos.x - newPos.x;
|
|
107 |
int deltaY = oldPos.y - newPos.y;
|
|
108 |
double distance = sqrt(deltaX * deltaX + deltaY * deltaY);
|
|
109 |
double xInc = deltaX / distance;
|
|
110 |
double yInc = deltaY / distance;
|
|
111 |
double currentX = oldPos.x;
|
|
112 |
double currentY = oldPos.y;
|
35
|
113 |
|
41
|
114 |
// This implementation is bit slow since it checks some squares twice.
|
|
115 |
for(unsigned int i = 1; i < distance; i++) {
|
|
116 |
currentX += xInc;
|
|
117 |
currentY += yInc;
|
|
118 |
if(terrain[(int)currentX][(int)currentY] != EMPTY)
|
|
119 |
return true;
|
|
120 |
}
|
|
121 |
return false;
|
|
122 |
}
|
|
123 |
|
|
124 |
/**
|
|
125 |
* Integrates given force over time and stores new position to
|
|
126 |
* posAfterTick and new velocity to velAfterTick.
|
|
127 |
* @param force Force vector.
|
|
128 |
* @param dt The time the force is applied (<=PHYSICS_TICK_MS)
|
|
129 |
*/
|
|
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);
|
35
|
136 |
|
41
|
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);
|
35
|
171 |
}
|
|
172 |
|
|
173 |
void PhysicsObject::updatePhysics (Vector position, Vector velocity) {
|
|
174 |
this->position = position;
|
|
175 |
this->velocity = velocity;
|
|
176 |
}
|
|
177 |
|
|
178 |
Vector PhysicsObject::getPosition () {
|
|
179 |
return this->position;
|
|
180 |
}
|
|
181 |
|
|
182 |
void PhysicsObject::tick () {
|
|
183 |
this->updatePosition();
|
|
184 |
}
|
|
185 |
|
41
|
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 |
}
|