Files
love/src/modules/graphics/opengl/Mesh.cpp
T
2015-02-20 01:20:08 -04:00

464 lines
10 KiB
C++

/**
* Copyright (c) 2006-2015 LOVE Development Team
*
* This software is provided 'as-is', without any express or implied
* warranty. In no event will the authors be held liable for any damages
* arising from the use of this software.
*
* Permission is granted to anyone to use this software for any purpose,
* including commercial applications, and to alter it and redistribute it
* freely, subject to the following restrictions:
*
* 1. The origin of this software must not be misrepresented; you must not
* claim that you wrote the original software. If you use this software
* in a product, an acknowledgment in the product documentation would be
* appreciated but is not required.
* 2. Altered source versions must be plainly marked as such, and must not be
* misrepresented as being the original software.
* 3. This notice may not be removed or altered from any source distribution.
**/
// LOVE
#include "Mesh.h"
#include "common/Matrix.h"
#include "common/Exception.h"
// C++
#include <algorithm>
namespace love
{
namespace graphics
{
namespace opengl
{
Mesh::Mesh(const std::vector<Vertex> &verts, Mesh::DrawMode mode)
: vbo(nullptr)
, vertex_count(0)
, ibo(nullptr)
, element_count(0)
, element_data_type(getGLDataTypeFromMax(verts.size()))
, draw_mode(mode)
, range_min(-1)
, range_max(-1)
, texture(nullptr)
, colors_enabled(false)
{
setVertices(verts);
}
Mesh::Mesh(int vertexcount, Mesh::DrawMode mode)
: vbo(nullptr)
, vertex_count(0)
, ibo(nullptr)
, element_count(0)
, element_data_type(getGLDataTypeFromMax(vertexcount))
, draw_mode(mode)
, range_min(-1)
, range_max(-1)
, texture(nullptr)
, colors_enabled(false)
{
if (vertexcount < 1)
throw love::Exception("Invalid number of vertices.");
std::vector<Vertex> verts(vertexcount);
// Default-initialized vertices should have a white opaque color.
for (size_t i = 0; i < verts.size(); i++)
{
verts[i].r = 255;
verts[i].g = 255;
verts[i].b = 255;
verts[i].a = 255;
}
setVertices(verts);
}
Mesh::~Mesh()
{
delete vbo;
delete ibo;
}
void Mesh::setVertices(const std::vector<Vertex> &verts)
{
if (verts.size() == 0)
throw love::Exception("At least one vertex is required.");
size_t size = sizeof(Vertex) * verts.size();
if (vbo && size > vbo->getSize())
{
delete vbo;
vbo = nullptr;
}
if (!vbo)
vbo = VertexBuffer::Create(size, GL_ARRAY_BUFFER, GL_DYNAMIC_DRAW);
vertex_count = verts.size();
VertexBuffer::Bind vbo_bind(*vbo);
VertexBuffer::Mapper vbo_mapper(*vbo);
// Fill the buffer with the vertices.
memcpy(vbo_mapper.get(), &verts[0], size);
}
const Vertex *Mesh::getVertices() const
{
if (vbo)
{
VertexBuffer::Bind vbo_bind(*vbo);
return (Vertex *) vbo->map();
}
return nullptr;
}
void Mesh::setVertex(size_t index, const Vertex &v)
{
if (index >= vertex_count)
throw love::Exception("Invalid vertex index: %ld", index + 1);
VertexBuffer::Bind vbo_bind(*vbo);
// We unmap the vertex buffer in Mesh::draw. This lets us coalesce the
// buffer transfer calls into just one.
Vertex *vertices = (Vertex *) vbo->map();
vertices[index] = v;
}
Vertex Mesh::getVertex(size_t index) const
{
if (index >= vertex_count)
throw love::Exception("Invalid vertex index: %ld", index + 1);
VertexBuffer::Bind vbo_bind(*vbo);
// We unmap the vertex buffer in Mesh::draw.
Vertex *vertices = (Vertex *) vbo->map();
return vertices[index];
}
size_t Mesh::getVertexCount() const
{
return vertex_count;
}
/**
* Copies index data from a vector to a mapped index buffer.
**/
template <typename T>
static void copyToIndexBuffer(const std::vector<uint32> &indices, VertexBuffer::Mapper &buffermap, size_t maxval)
{
T *elems = (T *) buffermap.get();
for (size_t i = 0; i < indices.size(); i++)
{
if (indices[i] >= maxval)
throw love::Exception("Invalid vertex map value: %d", indices[i] + 1);
elems[i] = (T) indices[i];
}
}
void Mesh::setVertexMap(const std::vector<uint32> &map)
{
GLenum datatype = getGLDataTypeFromMax(vertex_count);
// Calculate the size in bytes of the index buffer data.
size_t size = map.size() * getGLDataTypeSize(datatype);
if (ibo && size > ibo->getSize())
{
delete ibo;
ibo = nullptr;
}
if (!ibo && size > 0)
ibo = VertexBuffer::Create(size, GL_ELEMENT_ARRAY_BUFFER, GL_DYNAMIC_DRAW);
element_count = map.size();
if (!ibo || element_count == 0)
return;
VertexBuffer::Bind ibo_bind(*ibo);
VertexBuffer::Mapper ibo_map(*ibo);
// Fill the buffer with the index values from the vector.
switch (datatype)
{
case GL_UNSIGNED_BYTE:
copyToIndexBuffer<uint8>(map, ibo_map, vertex_count);
break;
case GL_UNSIGNED_SHORT:
copyToIndexBuffer<uint16>(map, ibo_map, vertex_count);
break;
case GL_UNSIGNED_INT:
default:
copyToIndexBuffer<uint32>(map, ibo_map, vertex_count);
break;
}
element_data_type = datatype;
}
/**
* Copies index data from a mapped buffer to a vector.
**/
template <typename T>
static void copyFromIndexBuffer(void *buffer, size_t count, std::vector<uint32> &indices)
{
T *elems = (T *) buffer;
for (size_t i = 0; i < count; i++)
indices.push_back((uint32) elems[i]);
}
void Mesh::getVertexMap(std::vector<uint32> &map) const
{
if (!ibo || element_count == 0)
return;
map.clear();
map.reserve(element_count);
VertexBuffer::Bind ibo_bind(*ibo);
// We unmap the buffer in Mesh::draw and Mesh::setVertexMap.
void *buffer = ibo->map();
// Fill the vector from the buffer.
switch (element_data_type)
{
case GL_UNSIGNED_BYTE:
copyFromIndexBuffer<uint8>(buffer, element_count, map);
break;
case GL_UNSIGNED_SHORT:
copyFromIndexBuffer<uint16>(buffer, element_count, map);
break;
case GL_UNSIGNED_INT:
default:
copyFromIndexBuffer<uint32>(buffer, element_count, map);
break;
}
}
size_t Mesh::getVertexMapCount() const
{
return element_count;
}
void Mesh::setTexture(Texture *tex)
{
texture.set(tex);
}
void Mesh::setTexture()
{
texture.set(nullptr);
}
Texture *Mesh::getTexture() const
{
return texture.get();
}
void Mesh::setDrawMode(Mesh::DrawMode mode)
{
draw_mode = mode;
}
Mesh::DrawMode Mesh::getDrawMode() const
{
return draw_mode;
}
void Mesh::setDrawRange(int min, int max)
{
if (min < 0 || max < 0 || min > max)
throw love::Exception("Invalid draw range.");
range_min = min;
range_max = max;
}
void Mesh::setDrawRange()
{
range_min = range_max = -1;
}
void Mesh::getDrawRange(int &min, int &max) const
{
min = range_min;
max = range_max;
}
void Mesh::setVertexColors(bool enable)
{
colors_enabled = enable;
}
bool Mesh::hasVertexColors() const
{
return colors_enabled;
}
void Mesh::draw(float x, float y, float angle, float sx, float sy, float ox, float oy, float kx, float ky)
{
const size_t pos_offset = offsetof(Vertex, x);
const size_t tex_offset = offsetof(Vertex, s);
const size_t color_offset = offsetof(Vertex, r);
if (vertex_count == 0)
return;
if (texture.get())
texture->predraw();
else
gl.bindTexture(gl.getDefaultTexture());
Matrix m;
m.setTransformation(x, y, angle, sx, sy, ox, oy, kx, ky);
OpenGL::TempTransform transform(gl);
transform.get() *= m;
VertexBuffer::Bind vbo_bind(*vbo);
// Make sure the VBO isn't mapped when we draw (sends data to GPU if needed.)
vbo->unmap();
glEnableVertexAttribArray(ATTRIB_POS);
glEnableVertexAttribArray(ATTRIB_TEXCOORD);
glVertexAttribPointer(ATTRIB_POS, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), vbo->getPointer(pos_offset));
glVertexAttribPointer(ATTRIB_TEXCOORD, 2, GL_FLOAT, GL_FALSE, sizeof(Vertex), vbo->getPointer(tex_offset));
if (hasVertexColors())
{
// Per-vertex colors.
glEnableVertexAttribArray(ATTRIB_COLOR);
glVertexAttribPointer(ATTRIB_COLOR, 4, GL_UNSIGNED_BYTE, GL_TRUE, sizeof(Vertex), vbo->getPointer(color_offset));
}
GLenum mode = getGLDrawMode(draw_mode);
gl.prepareDraw();
if (ibo && element_count > 0)
{
// Use the custom vertex map (index buffer) to draw the vertices.
VertexBuffer::Bind ibo_bind(*ibo);
// Make sure the index buffer isn't mapped (sends data to GPU if needed.)
ibo->unmap();
int max = (int) element_count - 1;
if (range_max >= 0)
max = std::min(range_max, max);
int min = 0;
if (range_min >= 0)
min = std::min(range_min, max);
GLenum type = element_data_type;
const void *indices = ibo->getPointer(min * getGLDataTypeSize(type));
gl.drawElements(mode, max - min + 1, type, indices);
}
else
{
int max = (int) vertex_count - 1;
if (range_max >= 0)
max = std::min(range_max, max);
int min = 0;
if (range_min >= 0)
min = std::min(range_min, max);
// Normal non-indexed drawing (no custom vertex map.)
gl.drawArrays(mode, min, max - min + 1);
}
glDisableVertexAttribArray(ATTRIB_TEXCOORD);
glDisableVertexAttribArray(ATTRIB_POS);
if (hasVertexColors())
{
glDisableVertexAttribArray(ATTRIB_COLOR);
// Using the color array leaves the GL constant color undefined.
gl.setColor(gl.getColor());
}
if (texture.get())
texture->postdraw();
}
GLenum Mesh::getGLDrawMode(DrawMode mode) const
{
switch (mode)
{
case DRAW_MODE_FAN:
return GL_TRIANGLE_FAN;
case DRAW_MODE_STRIP:
return GL_TRIANGLE_STRIP;
case DRAW_MODE_TRIANGLES:
default:
return GL_TRIANGLES;
case DRAW_MODE_POINTS:
return GL_POINTS;
}
}
GLenum Mesh::getGLDataTypeFromMax(size_t maxvalue) const
{
if (maxvalue > LOVE_UINT16_MAX)
return GL_UNSIGNED_INT;
else
return GL_UNSIGNED_SHORT;
}
size_t Mesh::getGLDataTypeSize(GLenum datatype) const
{
switch (datatype)
{
case GL_UNSIGNED_BYTE:
return sizeof(uint8);
case GL_UNSIGNED_SHORT:
return sizeof(uint16);
case GL_UNSIGNED_INT:
return sizeof(uint32);
default:
return 0;
}
}
bool Mesh::getConstant(const char *in, Mesh::DrawMode &out)
{
return drawModes.find(in, out);
}
bool Mesh::getConstant(Mesh::DrawMode in, const char *&out)
{
return drawModes.find(in, out);
}
StringMap<Mesh::DrawMode, Mesh::DRAW_MODE_MAX_ENUM>::Entry Mesh::drawModeEntries[] =
{
{"fan", Mesh::DRAW_MODE_FAN},
{"strip", Mesh::DRAW_MODE_STRIP},
{"triangles", Mesh::DRAW_MODE_TRIANGLES},
{"points", Mesh::DRAW_MODE_POINTS},
};
StringMap<Mesh::DrawMode, Mesh::DRAW_MODE_MAX_ENUM> Mesh::drawModes(Mesh::drawModeEntries, sizeof(Mesh::drawModeEntries));
} // opengl
} // graphics
} // love