1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
|
#include "Player.hpp"
namespace MC::Entities {
void Player::update(const Time& time, GFX::Window& window, GFX::Camera& camera, World::World& world) {
auto origin = m_transform.position();
auto r = window.mouse_delta();
auto key = [&](Int k) -> Real { return window.key(k, GLFW_PRESS); };
Real x = key(GLFW_KEY_D) - key(GLFW_KEY_A);
Real y = key(GLFW_KEY_SPACE) - key(GLFW_KEY_LEFT_SHIFT);
Real z = key(GLFW_KEY_S) - key(GLFW_KEY_W);
Real boost = key(GLFW_KEY_LEFT_CONTROL) * 75.0f;
auto move_speed = (10.0f + boost) * time.delta();
auto rotation_speed = 0.1f;
auto direction = m_transform.right() * x + Vec3(0, y, 0) + m_transform.forward() * z;
auto destination = origin + direction * move_speed;
destination = process_collisions(world, origin, destination);
move_to(destination);
rotate({r.y() * rotation_speed, r.x() * rotation_speed, 0.0f});
update_camera_position(camera);
}
void Player::move(Position::WorldOffset by) {
m_transform.position() += by;
}
void Player::rotate(Rotation by) {
m_transform.rotation() += by;
m_transform.rotation().pitch() = std::clamp(m_transform.rotation().pitch(), -89.0, 89.0);
}
void Player::move_to(Position::World to) {
m_transform.position() = to;
}
void Player::rotate_to(Rotation to) {
m_transform.rotation() = to;
}
AABB Player::bounds() const {
return bounding_box_for_position(m_transform.position());
}
#define STUCK_THRESHOLD 100
Position::World Player::process_collisions(World::World& world, Position::World from, Position::World to) {
auto current_box = bounding_box_for_position(from);
// All the blocks we could theoretically collide with.
auto collision_domain = terrain_collision_domain(from, to, world);
// Sort the pushouts by magnitude, and apply the biggest one.
// If we apply a pushout, we need to check again for collisions,
// since we might have slid into another block. (up to STUCK_THRESHOLD times)
// If there are no pushouts, we have no collisions,
// and the player can move freely to the proposed position.
std::vector<Vec3> pushouts;
for (UInt stuck = 0; stuck < STUCK_THRESHOLD; stuck++) {
// Find the pushout vector for each block we're colliding with.
for (auto possible_collision : collision_domain) {
auto pushout = current_box.pushout(to - from, possible_collision);
if (!pushout.is_zero()) pushouts.push_back(pushout);
}
if (pushouts.empty()) return to;
std::sort(pushouts.begin(), pushouts.end(), [=](const Vec3& a, const Vec3& b) -> bool {
return a.magnitude_squared() > b.magnitude_squared();
});
to += pushouts[0];
pushouts.clear();
}
// We got stuck, don't move.
return from;
}
std::vector<AABB> Player::terrain_collision_domain(
Position::World from, Position::World to,
World::World& world
) {
// Make the box a bit bigger so we don't clip through blocks.
auto domain_box = bounding_box_for_position(from)
.unite(bounding_box_for_position(to))
.unite(AABB{{1, 1, 1}});
std::vector<AABB> colliding_blocks;
// TODO: Unbind from chunks and loop through all the
// blocks individually inside domain.
// This will be more efficient and actually accurate,
// since right now if you're fast enough you can clip
// through whole chunks.
auto add_colliding = [&](Position::World chunk_pos, Position::BlockLocal pos, World::Chunk::BlockData& b) {
if (!b.type.is_solid()) return;
auto block_bounds = World::Chunk::block_bounds(pos).offset(chunk_pos);
if (domain_box.collides(block_bounds)) colliding_blocks.push_back(block_bounds);
};
auto chunk_from = world.chunks().find(to);
if (chunk_from.chunk.has_value()) {
auto position = chunk_from.chunk.value().position();
chunk_from.chunk->for_each([&](auto p, auto b) { add_colliding(position, p, b); });
}
if (World::ChunkIndex::from_position(to) == World::ChunkIndex::from_position(from))
return colliding_blocks;
auto chunk_to = world.chunks().find(to);
if (chunk_to.chunk.has_value()) {
auto position = chunk_to.chunk.value().position();
chunk_to.chunk->for_each([&](auto p, auto b) { add_colliding(position, p, b); });
}
return colliding_blocks;
}
void Player::update_camera_position(GFX::Camera& camera) {
auto camera_position = m_transform.position();
camera_position.y() += 1.5;
camera.set_position(camera_position);
camera.set_angles(m_transform.rotation());
}
AABB Player::bounding_box_for_position(Position::World position) {
Vec3 box_start = {
position.x() - s_bounds.max.x() / 2,
position.y(),
position.z() - s_bounds.max.z() / 2,
};
return s_bounds.offset(box_start);
}
Position::World Player::position_for_bounding_box(AABB box) {
auto center = box.center();
return {center.x(), box.min.y(), center.z()};
}
}
|