4 #include "../graphics/buffer.hpp"
7 #include <glm/gtx/io.hpp>
19 vao.EnableAttribute(0);
20 vao.AttributePointer<glm::vec3>(0, false, offsetof(Attributes, position));
25 void Cursor::Hide() noexcept {
29 void Cursor::FloorTile(const Floor &floor, int tile_x, int tile_z) {
30 // TODO: only update if changed
33 int x_begin = glm::clamp(tile_x, 0, floor.Width() - size);
34 int x_end = x_begin + size;
35 int z_begin = glm::clamp(tile_z, 0, floor.Depth() - size);
36 int z_end = z_begin + size;
40 vao.ReserveAttributes(size * size, GL_DYNAMIC_DRAW);
42 MappedBuffer<Attributes> attrib(vao.MapAttributes(GL_WRITE_ONLY));
43 for (int z = z_begin, index = 0; z < z_end; ++z) {
44 for (int x = x_begin; x < x_end; ++x, ++index) {
45 attrib[index].position = glm::vec3(x, floor.GetElevation(x, z) + offset, z);
51 vao.ReserveElements((size - 1) * (size - 1) * 6, GL_DYNAMIC_DRAW);
53 MappedBuffer<unsigned char> element(vao.MapElements(GL_WRITE_ONLY));
54 for (int z = 0, index = 0; z < size - 1; ++z) {
55 for (int x = 0; x < size - 1; ++x, ++index) {
56 element[index * 6 + 0] = (z + 0) * size + (x + 0);
57 element[index * 6 + 1] = (z + 0) * size + (x + 1);
58 element[index * 6 + 2] = (z + 1) * size + (x + 0);
59 element[index * 6 + 3] = (z + 0) * size + (x + 1);
60 element[index * 6 + 4] = (z + 1) * size + (x + 1);
61 element[index * 6 + 5] = (z + 1) * size + (x + 0);
68 void Cursor::Draw() const noexcept {
70 vao.DrawTriangles((size - 1) * (size - 1) * 6);
74 constexpr int Floor::VAO_DIVISOR;
76 Floor::Floor(int w, int d)
80 , tile_width(width - 1)
81 , tile_depth(depth - 1)
82 , unclean_width(tile_width % VAO_DIVISOR)
83 , unclean_depth(tile_depth % VAO_DIVISOR)
84 , vao_width(tile_width / VAO_DIVISOR + bool(unclean_width))
85 , vao_depth(tile_depth / VAO_DIVISOR + bool(unclean_depth))
86 , vaos(vao_width * vao_depth)
87 , general_elements(vao_width * vao_depth)
88 , unclean_width_elements(general_elements + bool(unclean_width))
89 , unclean_depth_elements(unclean_depth ? (unclean_width_elements + 1) : general_elements)
90 , unclean_corner_elements(std::max(unclean_width_elements, unclean_depth_elements) + (unclean_width && unclean_depth))
91 , buffers(unclean_corner_elements + 1) {
92 glGenVertexArrays(vaos.size(), vaos.data());
93 glGenBuffers(buffers.size(), buffers.data());
94 int x_end = vao_width - bool(unclean_width);
95 int z_end = vao_depth - bool(unclean_depth);
96 for (int z = 0; z < z_end; ++z) {
97 for (int x = 0; x < x_end; ++x) {
98 SetupVAO(z * vao_width + x, buffers[general_elements], (VAO_DIVISOR + 1) * (VAO_DIVISOR + 1));
101 // always fill general element buffer (there won't be one needed for maps with x or z less than divisor,
102 // but that shouldn't happen in practice, only during tests in which case I don't care about the overhead
103 FillElementBuffer(buffers[general_elements], VAO_DIVISOR, VAO_DIVISOR);
105 for (int z = 0; z < z_end; ++z) {
106 SetupVAO(z * vao_width + x_end, buffers[unclean_width_elements], (unclean_width + 1) * (VAO_DIVISOR + 1));
108 FillElementBuffer(buffers[unclean_width_elements], unclean_width, VAO_DIVISOR);
111 for (int x = 0; x < x_end; ++x) {
112 SetupVAO(z_end * vao_width + x, buffers[unclean_depth_elements], (VAO_DIVISOR + 1) * (unclean_depth + 1));
114 FillElementBuffer(buffers[unclean_depth_elements], VAO_DIVISOR, unclean_depth);
116 if (unclean_width && unclean_depth) {
117 SetupVAO(z_end * vao_width + x_end, buffers[unclean_corner_elements], (unclean_width + 1) * (unclean_depth + 1));
118 FillElementBuffer(buffers[unclean_corner_elements], unclean_width, unclean_depth);
122 Floor::~Floor() noexcept {
123 glDeleteBuffers(buffers.size(), buffers.data());
124 glDeleteVertexArrays(vaos.size(), vaos.data());
127 void Floor::SetupVAO(int which, GLuint element_buffer, int vertex_count) noexcept {
128 glBindVertexArray(vaos[which]);
129 glBindBuffer(GL_ARRAY_BUFFER, buffers[which]);
130 glBufferData(GL_ARRAY_BUFFER, vertex_count * sizeof(Attributes), nullptr, GL_STATIC_DRAW);
131 glEnableVertexAttribArray(0);
132 glVertexAttribPointer(0, 3, GL_FLOAT, 0, sizeof(Attributes), reinterpret_cast<const void *>(offsetof(Attributes, position)));
133 glEnableVertexAttribArray(1);
134 glVertexAttribPointer(1, 3, GL_FLOAT, 0, sizeof(Attributes), reinterpret_cast<const void *>(offsetof(Attributes, normal)));
135 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, element_buffer);
138 void Floor::FillElementBuffer(GLuint which, int tile_width, int tile_depth) {
139 // unbind VAO so we don't accidentally trash an element buffer binding
140 glBindVertexArray(0);
141 glBindBuffer(GL_ELEMENT_ARRAY_BUFFER, which);
142 glBufferData(GL_ELEMENT_ARRAY_BUFFER, tile_width * tile_depth * sizeof(short) * 6, nullptr, GL_STATIC_DRAW);
143 MappedBuffer<short> data(GL_ELEMENT_ARRAY_BUFFER, GL_WRITE_ONLY);
144 for (int z = 0, i = 0; z < tile_depth; ++z) {
145 for (int x = 0; x < tile_width; ++x, ++i) {
146 data[i * 6 + 0] = (z + 0) * (tile_width + 1) + (x + 0);
147 data[i * 6 + 1] = (z + 0) * (tile_width + 1) + (x + 1);
148 data[i * 6 + 2] = (z + 1) * (tile_width + 1) + (x + 0);
149 data[i * 6 + 3] = (z + 0) * (tile_width + 1) + (x + 1);
150 data[i * 6 + 4] = (z + 1) * (tile_width + 1) + (x + 1);
151 data[i * 6 + 5] = (z + 1) * (tile_width + 1) + (x + 0);
156 void Floor::FillAttribBuffer(int vao_x, int vao_z) {
157 glBindBuffer(GL_ARRAY_BUFFER, buffers[vao_z * vao_width + vao_x]);
158 MappedBuffer<Attributes> data(GL_ARRAY_BUFFER, GL_WRITE_ONLY);
159 glm::ivec2 tiles(Tiles(vao_x, vao_z));
160 for (int z = 0, abs_z = vao_z * VAO_DIVISOR, i = 0; z < tiles.y + 1; ++z, ++abs_z) {
161 for (int x = 0, abs_x = vao_x * VAO_DIVISOR; x < tiles.x + 1; ++x, ++abs_x, ++i) {
162 data[i].position = glm::vec3(x, GetElevation(abs_x, abs_z), z);
163 data[i].normal = GetNormal(abs_x, abs_z);
168 glm::vec3 Floor::GetNormal(int x, int z) const noexcept {
169 // TODO: not sure about the sign here
170 return normalize(glm::vec3(
171 ClampedElevation(x - 1, z) - ClampedElevation(x + 1, z),
173 ClampedElevation(x, z - 1) - ClampedElevation(x, z + 1)
177 glm::ivec2 Floor::Tiles(int vao_x, int vao_z) const noexcept {
179 (unclean_width && vao_x == vao_width - 1) ? unclean_width : VAO_DIVISOR,
180 (unclean_depth && vao_z == vao_depth - 1) ? unclean_depth : VAO_DIVISOR);
183 int Floor::NumTiles(int vao_x, int vao_z) const noexcept {
184 glm::ivec2 tiles = Tiles(vao_x, vao_z);
185 return tiles.x * tiles.y;
188 int Floor::NumVertices(int vao_x, int vao_z) const noexcept {
189 glm::ivec2 tiles = Tiles(vao_x, vao_z);
190 return (tiles.x + 1) * (tiles.y + 1);
193 void Floor::GenerateVertices() {
194 for (int z = 0; z < vao_depth; ++z) {
195 for (int x = 0; x < vao_width; ++x) {
196 FillAttribBuffer(x, z);
201 void Floor::DrawVAO(int vao_x, int vao_z) const noexcept {
202 glBindVertexArray(vaos[vao_z * vao_width + vao_x]);
203 // TODO: this cries for triangle strips
204 glDrawElements(GL_TRIANGLES, NumTiles(vao_x, vao_z) * 6, GL_UNSIGNED_SHORT, nullptr);
207 bool Floor::Intersection(const Ray &ray, glm::vec3 &point) {
208 // see http://www.cse.yorku.ca/~amana/research/grid.pdf and
209 // http://www.flipcode.com/archives/Raytracing_Topics_Techniques-Part_4_Spatial_Subdivisions.shtml section Grid Traversal
211 // cache 1/dir to avoid some conditionals and divisions
212 glm::vec3 inverse_direction(ray.InverseDirection());
214 // cell indicates the current tile we're considering
215 glm::ivec2 cell(int(ray.origin.x), int(ray.origin.z));
217 // holds the distance along the ray to advance by one cell in each direction
218 glm::vec3 tDelta(glm::abs(inverse_direction));
219 // holds the distance along the ray to the next cell boundary
220 // TODO: not sure if that is always correct (e.g. with negative components in ray direction)
221 glm::vec3 tMax(tDelta * (1.0f - glm::fract(ray.origin)));
223 // if ray's origin is outside the grid, advance to the first cell it hits
224 float x_near, x_far, z_near, z_far, t_min, t_max;
225 if (cell.x < 0 || cell.x >= width || cell.y < 0 || cell.y >= depth) {
226 x_near = (-ray.origin.x) * inverse_direction.x;
227 // subtracting one so the point is in the last cell, rather than after it, when approaching from the far end
228 x_far = (width - 1 - ray.origin.x) * inverse_direction.x;
229 z_near = (-ray.origin.z) * inverse_direction.z;
230 z_far = (depth - 1 - ray.origin.z) * inverse_direction.z;
231 t_min = std::max(std::min(x_near, x_far), std::min(z_near, z_far));
232 t_max = std::min(std::max(x_near, x_far), std::max(z_near, z_far));
233 if (t_max < 0.0f || t_min > t_max) {
234 // ray doesn't touch our grid at all
237 glm::vec3 contact = ray.origin + t_min * ray.direction;
238 cell.x = int(contact.x);
239 cell.y = int(contact.z);
240 // TODO: not sure if this is correct, could be that contact has to be recalculated with
241 // outer planes instead of the -1 ones (that might actually also apply to cell calculation)
242 tMax.x = (float(cell.x) - contact.x) * inverse_direction.x;
243 tMax.z = (float(cell.y) - contact.z) * inverse_direction.z;
246 // step hold the direction we're traversing the grid
247 glm::ivec2 step(glm::sign(ray.direction.x), glm::sign(ray.direction.z));
249 if (step.x == 0 && step.y == 0) {
250 // ray shoots straight up or down
251 // check the current cell (if it's valid)
252 if (cell.x >= 0 && cell.x < width && cell.y >= 0 && cell.y < depth) {
253 if (TriangleIntersection(
255 glm::vec3(float(cell.x + 0), GetElevation(cell.x + 0, cell.y + 0), float(cell.y + 0)),
256 glm::vec3(float(cell.x + 1), GetElevation(cell.x + 1, cell.y + 0), float(cell.y + 0)),
257 glm::vec3(float(cell.x + 0), GetElevation(cell.x + 0, cell.y + 1), float(cell.y + 1)),
262 if (TriangleIntersection(
264 glm::vec3(float(cell.x + 1), GetElevation(cell.x + 1, cell.y + 0), float(cell.y + 0)),
265 glm::vec3(float(cell.x + 1), GetElevation(cell.x + 1, cell.y + 1), float(cell.y + 1)),
266 glm::vec3(float(cell.x + 0), GetElevation(cell.x + 0, cell.y + 1), float(cell.y + 1)),
275 // cache for the height of the vertices of the current cell
278 while (cell.x >= 0 && cell.x < width && cell.y >= 0 && cell.y < depth) {
279 // pull heights for the current cell
280 height[0] = GetElevation(cell.x + 0, cell.y + 0);
281 height[1] = GetElevation(cell.x + 1, cell.y + 0);
282 height[2] = GetElevation(cell.x + 0, cell.y + 1);
283 height[3] = GetElevation(cell.x + 1, cell.y + 1);
284 // the triangles used for rendering are (x,z), (x+1,z), (x,z+1) and
285 // (x+1,z),(x+1,z+1), (x,z+1), so height indices 012 and 132
286 if (TriangleIntersection(
288 glm::vec3(float(cell.x + 0), height[0], float(cell.y + 0)),
289 glm::vec3(float(cell.x + 1), height[1], float(cell.y + 0)),
290 glm::vec3(float(cell.x + 0), height[2], float(cell.y + 1)),
295 if (TriangleIntersection(
297 glm::vec3(float(cell.x + 1), height[1], float(cell.y + 0)),
298 glm::vec3(float(cell.x + 1), height[3], float(cell.y + 1)),
299 glm::vec3(float(cell.x + 0), height[2], float(cell.y + 1)),
304 // advance to the next cell
305 if (tMax.x < tMax.z) {