#include "DrawCanvas.h"
#include "CasualCore/RKEngine/Private/Include/RKRender.h"
#include "CasualCore/RKEngine/Public/Include/RKIndexBuffer.h"
#include "CasualCore/RKEngine/Public/Include/RKVertexBuffer.h"
#include "CasualCore/RKEngine/Public/Include/RKVertexDeclaration.h"
static const char* s_guiMatName = "DrawCanvas_AlphaFalse";
static const char* s_guiMatAlphaName = "DrawCanvas_AlphaTrue";
DrawCanvas::DrawCanvas(RKVertexDeclaration* a_vertexDecl)
: m_vertexDecl(a_vertexDecl)
{
RKASSERT(m_vertexDecl->stride == sizeof(UpdateData), "Bad vertex type stride");
}
DrawCanvas::~DrawCanvas()
{
// Release the geometry chunks
for (uint32_t i = 0; i < m_batches.Size(); i++)
{
Batch& currBatch = m_batches[i];
if (currBatch.m_shareIndex < 0)
{
// If not shared, release the vertex buffer
RKVertexBuffer_Destroy(&currBatch.m_geometryChunk->m_pVB);
}
RKRender_DestroyGeometryChunk(&currBatch.m_geometryChunk);
}
m_batches.Clear();
// Release the internal materials
for (uint32_t i = 0; i < m_materials.Size(); i++)
{
if (m_materials[i])
{
RKMaterial_Destroy(&m_materials[i]);
}
}
m_materials.Clear();
if (m_drawIndexBuffer)
{
RKIndexBuffer_Destroy(&m_drawIndexBuffer);
}
}
void DrawCanvas::SetProjectionDrawArea(float a_width, float a_height, bool a_crop)
{
// Get the draw area
float startX = 0.0f;
float startY = 0.0f;
float newWidth = 0.0f;
float newHeight = 0.0f;
GetProjectionDrawArea(a_width, a_height, a_crop,
startX, startY, newWidth, newHeight);
// Set the projection area
m_projMatrix.MakeOrthoMatrix(startX, startX + newWidth, startY, startY + newHeight, -1000.0f, 1000.0f);
}
void DrawCanvas::GetProjectionDrawArea(float a_width, float a_height, bool a_crop,
float& a_retStartX, float& a_retStartY, float& a_retWidth, float& a_retHeight)
{
float deviceAspect = RKDevice_GetAspect();
float newWidth = a_height * deviceAspect;
float widthOffset = (a_width - newWidth) * 0.5f;
float newHeight = a_width / deviceAspect;
float heightOffset = (a_height - newHeight) * 0.5f;
// Set adjusting height based on cropping and aspect ratio adjustments
bool adjustHeight = a_crop;
if (a_width > newWidth)
{
adjustHeight = !adjustHeight;
}
// Set the projection area based on if adjusting the height area
if (adjustHeight)
{
a_retStartX = 0.0f;
a_retStartY = heightOffset;
a_retWidth = a_width;
a_retHeight = newHeight;
}
else
{
a_retStartX = widthOffset;
a_retStartY = 0.0f;
a_retWidth = newWidth;
a_retHeight = a_height;
}
}
uint32_t DrawCanvas::CreateBatchFromTexture(RKRenderLayer* a_layer, uint32_t a_maxQuads, const char* a_textureName, bool a_useBlending)
{
return CreateBatch(a_layer, a_maxQuads, CreateMaterialFromTexture(a_textureName, a_useBlending));
}
uint32_t DrawCanvas::CreateBatchFromMaterial(RKRenderLayer* a_layer, uint32_t a_maxQuads, const char* a_materialName)
{
// Add the new material to the internal batch
RKMaterial* newMat = RKMaterial_Create(a_materialName);
m_materials.Append(newMat);
return CreateBatch(a_layer, a_maxQuads, newMat);
}
uint32_t DrawCanvas::CreateBatch(RKRenderLayer* a_layer, uint32_t a_maxQuads, RKMaterial* a_material)
{
// Resize the index buffer if needed
if (!m_drawIndexBuffer ||
m_drawIndexBuffer->indexCount < (a_maxQuads * 6))
{
if (m_drawIndexBuffer)
{
RKIndexBuffer_Destroy(&m_drawIndexBuffer);
}
// Create the new index buffer
uint16_t * indexData = RKNEWARRAY(uint16_t, (a_maxQuads * 6));
for (uint32_t i = 0; i < a_maxQuads; i++)
{
indexData[(i * 6) + 0] = (i * 4) + 0;
indexData[(i * 6) + 1] = (i * 4) + 1;
indexData[(i * 6) + 2] = (i * 4) + 2;
indexData[(i * 6) + 3] = (i * 4) + 0;
indexData[(i * 6) + 4] = (i * 4) + 2;
indexData[(i * 6) + 5] = (i * 4) + 3;
}
m_drawIndexBuffer = RKIndexBuffer_Create(a_maxQuads * 6, RKBUFFER_ACCESS_NONE, RKBUFFER_UPDATE_NEVER, indexData);
RKDELETEARRAY(indexData);
}
// Create the vertex buffer (What should be the access flags?)
RKVertexBuffer * vertexBuf = RKVertexBuffer_Create(m_vertexDecl, a_maxQuads * 4, RKBUFFER_ACCESS_WRITE, RKBUFFER_UPDATE_OFTEN);
// Create the geometry chunk
RKGeometryChunk * newChunk = RKRender_CreateGeometryChunk(a_layer);
newChunk->m_pMaterial = a_material;
newChunk->m_pVB = vertexBuf;
newChunk->m_indexOffset = 0;
newChunk->m_maskFlag = 0;
newChunk->m_primitiveCount = 0;
newChunk->m_primitiveType = RKPT_TRIANGLELIST;
newChunk->m_sortPriority = 0.0f;
// Create the batch
Batch newBatch;
newBatch.m_geometryChunk = newChunk;
m_batches.Append(newBatch);
return m_batches.Size() - 1;
}
uint32_t DrawCanvas::CreateBatchShare(uint32_t a_shareBatch)
{
return CreateBatchShare(a_shareBatch, m_batches[a_shareBatch].m_geometryChunk->m_pMaterial);
}
uint32_t DrawCanvas::CreateBatchShare(uint32_t a_shareBatch, RKMaterial* a_material)
{
// Redirect to the parent if already a shared batch
if (m_batches[a_shareBatch].m_shareIndex >= 0)
{
a_shareBatch = m_batches[a_shareBatch].m_shareIndex;
}
Batch & parentBatch = m_batches[a_shareBatch];
// Create the geometry chunk
RKGeometryChunk* newChunk = RKRender_CreateGeometryChunk(parentBatch.m_geometryChunk->m_pRenderLayer);
newChunk->m_pMaterial = a_material;
newChunk->m_pVB = parentBatch.m_geometryChunk->m_pVB;
newChunk->m_indexOffset = 0;
newChunk->m_maskFlag = 0;
newChunk->m_primitiveCount = 0;
newChunk->m_primitiveType = RKPT_TRIANGLELIST;
newChunk->m_sortPriority = 0.0f;
// Create the shared batch
Batch newBatch;
newBatch.m_geometryChunk = newChunk;
newBatch.m_shareIndex = a_shareBatch;
m_batches.Append(newBatch);
return m_batches.Size() - 1;
}
void DrawCanvas::SetUserVector(uint32_t a_batch, uint32_t a_vectorIndex, const RKVector& a_vectorData)
{
if (a_batch >= m_batches.Size() ||
a_vectorIndex >= RKMATERIAL_MAX_USER_VECTORS)
{
return;
}
m_batches[a_batch].m_geometryChunk->SetUserVector(a_vectorIndex, a_vectorData);
}
RKMaterial* DrawCanvas::GetBatchMaterial(uint32_t a_batch)
{
return m_batches[a_batch].m_geometryChunk->m_pMaterial;
}
void DrawCanvas::SetQuad(uint32_t a_batch, float a_x, float a_y, float a_w, float a_h,
float a_u, float a_v, float a_texWidth, float a_texHeight)
{
// Create a simple textured quad
Begin(a_batch);
Quad(a_x, a_y, a_w, a_h, a_u, a_v, a_texWidth, a_texHeight);
End();
}
void DrawCanvas::Begin(uint32_t a_batch)
{
RKASSERT(m_updateChunk == nullptr && m_updateVertexData == nullptr, "In existing update");
// Re-direct to a parent if necessary
if (m_batches[a_batch].m_shareIndex >= 0)
{
a_batch = m_batches[a_batch].m_shareIndex;
}
// Get the current chunk and reset
m_updateChunk = m_batches[a_batch].m_geometryChunk;
m_updateVertexCount = 0;
m_updateVertexData = static_cast<UpdateData*>(RKVertexBuffer_Lock(m_updateChunk->m_pVB));
// Set initial color and texture coord
m_updateData.m_color[0] = 1.0f;
m_updateData.m_color[1] = 1.0f;
m_updateData.m_color[2] = 1.0f;
m_updateData.m_color[3] = 1.0f;
m_updateData.m_texCoord[0] = 0.0f;
m_updateData.m_texCoord[1] = 0.0f;
}
void DrawCanvas::Texcoord(float a_u, float a_v)
{
RKASSERT(m_updateChunk != nullptr && m_updateVertexData != nullptr, "Not in buffer update");
m_updateData.m_texCoord[0] = a_u;
m_updateData.m_texCoord[1] = a_v;
}
void DrawCanvas::Color(float a_r, float a_g, float a_b, float a_a)
{
RKASSERT(m_updateChunk != nullptr && m_updateVertexData != nullptr, "Not in buffer update");
m_updateData.m_color[0] = a_r;
m_updateData.m_color[1] = a_g;
m_updateData.m_color[2] = a_b;
m_updateData.m_color[3] = a_a;
}
void DrawCanvas::Vertex(float a_x, float a_y, float a_z)
{
RKASSERT(m_updateChunk != nullptr && m_updateVertexData != nullptr, "Not in buffer update");
// Ignore if no more room in the buffer
if (m_updateVertexCount >= m_updateChunk->m_pVB->vertexCount)
{
return;
}
// Set the position
m_updateData.m_position[0] = a_x;
m_updateData.m_position[1] = a_y;
m_updateData.m_position[2] = a_z;
// Copy over the data
memcpy(m_updateVertexData, &m_updateData, sizeof(m_updateData));
m_updateVertexData++;
m_updateVertexCount++;
}
void DrawCanvas::Quad(float a_x, float a_y, float a_w, float a_h,
float a_u, float a_v, float a_texWidth, float a_texHeight)
{
Texcoord(a_u, a_v);
Vertex(a_x, a_y);
Texcoord(a_u, a_v - a_texHeight);
Vertex(a_x, a_y + a_h);
Texcoord(a_u + a_texWidth, a_v - a_texHeight);
Vertex(a_x + a_w, a_y + a_h);
Texcoord(a_u + a_texWidth, a_v);
Vertex(a_x + a_w, a_y);
}
void DrawCanvas::End()
{
RKASSERT(m_updateChunk != nullptr && m_updateVertexData != nullptr, "Not in buffer update");
// Unlock the buffer and reset
RKVertexBuffer_Unlock(m_updateChunk->m_pVB);
// Set the primitive count (2 triangles per quad)
m_updateChunk->m_primitiveCount = m_updateVertexCount / 2;
// Reset
m_updateChunk = nullptr;
m_updateVertexCount = 0;
m_updateVertexData = nullptr;
}
void DrawCanvas::Draw()
{
// NOTE: It is very important for thread safety that this is not called except from an object draw call
if (!m_isEnabled)
{
return;
}
// Loop for all batches and draw them
for (uint32_t i = 0; i < m_batches.Size(); i++)
{
const Batch & currBatch = m_batches[i];
RKGeometryChunk * currChunk = currBatch.m_geometryChunk;
// Apply the instance data to the chunk
// NOTE: The shaders in DrawCanvas access model-world as if it is is model-view-projection (so a custom camera is not required)
currChunk->m_matWorld = m_projMatrix * currBatch.m_mvMatrix;
currChunk->m_pIB = m_drawIndexBuffer;
// If the buffer is shared, update the draw count from the parent
if (currBatch.m_shareIndex >= 0)
{
RKGeometryChunk * shareChunk = m_batches[currBatch.m_shareIndex].m_geometryChunk;
currChunk->m_primitiveCount = shareChunk->m_primitiveCount;
}
// Render the chunk
if (currChunk->m_primitiveCount > 0)
{
RKRender_AddGeometryChunk(currChunk);
}
}
}
RKMaterial* DrawCanvas::CreateMaterialFromTexture(const char* a_textureName, bool a_useBlending)
{
// Create the material based on if there is alpha
RKMaterial* newMat = nullptr;
if (a_useBlending)
{
newMat = RKMaterial_Create(GetAlphaMaterialName(), false, false, "bneUI");
}
else
{
newMat = RKMaterial_Create(GetSolidMaterialName(), false, false, "bneUI");
}
// Set the texture on the material
newMat->SetTexture(0, a_textureName);
m_materials.Append(newMat);
return newMat;
}
const char *DrawCanvas::GetAlphaMaterialName()
{
return s_guiMatAlphaName;
}
const char *DrawCanvas::GetSolidMaterialName()
{
return s_guiMatName;
}