// BlockyFroggy
// Copyright © 2017 John Ryland.
// All rights reserved.
// TODO: there is a real mix of levels of logic here
// Need to seperate out in to probably 3 classes or something
// 1) All the low level GL calls should be seperate
// 2) Voxel model related code seperated
// 3) game level logic - camera control - which models etc
#include "Graphics.h"
#include "SystemInformation.h"
#include "Shaders.h"
#include "Log.h"
#include "Debug.h"
#include <cstdlib>
#include <cstring>
#include <functional>
//#include "Font6.h"
//#include "Font7.h"
#include "PngImage.h"
#include "ResourceLoader.h"
#include "ObjModel.h"
#include "Utilities.h"
#ifdef USE_OPENGLES2
#include <OpenGLES/ES2/gl.h>
#include <OpenGLES/ES2/glext.h>
#include <GLKit/GLKMatrix4.h>
#include <GLKit/GLKMatrix3.h>
#include <GLKit/GLKMathUtils.h>
#endif
#include "TargetConditionals.h"
DECLARE_LOG_CONTEXT(GFX)
#define TIME_BASED 1
#define SHADOW_DIM 1024 // 2048 // 512
#define USE_ONE_BIG_VERTEX_ARRAY 0 // On iPhone6 it was slower to upload one big vert array per frame instead of having more draw calls but less to upload
/*
Potential ways to slice the code here:
Program class abstraction around a glProgram
Model class which abstracts vertex data
Camera class
Clipping
HUD
Some concepts:
Models
Scene - composition of models (varying formats etc)
Camera - players perspective (is it part of the scene?)
Lights - lighting data (also, is it part of the scene?)
Effects - last with alpha in sorted order with z-test and no z-writes
Shaders
Program
HUD
Submit scene to be rendered by shaders from camera, also from lights
Then submit HUD over the top
For lights, need to know the camera to work out the frustrum to find a light frustum that fits
*/
bool toggleDebugCameraView = false;
bool debugCameraView = false;
bool debugCollisions = false;
bool showDebugging = true;//false;
bool modelEditing = true;
#ifdef OLD_CAMERA
#if 0
const int cameraTransitionFrames = 30; // Fast for quicker debugging
const int cameraWaitFrames = 50; // Fast for quicker debugging
#else
const int cameraTransitionFrames = 150; // Nice value
const int cameraWaitFrames = 250;
#endif
int m_frame = 0;
#endif
const int vertSize = sizeof(PolyVertex);
const int fltsPerVert = vertSize / sizeof(GLfloat);
const int trisPerCube = 12;
const int vertsPerCube = 3*trisPerCube;
size_t m_hudVertexBufferSize = 0;
inline void addDebugString(GameUi::DrawItems& items, int line, int column, const char* string)
{
items.addContrastString(column*75, 90 + 19*line, string, 1);
items.addString( column*75, 90 + 19*line, string, 0xff00ff00, 1);
}
// One big triangle that covers the entire screen, each vertex is 2d (x,y)
GLfloat gOverlayVertexData[6] = { 1.0f, 1.0f, -3.0f, 1.0f, 1.0f, -3.0f };
GLfloat gBaseCubeVertexData[fltsPerVert * vertsPerCube];
uint32_t colorForObjectType(GameSim::ObjectType a_type)
{
uint32_t color = 0;
switch (a_type)
{
// Terrain
case GameSim::Ground: color = 0x47bd37; break;
case GameSim::Road: color = 0x6a6963; break;
case GameSim::Rail: color = 0x464441; break;
case GameSim::Water: color = 0x3a31f5; break;
case GameSim::WaterLane: color = 0x3a31f5; break;
// Obstacles
case GameSim::Tree: color = 0x508c23; break;
case GameSim::Shrub: color = 0x508c23; break;
case GameSim::Building: color = 0xd38b37; break;
case GameSim::Rocks: color = 0x5c4932; break;
// Vehicles
case GameSim::Bike: color = 0xdf1f24; break;
case GameSim::Car: color = 0xf52542; break;
case GameSim::Van: color = 0x3a4b88; break;
case GameSim::Truck: color = 0xdfd41f; break;
// Trains
case GameSim::Train1: color = 0x8bbb38; break;
case GameSim::Train2: color = 0xa2d051; break;
case GameSim::Train3: color = 0x6ca50a; break;
case GameSim::Train4: color = 0x0a53a5; break;
// Vessels
case GameSim::Log: color = 0x6b5331; break;
case GameSim::Boat: color = 0xd5d5d5; break;
case GameSim::Crocodile: color = 0x597e3e; break;
case GameSim::LilyPad: color = 0x71df1f; break;
case GameSim::SteppingStone: color = 0x71df1f; break;
case GameSim::Player: color = 0xffffff; break;
// Pickups
case GameSim::Coin: color = 0xffffff; break;
case GameSim::Food: color = 0xffffff; break;
case GameSim::Life: color = 0xffffff; break;
//case Bonus: color = 0xffffff; break;
default:
break;
}
return color;
}
void initCubeVerticesFromObject(PolyVertex* cubePtr, const GameSim::GameObject& obj, float scale)
{
uint32_t color = (obj.m_type == GameSim::Car) ? obj.m_color : colorForObjectType(obj.m_type);
for (int v = 0; v < 36; v++)
{
cubePtr->m_animParams.y = obj.m_w;
cubePtr->m_animParams.x = cubePtr->m_position.x - 0.5f;
cubePtr->m_normal.x += cubePtr->m_position.x;
cubePtr->m_normal.y += cubePtr->m_position.y;
cubePtr->m_normal.z += cubePtr->m_position.z;
cubePtr->m_position.x *= obj.m_w*scale;
cubePtr->m_position.y *= obj.m_h*scale;
cubePtr->m_position.z *= (obj.m_d < 0.0f) ? -obj.m_d*scale : obj.m_d*scale;
cubePtr->m_position.x += 0.5f*obj.m_w*scale;
cubePtr->m_position.y += 0.5f*obj.m_h*scale;
cubePtr->m_position.z += (obj.m_d < 0.0f) ? -0.5f*obj.m_d*scale : 0.5f*obj.m_d*scale;
#if TIME_BASED
cubePtr->m_position.x += obj.m_x*scale;
cubePtr->m_position.y += obj.m_y*scale;
cubePtr->m_position.z += obj.m_z*scale;
#endif
cubePtr->m_delta.x = obj.m_dx;
cubePtr->m_delta.y = obj.m_dy;
cubePtr->m_delta.z = 0.0f;
cubePtr->m_color.r = GLfloat((color >> 16) & 0xff) / 255.0;
cubePtr->m_color.g = GLfloat((color >> 8) & 0xff) / 255.0;
cubePtr->m_color.b = GLfloat((color >> 0) & 0xff) / 255.0;
cubePtr->m_color.a = 1.0f;
cubePtr++;
}
}
void initCubeVerticesFromVoxel(PolyVertex* cubePtr, const VoxelBox& obj, float scale)
{
uint32_t color = obj.m_color;
for (int v = 0; v < 36; v++)
{
cubePtr->m_animParams.y = obj.m_w;
cubePtr->m_animParams.x = cubePtr->m_position.x - 0.5f;
cubePtr->m_position.x *= obj.m_w*scale;
cubePtr->m_position.y *= obj.m_h*scale;
cubePtr->m_position.z *= (obj.m_d < 0.0f) ? -obj.m_d*scale : obj.m_d*scale;
cubePtr->m_position.x += 0.5*obj.m_w*scale;
cubePtr->m_position.y += 0.5*obj.m_h*scale;
cubePtr->m_position.z += (obj.m_d < 0.0f) ? -0.5f*obj.m_d*scale : 0.5f*obj.m_d*scale;
cubePtr->m_position.x += obj.m_x*scale;
cubePtr->m_position.y += obj.m_y*scale;
cubePtr->m_position.z += obj.m_z*scale;
cubePtr->m_delta.x = 0.0f;
cubePtr->m_delta.y = 0.0f;
cubePtr->m_delta.z = 0.0f;
cubePtr->m_color.r = GLfloat((color >> 16) & 0xff) / 255.0;
cubePtr->m_color.g = GLfloat((color >> 8) & 0xff) / 255.0;
cubePtr->m_color.b = GLfloat((color >> 0) & 0xff) / 255.0;
cubePtr->m_color.a = 1.0f;
cubePtr++;
}
}
void GameGraphics::resetVertexBuffers(const std::vector<GameSim::GameObjectList*>& a_objectLists /*,
const Game::GameObjectList& a_carModels*/ )
{
m_scene.reset(a_objectLists);
#ifdef OLD_CAMERA
m_frame = 0;
#endif
size_t m_cubeCount = 0;
m_cubeCount = m_scene.m_rest.size();
// for (Game::GameObjectList* objList : a_objectLists)
// if (objList->back().m_type != Game::Player)
// m_cubeCount += objList->size();
size_t m_triCount = 12 * m_cubeCount;
size_t vertCount = 3 * m_triCount;
m_staticPolyVertexArray.m_vertexData.clear();
m_staticPolyVertexArray.m_vertexData.resize(vertCount);
// A cube instance
// Probably don't need to do this every reset
Cube cube;
memcpy(gBaseCubeVertexData, cube.m_baseCubeData.data(), vertSize*vertsPerCube);
size_t index = 0;
/*
for (Game::GameObjectList* objList : a_objectLists)
{
if (objList->back().m_type != Game::Player)
for (unsigned i = 0; i < objList->size(); i++)
{
const Game::GameObject& obj = objList->at(i);
*/
{
for (const GameSim::GameObject& obj : m_scene.m_rest)
{
PolyVertex* cubePtr = &m_staticPolyVertexArray.m_vertexData[index*vertsPerCube];
memcpy(cubePtr, gBaseCubeVertexData, vertSize*vertsPerCube);
initCubeVerticesFromObject(cubePtr, obj, 1.0);
index++;
}
}
}
GameGraphics::GameGraphics()
{
m_viewMap["RotatingFrog"] = std::bind(&GameGraphics::drawRotatingFrog, this);
m_viewMap["ModelEditView"] = std::bind(&GameGraphics::drawModelEditLayer, this);
m_viewMap["RotatingModelPreview"] = std::bind(&GameGraphics::drawModelRotatingPreview, this);
m_viewMap["CaroselTest"] = std::bind(&GameGraphics::drawCaroselTest, this);
}
GameGraphics::~GameGraphics()
{
}
// Uniform index.
enum
{
UNIFORM_HUD_TEXTURE,
UNIFORM_HUD_INV_SCREEN,
NUM_UNIFORMS
};
GLint uniforms[NUM_UNIFORMS];
// Attribute index.
enum
{
ATTRIB_VERTEX,
ATTRIB_COLOR,
ATTRIB_CHAR,
ATTRIB_TEXTURE_IDX,
NUM_ATTRIBUTES
};
/*
struct Program
{
GLProgram m_program;
// ... uniforms ....
};
*/
ObjScene frogModel;
//bool frogModelLoaded = false;
uint8_t hudTextureIdx = 0;
uint8_t frogModelTextureIdx = 0;
uint8_t vehiclesTextureIdx1 = 0;
uint8_t vehiclesTextureIdx2 = 0;
uint8_t vehiclesTextureIdx3 = 0;
uint8_t rampTextureIdx1 = 0;
uint8_t rampTextureIdx2 = 0;
uint8_t playersTextureIdx1 = 0;
uint8_t playersTextureIdx2 = 0;
uint8_t playersTextureIdx3 = 0;
void GameGraphics::reloadShaders()
{
m_normalContext.setup(s_vertexShader, s_fragmentShader, Medium, m_options);
m_shadowContext.setup(s_shadowVertexShader, s_shadowFragmentShader, Medium, m_options);
// Setup hud program
m_hudProgram.deleteProgram();
m_hudProgram.loadShaders(s_hudVertexShader, s_hudFragmentShader, Medium, m_options,
[](GLuint a_program) {
auto bindAttribFunc = [a_program](const char* name, GLuint index, GLsizei, GLenum, GLboolean, GLsizei, const void*) {
glBindAttribLocation(a_program, index, name);
};
GameUi::UiVertex::visit(bindAttribFunc);
}, [](GLuint a_program) {
uniforms[UNIFORM_HUD_TEXTURE] = glGetUniformLocation(a_program, "texture");
uniforms[UNIFORM_HUD_INV_SCREEN] = glGetUniformLocation(a_program, "invScreen");
});
}
void objToVertexBuffer(std::vector<PolyVertex> &objVerts, const std::vector<Object>& obj)
{
size_t vertCount = 0;
for (const Object& o : obj)
vertCount += o.faces.size() * 3;
objVerts.resize(vertCount);
int x = 0;
for (const Object& o : frogModel.objects) {
for (const Face& f : o.faces) {
for (int i = 0; i < 3; i++) {
Position v = { 0.0f, 0.0f, 0.0f };
size_t vIdx = f.indices[i].vertexIndex;
if (vIdx && o.vertices.size() >= vIdx)
v = o.vertices[vIdx-1];
Normal n = { 0.0f, 1.0f, 0.0f };
size_t nIdx = f.indices[i].normalIndex;
if (nIdx && o.normals.size() >= nIdx)
n = o.normals[nIdx-1];
UV uv = { 0.0f, 0.0f };
size_t uIdx = f.indices[i].uvIndex;
if (uIdx && o.uvs.size() >= uIdx)
uv = o.uvs[uIdx-1];
objVerts[x].m_animParams.x = 0.0f;
objVerts[x].m_animParams.y = 0.0f;
objVerts[x].m_animParams.z = 0.0f;
// X,Y,Z
objVerts[x].m_position.x = -v.y * 1.5f + 3.0f;
objVerts[x].m_position.y = v.x * 1.5f + 0.3f;
objVerts[x].m_position.z = v.z * 1.5f + 6.0f;
// DX,DY,DZ
objVerts[x].m_delta.x = 0.0f;
objVerts[x].m_delta.y = 0.0f;
objVerts[x].m_delta.z = 0.0f;
// Normal
objVerts[x].m_normal.x = -n.y;
objVerts[x].m_normal.y = n.x;
objVerts[x].m_normal.z = n.z;
// RGB
objVerts[x].m_color.r = uv.u;
objVerts[x].m_color.g = 1.0f - uv.v;
objVerts[x].m_color.b = float(frogModelTextureIdx - 0);
objVerts[x].m_color.a = 0.0f;
// Barycentric
objVerts[x].m_barycentric.x = (i==0) ? 1.0f : 0.0f;
objVerts[x].m_barycentric.y = (i==1) ? 1.0f : 0.0f;
objVerts[x].m_barycentric.z = (i==2) ? 1.0f : 0.0f;
x++;
}
}
}
}
void GameGraphics::initialize()
{
// Setup default options
m_options["debugTriangles"] = false;
#if TARGET_IPHONE_SIMULATOR
m_options["enableShadows"] = false;//true;// false;
m_options["enableLighting"] = false;// true;//false;//true;
#else
m_options["enableShadows"] = true;//true;// false;
m_options["enableLighting"] = true;// true;//false;//true;
#endif
m_options["enableTextures"] = true;// true;//false;//true;
m_options["enableShadowAttenuation"] = true;//false;//true;
m_options["enableDistanceFog"] = false;//true;//false;//true;
m_options["enableRampShading"] = false;//true;//false;//true;
m_options["enableDarkenedSides"] = false;//true;//false;//true;
m_options["enableBevels"] = false;// true;//false;//true;
glEnable(GL_DEPTH_TEST);
//glClearDepthf(1.0f);
glClearColor(0.65f, 0.65f, 0.65f, 1.0f);
glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&m_defaultFramebufferId);
//glGenFramebuffers(1, &m_defaultFramebufferId);
//glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebufferId);
// glFramebufferTexture2D(GL_FRAMEBUFFER, GL_COLOR_ATTACHMENT0, GL_TEXTURE_2D, m_shadowTexId, 0);
//glBindFramebuffer(GL_FRAMEBUFFER, 0);
//m_vertBufferSize = vertSize * m_staticPolyVertexArray.m_vertexData.size();
reloadShaders();
m_cuboidVertexList.createVertexBuffer();
m_staticPolyVertexArray.createVertexBuffer();
m_dynamicPolyVertexArray.createVertexBuffer();
for (int i = 0; i < MODEL_COUNT; i++)
m_modelsVertexArrays[i].createVertexBuffer();
for (int i = 0; i < PLAYER_MODEL_COUNT; i++)
m_playerModelsVertexArrays[i].createVertexBuffer();
#if USE_ONE_BIG_VERTEX_ARRAY
m_combinedModelsVertexArrays.createVertexBuffer();
#endif
bool okay = readObjFile(frogModel, "froggy.obj", [this](bool okay){
LogTag(LL_Info, "OBJ", "Loaded test model called back with %s", okay ? "okay" : "not okay");
if (!okay)
// Might want to set a flag to retry loading later
return;
// The conversion of the frog model from obj to triangle soup can be done once off
// Perhaps a uniform to position the frog as player
objToVertexBuffer(m_dynamicPolyVertexArray.m_vertexData, frogModel.objects);
m_dynamicPolyVertexArray.sync();
});
LogTag(LL_Info, "OBJ", "Loading test model started %s", okay ? "okay" : "not okay");
if (m_hudEnabled)
{
glGenVertexArraysOES(1, &m_auxVertexArray);
glBindVertexArrayOES(m_auxVertexArray);
glGenBuffers(1, &m_auxVertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, m_auxVertexBuffer);
GameUi::UiVertex::visit(AttribPointerVisitor);
glGenVertexArraysOES(1, &m_hudVertexArray);
glBindVertexArrayOES(m_hudVertexArray);
glGenBuffers(1, &m_hudVertexBuffer);
glBindBuffer(GL_ARRAY_BUFFER, m_hudVertexBuffer);
GameUi::UiVertex::visit(AttribPointerVisitor);
glBindVertexArrayOES(0);
glGenTextures(1, &m_hudTexId);
glBindTexture(GL_TEXTURE_2D, m_hudTexId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);//_MIPMAP_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
/*
std::vector<unsigned char> fontData;
unsigned long fontW, fontH;
int error = ::decodePNG(fontData, fontW, fontH, Font7_png, Font7_png_len);
if (error != 0 || fontW != 256 || fontH != 256)
Log(LL_Error, "error: %d", error);
*/
glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 2048, 2048, 0, GL_RGBA, GL_UNSIGNED_BYTE, nullptr);
//glTexSubImage2D(GL_TEXTURE_2D, 0, 0, 0, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, (const GLvoid *)&fontData[0]);
}
if (m_shadowsEnabled)
{
// Depth texture. Slower than a depth buffer, but you can sample it later in your shader
glGenTextures(1, &m_shadowTexId);
glBindTexture(GL_TEXTURE_2D, m_shadowTexId);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_NEAREST);
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER, GL_LINEAR);//GL_NEAREST);
//glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER, GL_LINEAR_MIPMAP_LINEAR);//GL_NEAREST);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, GL_CLAMP_TO_EDGE);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, GL_CLAMP_TO_EDGE);
//glTexParameteri(GL_TEXTURE_2D, GL_GENERATE_MIPMAP, GL_TRUE); // automatic mipmap
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_FUNC_EXT, GL_LEQUAL);
// glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_COMPARE_MODE_EXT, GL_COMPARE_REF_TO_TEXTURE_EXT);
//glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT16, 1024, 1024, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, 0);
glTexImage2D(GL_TEXTURE_2D, 0, GL_DEPTH_COMPONENT, SHADOW_DIM, SHADOW_DIM, 0, GL_DEPTH_COMPONENT, GL_UNSIGNED_INT, 0);
// glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA8, SHADOW_DIM, SHADOW_DIM, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
//glTexImage2D(GL_TEXTURE_2D, 0, GL_RGBA, 1024, 1024, 0, GL_RGBA, GL_UNSIGNED_BYTE, 0);
glBindTexture(GL_TEXTURE_2D, 0);
/*
// create a renderbuffer object to store depth info
GLuint rboId;
glGenRenderbuffers(1, &rboId);
glBindRenderbuffer(GL_RENDERBUFFER, rboId);
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT, SHADOW_DIM, SHADOW_DIM);
*/
// The framebuffer, which regroups 0, 1, or more textures, and 0 or 1 depth buffer.
glGenFramebuffers(1, &m_shadowFramebufferId);
glBindFramebuffer(GL_FRAMEBUFFER, m_shadowFramebufferId);
//glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, rboId);
glFramebufferTexture2D(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_TEXTURE_2D, m_shadowTexId, 0);
//glBindRenderbuffer(GL_RENDERBUFFER, 0);
//glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH_COMPONENT16, 1024, 1024);
//glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_ATTACHMENT, GL_RENDERBUFFER, m_shadowFramebufferId);
//GLenum bufs[1] = {GL_NONE};
//glDrawBuffers(1, bufs); // No color buffer is drawn to.
/*
#ifndef USE_OPENGLES2
// gllsBuffer ?
glDrawBuffer(GL_NONE); // No color buffer is drawn to.
glReadBuffer(GL_NONE); // No color buffer is read from.
#endif
*/
// Always check that our framebuffer is ok
GLenum res = glCheckFramebufferStatus(GL_FRAMEBUFFER);
if (res != GL_FRAMEBUFFER_COMPLETE)
{
Log(LL_Error, "Error setting up FBO, error: %i\n", res);
}
glBindFramebuffer(GL_FRAMEBUFFER, 0);
}
// This finds the texture of the frog model, loads it from file, then loads it to the GPU in a texture slot
GameUi::UpdateState items;
//frogModelTextureIdx = items.getTextureIdForAsset(frogModel.materialLibrary[frogModel.objects[0].materialName].map_Kd.c_str());
//vehiclesTextureIdx = items.getTextureIdForAsset("models-texture.png");
hudTextureIdx = items.getTextureIdForAsset("ui.png");
vehiclesTextureIdx1 = items.getTextureIdForAsset("models.png");
rampTextureIdx1 = items.getTextureIdForAsset("ramp.png");
//rampTextureIdx2 = items.getTextureIdForAsset("ramp2.png");
rampTextureIdx2 = items.getTextureIdForAsset("tile-map.png");
frogModelTextureIdx = items.getTextureIdForAsset("froggy-spots.png");
frogModelTextureIdx = items.getTextureIdForAsset("froggy-strips.png");
frogModelTextureIdx = items.getTextureIdForAsset("froggy-common.png");
for (auto job : items.m_loadTexturesQueue)
LoadPendingTexture(job);
// Just reserve these texture slots, don't actually load anything in to them
vehiclesTextureIdx2 = items.getTextureIdForAsset("models-x-axis");
vehiclesTextureIdx3 = items.getTextureIdForAsset("models-z-axis");
playersTextureIdx1 = items.getTextureIdForAsset("player-models.png");
playersTextureIdx2 = items.getTextureIdForAsset("player-models-x-axis");
playersTextureIdx3 = items.getTextureIdForAsset("player-models-z-axis");
m_models.reloadDataFromFile("models.png", true);
m_playerModels.reloadDataFromFile("player-models.png", true);
}
void GameGraphics::shutdown()
{
m_staticPolyVertexArray.destroyVertexBuffer();
m_dynamicPolyVertexArray.destroyVertexBuffer();
m_cuboidVertexList.destroyVertexBuffer();
// TODO: car models
glDeleteVertexArraysOES(1, &m_hudVertexArray);
m_normalContext.shutdown();
m_shadowContext.shutdown();
}
// May need a way to collect unused textures to free up space
void GameGraphics::LoadPendingTexture(GameUi::TextureLoadJob& job)
{
int idx = job.texIdx;
//Resource* res = LoadFile(job.assetName.c_str(), true);
loadFileAsync(job.assetName.c_str(), true, [idx, this](Resource* res) {
if (res && res->data.size() > 1) {
glBindTexture(GL_TEXTURE_2D, m_hudTexId);
std::vector<unsigned char> texData;
unsigned long texW, texH;
int error = ::decodePNG(texData, texW, texH, res->data.data(), res->data.size());
if (error != 0 || texW != 256 || texH != 256)
Log(LL_Error, "error: %d", error);
// job.texIdx;
glTexSubImage2D(GL_TEXTURE_2D, 0, (idx%8)*256, (idx/8)*256, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, (const GLvoid *)&texData[0]);
// glTexSubImage2D(GL_TEXTURE_2D, 0, 256, 0, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, (const GLvoid *)&texData[0]);
}
});
}
void GameGraphics::loadVoxelModel(VertexArray<PolyVertex>& vertexArray, const char* name, int x, int y, int w, int h, int d, bool direction, int scaling, bool asCubes,
int yClamp0, int yClamp1)
{
float modelScale = 1.0f;
// int scaling = 8;
// loading asCubes is inefficient, but could have benefits if voxel editing just one model
if (asCubes)
{
VoxelBoxList& voxelModel = m_models.getModel(name, x, y, w, h, d, direction, m_model_x_axis, m_model_z_axis);
vertexArray.m_vertexData.clear();
int size = 0;
for (unsigned i = 0; i < voxelModel.size(); i++)
if (voxelModel.at(i).m_z >= yClamp0 && voxelModel.at(i).m_z < yClamp1)
size++;
vertexArray.m_vertexData.resize(size * 12 * 3);
int cube = 0;
for (unsigned i = 0; i < voxelModel.size(); i++)
{
if (voxelModel.at(i).m_z >= yClamp0 && voxelModel.at(i).m_z < yClamp1)
{
PolyVertex* cubePtr = &vertexArray.m_vertexData[cube*vertsPerCube];
memcpy(cubePtr, gBaseCubeVertexData, vertSize*vertsPerCube);
initCubeVerticesFromVoxel(cubePtr, voxelModel.at(i), modelScale);
cube++;
}
}
return;
}
if (scaling == 2)
{
modelScale = 2.0f;
x /= 2;
x += 128;
y /= 2;
y += 128;
w /= 2;
h /= 2;
d /= 2;
}
else if (scaling == 4)
{
modelScale = 4.0f;
x /= 4;
x += 128 + 64;
y /= 4;
y += 128 + 64;
w /= 4;
h /= 4;
d /= 4;
}
else if (scaling == 8)
{
modelScale = 8.0f;
x /= 8;
x += 128 + 64 + 32;
y /= 8;
y += 128 + 64 + 32;
w /= 8;
h /= 8;
d /= 8;
}
// TODO: is this needed?
if (m_model_x_axis == nullptr) {
m_model_x_axis = new uint32_t[256*256];
for (int j = 0; j < 256; j++)
for (int i = 0; i < 256; i++)
m_model_x_axis[j*256+i] = (0xff<<24)|(i<<16)|(j<<8);
glBindTexture(GL_TEXTURE_2D, m_hudTexId);
glTexSubImage2D(GL_TEXTURE_2D, 0, (vehiclesTextureIdx2%8)*256, (vehiclesTextureIdx2/8)*256, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, (const GLvoid *)m_model_x_axis);
}
if (m_model_z_axis == nullptr) {
m_model_z_axis = new uint32_t[256*256];
for (int j = 0; j < 256; j++)
for (int i = 0; i < 256; i++)
m_model_x_axis[j*256+i] = (0xff<<24)|(i<<8)|j;
glBindTexture(GL_TEXTURE_2D, m_hudTexId);
glTexSubImage2D(GL_TEXTURE_2D, 0, (vehiclesTextureIdx3%8)*256, (vehiclesTextureIdx3/8)*256, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, (const GLvoid *)m_model_z_axis);
}
VoxelTriangleList& voxelModel = m_models.getModelTriangles(name, x, y, w, h, d, direction, m_model_x_axis, m_model_z_axis);
vertexArray.m_vertexData.clear();
vertexArray.m_vertexData.resize(voxelModel.size() * 3);
unsigned i = 0;
for (unsigned t = 0; t < voxelModel.size(); t++)
{
const VoxelTriangle& tri = voxelModel.at(t);
if (tri.m_verts[0].m_z >= yClamp0 && tri.m_verts[0].m_z < yClamp1) {
// lookup the normal based on the axis
float normals[6][4] = {
{ 0.0,0.0,-1.0,0.0 },
{ -1.0,0.0,0.0,0.0 },
{ 0.0,-1.0,0.0,0.0 },
{ 0.0,0.0,1.0,0.0 },
{ 1.0,0.0,0.0,0.0 },
{ 0.0,1.0,0.0,0.0 },
};
int texIdxs[6] = {
vehiclesTextureIdx3,
vehiclesTextureIdx2,
vehiclesTextureIdx1,
vehiclesTextureIdx3,
vehiclesTextureIdx2,
vehiclesTextureIdx1,
};
float normal[4];
memcpy(normal, normals[tri.m_axis], sizeof(float)*4);
int texIdx;// = vehiclesTextureIdx1;
texIdx = texIdxs[tri.m_axis];
/*
// calculate the normal
float vt[2][4];
vt[0][0] = tri.m_verts[1].m_x - tri.m_verts[0].m_x;
vt[0][1] = tri.m_verts[1].m_y - tri.m_verts[0].m_y;
vt[0][2] = tri.m_verts[1].m_z - tri.m_verts[0].m_z;
vt[1][0] = tri.m_verts[1].m_x - tri.m_verts[2].m_x;
vt[1][1] = tri.m_verts[1].m_y - tri.m_verts[2].m_y;
vt[1][2] = tri.m_verts[1].m_z - tri.m_verts[2].m_z;
Math::normalizeVector(vt[0]);
Math::normalizeVector(vt[1]);
float normal[4];
Math::crossProduct(normal, vt[1], vt[0]);
*/
for (int v = 0; v < 3; v++)
{
PolyVertex& vert = vertexArray.m_vertexData[i*3 + v];
vert.m_animParams.y = 0.0;//obj.m_w;
vert.m_animParams.x = 0.0;//cubePtr->m_position.x - 0.5;
vert.m_position.x = tri.m_verts[v].m_x * modelScale;
vert.m_position.y = tri.m_verts[v].m_y * modelScale;
vert.m_position.z = tri.m_verts[v].m_z * modelScale;
vert.m_barycentric.x = (v == 0) ? 1.0 : 0.0;
vert.m_barycentric.y = (v == 1) ? 1.0 : 0.1;
vert.m_barycentric.z = (v == 2) ? 1.0 : 0.0;
if ( y == 128 || (tri.m_axis%3) == 0) // roads and stuff
{
vert.m_normal.x = normal[0];
vert.m_normal.y = normal[1];
vert.m_normal.z = normal[2];
}
else
{
vert.m_normal.x = normal[0]*0.5 + 0.5;
vert.m_normal.y = normal[1]*0.5 + 0.5;
vert.m_normal.z = normal[2]*0.5 + 0.5;
vert.m_normal.x += 1.2*(vert.m_position.x - w/2)/w;
vert.m_normal.y += 1.2*(vert.m_position.y - d/2)/d;
vert.m_normal.z += 1.2*(vert.m_position.z - h/2)/h;
/*
vert.m_normal.x = normal[0];
vert.m_normal.y = normal[1];
vert.m_normal.z = normal[2];
vert.m_normal.x = 2.0*(vert.m_position.x - w/2)/w;
vert.m_normal.y = 2.0*(vert.m_position.y - d/2)/d;
vert.m_normal.z = 2.0*(vert.m_position.z - h/2)/h;
*/
/*
vert.m_normal.x = vert.m_position.x - w/2;
vert.m_normal.y = vert.m_position.y - d/2;
vert.m_normal.z = vert.m_position.z - h/2;
*/
}
// Math::normalizeVector(&vert.m_normal.x);
vert.m_delta.x = 0.0f;
vert.m_delta.y = 0.0f;
vert.m_delta.z = 0.0f;
vert.m_color.r = (GLfloat(tri.m_uvs[v].m_u)-0.0) / 256.0;
vert.m_color.g = (GLfloat(tri.m_uvs[v].m_v)+0.0) / 256.0;
vert.m_color.b = GLfloat(texIdx);
vert.m_color.a = 0.0f;
}
}
i++;
}
}
struct ModelInfo {
const char* name;
int x, y;
int w, h, d;
bool direction;
int scaling;
};
ModelInfo g_modelInfos[MODEL_COUNT] = {
{ "car-left", 0, 0, 16, 16, 8, false , 1 },
{ "car-right", 0, 0, 16, 16, 8, true , 1 },
{ "bus-left", 16, 0, 32, 16, 8, false , 1 },
{ "bus-right", 16, 0, 32, 16, 8, true , 1 },
{ "bike-left", 48, 0, 16, 16, 8, false , 1 },
{ "bike-right", 48, 0, 16, 16, 8, true , 1 },
{ "van-left", 64, 0, 16, 16, 8, false , 1 },
{ "van-right", 64, 0, 16, 16, 8, true , 1 },
{ "trk-left", 80, 0, 48, 16, 8, false , 1 },
{ "trk-right", 80, 0, 48, 16, 8, true , 1 },
{ "trn-left", 128, 0, 128, 16, 8, false , 1 },
{ "trn-right", 128, 0, 128, 16, 8, true , 1 },
{ "road", 0, 128, 16, 8,16, true , 1 },
{ "water", 16, 128, 16, 8,16, true , 1 },
{ "tracks", 32, 128, 16, 8,16, true , 1 },
{ "road-2", 48, 128, 16, 8,16, true , 1 },
{ "tree", 64, 128, 16, 16, 8, true , 1 },
{ "shrub", 64+16*2, 128, 16, 16, 8, true , 1 },
{ "building", 64+16*3, 128, 16, 16, 8, true , 1 },
{ "rock", 64+16*4, 128, 16, 16, 8, true , 1 },
{ "logs", 64+16*5, 128, 16, 16, 8, false , 1 },
{ "logs", 64+16*5, 128, 16, 16, 8, true , 1 },
{ "boats", 64+16*6, 128, 16, 16, 8, false , 1 },
{ "boats", 64+16*6, 128, 16, 16, 8, true , 1 },
{ "crocs", 64+16*7, 128, 16, 16, 8, false , 1 },
{ "crocs", 64+16*7, 128, 16, 16, 8, true , 1 },
{ "edit-slot", 80, 128, 16, 16, 8, true , 1 },
};
/*
void GetBool(const GameUi::DrawItems& a_uiItems, const char* name)
{
if (a_uiItems.m_variables.count(name))
if (a_uiItems.m_variables.at(name) == "on")
return true;
return false;
}
*/
// Doesn't change the value if it is not in a_uiItems, otherwise sets it to what it is set as
bool MapBool(bool& var, const GameUi::UpdateState& state, const char* name)
{
if (state.m_variables.count(name)) {
std::string str = state.m_variables.at(name);
if (str == "on" && !var) {
var = true;
return true;
}
if (str == "off" && var) {
var = false;
return true;
}
}
return false;
}
void GameGraphics::prepareState(GameUi::UpdateState& a_state)
{
a_state.m_variables["DEBUG_TRIANGLES"] = m_options["debugTriangles"] ? "on" : "off";
a_state.m_variables["ENABLE_SHADOWS"] = m_options["enableShadows"] ? "on" : "off";
a_state.m_variables["ENABLE_SHADOW_ATTN"] = m_options["enableShadowAttenuation"] ? "on" : "off";
a_state.m_variables["ENABLE_DISTANCE_FOG"] = m_options["enableDistanceFog"] ? "on" : "off";
a_state.m_variables["ENABLE_RAMP_SHADING"] = m_options["enableRampShading"] ? "on" : "off";
a_state.m_variables["ENABLE_DARKENED_SIDES"] = m_options["enableDarkenedSides"] ? "on" : "off";
a_state.m_variables["ENABLE_TEXTURES"] = m_options["enableTextures"] ? "on" : "off";
a_state.m_variables["ENABLE_BEVELS"] = m_options["enableBevels"] ? "on" : "off";
a_state.m_variables["ENABLE_LIGHTING"] = m_options["enableLighting"] ? "on" : "off";
}
void GameGraphics::update(float a_elapsed, float a_aspect, const GameUi::UpdateState& a_state, float a_playerPos[3]
, float a_cameraStyle[8], float a_light[8], float a_debugCamera[8])
{
MapBool(debugCameraView, a_state, "DEBUG_CAMERA");
MapBool(debugCollisions, a_state, "DEBUG_COLLISIONS");
MapBool(showDebugging, a_state, "DEBUG");
MapBool(modelEditing, a_state, "MODEL_EDITING");
bool needToReloadShaders = false;
needToReloadShaders |= MapBool(m_options["debugTriangles"], a_state, "DEBUG_TRIANGLES");
needToReloadShaders |= MapBool(m_options["enableShadows"], a_state, "ENABLE_SHADOWS");
needToReloadShaders |= MapBool(m_options["enableShadowAttenuation"], a_state, "ENABLE_SHADOW_ATTN");
needToReloadShaders |= MapBool(m_options["enableDistanceFog"], a_state, "ENABLE_DISTANCE_FOG");
needToReloadShaders |= MapBool(m_options["enableRampShading"], a_state, "ENABLE_RAMP_SHADING");
needToReloadShaders |= MapBool(m_options["enableDarkenedSides"], a_state, "ENABLE_DARKENED_SIDES");
needToReloadShaders |= MapBool(m_options["enableTextures"], a_state, "ENABLE_TEXTURES");
needToReloadShaders |= MapBool(m_options["enableBevels"], a_state, "ENABLE_BEVELS");
needToReloadShaders |= MapBool(m_options["enableLighting"], a_state, "ENABLE_LIGHTING");
if (needToReloadShaders)
reloadShaders();
//Log(LL_Trace, "debug = %s", debugStr.c_str());
// allow it to be changed by key strokes too
if (toggleDebugCameraView)
debugCameraView = !debugCameraView;
const bool reloadModels = true;
if (reloadModels && m_models.dataUpdated())
{
m_models.ackDataUpdated();
for (int i = 0; i < MODEL_COUNT; i++) {
ModelInfo &mi = g_modelInfos[i];
loadVoxelModel(m_modelsVertexArrays[i], mi.name, mi.x, mi.y, mi.w, mi.h, mi.d, mi.direction, mi.scaling, (i == (MODEL_COUNT-1)));
m_modelsVertexArraysCopy[i] = m_modelsVertexArrays[i];
}
glBindTexture(GL_TEXTURE_2D, m_hudTexId);
glTexSubImage2D(GL_TEXTURE_2D, 0, (vehiclesTextureIdx1%8)*256, (vehiclesTextureIdx1/8)*256, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, (const GLvoid *)m_models.y_axis());
glTexSubImage2D(GL_TEXTURE_2D, 0, (vehiclesTextureIdx2%8)*256, (vehiclesTextureIdx2/8)*256, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, (const GLvoid *)m_model_x_axis);
glTexSubImage2D(GL_TEXTURE_2D, 0, (vehiclesTextureIdx3%8)*256, (vehiclesTextureIdx3/8)*256, 256, 256, GL_RGBA, GL_UNSIGNED_BYTE, (const GLvoid *)m_model_z_axis);
}
m_staticPolyVertexArray.sync();
for (int i = 0; i < MODEL_COUNT; i++)
m_modelsVertexArrays[i].sync();
#if USE_ONE_BIG_VERTEX_ARRAY
struct ObjMap {
const GameSim::GameObjectList& obj;
int modelIndex;
} objsMap[] = {
{ m_scene.m_cars, 0},
{ m_scene.m_bikes, 4},
{ m_scene.m_vans, 6},
{ m_scene.m_trucks, 8},
{ m_scene.m_trains, 10},
{ m_scene.m_trees, 16},
{ m_scene.m_shrubs, 17},
{ m_scene.m_buildings, 18},
{ m_scene.m_rocks, 19},
{ m_scene.m_logs, 20},
{ m_scene.m_boats, 22},
{ m_scene.m_crocs, 24},
};
// build up the new combined vertex arrays and sync
m_combinedModelsVertexArrays.m_vertexData.clear();
m_combinedModelsVertexArrays.m_vertexData.shrink_to_fit();
auto combiner = [this](const GameSim::GameObjectList& a_objs, int a_voxelModelIndex, int zoff, int wid) {
for (const GameSim::GameObject& obj : a_objs) {
for (int j = 0; j < int(obj.m_h/10.0); j++)
{
for (int i = 0; i < 1000/wid; i++)
{
float x = obj.m_x + i*wid;
if (!m_camera.isClipped(x, obj.m_y + j*10.0f + 1.0f, obj.m_z + zoff, wid, obj.m_h, obj.m_d))
{
// optimized version which moves invariant out of the loop
float pos[4] = { x, obj.m_y + j*10.0f + 0.0f, obj.m_z + zoff, 0.0f };
size_t oldSize = m_combinedModelsVertexArrays.m_vertexData.size();
size_t appendSize = m_modelsVertexArraysCopy[a_voxelModelIndex].m_vertexData.size();
m_combinedModelsVertexArrays.m_vertexData.resize(oldSize + appendSize);
for (int j = 0; j < appendSize; j++) {
auto newVertex = m_modelsVertexArraysCopy[a_voxelModelIndex].m_vertexData[j];
for (int t = 0; t < 3; t++)
newVertex.m_position.attrib[t] += pos[t];
m_combinedModelsVertexArrays.m_vertexData[oldSize + j] = newVertex;
}
}
}
}
}
};
for (int i = 0; i < sizeof(objsMap)/sizeof(ObjMap); i++)
{
int modelIndex = objsMap[i].modelIndex;
for (const GameSim::GameObject& obj : objsMap[i].obj) {
float x = Math::modulus(obj.m_x + obj.m_dx * m_time, 1000.0f + obj.m_w);
if (!m_camera.isClipped(x, obj.m_y, obj.m_z, obj.m_w, obj.m_h, obj.m_d))
{
float pos[4] = { x, obj.m_y, obj.m_z, 0.0f };
size_t oldSize = m_combinedModelsVertexArrays.m_vertexData.size();
if (obj.m_dx <= 0.0) {
size_t appendSize = m_modelsVertexArraysCopy[modelIndex].m_vertexData.size();
m_combinedModelsVertexArrays.m_vertexData.resize(oldSize + appendSize);
for (int j = 0; j < appendSize; j++) {
auto newVertex = m_modelsVertexArraysCopy[modelIndex].m_vertexData[j];
for (int t = 0; t < 3; t++)
newVertex.m_position.attrib[t] += pos[t];
m_combinedModelsVertexArrays.m_vertexData[oldSize + j] = newVertex;
}
} else {
size_t appendSize = m_modelsVertexArraysCopy[modelIndex+1].m_vertexData.size();
m_combinedModelsVertexArrays.m_vertexData.resize(oldSize + appendSize);
for (int j = 0; j < appendSize; j++) {
auto newVertex = m_modelsVertexArraysCopy[modelIndex+1].m_vertexData[j];
for (int t = 0; t < 3; t++)
newVertex.m_position.attrib[t] += pos[t];
m_combinedModelsVertexArrays.m_vertexData[oldSize + j] = newVertex;
}
}
}
}
}
combiner(m_scene.m_road, 12, 0, 16);
combiner(m_scene.m_water, 13, 0, 16);
combiner(m_scene.m_tracks, 14, 0, 15);
m_combinedModelsVertexArrays.sync();
#endif
modelIdx = int(a_playerPos[0]);
float currentPlayerX = a_playerPos[0];
float currentPlayerY = a_playerPos[1];
float currentPlayerZ = a_playerPos[2];
static float prevPlayerX = currentPlayerX;
static float prevPlayerY = currentPlayerY;
static float lastPlayerX = currentPlayerX;
static float lastPlayerY = currentPlayerY;
static float lastPlayerZ = currentPlayerZ;
static float jumpTime = 0.0;
static bool inJump = false;
//const float playerLerpSpeed = 0.5;
// lerp frog position - avoids too jumpy movement
float playerX = currentPlayerX; // Math::lerp(lastPlayerX, currentPlayerX, a_elapsed / playerLerpSpeed);
float playerY = currentPlayerY; // Math::lerp(lastPlayerY, currentPlayerY, a_elapsed / playerLerpSpeed);
float playerZ = 0.0;
if (currentPlayerX != prevPlayerX || currentPlayerY != prevPlayerY || inJump) {
jumpTime += a_elapsed;
float ratio = jumpTime * 2.0;
if (ratio >= 1.0) {
inJump = false;
ratio = 1.0;
} else {
playerX = Math::lerp(lastPlayerX, currentPlayerX, easeInBackCurve(ratio));
playerY = Math::lerp(lastPlayerY, currentPlayerY, easeInBackCurve(ratio));
inJump = true;
}
playerZ = Math::lerp(lastPlayerZ, currentPlayerZ, ratio) + 2.0;
if (ratio < 0.5f)
playerZ += easeInOutBackCurve(ratio * 2.0) * 5.0;
else
playerZ += easeInBackCurve(1.0 - (ratio-0.5)*2.0) * 5.0;
} else {
inJump = false;
jumpTime = 0.0;
lastPlayerZ = currentPlayerZ;
playerZ = currentPlayerZ;
}
lastPlayerX = playerX;
lastPlayerY = playerY;
prevPlayerX = currentPlayerX;
prevPlayerY = currentPlayerY;
m_playerX = playerX;
m_playerY = playerY;
m_playerZ = playerZ;
// setDebugValue("triangles:", int(m_triCount));
// setDebugValue("vertices:", int(m_triCount * 3));
#ifdef OLD_CAMERA
//const Game::GameObject& player = a_objectLists.back()->front();
m_cameraX = -a_playerPos[0] + 20;
m_cameraY = -a_playerPos[1] - 100;
float xOff = -300.0;
float yOff = -100.0;
float zOff = 20.0; // height of camera, smaller is higher
xOff = m_cameraX;//-player.m_x + 20;
yOff = m_cameraY;//-player.m_y - 100;
// lerp camera pos towards the player's position (avoids jumpy camera movements, feels nicer)
static float lastXOff = xOff;
static float lastYOff = yOff;
const float cameraLerpSpeed = 10.0;
#define LERP(A, B, Ratio) ((A) + (((B) - (A)) * (Ratio)))
xOff = Math::lerp(lastXOff, xOff, a_elapsed / cameraLerpSpeed);
yOff = Math::lerp(lastYOff, yOff, a_elapsed / cameraLerpSpeed);
lastXOff = xOff;
lastYOff = yOff;
// Layout of camera style parameters:
// float cameraStyle[8] = { fov, rx, ry, rz, s, tx, ty, tz };
// Frogger mode (perspective devolved to ortho and with top down view)
float debugCameraStyle[8] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.0035f, -20.0f, 20.0f, 80.0f }; // Debug Style - 2D top down mode
// float topDownCameraStyle[8] = { 1.0f, 0.0f, 0.0f, 0.0f, 0.02f, -20.0f, 20.0f, 80.0f }; // Frogger Style - 2D top down mode
float topDownCameraStyle[8] = { 0.5f, 0.0f, 0.0f, 0.0f, 0.015f, -20.0f, 40.0f, 80.0f }; // Frogger Style - 2D top down mode
float isoCameraStyle[8] = { 0.5f, -45.0f, 0.0f, -20.0f, 0.015f, 0.0f, 0.0f, 0.0f }; // Crossy Road Style - nearly isometric / orthographic
float perspectiveCameraStyle[8] = { 65.0f, -45.0f, 0.0f, -20.0f, 1.0f, 0.0f, 0.0f, 0.0f }; // Normal as designed style with perspective
float* defaultCameraStyle = perspectiveCameraStyle;
bool useIso = true;
if (useIso)
defaultCameraStyle = isoCameraStyle;
const int cwf = cameraWaitFrames;
const int ctf = cameraTransitionFrames;
float cameraStyle[8];
if (m_frame < cwf)
for (int i = 0; i < 8; i++)
cameraStyle[i] = topDownCameraStyle[i];
else if (m_frame < cwf+ctf)
for (int i = 0; i < 8; i++)
cameraStyle[i] = Math::lerp(topDownCameraStyle[i], isoCameraStyle[i], (m_frame-cwf) / float(ctf));
else if (m_frame < 2*cwf+ctf)
for (int i = 0; i < 8; i++)
cameraStyle[i] = isoCameraStyle[i];
else if (m_frame < 2*(cwf+ctf))
for (int i = 0; i < 8; i++)
cameraStyle[i] = Math::lerp(isoCameraStyle[i], defaultCameraStyle[i], (m_frame-(2*cwf+ctf)) / float(ctf));
else
for (int i = 0; i < 8; i++)
cameraStyle[i] = defaultCameraStyle[i];
/*
if (debugCameraView)
for (int i = 0; i < 8; i++)
cameraStyle[i] = debugCameraStyle[i];
*/
m_polyUniforms.m_shadowStrength = float(m_frame) / (3*cwf+2*ctf);
if (float(m_frame) >= (3*cwf+2*ctf))
m_polyUniforms.m_shadowStrength = 1.0f;
m_frame++;
m_camera.build(cameraStyle[0], a_aspect, &cameraStyle[1], cameraStyle[4] * 0.01f, xOff + cameraStyle[5], yOff + cameraStyle[6], zOff + cameraStyle[7]);
Log(LL_Trace, "mvp[] = {");
Log(LL_Trace, " %f %f %f %f", m_modelViewProjectionMatrix[0], m_modelViewProjectionMatrix[1], m_modelViewProjectionMatrix[2], m_modelViewProjectionMatrix[3]);
Log(LL_Trace, " %f %f %f %f", m_modelViewProjectionMatrix[4], m_modelViewProjectionMatrix[5], m_modelViewProjectionMatrix[6], m_modelViewProjectionMatrix[7]);
Log(LL_Trace, " %f %f %f %f", m_modelViewProjectionMatrix[8], m_modelViewProjectionMatrix[9], m_modelViewProjectionMatrix[10], m_modelViewProjectionMatrix[11]);
Log(LL_Trace, " %f %f %f %f", m_modelViewProjectionMatrix[12], m_modelViewProjectionMatrix[13], m_modelViewProjectionMatrix[14], m_modelViewProjectionMatrix[15]);
Log(LL_Trace, "};");
if (debugCameraView)
{
for (int i = 0; i < 8; i++)
cameraStyle[i] = debugCameraStyle[i];
m_debugCamera.build(cameraStyle[0], a_aspect, &cameraStyle[1], cameraStyle[4] * 0.01f, xOff + cameraStyle[5], yOff + cameraStyle[6], zOff + cameraStyle[7]);
}
#else
m_normalContext.m_uniforms.m_shadowStrength = 1.0f;
m_normalContext.m_uniforms.m_mixNoise = false;
m_camera.build(a_cameraStyle[0], a_aspect, &a_cameraStyle[1], a_cameraStyle[4], a_cameraStyle[5], a_cameraStyle[6], a_cameraStyle[7]);
m_light.build(a_light[0], 1.0f, &a_light[1], a_light[4], a_light[5], a_light[6], a_light[7], true);
if (debugCameraView)
{
m_debugCamera.build(a_debugCamera[0], a_aspect, &a_debugCamera[1], a_debugCamera[4], a_debugCamera[5], a_debugCamera[6], a_debugCamera[7]);
}
#endif
if (!modelEditing)
{
// This continually refreshes the model data from file - probably only want to do this quite periodically
const int fastRefresh = 50;
const int normalRefresh = 1000; // @60fps this is every 16s
static int tick = normalRefresh;
int refresh = (debugCameraView) ? fastRefresh : normalRefresh;
if (tick++ >= refresh) {
// etag checking would make this faster / better
m_models.reloadDataFromFile("models.png", true);
tick = 0;
}
}
}
void GameGraphics::drawDebug(GameUi::DrawItems& a_uiItems)
{
if (m_hudEnabled)
{
std::string fps = getDebugVariables().at("Average FPS:");
int fpsi = int(atof(fps.c_str()));
fps = std::to_string(fpsi);
a_uiItems.addContrastString(7*75, 90, fps.c_str(), 2);
a_uiItems.addString( 7*75, 90, fps.c_str(), (fpsi>58)?0xff00ff00:0xff0000ff, 2);
if (showDebugging)
{
addDebugString(a_uiItems, 2, 4, "Debugging variables:");
int line = 3;
for (auto var : getDebugVariables()) {
addDebugString(a_uiItems, line, 5, var.first.c_str());
addDebugString(a_uiItems, line, 7, var.second.c_str());
line++;
}
}
//Log(LL_Debug, "Doing update with %ld items", items.m_items.size());
glBindBuffer(GL_ARRAY_BUFFER, m_hudVertexBuffer);
m_hudItemCount = a_uiItems.m_items.size();
//if (m_hudItemCount > m_hudVertexBufferSize) {
glBufferData(GL_ARRAY_BUFFER, sizeof(GameUi::UiVertex)*m_hudItemCount, (void*)&a_uiItems.m_items[0], GL_DYNAMIC_DRAW);
m_hudVertexBufferSize = m_hudItemCount;
//} else {
// glBufferSubData(GL_ARRAY_BUFFER, 0, sizeof(GameUi::UiVertex)*m_hudItemCount, (void*)&hudItems->m_items[0]);
//}
}
}
void GameGraphics::drawCube(float x, float y, float z, float w, float h, float d, uint32_t /*color*/)
{
float pnts[8][4];
for (int i = 0; i < 8; i++) {
pnts[i][0] = x + ((i&1) ? w : 0);
pnts[i][1] = y + ((i&2) ? h : 0);
pnts[i][2] = z + ((i&4) ? d : 0);
}
drawCuboid(pnts);
}
void GameGraphics::drawObjectListHelper(const GameSim::GameObjectList& a_objs, GLsizei siz, bool direction)
{
// Rules of thumb - 100 to 1000 to 4000 tris per draw call (depends who you ask)
for (const GameSim::GameObject& obj : a_objs) {
if ((obj.m_dx <= 0.0) == direction) {
float x = Math::modulus(obj.m_x + obj.m_dx * m_time, 1000.0f + obj.m_w);
//if (!isClipped(fabs(fmod(obj.m_x + obj.m_dx * m_time, 1000.0)), obj.m_y, obj.m_z, obj.m_w, obj.m_h, obj.m_d))
//if (!isClipped(Math::modulus(obj.m_x + obj.m_dx * m_time, 1000.0), obj.m_y, obj.m_z, obj.m_w, obj.m_h, obj.m_d))
if (!m_camera.isClipped(x, obj.m_y, obj.m_z, obj.m_w, obj.m_h, obj.m_d))
{
//glVertexAttrib4f(deltaIndex, obj.m_dx, obj.m_dy, 0.0, 0.0);
//glVertexAttrib4f(deltaIndex, 0.0, 0.0, 0.0, 0.0);
float pos[4] = { x, obj.m_y, obj.m_z, 0.0f };
m_currentContext->m_uniforms.setUniforms();
glUniform4fv(m_currentContext->m_uniforms.m_translation_index, 1, (GLfloat*)pos);
glDrawArrays(GL_TRIANGLES, 0, siz); // tri-strips are better
//m_modelsVertexArrays[obj.m_dx > 0.0 ? a_voxelModelIndex+1 : a_voxelModelIndex].draw();
}
}
}
}
void GameGraphics::drawObjectTypeList(const GameSim::GameObjectList& a_objs, int a_voxelModelIndex)
{
GLint deltaIndex = VERTEX_ATTRIB_INDEX(PolyVertex, delta);
glDisableVertexAttribArray(deltaIndex);
glVertexAttrib4f(deltaIndex, 0.0, 0.0, 0.0, 0.0);
// part of the optimization
GLsizei siz = GLsizei(m_modelsVertexArrays[a_voxelModelIndex].m_vertexDataSize);
if (!siz)
return;
glBindVertexArrayOES(m_modelsVertexArrays[a_voxelModelIndex].m_vertexArray);
drawObjectListHelper(a_objs, siz, true);
// part of the optimization
siz = GLsizei(m_modelsVertexArrays[a_voxelModelIndex+1].m_vertexDataSize);
if (!siz)
return;
glBindVertexArrayOES(m_modelsVertexArrays[a_voxelModelIndex+1].m_vertexArray);
drawObjectListHelper(a_objs, siz, false);
if (debugCollisions || debugCameraView)
{
for (const GameSim::GameObject& obj : a_objs) {
float x = Math::modulus(obj.m_x + obj.m_dx * m_time, 1000.0f + obj.m_w);
bool clipped = m_camera.isClipped(x, obj.m_y, obj.m_z, obj.m_w, obj.m_h, obj.m_d);
if ((clipped && debugCameraView) || (debugCollisions && x > obj.m_w && x < (1000-obj.m_w)))
drawCube(x, obj.m_y, obj.m_z, obj.m_w, obj.m_h, obj.m_d, 0x00ff00);
}
}
//glEnableVertexAttribArray(deltaIndex);
glVertexAttrib4f(deltaIndex, 0.0, 0.0, 0.0, 0.0);
}
void GameGraphics::drawObjectTypeListTiled(const GameSim::GameObjectList& a_objs, int a_voxelModelIndex, int zoff, int wid)
{
GLint deltaIndex = VERTEX_ATTRIB_INDEX(PolyVertex, delta);
glDisableVertexAttribArray(deltaIndex);
/*
if (!shadowPass) {
#if !TARGET_IPHONE_SIMULATOR
m_polyUniforms.m_mixNoise = (a_voxelModelIndex==13); // if water, mix noise
#endif
m_polyUniforms.setUniforms();
}
*/
glVertexAttrib4f(deltaIndex, 0.0, 0.0, 0.0, 0.0);
// part of the optimization
GLsizei siz = GLsizei(m_modelsVertexArrays[a_voxelModelIndex].m_vertexDataSize);
if (!siz)
return;
glBindVertexArrayOES(m_modelsVertexArrays[a_voxelModelIndex].m_vertexArray);
for (const GameSim::GameObject& obj : a_objs) {
for (int j = 0; j < int(obj.m_h/10.0); j++)
{
for (int i = 0; i < 1000/wid; i++)
{
//float x = Math::modulus(obj.m_x + 650.0f + obj.m_dx * m_time, 1000.0 + obj.m_w);
float x = obj.m_x + i*wid;// obj.m_x + 650.0f;
if (!m_camera.isClipped(x, obj.m_y + j*10.0f + 1.0f, obj.m_z + zoff, wid, obj.m_h, obj.m_d))
{
/*
m_polyUniforms.m_translation = vec4attrib{ obj.m_x + i*10.0f, obj.m_y + j*10.0f, obj.m_z + (zoff/3) - 6.0f, 0.0f };
glVertexAttrib4f(deltaIndex, obj.m_dx, obj.m_dy, 0.0f, 0.0f);
m_polyUniforms.setUniforms();
m_modelsVertexArrays[obj.m_dx > 0.0f ? a_voxelModelIndex+1 : a_voxelModelIndex].draw();
*/
// optimized version which moves invariant out of the loop
float pos[4] = { x, obj.m_y + j*10.0f + 0.0f, obj.m_z + zoff, 0.0f };
m_currentContext->m_uniforms.setUniforms();
glUniform4fv(m_currentContext->m_uniforms.m_translation_index, 1, (GLfloat*)pos);
glDrawArrays(GL_TRIANGLES, 0, siz); // tri-strips are better
}
}
// float m_w, m_h, m_d;
}
}
//glEnableVertexAttribArray(deltaIndex);
glVertexAttrib4f(deltaIndex, 0.0, 0.0, 0.0, 0.0);
}
#define setProfileLocation(name) \
thisTime = GameTime::now(); \
setDebugValue(name ":", std::string(std::to_string(GameTime::Duration(thisTime - lastTime).count() * 0.000001f) + " ms").c_str()); \
lastTime = thisTime
void GameGraphics::preDraw(float a_time)
{
m_time = a_time;
glDisable(GL_BLEND);
glEnable(GL_DEPTH_TEST);
glEnable(GL_CULL_FACE);
glDepthMask(GL_TRUE);
// Get some parameters
GLint vp[4];
glGetIntegerv(GL_VIEWPORT, vp);
m_w = vp[2];
m_h = vp[3];
}
void GameGraphics::drawScene()
{
m_staticPolyVertexArray.draw();
setDebugValue("Tri count static:", int(m_staticPolyVertexArray.m_vertexDataSize));
m_currentContext->m_uniforms.m_translation = vec4attrib{ m_playerX, m_playerY, m_playerZ, 0.0f };
m_currentContext->m_uniforms.setUniforms();
m_dynamicPolyVertexArray.draw();
setDebugValue("Tri count dynamic:", int(m_dynamicPolyVertexArray.m_vertexDataSize));
/*
// instancing test
glUniform4fv(m_polyUniforms.m_translation_index, 100, (GLfloat*)trans);
glBindVertexArrayOES(m_carRightVertexArray.m_vertexArray);
glDrawArraysInstancedEXT(GL_TRIANGLES, 0, 100, GLsizei(m_carRightVertexArray.m_vertexDataSize)); // tri-strips are better
*/
#if USE_ONE_BIG_VERTEX_ARRAY
GLint deltaIndex = VERTEX_ATTRIB_INDEX(PolyVertex, delta);
glDisableVertexAttribArray(deltaIndex);
if (!shadowPass) { m_polyUniforms.setUniforms(); }
glVertexAttrib4f(deltaIndex, 0.0, 0.0, 0.0, 0.0);
GLsizei siz = GLsizei(m_combinedModelsVertexArrays.m_vertexDataSize);
glBindVertexArrayOES(m_combinedModelsVertexArrays.m_vertexArray);
float pos[4] = { 0.0f, 0.0f, 0.0f, 0.0f };
glUniform4fv((shadowPass) ? m_shadowUniforms.m_translation_index : m_polyUniforms.m_translation_index, 1, (GLfloat*)pos);
glDrawArrays(GL_TRIANGLES, 0, siz);
glVertexAttrib4f(deltaIndex, 0.0, 0.0, 0.0, 0.0);
#else
drawObjectTypeList(m_scene.m_cars, 0);
// TODO: add buses
drawObjectTypeList(m_scene.m_bikes, 4);
drawObjectTypeList(m_scene.m_vans, 6);
drawObjectTypeList(m_scene.m_trucks, 8);
drawObjectTypeList(m_scene.m_trains, 10);
drawObjectTypeList(m_scene.m_trees, 16);
drawObjectTypeList(m_scene.m_shrubs, 17);
drawObjectTypeList(m_scene.m_buildings, 18);
drawObjectTypeList(m_scene.m_rocks, 19);
drawObjectTypeList(m_scene.m_logs, 20);
drawObjectTypeList(m_scene.m_boats, 22);
drawObjectTypeList(m_scene.m_crocs, 24);
// TODO: below takes time (3ms)
drawObjectTypeListTiled(m_scene.m_road, 12, 0, 16);
drawObjectTypeListTiled(m_scene.m_water, 13, 0, 16);
drawObjectTypeListTiled(m_scene.m_tracks, 14, 0, 15);
#endif
}
void GameGraphics::prepareShadowContext()
{
glGetIntegerv(GL_FRAMEBUFFER_BINDING, (GLint*)&m_defaultFramebufferId);
// Render the objects from light POV to make shadow map
#ifndef USE_OPENGLES2
glDrawBuffer(GL_NONE); // No color buffer is drawn to.
glReadBuffer(GL_NONE); // No color buffer is read from.
#endif
//setProfileLocation("Draw Point B0");
glBindFramebuffer(GL_FRAMEBUFFER, m_shadowFramebufferId);
glViewport(0, 0, SHADOW_DIM, SHADOW_DIM);
//setProfileLocation("Draw Point B1");
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//glClear(GL_DEPTH_BUFFER_BIT);
//setProfileLocation("Draw Point B2");
glCullFace(GL_FRONT);
//glBindVertexArrayOES(m_dynamicPolyVertexArray.m_vertexArray);
//GLKVector3 lightInvDir = GLKVector3Make(0.5f,2,2);
// Compute the MVP matrix from the light's point of view
//const Game::GameObjec-75.0ft& player = m_sim.objectLists().back()->front();
//setProfileLocation("Draw Point B3");
#ifdef OLD_CAMERA
/*
float rotations[3] = { -75.0f, 20.0f, -30.0f };
// TODO: the light position in the shader(s) and here should line up for better realism
m_light.build(1.0f, 1.0f, rotations, 0.0175f, m_cameraX-20.0, m_cameraY+45.0, 20.0, true);
*/
#endif
m_shadowContext.m_program.useProgram();
memcpy(m_shadowContext.m_uniforms.m_modelViewProjectionMatrix.m, m_light.m_modelViewProjectionMatrix, sizeof(float)*16);
m_shadowContext.m_uniforms.m_time = m_time;
m_shadowContext.m_uniforms.m_translation = vec4attrib{ 0.0f, 0.0f, 0.0f, 0.0f };
m_shadowContext.m_uniforms.setUniforms();
}
void GameGraphics::renderShadows()
{
if (m_options["enableShadows"])
{
prepareShadowContext();
m_currentContext = &m_shadowContext;
drawScene();
m_currentContext = &m_normalContext;
// restore original framebuffer and viewport
glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebufferId);
}
}
void GameGraphics::prepareScreenContext()
{
//glViewport(vp[0], vp[1], vp[2], vp[3]);
glViewport(0, 0, m_w, m_h);
//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
#ifndef USE_OPENGLES2
glDrawBuffer(GL_BACK);
glReadBuffer(GL_NONE);
#endif
// Render the object again with ES2
m_normalContext.m_program.useProgram();
if (debugCameraView)
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
else
glClear(GL_DEPTH_BUFFER_BIT);
// setProfileLocation("Draw Point C2");
glCullFace(GL_BACK);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_shadowTexId);
glActiveTexture(GL_TEXTURE1);
glBindTexture(GL_TEXTURE_2D, m_hudTexId);
// TODO: work out why the shadows 'swim'
float biasedShadowMVP[16] = { 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.0, 0.0, 0.0, 0.5, 0.0, 0.5, 0.5, 0.5, 1.0 };
Math::multiplyMatrix4x4(m_normalContext.m_uniforms.m_shadowMapMVP.m, biasedShadowMVP, m_light.m_modelViewProjectionMatrix);
if (debugCameraView)
m_normalContext.m_uniforms.m_modelViewProjectionMatrix = m_debugCamera.m_modelViewProjectionMatrix;
else
m_normalContext.m_uniforms.m_modelViewProjectionMatrix = m_camera.m_modelViewProjectionMatrix;
m_normalContext.m_uniforms.m_shadowMap = 0;
m_normalContext.m_uniforms.m_texture = 1;
m_normalContext.m_uniforms.m_time = m_time;
m_normalContext.m_uniforms.m_translation = vec4attrib{ 0.0f, 0.0f, 0.0f, 0.0f };
m_normalContext.m_uniforms.setUniforms();
/*
*/
}
void GameGraphics::renderScreen()
{
prepareScreenContext();
//setProfileLocation("Draw Point C3");
//glEnable(GL_BLEND);
//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
drawScene();
//glDisable(GL_ALPHA_TEST);
//setProfileLocation("Draw Point C43");
}
void GameGraphics::draw()
{
GameTime::TimePoint startOfDraw = GameTime::now();
GameTime::TimePoint lastTime = startOfDraw;
GameTime::TimePoint thisTime = startOfDraw;
setProfileLocation("Draw Point A1");
renderShadows();
setProfileLocation("Draw Point C0");
renderScreen();
/*
// seems to just clip in screen space
int p = 0;
GLdouble plane[4] = { m_clipPlanes[p][0], m_clipPlanes[p][1], m_clipPlanes[p][2], m_clipPlanes[p][3] };
glClipPlane(GL_CLIP_PLANE0, plane);
glEnable(GL_CLIP_PLANE0);
*/
if (debugCameraView)
{
float frustumPts[8][4];
m_camera.calcFrustum(frustumPts);
drawCuboid(frustumPts);
m_light.calcFrustum(frustumPts);
drawCuboid(frustumPts);
}
if (m_options["enableShadows"])
{
glBindFramebuffer(GL_FRAMEBUFFER, m_shadowFramebufferId);
//glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
//glClear(GL_DEPTH_BUFFER_BIT);
const GLenum discards[] = {GL_DEPTH_ATTACHMENT};
#ifndef USE_OPENGLES2
glInvalidateFramebuffer(GL_READ_FRAMEBUFFER, 1, discards);
#else
glDiscardFramebufferEXT(GL_READ_FRAMEBUFFER_APPLE, 1, discards);
//glDiscardFramebufferEXT(GL_DRAW_FRAMEBUFFER_APPLE, 1, discards);
#endif
// restore original framebuffer and viewport
glBindFramebuffer(GL_FRAMEBUFFER, m_defaultFramebufferId);
//glViewport(vp[0], vp[1], vp[2], vp[3]);
}
//glFlush();
//glFinish();
}
void GameGraphics::prepareHUDContext()
{
glDisable(GL_CULL_FACE);
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glEnable(GL_BLEND);
glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA);
//glBlendFunc(GL_SRC_ALPHA, GL_ONE_MINUS_DST_COLOR);
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, m_hudTexId);
m_hudProgram.useProgram();
glUniform1i(uniforms[UNIFORM_HUD_TEXTURE], 0);
//glUniform2f(uniforms[UNIFORM_HUD_INV_SCREEN], 2.0f / m_w, -2.0f / m_h);
glUniform2f(uniforms[UNIFORM_HUD_INV_SCREEN], 2.0f / 640, -2.0f / 960);
}
void GameGraphics::drawHUD()
{
if (m_hudEnabled)
{
prepareHUDContext();
glBindVertexArrayOES(m_hudVertexArray);
glDrawArrays(GL_TRIANGLES, 0, GLsizei(m_hudItemCount));
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
}
}
void GameGraphics::drawAuxItems(const GameUi::DrawItems& a_uiItems)
{
prepareHUDContext();
glBindVertexArrayOES(m_auxVertexArray);
glBindBuffer(GL_ARRAY_BUFFER, m_auxVertexBuffer);
size_t itemCount = a_uiItems.m_items.size();
glBufferData(GL_ARRAY_BUFFER, sizeof(GameUi::UiVertex)*itemCount, (void*)&a_uiItems.m_items[0], GL_DYNAMIC_DRAW);
glDrawArrays(GL_TRIANGLES, 0, GLsizei(itemCount));
glDisable(GL_BLEND);
glDepthMask(GL_TRUE);
glEnable(GL_CULL_FACE);
glEnable(GL_DEPTH_TEST);
}
#include <functional>
void GameGraphics::drawViews(const std::vector<GameUi::View*>& a_views)
{
bool prepared = false;
for (GameUi::View* view : a_views)
{
if (m_viewMap.count(view->m_view)) {
if (!prepared) {
prepareScreenContext();
prepared = true;
}
//Log(LL_Info, "drawing view: %s", view->m_view.c_str());
GLint vp[4];
glGetIntegerv(GL_VIEWPORT, vp);
glViewport(view->m_geometry.x, vp[3]-(view->m_geometry.y+view->m_geometry.h), view->m_geometry.w, view->m_geometry.h);
m_w = view->m_geometry.w;
m_h = view->m_geometry.h;
m_viewMap[view->m_view]();
glViewport(vp[0], vp[1], vp[2], vp[3]);
m_w = vp[2];
m_h = vp[3];
}
}
}
void GameGraphics::drawRotatingFrog()
{
// Rotating model in center
static float zr = 0.0f;
float modelScale = 8.0f;
float aspectRatio = (m_h) ? (float(m_w)/float(m_h)) : 1.0;
float isoCameraStyle[8] = { 25.0f, -60.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f }; // Crossy Road Style - nearly isometric / orthographic
Camera camera;
camera.build(25.0f, aspectRatio, &isoCameraStyle[1], 0.005f, 0.0f, 0.0f, 0.0f);
float baseModelViewMatrix[16];
Math::makeIdentityMatrix4x4(baseModelViewMatrix);
Math::translateMatrix4x4(baseModelViewMatrix, 0.0f, 0.0f, -3.5f);
float modelViewMatrix[16];
Math::multiplyMatrix4x4(modelViewMatrix, baseModelViewMatrix, camera.m_cameraMatrix);
Math::translateMatrix4x4(modelViewMatrix, 0, 35, -35);
Math::rotateMatrix4x4(modelViewMatrix, zr, 0.0f, 0.0f, 1.0f);
Math::translateMatrix4x4(modelViewMatrix, -350.0f, -28.0f, 0);
Math::scaleMatrix4x4(modelViewMatrix, modelScale, modelScale, modelScale);
Math::multiplyMatrix4x4(m_normalContext.m_uniforms.m_modelViewProjectionMatrix.m, camera.m_projectionMatrix, modelViewMatrix);
m_normalContext.m_uniforms.m_translation = vec4attrib{ 40, 0, 0, 0.0f };
m_normalContext.m_uniforms.setUniforms();
// draw the frog model
m_dynamicPolyVertexArray.draw();
zr += 0.03f;
}
void GameGraphics::drawModelEditLayer()
{
// Rotating model in center
static float zr = 10.0f;//-0.5f;
float modelScale = 4.0f;
float aspectRatio = (m_h) ? (float(m_w)/float(m_h)) : 1.0;
float isoCameraStyle[8] = { 25.0f, -60.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f }; // Crossy Road Style - nearly isometric / orthographic
int idx = Math::modulus(m_editModel, MODEL_COUNT);
ModelInfo &mi = g_modelInfos[idx];
Camera camera;
camera.build(25.0f, aspectRatio, &isoCameraStyle[1], 0.005f, 0.0f, 0.0f, 0.0f);
Math::translateMatrix4x4(camera.m_cameraMatrix, 0, 200+154+35, -55-55-55-55-5 );
Math::rotateMatrix4x4(camera.m_cameraMatrix, zr, 0.0f, 0.0f, 1.0f);
Math::translateMatrix4x4(camera.m_cameraMatrix, -mi.w * 0.5*modelScale, -mi.d * 0.5*modelScale, -mi.h * 0.5*modelScale);
Math::scaleMatrix4x4(camera.m_cameraMatrix, modelScale, modelScale, modelScale);
Math::multiplyMatrix4x4(m_normalContext.m_uniforms.m_modelViewProjectionMatrix.m, camera.m_projectionMatrix, camera.m_cameraMatrix);
m_normalContext.m_uniforms.m_translation = vec4attrib{ 0, 0, 0, 0 };
m_normalContext.m_uniforms.setUniforms();
int editLayer = (mi.h-1) - int((mi.h-1) * m_editLayer + 0.0);
//if (m_editModelDirty)
{
loadVoxelModel(m_editModelVertexArray1, mi.name, mi.x, mi.y, mi.w, mi.h, mi.d, mi.direction, mi.scaling, true, 0, editLayer+1);
m_editModelVertexArray1.update();
}
glClear(GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
//m_editModelVertexArray1.sync();
//m_modelsVertexArrays[idx].update();
m_editModelVertexArray1.draw();
if (editLayer <= mi.h-1)
{
//if (m_editModelDirty)
{
loadVoxelModel(m_editModelVertexArray2, mi.name, mi.x, mi.y, mi.w, mi.h, mi.d, mi.direction, mi.scaling, true, editLayer+1, 256);
//m_editModelVertexArray2.sync();
}
glDisable(GL_DEPTH_TEST);
glDepthMask(GL_FALSE);
glDisable(GL_CULL_FACE);
glEnable(GL_BLEND);
/*
glBlendFunc(GL_ONE_MINUS_CONSTANT_ALPHA, GL_CONSTANT_ALPHA);
glBlendColor(0.7, 0.7, 0.7, 0.95);
*/
/*
#define GL_SRC_COLOR 0x0300
#define GL_ONE_MINUS_SRC_COLOR 0x0301
#define GL_SRC_ALPHA 0x0302
#define GL_ONE_MINUS_SRC_ALPHA 0x0303
#define GL_DST_ALPHA 0x0304
#define GL_ONE_MINUS_DST_ALPHA 0x0305
#define GL_DST_COLOR 0x0306
#define GL_ONE_MINUS_DST_COLOR 0x0307
#define GL_SRC_ALPHA_SATURATE 0x0308
#define GL_CONSTANT_COLOR 0x8001
#define GL_ONE_MINUS_CONSTANT_COLOR 0x8002
#define GL_CONSTANT_ALPHA 0x8003
#define GL_ONE_MINUS_CONSTANT_ALPHA 0x8004
*/
glBlendFuncSeparate(GL_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA, GL_ONE_MINUS_CONSTANT_ALPHA, GL_CONSTANT_ALPHA);
glBlendColor(0.7, 0.7, 0.7, 0.05);
//m_editModelVertexArray2.sync();
m_editModelVertexArray2.update();
m_editModelVertexArray2.draw();
glBlendColor(0.7, 0.7, 0.7, 0.15);
drawSolidCuboid( 0, 0, editLayer+1, mi.w, mi.d, 0.001, 0x3f3fff, 1.0);
glDisable(GL_BLEND);
glEnable(GL_CULL_FACE);
}
m_editModelDirty = false;
/*
//m_models.editPixel(mi.x, mi.y, 0);
//m_models.invalidateCache();
//loadVoxelModel(m_modelsVertexArrays[idx], mi.name, mi.x, mi.y, mi.w, mi.h, mi.d, mi.direction, mi.scaling, true);
*/
/*
float cuboid[8][4] = {
{ float( 0), float( 0), float(editLayer ), 0.0 },
{ float( 0), float(mi.d), float(editLayer ), 0.0 },
{ float(mi.w), float( 0), float(editLayer ), 0.0 },
{ float(mi.w), float(mi.d), float(editLayer ), 0.0 },
{ float( 0), float( 0), float(editLayer+1), 0.0 },
{ float( 0), float(mi.d), float(editLayer+1), 0.0 },
{ float(mi.w), float( 0), float(editLayer+1), 0.0 },
{ float(mi.w), float(mi.d), float(editLayer+1), 0.0 },
};
drawCuboid(cuboid);
*/
}
void GameGraphics::drawCaroselTest()
{
// Rotating model in center
static float zr = 0.0f;
float modelScale = 0.2f;
float aspectRatio = (m_h) ? (float(m_w)/float(m_h)) : 1.0;
float isoCameraStyle[8] = { 25.0f, -60.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f }; // Crossy Road Style - nearly isometric / orthographic
glClear(GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
//int mIdx = Math::modulus(m_editModel, MODEL_COUNT);
int mIdx = m_editModel;
static int lastEditModel = mIdx;
static int savedLastEditModel = mIdx;
static int frame = 0;
if (mIdx != lastEditModel) {
savedLastEditModel = lastEditModel;
lastEditModel = mIdx;
frame = 0;
} else {
frame++;
if (frame > 50) {
frame = 50;
}
}
int f = frame;
if (savedLastEditModel < lastEditModel) {
f = -f + 25;
} else {
f = f - 75;
}
for (int i = 0; i < MODEL_COUNT; i++)
{
const float radius = 250.0f;
float radian = (float(i) + 0.5 + (f/50.0)) * 2 * 3.14 / MODEL_COUNT - 0.5*3.14;
int idx = Math::modulus(i + m_editModel, MODEL_COUNT);
float x = cos(radian) * radius;
float y = (1.0 + sin(radian)) * radius;
int curIdx = Math::modulus(mIdx, MODEL_COUNT);
int lastIdx = Math::modulus(savedLastEditModel, MODEL_COUNT);
int f2 = 0;
if ( idx == curIdx ) {
f2 = frame;
} else if ( idx == lastIdx ) {
f2 = 50 - frame;
}
float zRot = Math::modulus(zr,3.14f*2) * (f2/50.0);
y = y * ((50-f2)/50.0) - f2;
modelScale = 0.2 + 0.8 * (f2/50.0);
//Log(LL_Info, "model idx: %i", idx);
ModelInfo &mi = g_modelInfos[idx];
Camera camera;
camera.build(28.0f, aspectRatio, &isoCameraStyle[1], 0.005f, 0.0f, 0.0f, 0.0f);
Math::rotateMatrix4x4(camera.m_cameraMatrix, -0.25, 1.0f, 0.0f, 0.0f);
Math::translateMatrix4x4(camera.m_cameraMatrix, x*0.2, 100+200+154+35 + y*modelScale, -105-15 );
Math::rotateMatrix4x4(camera.m_cameraMatrix, zRot, 0.0f, 0.0f, 1.0f);
Math::translateMatrix4x4(camera.m_cameraMatrix, (-100*modelScale) -mi.w * 0.5*modelScale, -mi.d * 0.5*modelScale, -mi.h * 0.5*modelScale);
Math::scaleMatrix4x4(camera.m_cameraMatrix, modelScale, modelScale, modelScale);
Math::multiplyMatrix4x4(m_normalContext.m_uniforms.m_modelViewProjectionMatrix.m, camera.m_projectionMatrix, camera.m_cameraMatrix);
m_normalContext.m_uniforms.m_translation = vec4attrib{ 100, 0, -5, 0 };
m_normalContext.m_uniforms.setUniforms();
//loadVoxelModel(m_modelsVertexArrays[idx], mi.name, mi.x, mi.y, mi.w, mi.h, mi.d, mi.direction, mi.scaling, false);
//m_modelsVertexArrays[idx].update();
m_modelsVertexArrays[idx].draw();
}
zr += 0.03f;
}
void GameGraphics::drawModelRotatingPreview()
{
// Rotating model in center
static float zr = 0.0f;
float modelScale = 4.0f;
float aspectRatio = (m_h) ? (float(m_w)/float(m_h)) : 1.0;
float isoCameraStyle[8] = { 25.0f, -60.0f, 0.0f, 0.0f, 0.5f, 0.0f, 0.0f, 0.0f }; // Crossy Road Style - nearly isometric / orthographic
int idx = Math::modulus(m_editModel, MODEL_COUNT);
ModelInfo &mi = g_modelInfos[idx];
Camera camera;
camera.build(25.0f, aspectRatio, &isoCameraStyle[1], 0.005f, 0.0f, 0.0f, 0.0f);
Math::translateMatrix4x4(camera.m_cameraMatrix, 0, 200+154+35, -55-55-55-55 );
Math::rotateMatrix4x4(camera.m_cameraMatrix, zr, 0.0f, 0.0f, 1.0f);
Math::translateMatrix4x4(camera.m_cameraMatrix, -mi.w * 0.5*modelScale, -mi.d * 0.5*modelScale, -mi.h * 0.5*modelScale);
Math::scaleMatrix4x4(camera.m_cameraMatrix, modelScale, modelScale, modelScale);
Math::multiplyMatrix4x4(m_normalContext.m_uniforms.m_modelViewProjectionMatrix.m, camera.m_projectionMatrix, camera.m_cameraMatrix);
m_normalContext.m_uniforms.m_translation = vec4attrib{ 0, 0, 0, 0 };
m_normalContext.m_uniforms.setUniforms();
//loadVoxelModel(m_modelsVertexArrays[idx], mi.name, mi.x, mi.y, mi.w, mi.h, mi.d, mi.direction, mi.scaling, false);
glClear(GL_DEPTH_BUFFER_BIT);
glEnable(GL_DEPTH_TEST);
glDepthMask(GL_TRUE);
//m_modelsVertexArrays[idx].sync();
//m_modelsVertexArrays[idx].update();
m_modelsVertexArrays[idx].draw();
setDebugValue("ModelTris:", int(m_modelsVertexArrays[idx].m_vertexDataSize/3));
setDebugValue("ModelVerts:", int(m_modelsVertexArrays[idx].m_vertexDataSize));
zr += 0.03f;
}
void GameGraphics::getEditObjectPixels(float a_editLayer, std::vector<uint32_t>& pixels, int& w, int& h, int& d)
{
int idx = Math::modulus(m_editModel, MODEL_COUNT);
ModelInfo &mi = g_modelInfos[idx];
int editLayer = (mi.h-1) - int((mi.h-1) * a_editLayer + 0.0);
w = mi.w;
h = mi.d;
d = mi.h;
pixels.clear();
pixels.resize(w*h);
for (int i = 0; i < w*h; i++)
pixels[i] = 0x3f000000;
for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++) {
int z = editLayer;
uint32_t col = m_models.getPixel( mi.x+x, mi.y+(y*mi.h)+(15-z));
pixels[y*w+x] = col;
}
/*
for (unsigned i = 0; i < voxelModel.size(); i++)
if (voxelModel.at(i).m_z == editLayer)
{
uint32_t col = voxelModel.at(i).m_color;
pixels[voxelModel.at(i).m_y * w + voxelModel.at(i).m_x] = ((col&0xff)<<16)|(col&0xff00)|((col>>16)&0xff)|0xff000000;
}
*/
}
void GameGraphics::setEditObjectPixels(float a_editLayer, const std::vector<uint32_t>& pixels, int w, int h)
{
int idx = Math::modulus(m_editModel, MODEL_COUNT);
ModelInfo &mi = g_modelInfos[idx];
int editLayer = (mi.h-1) - int((mi.h-1) * a_editLayer + 0.0);
for (int y = 0; y < h; y++)
for (int x = 0; x < w; x++) {
int z = editLayer;
uint32_t col = pixels[y*w+x];
if (col == 0x3f000000)
col = 0;
m_models.editPixel( mi.x+x, mi.y+(y*mi.h)+(15-z), col);
}
m_models.invalidateCache();
}
void GameGraphics::getPalettePixels(std::vector<uint32_t>& pixels)
{
pixels.clear();
pixels.resize(256);
for (int i = 0; i < 256; i++)
pixels[i] = m_models.getPixel(255-(i%16), 255-15+(i/16)) | 0xff000000;
}
void GameGraphics::drawCuboid(float cuboid[8][4])
{
m_normalContext.m_uniforms.m_translation = vec4attrib{ 0, 0, 0, 0.0f };
m_normalContext.m_uniforms.setUniforms();
const int lineSegments[12][2] = {{0,1},{1,3},{3,2},{2,0}, {4,5},{5,7},{7,6},{6,4}, {0,4},{1,5},{3,7},{2,6}};
std::vector<PolyVertex> &objVerts = m_cuboidVertexList.m_vertexData;
objVerts.resize(24);
for (int i = 0; i < 12; i++) {
for (int p = 0; p < 2; p++) {
memset(&objVerts[i*2+p], 0, sizeof(PolyVertex));
objVerts[i*2+p].m_color.r = 1.0;
objVerts[i*2+p].m_color.g = 0.5;
objVerts[i*2+p].m_color.b = 0.5;
objVerts[i*2+p].m_color.a = 1.0;
objVerts[i*2+p].m_position.x = cuboid[lineSegments[i][p]][0];
objVerts[i*2+p].m_position.y = cuboid[lineSegments[i][p]][1];
objVerts[i*2+p].m_position.z = cuboid[lineSegments[i][p]][2];
}
}
m_cuboidVertexList.update();
glBindVertexArrayOES(m_cuboidVertexList.m_vertexArray);
glDrawArrays(GL_LINES, 0, 24);
}
void GameGraphics::drawSolidCuboid(float x, float y, float z, float w, float h, float d, uint32_t col, float scale)
{
m_normalContext.m_uniforms.m_translation = vec4attrib{ 0, 0, 0, 0.0f };
m_normalContext.m_uniforms.setUniforms();
std::vector<PolyVertex> &objVerts = m_cuboidVertexList.m_vertexData;
objVerts.resize(12 * 3);
PolyVertex* cubePtr = &objVerts[0];
memcpy(cubePtr, gBaseCubeVertexData, vertSize*vertsPerCube);
VoxelBox obj = { x, y, z, 0.0, w, d, h, col };
initCubeVerticesFromVoxel(cubePtr, obj, scale);
for (int v = 0; v < 36; v++)
cubePtr->m_color.a = 0.9;
m_cuboidVertexList.update();
glBindVertexArrayOES(m_cuboidVertexList.m_vertexArray);
//glDrawArrays(GL_LINES, 0, 24);
glDrawArrays(GL_TRIANGLES, 0, 36);
}