Newer
Older
Import / projects / Gameloft / bne_lib / code / GUI / DrawCanvas.cpp
#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;
}