Compare commits

..

No commits in common. "master" and "memory_block" have entirely different histories.

46 changed files with 4584 additions and 4167 deletions

View File

@ -121,7 +121,6 @@
</ItemDefinitionGroup>
<ItemGroup>
<ClCompile Include="src\AssetManager.c" />
<ClCompile Include="src\Asteroid.c" />
<ClCompile Include="src\Audio.c" />
<ClCompile Include="src\Common.c" />
<ClCompile Include="src\Debug.c" />
@ -130,7 +129,11 @@
<ClCompile Include="src\MemoryArena.c" />
<ClCompile Include="src\Platform.c" />
<ClCompile Include="src\Renderer.c" />
<ClCompile Include="src\Ui.c" />
<ClCompile Include="src\Shader.c" />
<ClCompile Include="src\String.c" />
<ClCompile Include="src\UserInterface.c" />
<ClCompile Include="src\WorldTraveller.c" />
<ClCompile Include="src\Texture.c" />
</ItemGroup>
<ItemGroup>
<None Include="data\shaders\default.frag.glsl" />
@ -140,19 +143,21 @@
</ItemGroup>
<ItemGroup>
<ClInclude Include="src\include\Dengine\Assets.h" />
<ClInclude Include="src\include\Dengine\Asteroid.h" />
<ClInclude Include="src\include\Dengine\Audio.h" />
<ClInclude Include="src\include\Dengine\Debug.h" />
<ClInclude Include="src\include\Dengine\Entity.h" />
<ClInclude Include="src\include\Dengine\MemoryArena.h" />
<ClInclude Include="src\include\Dengine\Platform.h" />
<ClInclude Include="src\include\Dengine\AssetManager.h" />
<ClInclude Include="src\include\Dengine\Common.h" />
<ClInclude Include="src\include\Breakout\Game.h" />
<ClInclude Include="src\include\Dengine\Entity.h" />
<ClInclude Include="src\include\Dengine\Math.h" />
<ClInclude Include="src\include\Dengine\OpenGL.h" />
<ClInclude Include="src\include\Dengine\Renderer.h" />
<ClInclude Include="src\include\Dengine\Ui.h" />
<ClInclude Include="src\include\Dengine\Shader.h" />
<ClInclude Include="src\include\Dengine\String.h" />
<ClInclude Include="src\include\Dengine\Texture.h" />
<ClInclude Include="src\include\Dengine\UserInterface.h" />
<ClInclude Include="src\include\Dengine\WorldTraveller.h" />
</ItemGroup>
<Import Project="$(VCTargetsPath)\Microsoft.Cpp.targets" />

View File

@ -15,6 +15,15 @@
</Filter>
</ItemGroup>
<ItemGroup>
<ClCompile Include="src\WorldTraveller.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\Texture.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\Shader.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\Renderer.c">
<Filter>Source Files</Filter>
</ClCompile>
@ -33,19 +42,19 @@
<ClCompile Include="src\Audio.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\dengine.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\MemoryArena.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\Asteroid.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\Entity.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\Ui.c">
<ClCompile Include="src\dengine.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\UserInterface.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\String.c">
<Filter>Source Files</Filter>
</ClCompile>
<ClCompile Include="src\MemoryArena.c">
<Filter>Source Files</Filter>
</ClCompile>
</ItemGroup>
@ -59,9 +68,15 @@
<ClInclude Include="src\include\Dengine\Common.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\include\Dengine\Shader.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\include\Dengine\OpenGL.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\include\Dengine\Texture.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\include\Dengine\AssetManager.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -71,6 +86,9 @@
<ClInclude Include="src\include\Dengine\Renderer.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\include\Dengine\Entity.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\include\Dengine\Math.h">
<Filter>Header Files</Filter>
</ClInclude>
@ -92,13 +110,10 @@
<ClInclude Include="src\include\Dengine\WorldTraveller.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\include\Dengine\Asteroid.h">
<ClInclude Include="src\include\Dengine\UserInterface.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\include\Dengine\Entity.h">
<Filter>Header Files</Filter>
</ClInclude>
<ClInclude Include="src\include\Dengine\Ui.h">
<ClInclude Include="src\include\Dengine\String.h">
<Filter>Header Files</Filter>
</ClInclude>
</ItemGroup>

View File

@ -1,31 +1,41 @@
# Dengine
Dengine is a 2d game engine developed with OpenGL and low-level libraries in C. The main goal of Dengine is to facilitate learning from many aspects of Computer Science. Alongside Dengine a game is being built which guides the development of features for the game. Dengine is inspired by Handmade Hero, with a focus on developing as many features with minimal libraries, such that it is not too time-consuming and counter-productive to do so.
![Asteroids Demo](extern/_assets/20161130_dengine_asteroids_low_q.gif)
## Milestones
Features/research that has been implemented at one point in the making of the engine.
Milestones/features/capabilities that have been added to the engine.
- Asset Management (load data into intermediate formats)
- Audio API from OpenAL
- Basic 2D Game Mathematics/Physics (Vector, Trig, Explicit Euler)
- Collision Detection (Single Axis Theorem, Minkowski Sum/Diff, AABB)
- Debug Services & Displays
- Entity Component Systems
- IMGUI Systems
- Input Parsing
- Push-Buffer Memory Systems
- Small Size Array Optimisations
- Standard Library Replacements (e.g. atoi, strlen, itoa, string)
- Texture Atlas Support, Runtime Font Packing
- OpenGL 2D Rendering Pipeline & Batching Render Calls
### "Handmade" API
- Custom Standard Library: (improved as is required) (e.g. strlen, atoi, itoa)
- Custom Math Library: (improved as is required)
- Custom String Library
- Custom Memory Allocator: alloc memory from preallocated blocks
- Custom XML Parser: for reading game data config
### Rendering
- Animation playback: from spritesheets
- Batched rendering: minimise draw calls grouped on texture & color modulation
- Generate on runtime bitmap font sheet from font file
- Rendering sprites: through GLEW and GLFW to OpenGL programmable pipeline
- Rendering rects and text
### Other
- Audio API: through OpenAL and STB Vorbis for audio functionality
- Audio streaming
- Asset loading system using hashtables
- Collision using AABB
- Debug services/display
- Input parsing, delayed, raw read
- IMGUI style GUI: Buttons, textfields, scroll bars
- Moveable windows
- Primitive entity system
- Primitive battle system
## Building
Dengine is currently being developed using Visual Studio 2015. You can build the project by executing the **build.bat** in the src folder, or simply opening the project in Visual Studio and compiling.
Dengine is currently being developed using Visual Studio 2015. You can build the project by executing the (preferred) **build.bat** in the src folder, or simply opening the project in Visual Studio and compiling.
Additionally, an OpenAL-Soft DLL is required which can be downloaded from the Window pre-built binaries of [OpenAL here.](http://kcat.strangesoft.net/openal.html)
NOTE: Assets are not included in the repository. This only excludes audio as game assets are rendered with vertexes.
NOTE: Assets are not included in the repository.
## Dependencies
- [GLEW](http://glew.sourceforge.net/): Cross platform OpenGL wrapper.

Binary file not shown.

View File

@ -0,0 +1,12 @@
#version 330 core
in vec3 OurColor;
in vec2 TexCoord;
out vec4 color;
uniform sampler2D ourTexture1;
void main()
{
color = texture(ourTexture1, TexCoord);
}

View File

@ -1,9 +1,19 @@
#version 330 core
layout(location = 0) in vec4 data; // (vec2)pos, (vec2)texCoord
layout (location = 0) in vec3 position;
layout (location = 1) in vec3 color;
layout (location = 2) in vec2 texCoord;
out vec3 OurColor;
out vec2 TexCoord;
uniform mat4 model;
uniform mat4 view;
uniform mat4 projection;
void main()
{
gl_Position = projection * vec4(data.xy, 0.0f, 1.0f);
gl_Position = projection * view * model * vec4(position, 1.0f);
OurColor = color;
TexCoord = texCoord;
}

View File

@ -1,8 +0,0 @@
#version 330 core
out vec4 color;
uniform vec4 spriteColor;
void main()
{
color = spriteColor;
}

View File

@ -1,9 +0,0 @@
#version 330 core
layout(location = 0) in vec4 data; // (vec2)pos, (vec2)texCoord
uniform mat4 projection;
void main()
{
gl_Position = projection * vec4(data.xy, 0.0f, 1.0f);
}

Binary file not shown.

Before

Width:  |  Height:  |  Size: 1.8 MiB

View File

@ -22,111 +22,6 @@
#include "Dengine/OpenGL.h"
#include "Dengine/Platform.h"
enum BytesPerPixel
{
bytesPerPixel_Greyscale = 1,
bytesPerPixel_GreyscaleAlpha = 2,
bytesPerPixel_RGB = 3,
bytesPerPixel_RGBA = 4,
};
INTERNAL GLint getGLFormat(i32 bytesPerPixel, b32 srgb)
{
switch (bytesPerPixel)
{
case bytesPerPixel_Greyscale:
return GL_LUMINANCE;
case bytesPerPixel_GreyscaleAlpha:
return GL_LUMINANCE_ALPHA;
case bytesPerPixel_RGB:
return (srgb ? GL_SRGB : GL_RGB);
case bytesPerPixel_RGBA:
return (srgb ? GL_SRGB_ALPHA : GL_RGBA);
default:
// TODO(doyle): Invalid
// std::cout << "getGLFormat() invalid bytesPerPixel: "
// << bytesPerPixel << std::endl;
return GL_LUMINANCE;
}
}
Texture textureGen(const GLuint width, const GLuint height,
const GLint bytesPerPixel, const u8 *const image)
{
// TODO(doyle): Let us set the parameters gl params as well
GL_CHECK_ERROR();
Texture tex = {0};
tex.width = width;
tex.height = height;
tex.internalFormat = GL_RGBA;
tex.wrapS = GL_REPEAT;
tex.wrapT = GL_REPEAT;
tex.filterMinification = GL_NEAREST;
tex.filterMagnification = GL_NEAREST;
glGenTextures(1, &tex.id);
GL_CHECK_ERROR();
glBindTexture(GL_TEXTURE_2D, tex.id);
GL_CHECK_ERROR();
/* Load image into texture */
// TODO(doyle) Figure out the gl format
tex.imageFormat = getGLFormat(bytesPerPixel, FALSE);
ASSERT(tex.imageFormat == GL_RGBA);
GL_CHECK_ERROR();
glTexImage2D(GL_TEXTURE_2D, 0, tex.internalFormat, tex.width, tex.height, 0,
tex.imageFormat, GL_UNSIGNED_BYTE, image);
GL_CHECK_ERROR();
// TODO(doyle): Not needed for sprites? glGenerateMipmap(GL_TEXTURE_2D);
/* Set parameter of currently bound texture */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, tex.wrapS);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tex.wrapT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
tex.filterMinification);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
tex.filterMagnification);
GL_CHECK_ERROR();
/* Unbind and clean up */
glBindTexture(GL_TEXTURE_2D, 0);
GL_CHECK_ERROR();
return tex;
}
void asset_init(AssetManager *assetManager, MemoryArena_ *arena)
{
i32 texAtlasEntries = 8;
assetManager->texAtlas.size = texAtlasEntries;
assetManager->texAtlas.entries =
memory_pushBytes(arena, texAtlasEntries * sizeof(HashTableEntry));
i32 animEntries = 1024;
assetManager->anims.size = animEntries;
assetManager->anims.entries =
memory_pushBytes(arena, animEntries * sizeof(HashTableEntry));
i32 texEntries = 32;
assetManager->textures.size = texEntries;
assetManager->textures.entries =
memory_pushBytes(arena, texEntries * sizeof(HashTableEntry));
/* Create empty 1x1 4bpp black texture */
u32 bitmap = (0xFF << 24) | (0xFF << 16) | (0xFF << 8) | (0xFF << 0);
Texture *tex = asset_texGetFreeSlot(assetManager, arena, "nullTex");
*tex = textureGen(1, 1, 4, CAST(u8 *)(&bitmap));
i32 audioEntries = 32;
assetManager->audio.size = audioEntries;
assetManager->audio.entries =
memory_pushBytes(arena, audioEntries * sizeof(HashTableEntry));
}
/*
*********************************
* Hash Table Operations
@ -205,7 +100,7 @@ INTERNAL SubTexture *getFreeAtlasSubTexSlot(TexAtlas *const atlas,
}
}
const SubTexture asset_atlasGetSubTex(TexAtlas *const atlas, const char *const key)
const SubTexture asset_getAtlasSubTex(TexAtlas *const atlas, const char *const key)
{
HashTableEntry *entry = getEntryFromHash(&atlas->subTex, key);
@ -217,11 +112,11 @@ const SubTexture asset_atlasGetSubTex(TexAtlas *const atlas, const char *const k
return result;
}
DEBUG_LOG("asset_atlasGetSubTex() failed: Sub texture does not exist");
DEBUG_LOG("asset_getAtlasSubTex() failed: Sub texture does not exist");
return result;
}
Texture *asset_texGet(AssetManager *const assetManager, const char *const key)
Texture *asset_getTex(AssetManager *const assetManager, const char *const key)
{
HashTableEntry *entry = getEntryFromHash(&assetManager->textures, key);
@ -231,7 +126,7 @@ Texture *asset_texGet(AssetManager *const assetManager, const char *const key)
return result;
}
TexAtlas *asset_atlasGetFreeSlot(AssetManager *const assetManager,
TexAtlas *asset_getFreeTexAtlasSlot(AssetManager *const assetManager,
MemoryArena_ *arena, const char *const key,
i32 numSubTex)
{
@ -268,7 +163,7 @@ TexAtlas *asset_atlasGetFreeSlot(AssetManager *const assetManager,
}
}
TexAtlas *asset_atlasGet(AssetManager *const assetManager,
TexAtlas *asset_getTexAtlas(AssetManager *const assetManager,
const char *const key)
{
@ -281,7 +176,7 @@ TexAtlas *asset_atlasGet(AssetManager *const assetManager,
}
Texture *asset_texGetFreeSlot(AssetManager *const assetManager,
Texture *asset_getFreeTexSlot(AssetManager *const assetManager,
MemoryArena_ *const arena, const char *const key)
{
@ -300,51 +195,39 @@ Texture *asset_texGetFreeSlot(AssetManager *const assetManager,
}
}
Texture *asset_texLoadImage(AssetManager *assetManager, MemoryArena_ *arena,
Texture *asset_loadTextureImage(AssetManager *assetManager, MemoryArena_ *arena,
const char *const path, const char *const key)
{
/* Open the texture image */
i32 imgWidth, imgHeight, bytesPerPixel;
stbi_set_flip_vertically_on_load(TRUE);
u8 *image =
asset_imageLoad(&imgWidth, &imgHeight, &bytesPerPixel, path, TRUE);
stbi_load(path, &imgWidth, &imgHeight, &bytesPerPixel, 0);
Texture *result = NULL;
if (image)
{
result = asset_texGetFreeSlot(assetManager, arena, key);
*result = textureGen(CAST(GLuint)(imgWidth), CAST(GLuint)(imgHeight),
CAST(GLint)(bytesPerPixel), image);
GL_CHECK_ERROR();
asset_imageFree(image);
}
return result;
}
u8 *asset_imageLoad(i32 *width, i32 *height, i32 *bpp, const char *const path,
b32 flipVertically)
{
stbi_set_flip_vertically_on_load(flipVertically);
u8 *image = stbi_load(path, width, height, bpp, 0);
if (*width != *height)
#ifdef DENGINE_DEBUG
if (imgWidth != imgHeight)
{
printf(
"asset_texLoadImage() warning: Sprite sheet is not square: "
"%dx%dpx\n",
*width, *height);
"asset_loadTextureImage() warning: Sprite sheet is not square: "
"%dx%dpx\n", imgWidth, imgHeight);
}
#endif
if (!image)
{
printf("stdbi_load() failed: %s\n", stbi_failure_reason());
return NULL;
}
return image;
}
Texture *result = asset_getFreeTexSlot(assetManager, arena, key);
*result = texture_gen(CAST(GLuint)(imgWidth), CAST(GLuint)(imgHeight),
CAST(GLint)(bytesPerPixel), image);
void asset_imageFree(u8 *image) { stbi_image_free(image); }
GL_CHECK_ERROR();
stbi_image_free(image);
return result;
}
/*
*********************************
@ -369,7 +252,7 @@ INTERNAL Animation *getFreeAnimationSlot(AssetManager *const assetManager,
}
}
void asset_animAdd(AssetManager *const assetManager,
void asset_addAnimation(AssetManager *const assetManager,
MemoryArena_ *const arena, const char *const animName,
TexAtlas *const atlas,
char **const subTextureNames,
@ -393,7 +276,7 @@ void asset_animAdd(AssetManager *const assetManager,
}
Animation *asset_animGet(AssetManager *const assetManager,
Animation *asset_getAnim(AssetManager *const assetManager,
const char *const key)
{
HashTableEntry *entry = getEntryFromHash(&assetManager->anims, key);
@ -706,7 +589,7 @@ INTERNAL void parseXmlTreeToGame(AssetManager *assetManager, MemoryArena_ *arena
*/
char *imageName = atlasXmlNode->attribute.value;
i32 numSubTex = 1024;
atlas = asset_atlasGetFreeSlot(assetManager, arena,
atlas = asset_getFreeTexAtlasSlot(assetManager, arena,
imageName, numSubTex);
if (!atlas)
@ -732,7 +615,7 @@ INTERNAL void parseXmlTreeToGame(AssetManager *assetManager, MemoryArena_ *arena
common_strncat(imagePath, dataDir, dataDirLen);
common_strncat(imagePath, imageName, imageNameLen);
Texture *tex = asset_texLoadImage(assetManager, arena,
Texture *tex = asset_loadTextureImage(assetManager, arena,
imagePath, imageName);
if (!tex)
@ -783,7 +666,7 @@ INTERNAL void parseXmlTreeToGame(AssetManager *assetManager, MemoryArena_ *arena
i32 valueLen = common_strlen(value);
i32 intValue = common_atoi(value, valueLen);
subTex.rect.min.x = CAST(f32) intValue;
subTex.rect.pos.x = CAST(f32) intValue;
}
else if (common_strcmp(subTexAttrib->name, "y") ==
0)
@ -792,7 +675,7 @@ INTERNAL void parseXmlTreeToGame(AssetManager *assetManager, MemoryArena_ *arena
i32 valueLen = common_strlen(value);
i32 intValue = common_atoi(value, valueLen);
subTex.rect.min.y = CAST(f32) intValue;
subTex.rect.pos.y = CAST(f32) intValue;
}
else if (common_strcmp(subTexAttrib->name,
"width") == 0)
@ -801,7 +684,7 @@ INTERNAL void parseXmlTreeToGame(AssetManager *assetManager, MemoryArena_ *arena
i32 valueLen = common_strlen(value);
i32 intValue = common_atoi(value, valueLen);
subTex.rect.max.w = CAST(f32) intValue;
subTex.rect.size.w = CAST(f32) intValue;
}
else if (common_strcmp(subTexAttrib->name,
"height") == 0)
@ -810,7 +693,7 @@ INTERNAL void parseXmlTreeToGame(AssetManager *assetManager, MemoryArena_ *arena
i32 valueLen = common_strlen(value);
i32 intValue = common_atoi(value, valueLen);
subTex.rect.max.h = CAST(f32) intValue;
subTex.rect.size.h = CAST(f32) intValue;
}
else if (common_strcmp(subTexAttrib->name,
"hand_offset_x") == 0)
@ -843,9 +726,9 @@ INTERNAL void parseXmlTreeToGame(AssetManager *assetManager, MemoryArena_ *arena
// TODO(doyle): XML specifies 0,0 top left, we
// prefer 0,0 bottom right, so offset by size since 0,0
// is top left and size creates a bounding box below it
subTex.rect.min.y = 1024 - subTex.rect.min.y;
subTex.rect.min.y -= subTex.rect.max.h;
subTex.offset.y = subTex.rect.max.h - subTex.offset.y;
subTex.rect.pos.y = 1024 - subTex.rect.pos.y;
subTex.rect.pos.y -= subTex.rect.size.h;
subTex.offset.y = subTex.rect.size.h - subTex.offset.y;
#ifdef DENGINE_DEBUG
ASSERT(key);
@ -928,7 +811,7 @@ INTERNAL void freeXmlData(MemoryArena_ *const arena, XmlToken *tokens,
* Everything else
*********************************
*/
const i32 asset_xmlLoad(AssetManager *const assetManager,
const i32 asset_loadXmlFile(AssetManager *const assetManager,
MemoryArena_ *const arena,
const PlatformFileRead *const fileRead)
{
@ -957,7 +840,7 @@ const i32 asset_xmlLoad(AssetManager *const assetManager,
return result;
}
AudioVorbis *const asset_vorbisGet(AssetManager *const assetManager,
AudioVorbis *const asset_getVorbis(AssetManager *const assetManager,
const char *const key)
{
@ -969,7 +852,7 @@ AudioVorbis *const asset_vorbisGet(AssetManager *const assetManager,
return result;
}
const i32 asset_vorbisLoad(AssetManager *assetManager, MemoryArena_ *arena,
const i32 asset_loadVorbis(AssetManager *assetManager, MemoryArena_ *arena,
const char *const path, const char *const key)
{
HashTableEntry *entry = getFreeHashSlot(&assetManager->audio, arena, key);
@ -1035,45 +918,43 @@ INTERNAL GLuint createShaderFromPath(MemoryArena_ *arena, const char *const path
return result;
}
INTERNAL u32 shaderLoadProgram(const GLuint vertexShader,
INTERNAL i32 shaderLoadProgram(Shader *const shader, const GLuint vertexShader,
const GLuint fragmentShader)
{
u32 result = glCreateProgram();
glAttachShader(result, vertexShader);
glAttachShader(result, fragmentShader);
glLinkProgram(result);
GL_CHECK_ERROR();
shader->id = glCreateProgram();
glAttachShader(shader->id, vertexShader);
glAttachShader(shader->id, fragmentShader);
glLinkProgram(shader->id);
glDeleteShader(fragmentShader);
glDeleteShader(vertexShader);
GL_CHECK_ERROR();
GLint success;
GLchar infoLog[512];
glGetProgramiv(result, GL_LINK_STATUS, &success);
GL_CHECK_ERROR();
if (result == 0)
glGetProgramiv(shader->id, GL_LINK_STATUS, &success);
if (!success)
{
glGetProgramInfoLog(result, 512, NULL, infoLog);
glGetProgramInfoLog(shader->id, 512, NULL, infoLog);
printf("glLinkProgram failed: %s\n", infoLog);
ASSERT(TRUE);
return -1;
}
GL_CHECK_ERROR();
return result;
return 0;
}
u32 asset_shaderGet(AssetManager *assetManager, const enum ShaderList type)
Shader *const asset_getShader(AssetManager *assetManager,
const enum ShaderList type)
{
if (type < shaderlist_count) return assetManager->shaders[type];
if (type < shaderlist_count)
return &assetManager->shaders[type];
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
return -1;
return NULL;
}
const i32 asset_shaderLoad(AssetManager *assetManager, MemoryArena_ *arena,
const i32 asset_loadShaderFiles(AssetManager *assetManager, MemoryArena_ *arena,
const char *const vertexPath,
const char *const fragmentPath,
const enum ShaderList type)
@ -1082,89 +963,15 @@ const i32 asset_shaderLoad(AssetManager *assetManager, MemoryArena_ *arena,
GLuint fragmentShader =
createShaderFromPath(arena, fragmentPath, GL_FRAGMENT_SHADER);
u32 shaderId = shaderLoadProgram(vertexShader, fragmentShader);
if (shaderId == 0) return -1;
Shader shader;
i32 result = shaderLoadProgram(&shader, vertexShader, fragmentShader);
if (result)
return result;
assetManager->shaders[type] = shaderId;
assetManager->shaders[type] = shader;
return 0;
}
INTERNAL FontPack *getMatchingFontPack(AssetManager *assetManager,
const char *const name)
{
FontPack *result = NULL;
for (i32 i = 0; i < ARRAY_COUNT(assetManager->fontPack); i++)
{
FontPack *checkFontPack = &assetManager->fontPack[i];
if (common_strcmp(checkFontPack->name, name) == 0)
{
result = checkFontPack;
break;
}
}
return result;
}
Font *asset_fontGetOrCreateOnDemand(AssetManager *assetManager,
MemoryArena_ *persistentArena,
MemoryArena_ *transientArena, char *name,
i32 size)
{
Font *result = asset_fontGet(assetManager, name, size);
if (result == NULL)
{
FontPack *pack = getMatchingFontPack(assetManager, name);
if (pack != NULL)
{
for(i32 i = 0; i < pack->fontIndex; i++)
{
Font *checkFont = &pack->font[i];
if (checkFont->size == size)
{
result = checkFont;
break;
}
}
if (result == NULL)
{
asset_fontLoadTTF(assetManager, persistentArena, transientArena,
pack->filePath, name, size);
result = asset_fontGet(assetManager, name, size);
}
}
else
{
// TODO(doyle): Logging
}
}
return result;
}
Font *asset_fontGet(AssetManager *assetManager, char *name, i32 size)
{
Font *result = NULL;
FontPack *pack = getMatchingFontPack(assetManager, name);
if (pack != NULL) {
for (i32 j = 0; j < ARRAY_COUNT(pack->font); j++)
{
if (pack->font[j].fontHeight == size)
{
result = &pack->font[j];
break;
}
}
}
return result;
}
/* Individual glyph bitmap generated from STB used for creating a font sheet */
typedef struct GlyphBitmap
{
@ -1173,77 +980,33 @@ typedef struct GlyphBitmap
i32 codepoint;
} GlyphBitmap;
const i32 asset_fontLoadTTF(AssetManager *assetManager,
MemoryArena_ *persistentArena,
MemoryArena_ *transientArena, char *filePath,
char *name, i32 targetFontHeight)
const i32 asset_loadTTFont(AssetManager *assetManager, MemoryArena_ *arena,
const char *filePath)
{
/*
****************************************
* Initialise assetmanager font reference
****************************************
*/
FontPack *fontPack = NULL;
for (i32 i = 0; i < assetManager->fontPackIndex; i++)
{
FontPack *checkFontPack = &assetManager->fontPack[i];
if (common_strcmp(checkFontPack->name, name) == 0)
{
fontPack = checkFontPack;
break;
}
}
if (fontPack == NULL) {
fontPack = &assetManager->fontPack[assetManager->fontPackIndex++];
fontPack->name = name;
fontPack->filePath = filePath;
ASSERT(assetManager->fontPackIndex <
ARRAY_COUNT(assetManager->fontPack));
}
Font *font = NULL;
for (i32 j = 0; j < fontPack->fontIndex; j++)
{
Font *checkFont = &fontPack->font[j];
if (checkFont->fontHeight == targetFontHeight)
{
font = &fontPack->font[j];
break;
}
}
if (font == NULL)
{
font = &fontPack->font[fontPack->fontIndex++];
ASSERT(fontPack->fontIndex < ARRAY_COUNT(fontPack->font));
}
else
{
return 0;
}
TempMemory tempRegion = memory_beginTempRegion(transientArena);
PlatformFileRead fontFileRead = {0};
i32 result =
platform_readFileToBuffer(transientArena, filePath, &fontFileRead);
i32 result = platform_readFileToBuffer(arena, filePath, &fontFileRead);
if (result) return result;
stbtt_fontinfo fontInfo = {0};
stbtt_InitFont(&fontInfo, fontFileRead.buffer,
stbtt_GetFontOffsetForIndex(fontFileRead.buffer, 0));
/*
****************************************
* Initialise assetmanager font reference
****************************************
*/
Font *font = &assetManager->font;
font->codepointRange = V2i(32, 127);
v2 codepointRange = font->codepointRange;
const i32 numGlyphs = CAST(i32)(codepointRange.y - codepointRange.x);
GlyphBitmap *glyphBitmaps =
memory_pushBytes(transientArena, numGlyphs * sizeof(GlyphBitmap));
memory_pushBytes(arena, numGlyphs * sizeof(GlyphBitmap));
v2 largestGlyphDimension = V2(0, 0);
font->fontHeight = targetFontHeight;
f32 scaleY = stbtt_ScaleForPixelHeight(&fontInfo, (f32)targetFontHeight);
const f32 targetFontHeight = 15.0f;
f32 scaleY = stbtt_ScaleForPixelHeight(&fontInfo, targetFontHeight);
i32 ascent, descent, lineGap;
stbtt_GetFontVMetrics(&fontInfo, &ascent, &descent, &lineGap);
@ -1255,7 +1018,7 @@ const i32 asset_fontLoadTTF(AssetManager *assetManager,
font->metrics = CAST(FontMetrics){ascent, descent, lineGap};
font->charMetrics =
memory_pushBytes(persistentArena, numGlyphs * sizeof(CharMetrics));
memory_pushBytes(arena, numGlyphs * sizeof(CharMetrics));
/*
************************************************************
@ -1275,7 +1038,7 @@ const i32 asset_fontLoadTTF(AssetManager *assetManager,
u8 *source = monoBitmap;
u32 *colorBitmap =
memory_pushBytes(transientArena, width * height * sizeof(u32));
memory_pushBytes(arena, width * height * sizeof(u32));
u32 *dest = colorBitmap;
// NOTE(doyle): STB generates 1 byte per pixel bitmaps, we use 4bpp, so
@ -1319,7 +1082,7 @@ const i32 asset_fontLoadTTF(AssetManager *assetManager,
if ((largestGlyphDimension.h - CAST(i32)targetFontHeight) >= 50)
{
printf(
"asset_fontLoadTTF() warning: The loaded font file has a glyph "
"asset_loadTTFont() warning: The loaded font file has a glyph "
"considerably larger than our target .. font packing is "
"unoptimal\n");
}
@ -1343,7 +1106,7 @@ const i32 asset_fontLoadTTF(AssetManager *assetManager,
if ((glyphsPerRow * glyphsPerCol) <= numGlyphs)
{
printf(
"asset_fontLoadTTF() warning: The target font height creates a "
"asset_loadTTFont() warning: The target font height creates a "
"glyph sheet that exceeds the available space!");
ASSERT(INVALID_CODE_PATH);
@ -1351,8 +1114,7 @@ const i32 asset_fontLoadTTF(AssetManager *assetManager,
#endif
i32 bitmapSize = SQUARED(TARGET_TEXTURE_SIZE) * TARGET_BYTES_PER_PIXEL;
u32 *fontBitmap =
memory_pushBytes(transientArena, bitmapSize * sizeof(u32));
u32 *fontBitmap = memory_pushBytes(arena, bitmapSize * sizeof(u32));
const i32 pitch = MAX_TEXTURE_SIZE * TARGET_BYTES_PER_PIXEL;
// Check value to determine when a row of glyphs is completely printed
@ -1369,8 +1131,8 @@ const i32 asset_fontLoadTTF(AssetManager *assetManager,
char charToEncode = CAST(char)codepointRange.x;
i32 numSubTex = numGlyphs;
TexAtlas *fontAtlas = asset_atlasGetFreeSlot(
assetManager, persistentArena, "font", numSubTex);
TexAtlas *fontAtlas =
asset_getFreeTexAtlasSlot(assetManager, arena, "font", numSubTex);
/*
*********************************************************
@ -1397,8 +1159,7 @@ const i32 asset_fontLoadTTF(AssetManager *assetManager,
// all ascii characters, charToEncode represents the character
// 1:1
const char key[2] = {charToEncode, 0};
SubTexture *subTex =
getFreeAtlasSubTexSlot(fontAtlas, persistentArena, key);
SubTexture *subTex = getFreeAtlasSubTexSlot(fontAtlas, arena, key);
subTex->rect = CAST(Rect){origin, font->maxSize};
charToEncode++;
}
@ -1452,8 +1213,8 @@ const i32 asset_fontLoadTTF(AssetManager *assetManager,
* Generate and store font bitmap to assets
*******************************************
*/
Texture *tex = asset_texGetFreeSlot(assetManager, persistentArena, "font");
*tex = textureGen(MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, 4,
Texture *tex = asset_getFreeTexSlot(assetManager, arena, "font");
*tex = texture_gen(MAX_TEXTURE_SIZE, MAX_TEXTURE_SIZE, 4,
CAST(u8 *) fontBitmap);
#ifdef WT_RENDER_FONT_FILE
@ -1477,14 +1238,19 @@ const i32 asset_fontLoadTTF(AssetManager *assetManager,
i32 glyphBitmapSizeInBytes = CAST(i32) glyphBitmaps[i].dimensions.w *
CAST(i32) glyphBitmaps[i].dimensions.h *
sizeof(u32);
// TODO(doyle): Mem free
// PLATFORM_MEM_FREE(arena, glyphBitmaps[i].pixels, glyphBitmapSizeInBytes);
}
memory_endTempRegion(tempRegion);
// TODO(doyle): Mem free
// PLATFORM_MEM_FREE(arena, glyphBitmaps, numGlyphs * sizeof(GlyphBitmap));
platform_closeFileRead(arena, &fontFileRead);
return 0;
}
const v2 asset_fontStringDimInPixels(const Font *const font,
const char *const string)
const v2 asset_stringDimInPixels(const Font *const font,
const char *const string)
{
v2 stringDim = V2(0, 0);
for (i32 i = 0; i < common_strlen(string); i++)

File diff suppressed because it is too large Load Diff

View File

@ -129,7 +129,7 @@ INTERNAL i32 rendererAcquire(MemoryArena_ *arena, AudioManager *audioManager,
"rendererAcquire(): Renderer has not been released before "
"acquiring, force release by stopping stream");
#endif
audio_vorbisStop(arena, audioManager, audioRenderer);
audio_stopVorbis(arena, audioManager, audioRenderer);
}
// TODO(doyle): Super bad linear O(n) search for every audio-enabled entity
@ -239,14 +239,14 @@ INTERNAL i32 initRendererForPlayback(MemoryArena_ *arena,
ASSERT(audioManager && audioRenderer && vorbis);
if (numPlays != AUDIO_REPEAT_INFINITE && numPlays <= 0)
{
DEBUG_LOG("audio_vorbisStream() warning: Number of plays is less than 0");
DEBUG_LOG("audio_streamPlayVorbis() warning: Number of plays is less than 0");
}
#endif
i32 result = rendererAcquire(arena, audioManager, audioRenderer);
if (result)
{
DEBUG_LOG("audio_vorbisStream() failed: Could not acquire renderer");
DEBUG_LOG("audio_streamPlayVorbis() failed: Could not acquire renderer");
return result;
}
@ -258,7 +258,7 @@ INTERNAL i32 initRendererForPlayback(MemoryArena_ *arena,
{
#ifdef DENGINE_DEBUG
DEBUG_LOG(
"audio_vorbisStream() warning: Unexpected channel format");
"audio_streamPlayVorbis() warning: Unexpected channel format");
#endif
}
@ -268,12 +268,10 @@ INTERNAL i32 initRendererForPlayback(MemoryArena_ *arena,
}
#include <stdlib.h>
const i32 audio_vorbisPlay(MemoryArena_ *arena, AudioManager *audioManager,
const i32 audio_playVorbis(MemoryArena_ *arena, AudioManager *audioManager,
AudioRenderer *audioRenderer, AudioVorbis *vorbis,
i32 numPlays)
{
if (vorbis == NULL) return -1;
i32 result = initRendererForPlayback(arena, audioManager, audioRenderer,
vorbis, numPlays);
@ -296,16 +294,11 @@ const i32 audio_vorbisPlay(MemoryArena_ *arena, AudioManager *audioManager,
return result;
}
const i32 audio_vorbisStream(MemoryArena_ *arena, AudioManager *audioManager,
const i32 audio_streamPlayVorbis(MemoryArena_ *arena, AudioManager *audioManager,
AudioRenderer *audioRenderer,
AudioVorbis *vorbis, i32 numPlays)
{
// TODO(doyle): Streaming leaks memory, we don't free the "copy audio"
ASSERT(INVALID_CODE_PATH);
if (vorbis == NULL) return -1;
i32 result = initRendererForPlayback(arena, audioManager, audioRenderer,
vorbis, numPlays);
// NOTE(doyle): We make a copy of the audio vorbis file using all the same
@ -326,7 +319,7 @@ const i32 audio_vorbisStream(MemoryArena_ *arena, AudioManager *audioManager,
return result;
}
const i32 audio_vorbisStop(MemoryArena_ *arena, AudioManager *audioManager,
const i32 audio_stopVorbis(MemoryArena_ *arena, AudioManager *audioManager,
AudioRenderer *audioRenderer)
{
i32 result = 0;
@ -354,7 +347,7 @@ const i32 audio_vorbisStop(MemoryArena_ *arena, AudioManager *audioManager,
return result;
}
const i32 audio_vorbisPause(AudioManager *audioManager,
const i32 audio_pauseVorbis(AudioManager *audioManager,
AudioRenderer *audioRenderer)
{
i32 result = 0;
@ -373,7 +366,7 @@ const i32 audio_vorbisPause(AudioManager *audioManager,
return result;
}
const i32 audio_vorbisResume(AudioManager *audioManager,
const i32 audio_resumeVorbis(AudioManager *audioManager,
AudioRenderer *audioRenderer)
{
i32 result = 0;
@ -387,7 +380,7 @@ const i32 audio_vorbisResume(AudioManager *audioManager,
else
{
#ifdef DENGINE_DEBUG
DEBUG_LOG("audio_vorbisResume(): Tried to resume invalid source")
DEBUG_LOG("audio_resumeVorbis(): Tried to resume invalid source")
#endif
result = -1;
}

View File

@ -1,173 +1,10 @@
#include <stdlib.h>
#include "Dengine/Common.h"
#include "Dengine/MemoryArena.h"
void common_optimalArrayV2Create(OptimalArrayV2 *array)
{
array->ptr = array->fastStorage;
array->size = ARRAY_COUNT(array->fastStorage);
}
i32 common_optimalArrayV2Push(OptimalArrayV2 *array, v2 data)
{
if (array->index + 1 > array->size)
{
array->size += ARRAY_COUNT(array->fastStorage);
i32 newSizeInBytes = array->size * sizeof(v2);
/* If first time expanding, we need to manually malloc and copy */
if (array->ptr == array->fastStorage)
{
array->ptr = malloc(newSizeInBytes);
for (i32 i = 0; i < ARRAY_COUNT(array->fastStorage); i++)
{
array->ptr[i] = array->fastStorage[i];
}
}
else
{
array->ptr = realloc(array->ptr, newSizeInBytes);
}
if (!array->ptr) return optimalarrayerror_out_of_memory;
}
array->ptr[array->index++] = data;
return 0;
}
void common_optimalArrayV2Destroy(OptimalArrayV2 *array)
{
if (array->ptr != array->fastStorage)
{
free(array->ptr);
}
}
/*
* +-------------------------------------+
* | Header | C-String | Null Terminator |
* +-------------------------------------+
* |
* +--> Functions return the c-string for compatibility with other
* string libraries
*
* Headers are retrieved using pointer arithmetric from the C string. These
* strings are typechecked by their own typedef char String.
*/
typedef struct StringHeader
{
i32 len;
// NOTE(doyle): A string is stored as one contiguous chunk of memory. We
// don't use a pointer for storing the string as this'd require an extra
// 4 bytes to store the pointer, which we don't need if everything is
// contiguous. The string follows on from the len, and we return the address
// of the string to simulate a pointer.
String string;
} StringHeader;
// TODO(doyle): string capacity- append if already enough space
INTERNAL StringHeader *stringGetHeader(String *const string)
{
StringHeader *result = NULL;
// NOTE(doyle): C-String must be located at end of struct type for offset to
// be correct! We cannot just subtract the string-header since we start at
// the string ptr position
if (string)
{
i32 byteOffsetToHeader = sizeof(StringHeader) - sizeof(String *);
result = CAST(StringHeader *)((CAST(u8 *) string) - byteOffsetToHeader);
}
return result;
}
i32 common_stringLen(String *const string)
{
if (!string) return -1;
StringHeader *header = stringGetHeader(string);
i32 result = header->len;
return result;
}
String *const common_stringAppend(MemoryArena_ *const arena, String *oldString,
char *appendString, i32 appendLen)
{
if (!oldString || !appendString || !arena) return oldString;
/* Calculate size of new string */
StringHeader *oldHeader = stringGetHeader(oldString);
i32 newLen = oldHeader->len + appendLen;
String *newString = common_stringMakeLen(arena, newLen);
/* Append strings together */
String *insertPtr = newString;
common_strncpy(insertPtr, oldString, oldHeader->len);
insertPtr += oldHeader->len;
common_strncpy(insertPtr, appendString, appendLen);
/* Free old string */
common_stringFree(arena, oldString);
return newString;
}
void common_stringFree(MemoryArena_ *arena, String *string)
{
if (!string || !arena) return;
StringHeader *header = stringGetHeader(string);
i32 bytesToFree = sizeof(StringHeader) + header->len;
common_memset((u8 *)header, 0, bytesToFree);
// TODO(doyle): Mem free
// PLATFORM_MEM_FREE(arena, header, bytesToFree);
string = NULL;
}
String *const common_stringMake(MemoryArena_ *const arena, char *string)
{
if (!arena) return NULL;
i32 len = common_strlen(string);
String *result = common_stringMakeLen(arena, len);
common_strncpy(result, string, len);
return result;
}
String *const common_stringMakeLen(MemoryArena_ *const arena, i32 len)
{
if (!arena) return NULL;
// NOTE(doyle): Allocate the string header size plus the len. But _note_
// that StringHeader contains a single String character. This has
// a side-effect of already preallocating a byte for the null-terminating
// character. Whilst the len of a string counts up to the last character
// _not_ including null-terminator.
i32 bytesToAllocate = sizeof(StringHeader) + len;
void *chunk = memory_pushBytes(arena, bytesToAllocate * sizeof(u8));
if (!chunk) return NULL;
StringHeader *header = CAST(StringHeader *) chunk;
header->len = len;
return &header->string;
}
#include "Dengine/Math.h"
i32 common_strlen(const char *const string)
{
i32 result = 0;
while (string[result])
result++;
while (string[result]) result++;
return result;
}
@ -175,7 +12,8 @@ i32 common_strcmp(const char *a, const char *b)
{
while (*a == *b)
{
if (!*a) return 0;
if (!*a)
return 0;
a++;
b++;
}
@ -189,13 +27,13 @@ void common_strncat(char *dest, const char *src, i32 numChars)
while (*stringPtr)
stringPtr++;
for (i32 i = 0; i < numChars; i++)
for (i32 i = 0; i < numChars; i++)
*(stringPtr++) = src[i];
}
char *common_strncpy(char *dest, const char *src, i32 numChars)
{
for (i32 i = 0; i < numChars; i++)
for (i32 i = 0; i < numChars; i++)
dest[i] = src[i];
return dest;
@ -216,9 +54,9 @@ INTERNAL void reverseString(char *const buf, const i32 bufSize)
for (i32 i = 0; i < mid; i++)
{
char tmp = buf[i];
buf[i] = buf[(bufSize - 1) - i];
buf[(bufSize - 1) - i] = tmp;
char tmp = buf[i];
buf[i] = buf[(bufSize-1) - i];
buf[(bufSize-1) - i] = tmp;
}
}
@ -226,15 +64,10 @@ void common_itoa(i32 value, char *buf, i32 bufSize)
{
if (!buf || bufSize == 0) return;
if (value == 0)
{
buf[0] = '0';
return;
}
// NOTE(doyle): Max 32bit integer (+-)2147483647
i32 charIndex = 0;
b32 negative = FALSE;
i32 charIndex = 0;
b32 negative = FALSE;
if (value < 0) negative = TRUE;
if (negative) buf[charIndex++] = '-';
@ -242,21 +75,13 @@ void common_itoa(i32 value, char *buf, i32 bufSize)
i32 val = ABS(value);
while (val != 0 && charIndex < bufSize)
{
i32 rem = val % 10;
i32 rem = val % 10;
buf[charIndex++] = rem + '0';
val /= 10;
}
// NOTE(doyle): If string is negative, we only want to reverse starting
// from the second character, so we don't put the negative sign at the end
if (negative)
{
reverseString(buf + 1, charIndex - 1);
}
else
{
reverseString(buf, charIndex);
}
// NOTE(doyle): The actual string length may differ from the bufSize
reverseString(buf, common_strlen(buf));
}
// TODO(doyle): Consider if we should trash ptrs in string operations in general
@ -312,34 +137,31 @@ u32 common_murmurHash2(const void *key, i32 len, u32 seed)
// Mix 4 bytes at a time into the hash
const unsigned char *data = (const unsigned char *)key;
const unsigned char * data = (const unsigned char *)key;
while (len >= 4)
while(len >= 4)
{
u32 k = *(u32 *)data;
k *= m;
k ^= k >> r;
k *= m;
h *= m;
h ^= k;
data += 4;
len -= 4;
}
// Handle the last few bytes of the input array
switch (len)
switch(len)
{
case 3:
h ^= data[2] << 16;
case 2:
h ^= data[1] << 8;
case 1:
h ^= data[0];
h *= m;
case 3: h ^= data[2] << 16;
case 2: h ^= data[1] << 8;
case 1: h ^= data[0];
h *= m;
};
// Do a few final mixes of the hash to ensure the last few
@ -350,4 +172,4 @@ u32 common_murmurHash2(const void *key, i32 len, u32 seed)
h ^= h >> 15;
return h;
}
}

View File

@ -1,15 +1,16 @@
#include "Dengine/Debug.h"
#include "Dengine/AssetManager.h"
#include "Dengine/Entity.h"
#include "Dengine/MemoryArena.h"
#include "Dengine/Platform.h"
#include "Dengine/Renderer.h"
#include "Dengine/Asteroid.h"
#include "Dengine/WorldTraveller.h"
typedef struct DebugState
{
b32 init;
Font font;
i32 callCount[debugcount_num];
i32 *callCount;
f32 stringLineGap;
/* Debug strings rendered in top left corner */
@ -28,12 +29,57 @@ typedef struct DebugState
GLOBAL_VAR DebugState GLOBAL_debug;
void debug_init(v2 windowSize, Font font)
inline char *debug_entitystate_string(i32 val)
{
char *string;
switch(val)
{
case entitystate_idle:
string = "EntityState_Idle";
break;
case entitystate_battle:
string = "EntityState_Battle";
break;
case entitystate_attack:
string = "EntityState_Attack";
break;
case entitystate_dead:
string = "EntityState_Dead";
break;
case entitystate_count:
string = "EntityState_Count (Error!)";
break;
case entitystate_invalid:
string = "EntityState_Invalid (Error!)";
break;
default:
string = "EntityState Unknown (NOT DEFINED)";
}
return string;
}
inline char *debug_entityattack_string(i32 val)
{
char *string;
switch(val)
{
case entityattack_count:
string = "EntityAttack_Count (Error!)";
break;
case entityattack_invalid:
string = "EntityAttack_Invalid";
break;
default:
string = "EntityAttack Unknown (NOT DEFINED)";
}
return string;
}
void debug_init(MemoryArena_ *arena, v2 windowSize, Font font)
{
GLOBAL_debug.font = font;
for (i32 i = 0; i < debugcount_num; i++) GLOBAL_debug.callCount[i] = 0;
GLOBAL_debug.callCount =
memory_pushBytes(arena, debugcount_num * sizeof(i32));
GLOBAL_debug.stringLineGap = CAST(f32) font.verticalSpacing;
/* Init debug string stack */
@ -41,17 +87,18 @@ void debug_init(v2 windowSize, Font font)
GLOBAL_debug.stringUpdateTimer = 0.0f;
GLOBAL_debug.stringUpdateRate = 0.15f;
GLOBAL_debug.initialStringP = V2(0.0f, (windowSize.h - 1.8f * GLOBAL_debug.stringLineGap));
GLOBAL_debug.currStringP = GLOBAL_debug.initialStringP;
GLOBAL_debug.initialStringP =
V2(0.0f, (windowSize.h - 1.8f * GLOBAL_debug.stringLineGap));
GLOBAL_debug.currStringP = GLOBAL_debug.initialStringP;
/* Init gui console */
i32 maxConsoleStrLen = ARRAY_COUNT(GLOBAL_debug.console[0]);
i32 maxConsoleStrLen = ARRAY_COUNT(GLOBAL_debug.console[0]);
GLOBAL_debug.consoleIndex = 0;
// TODO(doyle): Font max size not entirely correct? using 1 * font.maxSize.w
// reveals around 4 characters ..
f32 consoleXPos = font.maxSize.w * 20;
f32 consoleYPos = windowSize.h - 1.8f * GLOBAL_debug.stringLineGap;
f32 consoleXPos = font.maxSize.w * 20;
f32 consoleYPos = windowSize.h - 1.8f * GLOBAL_debug.stringLineGap;
GLOBAL_debug.initialConsoleP = V2(consoleXPos, consoleYPos);
GLOBAL_debug.init = TRUE;
@ -95,6 +142,12 @@ void debug_countIncrement(i32 id)
GLOBAL_debug.callCount[id]++;
}
void debug_clearCounter()
{
for (i32 i = 0; i < debugcount_num; i++)
GLOBAL_debug.callCount[i] = 0;
}
void debug_consoleLog(char *string, char *file, int lineNum)
{
@ -222,10 +275,10 @@ INTERNAL void updateAndRenderDebugStack(Renderer *renderer, MemoryArena_ *arena,
for (i32 i = 0; i < GLOBAL_debug.numDebugStrings; i++)
{
f32 rotate = 0;
v4 color = V4(1, 1, 1, 1);
renderer_stringFixed(
renderer, arena, &GLOBAL_debug.font, GLOBAL_debug.debugStrings[i],
GLOBAL_debug.currStringP, V2(0, 0), rotate, color, 0, 0);
v4 color = V4(0, 0, 0, 1);
renderer_staticString(renderer, arena, &GLOBAL_debug.font,
GLOBAL_debug.debugStrings[i],
GLOBAL_debug.currStringP, V2(0, 0), rotate, color);
GLOBAL_debug.currStringP.y -= (0.9f * GLOBAL_debug.stringLineGap);
}
@ -254,51 +307,248 @@ INTERNAL void renderConsole(Renderer *renderer, MemoryArena_ *arena)
for (i32 i = 0; i < maxConsoleLines; i++)
{
f32 rotate = 0;
v4 color = V4(1.0f, 1.0f, 1.0f, 1.0f);
renderer_stringFixed(renderer, arena, &GLOBAL_debug.font,
v4 color = V4(0, 0, 0, 1);
renderer_staticString(renderer, arena, &GLOBAL_debug.font,
GLOBAL_debug.console[i], consoleStrP,
V2(0, 0), rotate, color, 0, 0);
V2(0, 0), rotate, color);
consoleStrP.y -= (0.9f * GLOBAL_debug.stringLineGap);
}
}
void debug_drawUi(GameState *state, f32 dt)
{
{ // Print Memory Arena Info
DEBUG_PUSH_STRING("== MEMORY ARENAS ==");
MemoryArena_ *transient = &state->transientArena;
i32 transientSizeInKbs = transient->size / 1024;
i32 transientUsedInKbs = transient->used / 1024;
v2 transientUsage = V2i(transientUsedInKbs, transientSizeInKbs);
DEBUG_PUSH_VAR("Transient Usage: %.0f/%.0f", transientUsage, "v2");
AssetManager *assetManager = &state->assetManager;
Renderer *renderer = &state->renderer;
World *const world = &state->world[state->currWorldIndex];
Entity *hero = &world->entities[entity_getIndex(world, world->heroId)];
MemoryArena_ *transientArena = &state->transientArena;
MemoryArena_ *persistent = &state->persistentArena;
i32 persistentSizeInKbs = persistent->size / 1024;
i32 persistentUsedInKbs = persistent->used / 1024;
v2 persistentUsage = V2i(persistentUsedInKbs, persistentSizeInKbs);
DEBUG_PUSH_VAR("Permanent Usage: %.0f/%.0f", persistentUsage, "v2");
// TODO(doyle): Dumb copy function from game so we don't expose api
Rect camera = {world->cameraPos, renderer->size};
// NOTE(doyle): Lock camera if it passes the bounds of the world
if (camera.pos.x <= world->bounds.x)
camera.pos.x = world->bounds.x;
GameWorldState *world = ASTEROID_GET_STATE_DATA(state, GameWorldState);
if (world)
// TODO(doyle): Allow Y panning when we need it
f32 cameraTopBoundInPixels = camera.pos.y + camera.size.h;
if (cameraTopBoundInPixels >= world->bounds.w)
camera.pos.y = (world->bounds.w - camera.size.h);
f32 cameraRightBoundInPixels = camera.pos.x + camera.size.w;
if (cameraRightBoundInPixels >= world->bounds.z)
camera.pos.x = (world->bounds.z - camera.size.w);
if (camera.pos.y <= world->bounds.y) camera.pos.y = world->bounds.y;
Font *font = &GLOBAL_debug.font;
if (world->numEntitiesInBattle > 0)
{
v4 color = V4(1.0f, 0, 0, 1);
char *battleStr = "IN-BATTLE RANGE";
f32 strLenInPixels =
CAST(f32)(font->maxSize.w * common_strlen(battleStr));
v2 strPos = V2((renderer->size.w * 0.5f) - (strLenInPixels * 0.5f),
renderer->size.h - 300.0f);
renderer_staticString(&state->renderer, transientArena, font, battleStr,
strPos, V2(0, 0), 0, color);
}
for (i32 i = 0; i < world->freeEntityIndex; i++)
{
Entity *const entity = &world->entities[i];
if (entity->state == entitystate_dead) continue;
/* Render debug markers on entities */
v4 color = V4(1, 1, 1, 1);
char *debugString = NULL;
switch (entity->type)
{
MemoryArena_ *entityArena = &world->entityArena;
i32 entitySizeInKbs = entityArena->size / 1024;
i32 entityUsedInKbs = entityArena->used / 1024;
v2 entityUsage = V2i(entityUsedInKbs, entitySizeInKbs);
DEBUG_PUSH_VAR("Entity Usage: %.0f/%.0f", entityUsage, "v2");
DEBUG_PUSH_STRING("== ==");
case entitytype_mob:
color = V4(1, 0, 0, 1);
debugString = "MOB";
break;
case entitytype_hero:
color = V4(0, 0, 1.0f, 1);
debugString = "HERO";
break;
case entitytype_npc:
color = V4(0, 1.0f, 0, 1);
debugString = "NPC";
break;
default:
break;
}
if (debugString)
{
v2 strPos = v2_add(entity->pos, entity->hitbox);
i32 indexOfLowerAInMetrics = 'a' - CAST(i32) font->codepointRange.x;
strPos.y += font->charMetrics[indexOfLowerAInMetrics].offset.y;
renderer_string(&state->renderer, transientArena, camera, font,
debugString, strPos, V2(0, 0), 0, color);
f32 stringLineGap = 1.1f * font->verticalSpacing;
strPos.y -= GLOBAL_debug.stringLineGap;
char entityPosStr[128];
snprintf(entityPosStr, ARRAY_COUNT(entityPosStr), "%06.2f, %06.2f",
entity->pos.x, entity->pos.y);
renderer_string(&state->renderer, transientArena, camera, font,
entityPosStr, strPos, V2(0, 0), 0, color);
strPos.y -= GLOBAL_debug.stringLineGap;
char entityIDStr[32];
snprintf(entityIDStr, ARRAY_COUNT(entityIDStr), "ID: %4d/%d", entity->id,
world->uniqueIdAccumulator-1);
renderer_string(&state->renderer, transientArena, camera, font,
entityIDStr, strPos, V2(0, 0), 0, color);
if (entity->stats)
{
strPos.y -= GLOBAL_debug.stringLineGap;
char entityHealth[32];
snprintf(entityHealth, ARRAY_COUNT(entityHealth), "HP: %3.0f/%3.0f",
entity->stats->health, entity->stats->maxHealth);
renderer_string(&state->renderer, transientArena, camera,
font, entityHealth, strPos, V2(0, 0), 0, color);
strPos.y -= GLOBAL_debug.stringLineGap;
char entityTimer[32];
snprintf(entityTimer, ARRAY_COUNT(entityTimer), "ATB: %3.0f/%3.0f",
entity->stats->actionTimer, entity->stats->actionRate);
renderer_string(&state->renderer, transientArena, camera,
font, entityTimer, strPos, V2(0, 0), 0, color);
strPos.y -= GLOBAL_debug.stringLineGap;
char entityIdTarget[32];
snprintf(entityIdTarget, ARRAY_COUNT(entityIdTarget),
"Targetting ID: %d", entity->stats->entityIdToAttack);
renderer_string(&state->renderer, transientArena, camera,
font, entityIdTarget, strPos, V2(0, 0), 0, color);
}
strPos.y -= GLOBAL_debug.stringLineGap;
char *entityStateStr = debug_entitystate_string(entity->state);
renderer_string(&state->renderer, transientArena, camera, font,
entityStateStr, strPos, V2(0, 0), 0, color);
if (entity->audioRenderer)
{
strPos.y -= GLOBAL_debug.stringLineGap;
char entityAudioSourceIndex[32];
snprintf(entityAudioSourceIndex,
ARRAY_COUNT(entityAudioSourceIndex),
"AudioSource Index: %d",
entity->audioRenderer->sourceIndex);
renderer_string(&state->renderer, transientArena, camera,
font, entityAudioSourceIndex, strPos, V2(0, 0),
0, color);
}
}
}
DEBUG_PUSH_VAR("Num RenderGroups: %d", &state->renderer.groupsInUse,
/* Render debug info stack */
DEBUG_PUSH_STRING("== Controls ==");
DEBUG_PUSH_STRING(" [: Spawn a mob");
DEBUG_PUSH_STRING("<TAB>: Switch UI element");
DEBUG_PUSH_STRING("<ESC>: Close program");
DEBUG_PUSH_STRING("");
DEBUG_PUSH_STRING("== Config == ");
DEBUG_PUSH_VAR("Toggle World Audio: %d", state->config.playWorldAudio,
"i32");
DEBUG_PUSH_VAR("Num Vertex: %d",
GLOBAL_debug.callCount[debugcount_numVertex], "i32");
DEBUG_PUSH_VAR("Toggle Debug Display: %d", state->config.showDebugDisplay,
"i32");
DEBUG_PUSH_STRING("");
updateAndRenderDebugStack(&state->renderer, &state->transientArena, dt);
renderConsole(&state->renderer, &state->transientArena);
DEBUG_PUSH_STRING("== Hero Properties == ");
DEBUG_PUSH_VAR("Hero Pos: %06.2f, %06.2f", hero->pos, "v2");
DEBUG_PUSH_VAR("Hero dPos: %06.2f, %06.2f", hero->dPos, "v2");
DEBUG_PUSH_VAR("Hero Busy Duration: %05.3f", hero->stats->busyDuration, "f32");
char *heroStateString = debug_entitystate_string(hero->state);
DEBUG_PUSH_VAR("Hero State: %s", *heroStateString, "char");
char *heroQueuedAttackStr =
debug_entityattack_string(hero->stats->queuedAttack);
DEBUG_PUSH_VAR("Hero QueuedAttack: %s", *heroQueuedAttackStr, "char");
DEBUG_PUSH_STRING("");
{ // Clear debug call counters
for (i32 i = 0; i < debugcount_num; i++) GLOBAL_debug.callCount[i] = 0;
DEBUG_PUSH_STRING("== State Properties == ");
DEBUG_PUSH_VAR("FreeEntityIndex: %d", world->freeEntityIndex, "i32");
DEBUG_PUSH_VAR("GLDrawArray Calls: %d",
GLOBAL_debug.callCount[debugcount_drawArrays], "i32");
DEBUG_PUSH_VAR("PlatformMemAlloc Calls: %d",
GLOBAL_debug.callCount[debugcount_platformMemAlloc], "i32");
DEBUG_PUSH_VAR("PlatformMemFree Calls: %d",
GLOBAL_debug.callCount[debugcount_platformMemFree], "i32");
i32 vertexesUsed = GLOBAL_debug.callCount[debugcount_numVertex];
i32 vertexesAvail =
(ARRAY_COUNT(state->renderer.groups) * state->renderer.groupCapacity);
i32 vertexesLeft = vertexesAvail - vertexesUsed;
v2 vertexData = V2i(vertexesUsed, vertexesAvail);
DEBUG_PUSH_VAR("Vertexes Rendered: %1.0f/%1.0f", vertexData, "v2");
DEBUG_PUSH_VAR("Vertexes Left: %d", vertexesLeft, "i32");
i32 groupsUsed = GLOBAL_debug.callCount[debugcount_renderGroups];
i32 groupsAvail = ARRAY_COUNT(state->renderer.groups);
v2 groupData = V2i(groupsUsed, groupsAvail);
DEBUG_PUSH_VAR("Render Groups Used: %1.0f/%1.0f", groupData, "v2");
DEBUG_PUSH_VAR("Mouse Pos: %06.2f, %06.2f", state->input.mouseP, "v2");
/*
*****************
* MEMORY DISPLAY
*****************
*/
i32 debug_bAllocated = transientArena->used;
i32 debug_kbAllocated = debug_bAllocated / 1024;
i32 debug_mbAllocated = debug_kbAllocated / 1024;
DEBUG_PUSH_VAR("TransientArena Used: %db", debug_bAllocated, "i32");
DEBUG_PUSH_VAR("TransientArena Used: %dkb", debug_kbAllocated, "i32");
DEBUG_PUSH_VAR("TransientArena Used: %dmb", debug_mbAllocated, "i32");
DEBUG_PUSH_STRING("");
debug_bAllocated = state->arena_.used;
debug_kbAllocated = debug_bAllocated / 1024;
debug_mbAllocated = debug_kbAllocated / 1024;
DEBUG_PUSH_VAR("PersistentArena Used: %db", debug_bAllocated, "i32");
DEBUG_PUSH_VAR("PersistentArena Used: %dkb", debug_kbAllocated, "i32");
DEBUG_PUSH_VAR("PersistentArena Used: %dmb", debug_mbAllocated, "i32");
DEBUG_PUSH_STRING("");
AudioManager *audioManager = &state->audioManager;
DEBUG_PUSH_STRING("== Audio System ==");
for (i32 i = 0; i < ARRAY_COUNT(audioManager->sourceList); i++)
{
if (audioManager->sourceList[i].isFree) continue;
v3 tmp = V3i(i, audioManager->sourceList[i].id,
audioManager->sourceList[i].isFree);
DEBUG_PUSH_VAR("Source ID[%02.0f].id[%02.0f].isFree: %1.0f", tmp, "v3");
}
DEBUG_PUSH_STRING("");
DEBUG_PUSH_STRING("== EntityIDs in Battle List == ");
DEBUG_PUSH_VAR("NumEntitiesInBattle: %d", world->numEntitiesInBattle,
"i32");
if (world->numEntitiesInBattle > 0)
{
for (i32 i = 0; i < world->maxEntities; i++)
{
if (world->entityIdInBattle[i])
DEBUG_PUSH_VAR("Entity ID: %d", i, "i32");
}
}
else
{
DEBUG_PUSH_STRING("-none-");
}
updateAndRenderDebugStack(&state->renderer, transientArena, dt);
renderConsole(&state->renderer, transientArena);
debug_clearCounter();
}

View File

@ -1,23 +1,24 @@
#include "Dengine/Entity.h"
#include "Dengine/AssetManager.h"
#include "Dengine/Debug.h"
#include "Dengine/Math.h"
#include "Dengine/MemoryArena.h"
#include "Dengine/WorldTraveller.h"
SubTexture entity_subTexGetCurr(Entity *const entity)
SubTexture entity_getActiveSubTexture(Entity *const entity)
{
EntityAnim *entityAnim = &entity->animList[entity->animListIndex];
EntityAnim *entityAnim = &entity->animList[entity->currAnimId];
Animation *anim = entityAnim->anim;
char *frameName = anim->frameList[entityAnim->currFrame];
SubTexture result = asset_atlasGetSubTex(anim->atlas, frameName);
SubTexture result = asset_getAtlasSubTex(anim->atlas, frameName);
return result;
}
void entity_animSet(Entity *const entity, const char *const animName)
void entity_setActiveAnim(EventQueue *eventQueue, Entity *const entity,
const char *const animName)
{
/* Reset current anim data */
EntityAnim *currEntityAnim = &entity->animList[entity->animListIndex];
EntityAnim *currEntityAnim = &entity->animList[entity->currAnimId];
currEntityAnim->currDuration = currEntityAnim->anim->frameDuration;
currEntityAnim->currFrame = 0;
currEntityAnim->timesCompleted = 0;
@ -31,11 +32,14 @@ void entity_animSet(Entity *const entity, const char *const animName)
// TODO(doyle): Linear search, but not a problem if list is small
if (common_strcmp(anim->key, animName) == 0)
{
entity->animListIndex = i;
entity->currAnimId = i;
EntityAnim *newEntityAnim = &entity->animList[i];
newEntityAnim->currDuration =
newEntityAnim->anim->frameDuration;
newEntityAnim->currFrame = 0;
worldTraveller_registerEvent(eventQueue, eventtype_start_anim,
newEntityAnim);
return;
}
}
@ -44,12 +48,13 @@ void entity_animSet(Entity *const entity, const char *const animName)
DEBUG_LOG("Entity does not have access to desired anim");
}
void entity_animUpdate(Entity *const entity, const f32 dt)
void entity_updateAnim(EventQueue *eventQueue, Entity *const entity,
const f32 dt)
{
if (!entity->tex)
return;
EntityAnim *currEntityAnim = &entity->animList[entity->animListIndex];
EntityAnim *currEntityAnim = &entity->animList[entity->currAnimId];
Animation *anim = currEntityAnim->anim;
currEntityAnim->currDuration -= dt;
@ -59,18 +64,33 @@ void entity_animUpdate(Entity *const entity, const f32 dt)
currEntityAnim->currFrame = currEntityAnim->currFrame % anim->numFrames;
if (currEntityAnim->currFrame == 0)
{
worldTraveller_registerEvent(eventQueue, eventtype_end_anim,
currEntityAnim);
currEntityAnim->timesCompleted++;
}
currEntityAnim->currDuration = anim->frameDuration;
}
char *frameName = anim->frameList[currEntityAnim->currFrame];
SubTexture texRect = asset_atlasGetSubTex(anim->atlas, frameName);
entity->size = v2_scale(texRect.rect.max, entity->scale);
// NOTE(doyle): If humanoid entity, let animation dictate render size which
// may exceed the hitbox size of the entity
switch (entity->type)
{
case entitytype_hero:
case entitytype_mob:
case entitytype_npc:
case entitytype_weapon:
case entitytype_projectile:
char *frameName = anim->frameList[currEntityAnim->currFrame];
SubTexture texRect =
asset_getAtlasSubTex(anim->atlas, frameName);
entity->size = v2_scale(texRect.rect.size, entity->scale);
default:
break;
}
}
void entity_animAdd(AssetManager *const assetManager, Entity *const entity,
void entity_addAnim(AssetManager *const assetManager, Entity *const entity,
const char *const animName)
{
i32 freeAnimIndex = 0;
@ -79,7 +99,7 @@ void entity_animAdd(AssetManager *const assetManager, Entity *const entity,
EntityAnim *entityAnim = &entity->animList[i];
if (!entityAnim->anim)
{
entityAnim->anim = asset_animGet(assetManager, animName);
entityAnim->anim = asset_getAnim(assetManager, animName);
entityAnim->currFrame = 0;
entityAnim->currDuration = entityAnim->anim->frameDuration;
return;
@ -89,26 +109,126 @@ void entity_animAdd(AssetManager *const assetManager, Entity *const entity,
DEBUG_LOG("No more free entity animation slots");
}
v2 *entity_generateUpdatedVertexList(MemoryArena_ *transientArena,
Entity *entity)
Entity *const entity_add(MemoryArena_ *const arena, World *const world,
const v2 pos, const v2 size, const f32 scale,
const enum EntityType type,
const enum Direction direction, Texture *const tex,
const b32 collides)
{
ASSERT(entity->vertexPoints);
ASSERT(entity->numVertexPoints >= 3);
v2 *result =
memory_pushBytes(transientArena, entity->numVertexPoints * sizeof(v2));
#ifdef DENGINE_DEBUG
ASSERT(world);
ASSERT(world->freeEntityIndex < world->maxEntities);
ASSERT(type < entitytype_count);
#endif
for (i32 i = 0; i < entity->numVertexPoints; i++)
Entity entity = {0};
entity.id = world->uniqueIdAccumulator++;
entity.pos = pos;
entity.size = size;
entity.hitbox = size;
entity.scale = scale;
entity.type = type;
entity.direction = direction;
entity.tex = tex;
entity.collides = collides;
switch (type)
{
result[i] = v2_add(entity->vertexPoints[i], entity->offset);
result[i] = v2_add(result[i], entity->pos);
case entitytype_hero:
entity.stats = MEMORY_PUSH_STRUCT(arena, EntityStats);
entity.stats->maxHealth = 100;
entity.stats->health = entity.stats->maxHealth;
entity.stats->actionRate = 100;
entity.stats->actionTimer = entity.stats->actionRate;
entity.stats->actionSpdMul = 100;
entity.stats->entityIdToAttack = -1;
entity.stats->queuedAttack = entityattack_invalid;
entity.state = entitystate_idle;
entity.collidesWith[entitytype_mob] = TRUE;
break;
case entitytype_mob:
{
entity.stats = MEMORY_PUSH_STRUCT(arena, EntityStats);
entity.stats->maxHealth = 100;
entity.stats->health = entity.stats->maxHealth;
entity.stats->actionRate = 80;
entity.stats->actionTimer = entity.stats->actionRate;
entity.stats->actionSpdMul = 100;
entity.stats->entityIdToAttack = -1;
entity.stats->queuedAttack = entityattack_invalid;
entity.state = entitystate_idle;
entity.collidesWith[entitytype_hero] = TRUE;
break;
}
case entitytype_projectile:
case entitytype_attackObject:
entity.stats = MEMORY_PUSH_STRUCT(arena, EntityStats);
entity.stats->maxHealth = 100;
entity.stats->health = entity.stats->maxHealth;
entity.stats->actionRate = 100;
entity.stats->actionTimer = entity.stats->actionRate;
entity.stats->actionSpdMul = 100;
entity.stats->entityIdToAttack = -1;
entity.stats->queuedAttack = entityattack_invalid;
entity.state = entitystate_idle;
break;
default:
break;
}
math_applyRotationToVertexes(entity->pos, V2(0 ,0),
DEGREES_TO_RADIANS(entity->rotation), result,
entity->numVertexPoints);
world->entities[world->freeEntityIndex++] = entity;
Entity *result = &world->entities[world->freeEntityIndex - 1];
return result;
}
void entity_clearData(MemoryArena_ *const arena, World *const world,
Entity *const entity)
{
// TODO(doyle): Mem free// memory leak!!
/*
if (entity->stats)
PLATFORM_MEM_FREE(arena, entity->stats, sizeof(EntityStats));
if (entity->audioRenderer)
PLATFORM_MEM_FREE(arena, entity->audioRenderer,
sizeof(AudioRenderer) * entity->numAudioRenderers);
*/
entity->type = entitytype_null;
}
i32 entity_getIndex(World *const world, const i32 entityId)
{
i32 first = 0;
i32 last = world->freeEntityIndex - 1;
while (first <= last)
{
i32 middle = (first + last) / 2;
if (world->entities[middle].id > entityId)
last = middle - 1;
else if (world->entities[middle].id < entityId)
first = middle + 1;
else
return middle;
}
#ifdef DENGINE_DEBUG
ASSERT(INVALID_CODE_PATH);
#endif
return -1;
}
Entity *entity_get(World *const world, const i32 entityId)
{
Entity *result = NULL;
i32 worldIndex = entity_getIndex(world, entityId);
if (worldIndex != -1) result = &world->entities[worldIndex];
return result;
}

View File

@ -2,30 +2,7 @@
void memory_arenaInit(MemoryArena_ *arena, void *base, size_t size)
{
arena->size = size;
arena->used = 0;
arena->base = CAST(u8 *) base;
arena->tempMemoryCount = 0;
}
TempMemory memory_beginTempRegion(MemoryArena_ *arena)
{
TempMemory result = {0};
result.arena = arena;
result.used = arena->used;
arena->tempMemoryCount++;
return result;
}
void memory_endTempRegion(TempMemory tempMemory)
{
MemoryArena_ *arena = tempMemory.arena;
ASSERT(arena->used > tempMemory.used)
arena->used = tempMemory.used;
ASSERT(arena->tempMemoryCount > 0)
arena->tempMemoryCount--;
arena->size = size;
arena->used = 0;
arena->base = CAST(u8 *)base;
}

View File

@ -6,7 +6,7 @@
#include "Dengine/Debug.h"
#include "Dengine/MemoryArena.h"
void platform_memoryFree(MemoryArena_ *arena, void *data, size_t numBytes)
void platform_memoryFree(MemoryArena_ *arena, void *data, i32 numBytes)
{
if (data) free(data);
@ -16,7 +16,7 @@ void platform_memoryFree(MemoryArena_ *arena, void *data, size_t numBytes)
#endif
}
void *platform_memoryAlloc(MemoryArena_ *arena, size_t numBytes)
void *platform_memoryAlloc(MemoryArena_ *arena, i32 numBytes)
{
void *result = calloc(1, numBytes);
@ -28,7 +28,6 @@ void *platform_memoryAlloc(MemoryArena_ *arena, size_t numBytes)
return result;
}
// TODO(doyle): If we use arena temporary memory this is not necessary
void platform_closeFileRead(MemoryArena_ *arena, PlatformFileRead *file)
{
// TODO(doyle): Mem free
@ -95,87 +94,3 @@ i32 platform_readFileToBuffer(MemoryArena_ *arena, const char *const filePath,
return 0;
}
void platform_inputBufferProcess(InputBuffer *inputBuffer, f32 dt)
{
KeyState *keyBuffer = inputBuffer->keys;
for (enum KeyCode code = 0; code < keycode_count; code++)
{
KeyState *key = &keyBuffer[code];
u32 halfTransitionCount =
key->newHalfTransitionCount - key->oldHalfTransitionCount;
if (halfTransitionCount > 0)
{
b32 transitionCountIsOdd = ((halfTransitionCount & 1) == 1);
if (transitionCountIsOdd)
{
/* If it was not last ended down, then update interval if
* necessary */
if (!common_isSet(key->flags, keystateflag_ended_down))
{
if (key->delayInterval > 0) key->delayInterval -= dt;
key->flags |= keystateflag_pressed_on_curr_frame;
}
key->flags ^= keystateflag_ended_down;
}
}
else
{
key->flags &= (~keystateflag_pressed_on_curr_frame);
}
key->newHalfTransitionCount = key->oldHalfTransitionCount;
}
}
b32 platform_queryKey(KeyState *key, enum ReadKeyType readType,
f32 delayInterval)
{
if (!common_isSet(key->flags, keystateflag_ended_down)) return FALSE;
switch (readType)
{
case readkeytype_one_shot:
{
if (common_isSet(key->flags, keystateflag_pressed_on_curr_frame))
return TRUE;
}
break;
case readkeytype_repeat:
case readkeytype_delay_repeat:
{
if (common_isSet(key->flags, keystateflag_pressed_on_curr_frame))
{
if (readType == readkeytype_delay_repeat)
{
// TODO(doyle): Let user set arbitrary delay after initial input
key->delayInterval = 2 * delayInterval;
}
else
{
key->delayInterval = delayInterval;
}
return TRUE;
}
else if (key->delayInterval <= 0.0f)
{
key->delayInterval = delayInterval;
return TRUE;
}
}
break;
default:
{
ASSERT(INVALID_CODE_PATH);
}
break;
}
return FALSE;
}

View File

@ -5,188 +5,18 @@
#include "Dengine/Entity.h"
#include "Dengine/MemoryArena.h"
#include "Dengine/OpenGL.h"
#include "Dengine/Shader.h"
#include "Dengine/Texture.h"
INTERNAL void shaderUniformSet1i(u32 shaderId, const GLchar *name,
const GLuint data)
#define RENDER_BOUNDING_BOX FALSE
INTERNAL void addVertexToRenderGroup(Renderer *renderer, Texture *tex, v4 color,
Vertex *vertexList, i32 numVertexes)
{
GLint uniformLoc = glGetUniformLocation(shaderId, name);
glUniform1i(uniformLoc, data);
}
INTERNAL void shaderUniformSetMat4fv(u32 shaderId, const GLchar *name,
mat4 data)
{
GLint uniformLoc = glGetUniformLocation(shaderId, name);
GL_CHECK_ERROR();
glUniformMatrix4fv(uniformLoc, 1, GL_FALSE, data.e[0]);
GL_CHECK_ERROR();
}
INTERNAL void shaderUniformSetVec4f(u32 shaderId, const GLchar *name,
v4 data)
{
GLint uniformLoc = glGetUniformLocation(shaderId, name);
glUniform4f(uniformLoc, data.e[0], data.e[1], data.e[2], data.e[3]);
}
INTERNAL void shaderUse(u32 shaderId) { glUseProgram(shaderId); }
void renderer_updateSize(Renderer *renderer, AssetManager *assetManager, v2 windowSize)
{
renderer->size = windowSize;
// renderer->displayScale =
// V2(windowSize.x * 1.0f / renderer->referenceScale.x,
// windowSize.y * 1.0f / renderer->referenceScale.y);
// NOTE(doyle): Value to map a screen coordinate to NDC coordinate
renderer->vertexNdcFactor =
V2(1.0f / renderer->size.w, 1.0f / renderer->size.h);
renderer->groupIndexForVertexBatch = -1;
const mat4 projection =
mat4_ortho(0.0f, renderer->size.w, 0.0f, renderer->size.h, 0.0f, 1.0f);
for (i32 i = 0; i < shaderlist_count; i++)
{
renderer->shaderList[i] = asset_shaderGet(assetManager, i);
shaderUse(renderer->shaderList[i]);
shaderUniformSetMat4fv(renderer->shaderList[i], "projection",
projection);
GL_CHECK_ERROR();
}
renderer->activeShaderId = renderer->shaderList[shaderlist_default];
GL_CHECK_ERROR();
}
void renderer_init(Renderer *renderer, AssetManager *assetManager,
MemoryArena_ *persistentArena, v2 windowSize)
{
renderer->referenceScale = V2(1280, 720);
renderer_updateSize(renderer, assetManager, windowSize);
/* Create buffers */
glGenVertexArrays(ARRAY_COUNT(renderer->vao), renderer->vao);
glGenBuffers(ARRAY_COUNT(renderer->vbo), renderer->vbo);
GL_CHECK_ERROR();
// Bind buffers and configure vao, vao automatically intercepts
// glBindCalls and associates the state with that buffer for us
for (enum RenderMode mode = 0; mode < rendermode_count; mode++)
{
glBindVertexArray(renderer->vao[mode]);
glBindBuffer(GL_ARRAY_BUFFER, renderer->vbo[mode]);
glEnableVertexAttribArray(0);
u32 numVertexElements = 4;
u32 stride = sizeof(RenderVertex);
glVertexAttribPointer(0, numVertexElements, GL_FLOAT,
GL_FALSE, stride, (GLvoid *)0);
glBindBuffer(GL_ARRAY_BUFFER, 0);
glBindVertexArray(0);
}
/* Unbind */
GL_CHECK_ERROR();
// TODO(doyle): Lazy allocate render group capacity
renderer->groupCapacity = 4096;
for (i32 i = 0; i < ARRAY_COUNT(renderer->groups); i++)
{
renderer->groups[i].vertexList = memory_pushBytes(
persistentArena, renderer->groupCapacity * sizeof(RenderVertex));
}
}
typedef struct RenderQuad
{
RenderVertex vertexList[4];
} RenderQuad;
// NOTE(doyle): A vertex batch is the batch of vertexes comprised to make one
// shape
INTERNAL void beginVertexBatch(Renderer *renderer)
{
ASSERT(renderer->vertexBatchState == vertexbatchstate_off);
ASSERT(renderer->groupIndexForVertexBatch == -1);
renderer->vertexBatchState = vertexbatchstate_initial_add;
}
/*
NOTE(doyle): Entity rendering is always done in two pairs of
triangles, i.e. quad. To batch render quads as a triangle strip, we
need to create zero-area triangles which OGL will omit from
rendering.
The implementation is recognising if the rendered
entity is the first in its render group, then we don't need to init
a degenerate vertex, and only at the end of its vertex list. But on
subsequent renders, we need a degenerate vertex at the front to
create the zero-area triangle strip.
*/
INTERNAL void endVertexBatch(Renderer *renderer)
{
ASSERT(renderer->vertexBatchState != vertexbatchstate_off);
ASSERT(renderer->groupIndexForVertexBatch != -1);
i32 numDegenerateVertexes = 1;
RenderGroup *group = &renderer->groups[renderer->groupIndexForVertexBatch];
i32 freeVertexSlots = renderer->groupCapacity - group->vertexIndex;
if (numDegenerateVertexes < freeVertexSlots)
{
RenderVertex degenerateVertex =
group->vertexList[group->vertexIndex - 1];
group->vertexList[group->vertexIndex++] = degenerateVertex;
}
renderer->vertexBatchState = vertexbatchstate_off;
renderer->groupIndexForVertexBatch = -1;
}
INTERNAL void applyRotationToVertexes(v2 pos, v2 pivotPoint, Radians rotate,
RenderVertex *vertexList,
i32 vertexListSize)
{
if (rotate == 0) return;
// NOTE(doyle): Move the world origin to the base position of the object.
// Then move the origin to the pivot point (e.g. center of object) and
// rotate from that point.
v2 pointOfRotation = v2_add(pivotPoint, pos);
mat4 rotateMat = mat4_translate(pointOfRotation.x, pointOfRotation.y, 0.0f);
rotateMat = mat4_mul(rotateMat, mat4_rotate(rotate, 0.0f, 0.0f, 1.0f));
rotateMat = mat4_mul(rotateMat, mat4_translate(-pointOfRotation.x,
-pointOfRotation.y, 0.0f));
for (i32 i = 0; i < vertexListSize; i++)
{
// NOTE(doyle): Manual matrix multiplication since vertex pos is 2D and
// matrix is 4D
v2 oldP = vertexList[i].pos;
v2 newP = {0};
newP.x = (oldP.x * rotateMat.e[0][0]) + (oldP.y * rotateMat.e[1][0]) +
(rotateMat.e[3][0]);
newP.y = (oldP.x * rotateMat.e[0][1]) + (oldP.y * rotateMat.e[1][1]) +
(rotateMat.e[3][1]);
vertexList[i].pos = newP;
}
}
INTERNAL void addVertexToRenderGroup_(Renderer *renderer, Texture *tex,
v4 color, i32 zDepth,
RenderVertex *vertexList, i32 numVertexes,
enum RenderMode targetRenderMode,
RenderFlags flags)
{
ASSERT(renderer->vertexBatchState != vertexbatchstate_off);
ASSERT(numVertexes > 0);
#ifdef DENGINE_DEBUG
ASSERT(numVertexes > 0);
for (i32 i = 0; i < numVertexes; i++)
debug_countIncrement(debugcount_numVertex);
#endif
@ -197,50 +27,35 @@ INTERNAL void addVertexToRenderGroup_(Renderer *renderer, Texture *tex,
{
RenderGroup *group = &renderer->groups[i];
b32 groupIsValid = FALSE;
if (group->init)
if (group->tex)
{
/* If the textures match and have the same color modulation, we can
* add these vertices to the current group */
if (!(group->mode == targetRenderMode)) continue;
if (!(v4_equals(group->color, color))) continue;
if (!(group->flags == flags)) continue;
if (!(group->zDepth == zDepth)) continue;
if (!tex && group->tex) continue;
if (tex && group->tex)
if (group->tex->id == tex->id &&
v4_equals(group->color, color))
{
if (!(group->tex->id == tex->id)) continue;
groupIsValid = TRUE;
}
groupIsValid = TRUE;
}
else
{
/* New group, unused so initialise it */
groupIsValid = TRUE;
group->init = TRUE;
group->tex = tex;
group->color = color;
group->mode = targetRenderMode;
group->flags = flags;
group->zDepth = zDepth;
// NOTE(doyle): Mark first vertex as degenerate vertex
group->vertexIndex++;
group->tex = tex;
group->color = color;
renderer->groupsInUse++;
#ifdef DENGINE_DEBUG
debug_countIncrement(debugcount_renderGroups);
#endif
}
if (groupIsValid)
{
i32 freeVertexSlots = renderer->groupCapacity - group->vertexIndex;
// NOTE(doyle): Two at start, two at end
i32 numDegenerateVertexes = 0;
if (renderer->vertexBatchState == vertexbatchstate_initial_add)
numDegenerateVertexes = 1;
if ((numDegenerateVertexes + numVertexes) < freeVertexSlots)
if (numVertexes < freeVertexSlots)
{
if (i != 0)
{
@ -257,46 +72,6 @@ INTERNAL void addVertexToRenderGroup_(Renderer *renderer, Texture *tex,
/* Valid group, add to the render group for rendering */
if (targetGroup)
{
// NOTE(doyle): If we are adding 3 vertexes, then we are adding a
// triangle to the triangle strip. If so, then depending on which "n-th"
// triangle it is we're adding, the winding order in a t-strip
// alternates with each triangle (including degenerates). Hence we track
// so we know the last winding order in the group.
// For this to work, we must ensure all incoming vertexes are winding in
// ccw order initially. There is also the presumption that, other
// rendering methods, such as the quad, consists of an even number of
// triangles such that the winding order gets alternated back to the
// same order it started with.
if (numVertexes == 3)
{
if (targetGroup->clockwiseWinding)
{
RenderVertex tmp = vertexList[0];
vertexList[0] = vertexList[2];
vertexList[2] = tmp;
}
targetGroup->clockwiseWinding =
(targetGroup->clockwiseWinding) ? FALSE : TRUE;
}
if (renderer->vertexBatchState == vertexbatchstate_initial_add)
{
if (targetGroup->vertexIndex != 0)
{
targetGroup->vertexList[targetGroup->vertexIndex++] =
vertexList[0];
}
renderer->vertexBatchState = vertexbatchstate_active;
// NOTE(doyle): We swap groups to the front if it is valid, so
// target group should always be 0
ASSERT(renderer->groupIndexForVertexBatch == -1);
renderer->groupIndexForVertexBatch = 0;
}
for (i32 i = 0; i < numVertexes; i++)
{
targetGroup->vertexList[targetGroup->vertexIndex++] = vertexList[i];
@ -315,6 +90,37 @@ INTERNAL void addVertexToRenderGroup_(Renderer *renderer, Texture *tex,
}
}
INTERNAL inline void addRenderQuadToRenderGroup(Renderer *renderer,
RenderQuad_ quad,
Texture *tex, v4 color)
{
/*
NOTE(doyle): Entity rendering is always done in two pairs of
triangles, i.e. quad. To batch render quads as a triangle strip, we
need to create zero-area triangles which OGL will omit from
rendering. Render groups are initialised with 1 degenerate vertex and
then the first two vertexes sent to the render group are the same to
form 1 zero-area triangle strip.
A degenerate vertex has to be copied from the last vertex in the
rendering quad, to repeat this process as more entities are
renderered.
Alternative implementation is recognising if the rendered
entity is the first in its render group, then we don't need to init
a degenerate vertex, and only at the end of its vertex list. But on
subsequent renders, we need a degenerate vertex at the front to
create the zero-area triangle strip.
The first has been chosen for simplicity of code, at the cost of
1 degenerate vertex at the start of each render group.
*/
Vertex vertexList[6] = {quad.vertex[0], quad.vertex[0], quad.vertex[1],
quad.vertex[2], quad.vertex[3], quad.vertex[3]};
addVertexToRenderGroup(renderer, tex, color, vertexList,
ARRAY_COUNT(vertexList));
};
INTERNAL inline void flipTexCoord(v4 *texCoords, b32 flipX, b32 flipY)
{
if (flipX)
@ -332,27 +138,27 @@ INTERNAL inline void flipTexCoord(v4 *texCoords, b32 flipX, b32 flipY)
}
}
INTERNAL v4 getTexRectNormaliseDeviceCoords(RenderTex renderTex)
INTERNAL void updateBufferObject(Renderer *const renderer,
const Vertex *const vertexList,
const i32 numVertex)
{
/* Convert texture coordinates to normalised texture coordinates */
v4 result = renderTex.texRect;
if (renderTex.tex)
{
v2 texNdcFactor =
V2(1.0f / renderTex.tex->width, 1.0f / renderTex.tex->height);
result.e[0] *= texNdcFactor.w;
result.e[1] *= texNdcFactor.h;
result.e[2] *= texNdcFactor.w;
result.e[3] *= texNdcFactor.h;
}
return result;
// TODO(doyle): We assume that vbo and vao are assigned
renderer->numVertexesInVbo = numVertex;
glBindBuffer(GL_ARRAY_BUFFER, renderer->vbo);
glBufferData(GL_ARRAY_BUFFER, numVertex * sizeof(Vertex), vertexList,
GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
INTERNAL RenderQuad createRenderQuad(Renderer *renderer, v2 pos, v2 size,
v2 pivotPoint, Radians rotate,
RenderTex renderTex)
INTERNAL void bufferRenderGroupToGL(Renderer *renderer, RenderGroup *group)
{
updateBufferObject(renderer, group->vertexList, group->vertexIndex);
}
INTERNAL RenderQuad_ createRenderQuad(Renderer *renderer, v2 pos, v2 size,
v2 pivotPoint, f32 rotate,
RenderTex renderTex)
{
/*
* Rendering order
@ -367,194 +173,137 @@ INTERNAL RenderQuad createRenderQuad(Renderer *renderer, v2 pos, v2 size,
*
*/
v4 vertexPair = {0};
vertexPair.vec2[0] = pos;
vertexPair.vec2[1] = v2_add(pos, size);
v4 texRectNdc = getTexRectNormaliseDeviceCoords(renderTex);
v4 vertexPair = {0};
vertexPair.vec2[0] = pos;
vertexPair.vec2[1] = v2_add(pos, size);
/* Convert texture coordinates to normalised texture coordinates */
v4 texRectNdc = renderTex.texRect;
if (renderTex.tex)
{
v2 texNdcFactor =
V2(1.0f / renderTex.tex->width, 1.0f / renderTex.tex->height);
texRectNdc.e[0] *= texNdcFactor.w;
texRectNdc.e[1] *= texNdcFactor.h;
texRectNdc.e[2] *= texNdcFactor.w;
texRectNdc.e[3] *= texNdcFactor.h;
}
// NOTE(doyle): Create a quad composed of 4 vertexes to be rendered as
// a triangle strip using vertices v0, v1, v2, then v2, v1, v3 (note the
// order)
v2 vertexList[4] = {0};
v2 texCoordList[4] = {0};
RenderQuad_ result = {0};
result.vertex[0].pos = V2(vertexPair.x, vertexPair.w); // Top left
result.vertex[0].texCoord = V2(texRectNdc.x, texRectNdc.w);
// Top left
vertexList[0] = V2(vertexPair.x, vertexPair.w);
texCoordList[0] = V2(texRectNdc.x, texRectNdc.w);
result.vertex[1].pos = V2(vertexPair.x, vertexPair.y); // Bottom left
result.vertex[1].texCoord = V2(texRectNdc.x, texRectNdc.y);
// Bottom left
vertexList[1] = V2(vertexPair.x, vertexPair.y);
texCoordList[1] = V2(texRectNdc.x, texRectNdc.y);
result.vertex[2].pos = V2(vertexPair.z, vertexPair.w); // Top right
result.vertex[2].texCoord = V2(texRectNdc.z, texRectNdc.w);
// Top right
vertexList[2] = V2(vertexPair.z, vertexPair.w);
texCoordList[2] = V2(texRectNdc.z, texRectNdc.w);
// Bottom right
vertexList[3] = V2(vertexPair.z, vertexPair.y);
texCoordList[3] = V2(texRectNdc.z, texRectNdc.y);
result.vertex[3].pos = V2(vertexPair.z, vertexPair.y); // Bottom right
result.vertex[3].texCoord = V2(texRectNdc.z, texRectNdc.y);
if (rotate == 0) return result;
// NOTE(doyle): Precalculate rotation on vertex positions
// NOTE(doyle): No translation/scale matrix as we pre-calculate it from
// entity data and work in world space until GLSL uses the projection matrix
math_applyRotationToVertexes(pos, pivotPoint, rotate, vertexList,
ARRAY_COUNT(vertexList));
// NOTE(doyle): Move the world origin to the base position of the object.
// Then move the origin to the pivot point (e.g. center of object) and
// rotate from that point.
v2 pointOfRotation = v2_add(pivotPoint, pos);
RenderQuad result = {0};
ASSERT(ARRAY_COUNT(vertexList) == ARRAY_COUNT(result.vertexList));
for (i32 i = 0; i < ARRAY_COUNT(vertexList); i++)
mat4 rotateMat = mat4_translate(pointOfRotation.x, pointOfRotation.y, 0.0f);
rotateMat = mat4_mul(rotateMat, mat4_rotate(rotate, 0.0f, 0.0f, 1.0f));
rotateMat = mat4_mul(rotateMat, mat4_translate(-pointOfRotation.x,
-pointOfRotation.y, 0.0f));
for (i32 i = 0; i < ARRAY_COUNT(result.vertex); i++)
{
result.vertexList[i].pos = vertexList[i];
result.vertexList[i].texCoord = texCoordList[i];
// NOTE(doyle): Manual matrix multiplication since vertex pos is 2D and
// matrix is 4D
v2 oldP = result.vertex[i].pos;
v2 newP = {0};
newP.x = (oldP.x * rotateMat.e[0][0]) + (oldP.y * rotateMat.e[1][0]) +
(rotateMat.e[3][0]);
newP.y = (oldP.x * rotateMat.e[0][1]) + (oldP.y * rotateMat.e[1][1]) +
(rotateMat.e[3][1]);
result.vertex[i].pos = newP;
}
return result;
}
INTERNAL inline RenderQuad
createDefaultTexQuad(Renderer *renderer, RenderTex *renderTex)
INTERNAL inline RenderQuad_
createDefaultTexQuad(Renderer *renderer, RenderTex renderTex)
{
RenderQuad result = {0};
result = createRenderQuad(renderer, V2(0, 0), V2(0, 0), V2(0, 0), 0.0f,
*renderTex);
RenderQuad_ result = {0};
result = createRenderQuad(renderer, V2(0, 0), V2(0, 0), V2(0, 0),
0.0f, renderTex);
return result;
}
INTERNAL void renderGLBufferedData(Renderer *renderer, RenderGroup *group)
INTERNAL void renderGLBufferedData(Renderer *renderer, RenderGroup *renderGroup)
{
/* Load transformation matrix */
shader_use(renderer->shader);
GL_CHECK_ERROR();
/* Set color modulation value */
shader_uniformSetVec4f(renderer->shader, "spriteColor",
renderGroup->color);
/* Send draw calls */
#if RENDER_BOUNDING_BOX
glBindVertexArray(renderer->vao);
glDrawArrays(GL_TRIANGLE_STRIP, 0, renderer->numVertexesInVbo);
glBindVertexArray(0);
#endif
Texture *tex = renderGroup->tex;
if (tex)
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex->id);
shader_uniformSet1i(renderer->shader, "tex", 0);
}
glBindVertexArray(renderer->vao);
glDrawArrays(GL_TRIANGLE_STRIP, 0, renderer->numVertexesInVbo);
#ifdef DENGINE_DEBUG
debug_countIncrement(debugcount_drawArrays);
#endif
/* Unbind */
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
GL_CHECK_ERROR();
}
RenderTex renderer_createNullRenderTex(AssetManager *const assetManager)
{
Texture *emptyTex = asset_texGet(assetManager, "nullTex");
Texture *emptyTex = asset_getTex(assetManager, "nullTex");
RenderTex result = {emptyTex, V4(0, 1, 1, 0)};
return result;
}
void renderer_rect(Renderer *const renderer, Rect camera, v2 pos, v2 size,
v2 pivotPoint, Radians rotate, RenderTex *renderTex,
v4 color, i32 zDepth, RenderFlags flags)
v2 pivotPoint, f32 rotate, RenderTex renderTex, v4 color)
{
// NOTE(doyle): Bottom left and top right position of quad in world space
v2 posInCameraSpace = v2_sub(pos, camera.min);
RenderTex emptyRenderTex = {0};
if (!renderTex)
{
renderTex = &emptyRenderTex;
ASSERT(common_isSet(flags, renderflag_no_texture));
}
RenderQuad quad = createRenderQuad(renderer, posInCameraSpace, size,
pivotPoint, rotate, *renderTex);
beginVertexBatch(renderer);
addVertexToRenderGroup_(renderer, renderTex->tex, color, zDepth,
quad.vertexList, ARRAY_COUNT(quad.vertexList),
rendermode_quad, flags);
endVertexBatch(renderer);
}
void renderer_polygon(Renderer *const renderer, Rect camera,
v2 *polygonPoints, i32 numPoints, v2 pivotPoint,
Radians rotate, RenderTex *renderTex, v4 color, i32 zDepth,
RenderFlags flags)
{
ASSERT(numPoints >= 3);
{ // Validate polygon is CCW
/*
NOTE(doyle): Polygon vertexes must be specified in a CCW order!
This check utilises the equation for calculating the bounding area
of a polygon by decomposing the shapes into line segments and
calculating the area under the segment. On cartesian plane, if the
polygon is CCW, then by creating these "area" calculatings in
sequential order, we'll produce negative valued areas since we
determine line segment length by subtracting x2-x1.
Better explanation over here
http://blog.element84.com/polygon-winding.html
*/
f32 areaSum = 0.0f;
for (i32 i = 0; i < numPoints - 1; i++)
{
f32 lengthX = polygonPoints[i + 1].x - polygonPoints[i].x;
// NOTE(doyle): The height of a line segment is actually (y1 + y2)/2
// But since the (1/2) is a constant factor we can get rid of for
// checking the winding order..
// i.e. a negative number halved is still always negative.
f32 lengthY = polygonPoints[i + 1].y + polygonPoints[i].y;
areaSum += (lengthX * lengthY);
}
f32 lengthX = polygonPoints[0].x - polygonPoints[numPoints - 1].x;
f32 lengthY = polygonPoints[0].y + polygonPoints[numPoints - 1].y;
areaSum += (lengthX * lengthY);
if (areaSum < 0)
{
// NOTE(doyle): Is counter clockwise
}
else if (areaSum > 0)
{
// NOTE(doyle): Is clockwise
ASSERT(INVALID_CODE_PATH);
}
else
{
// NOTE(doyle): CW + CCW combination, i.e. figure 8 shape
ASSERT(INVALID_CODE_PATH);
}
}
for (i32 i = 0; i < numPoints; i++)
polygonPoints[i] = v2_sub(polygonPoints[i], camera.min);
// TODO(doyle): Do something with render texture
RenderTex emptyRenderTex = {0};
if (!renderTex)
{
renderTex = &emptyRenderTex;
ASSERT(common_isSet(flags, renderflag_no_texture));
}
v2 triangulationBaseP = polygonPoints[0];
RenderVertex triangulationBaseVertex = {0};
triangulationBaseVertex.pos = triangulationBaseP;
i32 numTrisInTriangulation = numPoints - 2;
i32 triangulationIndex = 0;
for (i32 i = 1; triangulationIndex < numTrisInTriangulation; i++)
{
ASSERT((i + 1) < numPoints);
v2 vertexList[3] = {triangulationBaseP, polygonPoints[i],
polygonPoints[i + 1]};
beginVertexBatch(renderer);
RenderVertex triangle[3] = {0};
triangle[0].pos = vertexList[0];
triangle[1].pos = vertexList[1];
triangle[2].pos = vertexList[2];
addVertexToRenderGroup_(renderer, renderTex->tex, color, zDepth,
triangle, ARRAY_COUNT(triangle),
rendermode_polygon, flags);
endVertexBatch(renderer);
triangulationIndex++;
}
v2 posInCameraSpace = v2_sub(pos, camera.pos);
RenderQuad_ quad = createRenderQuad(renderer, posInCameraSpace, size,
pivotPoint, rotate, renderTex);
addRenderQuadToRenderGroup(renderer, quad, renderTex.tex, color);
}
void renderer_string(Renderer *const renderer, MemoryArena_ *arena, Rect camera,
Font *const font, const char *const string, v2 pos,
v2 pivotPoint, Radians rotate, v4 color, i32 zDepth,
RenderFlags flags)
v2 pivotPoint, f32 rotate, v4 color)
{
i32 strLen = common_strlen(string);
if (strLen <= 0) return;
@ -566,16 +315,16 @@ void renderer_string(Renderer *const renderer, MemoryArena_ *arena, Rect camera,
v2_add(pos, V2((CAST(f32) font->maxSize.w * CAST(f32) strLen),
CAST(f32) font->maxSize.h));
v2 leftAlignedP = pos;
if (math_rectContainsP(camera, leftAlignedP) ||
math_rectContainsP(camera, rightAlignedP))
if (math_pointInRect(camera, leftAlignedP) ||
math_pointInRect(camera, rightAlignedP))
{
i32 vertexIndex = 0;
i32 numVertexPerQuad = 4;
i32 numVertexesToAlloc = (strLen * (numVertexPerQuad + 2));
RenderVertex *vertexList =
memory_pushBytes(arena, numVertexesToAlloc * sizeof(RenderVertex));
Vertex *vertexList =
memory_pushBytes(arena, numVertexesToAlloc * sizeof(Vertex));
v2 posInCameraSpace = v2_sub(pos, camera.min);
v2 posInCameraSpace = v2_sub(pos, camera.pos);
pos = posInCameraSpace;
// TODO(doyle): Find why font is 1px off, might be arial font semantics
@ -590,195 +339,87 @@ void renderer_string(Renderer *const renderer, MemoryArena_ *arena, Rect camera,
/* Get texture out */
SubTexture subTexture =
asset_atlasGetSubTex(font->atlas, &CAST(char)codepoint);
asset_getAtlasSubTex(font->atlas, &CAST(char)codepoint);
v4 charTexRect = {0};
charTexRect.vec2[0] = subTexture.rect.min;
charTexRect.vec2[0] = subTexture.rect.pos;
charTexRect.vec2[1] =
v2_add(subTexture.rect.min, subTexture.rect.max);
v2_add(subTexture.rect.pos, subTexture.rect.size);
flipTexCoord(&charTexRect, FALSE, TRUE);
RenderTex renderTex = {tex, charTexRect};
RenderQuad quad = createRenderQuad(renderer, pos, font->maxSize,
pivotPoint, rotate, renderTex);
RenderQuad_ quad = createRenderQuad(renderer, pos, font->maxSize,
pivotPoint, rotate, renderTex);
beginVertexBatch(renderer);
addVertexToRenderGroup_(renderer, tex, color, zDepth, quad.vertexList,
ARRAY_COUNT(quad.vertexList),
rendermode_quad, flags);
endVertexBatch(renderer);
vertexList[vertexIndex++] = quad.vertex[0];
for (i32 i = 0; i < ARRAY_COUNT(quad.vertex); i++)
{
vertexList[vertexIndex++] = quad.vertex[i];
}
vertexList[vertexIndex++] = quad.vertex[3];
pos.x += metric.advance;
}
addVertexToRenderGroup(renderer, tex, color, vertexList,
numVertexesToAlloc);
// TODO(doyle): Mem free
// PLATFORM_MEM_FREE(arena, vertexList,
// sizeof(Vertex) * numVertexesToAlloc);
}
}
void renderer_entity(Renderer *renderer, MemoryArena_ *transientArena,
Rect camera, Entity *entity, v2 pivotPoint, Degrees rotate,
v4 color, i32 zDepth, RenderFlags flags)
void renderer_entity(Renderer *renderer, Rect camera, Entity *entity,
v2 pivotPoint, f32 rotate, v4 color)
{
// TODO(doyle): Add early exit on entities out of camera bounds
Radians totalRotation = DEGREES_TO_RADIANS((entity->rotation + rotate));
RenderTex renderTex = {0};
if (entity->tex)
{
EntityAnim *entityAnim = &entity->animList[entity->animListIndex];
v4 texRect = {0};
if (entityAnim->anim)
{
Animation *anim = entityAnim->anim;
char *frameName = anim->frameList[entityAnim->currFrame];
SubTexture subTex = asset_atlasGetSubTex(anim->atlas, frameName);
// TODO(doyle): Batch into render groups
texRect.vec2[0] = subTex.rect.min;
texRect.vec2[1] = v2_add(subTex.rect.min, subTex.rect.max);
flipTexCoord(&texRect, entity->flipX, entity->flipY);
}
else
{
texRect = V4(0.0f, 0.0f, (f32)entity->tex->width,
(f32)entity->tex->height);
}
// NOTE(doyle): Pos + Size since the origin of an entity is it's bottom left
// corner. Add the two together so that the clipping point is the far right
// side of the entity
v2 rightAlignedP = v2_add(entity->pos, entity->hitbox);
v2 leftAlignedP = entity->pos;
if (math_pointInRect(camera, leftAlignedP) ||
math_pointInRect(camera, rightAlignedP))
{
EntityAnim *entityAnim = &entity->animList[entity->currAnimId];
Animation *anim = entityAnim->anim;
char *frameName = anim->frameList[entityAnim->currFrame];
SubTexture animRect = asset_getAtlasSubTex(anim->atlas, frameName);
v4 animTexRect = {0};
animTexRect.vec2[0] = animRect.rect.pos;
animTexRect.vec2[1] = v2_add(animRect.rect.pos, animRect.rect.size);
flipTexCoord(&animTexRect, entity->flipX, entity->flipY);
if (entity->direction == direction_east)
{
flipTexCoord(&texRect, TRUE, FALSE);
flipTexCoord(&animTexRect, TRUE, FALSE);
}
renderTex.tex = entity->tex;
renderTex.texRect = texRect;
}
// TODO(doyle): Proper blending
v4 renderColor = color;
if (v4_equals(color, V4(0, 0, 0, 0))) renderColor = entity->color;
if (entity->renderMode == rendermode_quad)
{
renderer_rect(renderer, camera, entity->pos, entity->size,
v2_add(entity->offset, pivotPoint), totalRotation,
&renderTex, entity->color, zDepth, flags);
}
else if (entity->renderMode == rendermode_polygon)
{
ASSERT(entity->numVertexPoints >= 3);
ASSERT(entity->vertexPoints);
v2 *offsetVertexPoints =
entity_generateUpdatedVertexList(transientArena, entity);
renderer_polygon(renderer, camera, offsetVertexPoints,
entity->numVertexPoints,
v2_add(entity->offset, pivotPoint), totalRotation,
&renderTex, renderColor, zDepth, flags);
}
else
{
ASSERT(INVALID_CODE_PATH);
RenderTex renderTex = {entity->tex, animTexRect};
renderer_rect(renderer, camera, entity->pos, entity->size, pivotPoint,
entity->rotation + rotate, renderTex, color);
}
}
// TODO(doyle): We have no notion of sort order!!
void renderer_renderGroups(Renderer *renderer)
{
/* Sort the group by zdepth */
b32 groupHasSwapped = TRUE;
i32 numGroupsToCheck = renderer->groupsInUse - 1;
while (groupHasSwapped)
for (i32 i = 0; i < ARRAY_COUNT(renderer->groups); i++)
{
groupHasSwapped = FALSE;
for (i32 i = 0; i < numGroupsToCheck; i++)
RenderGroup *currGroup = &renderer->groups[i];
if (currGroup->tex)
{
RenderGroup *group = &renderer->groups[i];
RenderGroup *checkGroup = &renderer->groups[i + 1];
bufferRenderGroupToGL(renderer, currGroup);
renderGLBufferedData(renderer, currGroup);
if (checkGroup->zDepth < group->zDepth)
{
RenderGroup tmp = *group;
*group = *checkGroup;
*checkGroup = tmp;
groupHasSwapped = TRUE;
}
RenderGroup cleanGroup = {0};
cleanGroup.vertexList = currGroup->vertexList;
*currGroup = cleanGroup;
}
else
{
break;
}
numGroupsToCheck--;
}
/* Render groups */
for (i32 i = 0; i < renderer->groupsInUse; i++)
{
RenderGroup *group = &renderer->groups[i];
{ // Buffer render group to OpenGL
RenderVertex *vertexList = group->vertexList;
i32 numVertex = group->vertexIndex;
// TODO(doyle): We assume that vbo and vao are assigned
renderer->numVertexesInVbo = numVertex;
glBindBuffer(GL_ARRAY_BUFFER, renderer->vbo[group->mode]);
glBufferData(GL_ARRAY_BUFFER, numVertex * sizeof(RenderVertex),
vertexList, GL_STREAM_DRAW);
glBindBuffer(GL_ARRAY_BUFFER, 0);
}
{ // Render buffered data in OpenGl
ASSERT(group->mode < rendermode_invalid);
if (group->flags & renderflag_wireframe)
{
glPolygonMode(GL_FRONT_AND_BACK, GL_LINE);
}
else
{
glPolygonMode(GL_FRONT_AND_BACK, GL_FILL);
}
GL_CHECK_ERROR();
if (group->flags & renderflag_no_texture)
{
renderer->activeShaderId =
renderer->shaderList[shaderlist_default_no_tex];
shaderUse(renderer->activeShaderId);
}
else
{
renderer->activeShaderId =
renderer->shaderList[shaderlist_default];
shaderUse(renderer->activeShaderId);
Texture *tex = group->tex;
if (tex)
{
glActiveTexture(GL_TEXTURE0);
glBindTexture(GL_TEXTURE_2D, tex->id);
shaderUniformSet1i(renderer->activeShaderId, "tex", 0);
GL_CHECK_ERROR();
}
}
#if 0
glDisable(GL_CULL_FACE);
#endif
/* Set color modulation value */
shaderUniformSetVec4f(renderer->activeShaderId, "spriteColor",
group->color);
glBindVertexArray(renderer->vao[group->mode]);
glDrawArrays(GL_TRIANGLE_STRIP, 0, renderer->numVertexesInVbo);
GL_CHECK_ERROR();
debug_countIncrement(debugcount_drawArrays);
/* Unbind */
glBindVertexArray(0);
glBindTexture(GL_TEXTURE_2D, 0);
GL_CHECK_ERROR();
}
RenderGroup cleanGroup = {0};
cleanGroup.vertexList = group->vertexList;
*group = cleanGroup;
}
renderer->groupsInUse = 0;
}

25
src/Shader.c Normal file
View File

@ -0,0 +1,25 @@
#include "Dengine/Shader.h"
void shader_uniformSet1i(Shader *const shader, const GLchar *name,
const GLuint data)
{
GLint uniformLoc = glGetUniformLocation(shader->id, name);
glUniform1i(uniformLoc, data);
}
void shader_uniformSetMat4fv(Shader *const shader, const GLchar *name,
mat4 data)
{
GLint uniformLoc = glGetUniformLocation(shader->id, name);
glUniformMatrix4fv(uniformLoc, 1, GL_FALSE, data.e[0]);
}
void shader_uniformSetVec4f(Shader *const shader, const GLchar *name,
v4 data)
{
GLint uniformLoc = glGetUniformLocation(shader->id, name);
glUniform4f(uniformLoc, data.e[0], data.e[1], data.e[2], data.e[3]);
}
void shader_use(const Shader *const shader) { glUseProgram(shader->id); }

120
src/String.c Normal file
View File

@ -0,0 +1,120 @@
#include "Dengine/String.h"
#include "Dengine/MemoryArena.h"
/*
* +-------------------------------------+
* | Header | C-String | Null Terminator |
* +-------------------------------------+
* |
* +--> Functions return the c-string for compatibility with other
* string libraries
*
* Headers are retrieved using pointer arithmetric from the C string. These
* strings are typechecked by their own typedef char String.
*/
typedef struct StringHeader
{
i32 len;
// NOTE(doyle): A string is stored as one contiguous chunk of memory. We
// don't use a pointer for storing the string as this'd require an extra
// 4 bytes to store the pointer, which we don't need if everything is
// contiguous. The string follows on from the len, and we return the address
// of the string to simulate a pointer.
String string;
} StringHeader;
// TODO(doyle): string capacity- append if already enough space
INTERNAL StringHeader *string_getHeader(String *const string)
{
StringHeader *result = NULL;
// NOTE(doyle): C-String must be located at end of struct type for offset to
// be correct! We cannot just subtract the string-header since we start at
// the string ptr position
if (string)
{
i32 byteOffsetToHeader = sizeof(StringHeader) - sizeof(String *);
result = CAST(StringHeader *)((CAST(u8 *) string) - byteOffsetToHeader);
}
return result;
}
i32 string_len(String *const string)
{
if (!string) return -1;
StringHeader *header = string_getHeader(string);
i32 result = header->len;
return result;
}
String *const string_append(MemoryArena_ *const arena, String *oldString,
char *appendString, i32 appendLen)
{
if (!oldString || !appendString || !arena) return oldString;
/* Calculate size of new string */
StringHeader *oldHeader = string_getHeader(oldString);
i32 newLen = oldHeader->len + appendLen;
String *newString = string_makeLen(arena, newLen);
/* Append strings together */
String *insertPtr = newString;
common_strncpy(insertPtr, oldString, oldHeader->len);
insertPtr += oldHeader->len;
common_strncpy(insertPtr, appendString, appendLen);
/* Free old string */
string_free(arena, oldString);
return newString;
}
void string_free(MemoryArena_ *arena, String *string)
{
if (!string || !arena) return;
StringHeader *header = string_getHeader(string);
i32 bytesToFree = sizeof(StringHeader) + header->len;
common_memset((u8 *)header, 0, bytesToFree);
// TODO(doyle): Mem free
// PLATFORM_MEM_FREE(arena, header, bytesToFree);
string = NULL;
}
String *const string_make(MemoryArena_ *const arena, char *string)
{
if (!arena) return NULL;
i32 len = common_strlen(string);
String *result = string_makeLen(arena, len);
common_strncpy(result, string, len);
return result;
}
String *const string_makeLen(MemoryArena_ *const arena, i32 len)
{
if (!arena) return NULL;
// NOTE(doyle): Allocate the string header size plus the len. But _note_
// that StringHeader contains a single String character. This has
// a side-effect of already preallocating a byte for the null-terminating
// character. Whilst the len of a string counts up to the last character
// _not_ including null-terminator.
i32 bytesToAllocate = sizeof(StringHeader) + len;
void *chunk = memory_pushBytes(arena, bytesToAllocate * sizeof(u8));
if (!chunk) return NULL;
StringHeader *header = CAST(StringHeader *) chunk;
header->len = len;
return &header->string;
}

78
src/Texture.c Normal file
View File

@ -0,0 +1,78 @@
#include "Dengine/Texture.h"
enum BytesPerPixel
{
bytesPerPixel_Greyscale = 1,
bytesPerPixel_GreyscaleAlpha = 2,
bytesPerPixel_RGB = 3,
bytesPerPixel_RGBA = 4,
};
INTERNAL GLint getGLFormat(i32 bytesPerPixel, b32 srgb)
{
switch (bytesPerPixel)
{
case bytesPerPixel_Greyscale:
return GL_LUMINANCE;
case bytesPerPixel_GreyscaleAlpha:
return GL_LUMINANCE_ALPHA;
case bytesPerPixel_RGB:
return (srgb ? GL_SRGB : GL_RGB);
case bytesPerPixel_RGBA:
return (srgb ? GL_SRGB_ALPHA : GL_RGBA);
default:
// TODO(doyle): Invalid
// std::cout << "getGLFormat() invalid bytesPerPixel: "
// << bytesPerPixel << std::endl;
return GL_LUMINANCE;
}
}
Texture texture_gen(const GLuint width, const GLuint height,
const GLint bytesPerPixel, const u8 *const image)
{
// TODO(doyle): Let us set the parameters gl params as well
GL_CHECK_ERROR();
Texture tex = {0};
tex.width = width;
tex.height = height;
tex.internalFormat = GL_RGBA;
tex.wrapS = GL_REPEAT;
tex.wrapT = GL_REPEAT;
tex.filterMinification = GL_NEAREST;
tex.filterMagnification = GL_NEAREST;
glGenTextures(1, &tex.id);
GL_CHECK_ERROR();
glBindTexture(GL_TEXTURE_2D, tex.id);
GL_CHECK_ERROR();
/* Load image into texture */
// TODO(doyle) Figure out the gl format
tex.imageFormat = getGLFormat(bytesPerPixel, FALSE);
ASSERT(tex.imageFormat == GL_RGBA);
GL_CHECK_ERROR();
glTexImage2D(GL_TEXTURE_2D, 0, tex.internalFormat, tex.width, tex.height, 0,
tex.imageFormat, GL_UNSIGNED_BYTE, image);
GL_CHECK_ERROR();
// TODO(doyle): Not needed for sprites? glGenerateMipmap(GL_TEXTURE_2D);
/* Set parameter of currently bound texture */
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_S, tex.wrapS);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_WRAP_T, tex.wrapT);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MIN_FILTER,
tex.filterMinification);
glTexParameteri(GL_TEXTURE_2D, GL_TEXTURE_MAG_FILTER,
tex.filterMagnification);
GL_CHECK_ERROR();
/* Unbind and clean up */
glBindTexture(GL_TEXTURE_2D, 0);
GL_CHECK_ERROR();
return tex;
}

356
src/Ui.c
View File

@ -1,356 +0,0 @@
#include "Dengine/Ui.h"
#include "Dengine/AssetManager.h"
#include "Dengine/Assets.h"
#include "Dengine/Asteroid.h"
#include "Dengine/Debug.h"
#include "Dengine/Renderer.h"
void ui_beginState(UiState *state) { state->hotItem = 0; }
void ui_endState(UiState *state, InputBuffer *input)
{
if (!common_isSet(input->keys[keycode_mouseLeft].flags,
keystateflag_ended_down))
{
state->activeItem = 0;
}
else if (state->activeItem == 0)
{
state->activeItem = -1;
}
if (state->keyEntered == keycode_tab) state->kbdItem = 0;
state->keyEntered = keycode_null;
state->keyChar = keycode_null;
}
i32 ui_button(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager, Renderer *const renderer,
Font *const font, const InputBuffer input, const i32 id,
const Rect rect, const char *const label, i32 zDepth)
{
if (math_rectContainsP(rect, input.mouseP))
{
uiState->hotItem = id;
if (uiState->activeItem == 0)
{
if (common_isSet(input.keys[keycode_mouseLeft].flags,
keystateflag_ended_down))
{
uiState->activeItem = id;
}
}
}
#if 0
// Draw shadow
renderer_staticRect(renderer, v2_add(V2(1, 1), rect.min), rect.size,
V2(0, 0), 0, renderTex, V4(0, 0, 0, 1));
#endif
v2 buttonOffset = V2(0, 0);
v4 buttonColor = V4(0, 0, 0, 1);
if (uiState->hotItem == id)
{
if (uiState->activeItem == id)
{
buttonOffset = V2(1, 1);
buttonColor = V4(0.8f, 0.8f, 0.8f, 1);
}
else
{
// NOTE(doyle): Hover effect
buttonColor = V4(0.05f, 0.05f, 0.05f, 1);
}
}
/* If no widget has keyboard focus, take it */
if (uiState->kbdItem == 0) uiState->kbdItem = id;
v2 rectSize = math_rectGetSize(rect);
/* If we have keyboard focus, show it */
if (uiState->kbdItem == id)
{
// Draw outline
renderer_rectFixed(renderer,
v2_add(V2(-2, -2), v2_add(buttonOffset, rect.min)),
v2_add(V2(4, 4), rectSize), V2(0, 0), 0, NULL,
buttonColor, zDepth, renderflag_no_texture);
}
renderer_rectFixed(renderer, v2_add(buttonOffset, rect.min), rectSize,
V2(0, 0), 0, NULL, buttonColor, zDepth,
renderflag_no_texture);
if (label)
{
v2 labelDim = asset_fontStringDimInPixels(font, label);
v2 labelPos = rect.min;
// Initially position the label to half the width of the button
labelPos.x += (rectSize.w * 0.5f);
// Move the label pos back half the length of the string (i.e.
// center it)
labelPos.x -= (CAST(f32) labelDim.w * 0.5f);
if (labelDim.h < rectSize.h)
{
labelPos.y += (rectSize.h * 0.5f);
labelPos.y -= (CAST(f32) labelDim.h * 0.5f);
}
// TODO(doyle): We're using odd colors to overcome z-sorting by forcing
// button text into another render group
labelPos = v2_add(labelPos, buttonOffset);
renderer_stringFixed(renderer, arena, font, label, labelPos, V2(0, 0),
0, V4(0.9f, 0.9f, 0.9f, 0.9f), zDepth, 0);
}
// After renderering before click check, see if we need to process keys
if (uiState->kbdItem == id)
{
switch (uiState->keyEntered)
{
case keycode_tab:
// Set focus to nothing and let next widget get focus
uiState->kbdItem = 0;
if (uiState->keyMod == keycode_leftShift)
uiState->kbdItem = uiState->lastWidget;
// Clear key state so next widget doesn't auto grab
uiState->keyEntered = keycode_null;
break;
case keycode_enter:
return 1;
default:
break;
}
}
uiState->lastWidget = id;
// If button is hot and active, but mouse button is not
// down, the user must have clicked the button.
if (!common_isSet(input.keys[keycode_mouseLeft].flags,
keystateflag_ended_down) &&
uiState->hotItem == id && uiState->activeItem == id)
{
return id;
}
return 0;
}
i32 ui_scrollbar(UiState *const uiState, AssetManager *const assetManager,
Renderer *const renderer, const InputBuffer input,
const i32 id, const Rect scrollBarRect, i32 *const value,
const i32 maxValue, i32 zDepth)
{
#ifdef DENGINE_DEBUG
ASSERT(*value <= maxValue);
#endif
if (math_rectContainsP(scrollBarRect, input.mouseP))
{
uiState->hotItem = id;
if (uiState->activeItem == 0)
{
if (common_isSet(input.keys[keycode_mouseLeft].flags,
keystateflag_ended_down))
{
uiState->activeItem = id;
}
}
}
RenderTex renderTex = renderer_createNullRenderTex(assetManager);
/* If no widget has keyboard focus, take it */
if (uiState->kbdItem == 0) uiState->kbdItem = id;
v2 rectSize = math_rectGetSize(scrollBarRect);
/* If we have keyboard focus, show it */
if (uiState->kbdItem == id)
{
// Draw outline
renderer_rectFixed(renderer, v2_add(V2(-2, -2), scrollBarRect.min),
v2_add(V2(4, 4), rectSize), V2(0, 0), 0, &renderTex,
V4(1, 0, 0, 1), zDepth, 0);
}
// Render scroll bar background
renderer_rectFixed(renderer, scrollBarRect.min, rectSize, V2(0, 0), 0,
&renderTex, V4(0.75f, 0.5f, 0.5f, 1), zDepth, 0);
// Render scroll bar slider
v2 sliderSize = V2(16, 16);
v4 sliderColor = V4(0, 0, 0, 1);
f32 sliderPercentageOffset = (CAST(f32) * value / CAST(f32) maxValue);
f32 sliderYOffsetToBar =
(rectSize.h - sliderSize.h) * sliderPercentageOffset;
v2 sliderPos = v2_add(scrollBarRect.min, V2(0, sliderYOffsetToBar));
if (uiState->hotItem == id || uiState->activeItem == id)
sliderColor = V4(1.0f, 0, 0, 1);
else
sliderColor = V4(0.0f, 1.0f, 0, 1);
renderer_rectFixed(renderer, sliderPos, sliderSize, V2(0, 0), 0, &renderTex,
sliderColor, zDepth, 0);
if (uiState->kbdItem == id)
{
switch (uiState->keyEntered)
{
case keycode_tab:
uiState->kbdItem = 0;
if (uiState->keyMod == keycode_leftShift)
uiState->kbdItem = uiState->lastWidget;
// Clear key state so next widget doesn't auto grab
uiState->keyEntered = keycode_null;
break;
case keycode_up:
// TODO(doyle): Fix input for this to work, i.e. proper rate limited
// input poll
if (*value < maxValue)
{
(*value)++;
return id;
}
case keycode_down:
if (*value > 0)
{
(*value)--;
return id;
}
default:
break;
}
}
uiState->lastWidget = id;
if (uiState->activeItem == id)
{
f32 mouseYRelToRect = input.mouseP.y - scrollBarRect.min.y;
// Bounds check
if (mouseYRelToRect < 0)
mouseYRelToRect = 0;
else if (mouseYRelToRect > rectSize.h)
mouseYRelToRect = rectSize.h;
f32 newSliderPercentOffset = (CAST(f32) mouseYRelToRect / rectSize.h);
i32 newValue = CAST(i32)(newSliderPercentOffset * CAST(f32) maxValue);
if (newValue != *value)
{
*value = newValue;
return id;
}
}
return 0;
}
i32 ui_textfield(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager, Renderer *const renderer,
Font *const font, InputBuffer input, const i32 id,
const Rect rect, char *const string, i32 zDepth)
{
i32 strLen = common_strlen(string);
b32 changed = FALSE;
if (math_rectContainsP(rect, input.mouseP))
{
uiState->hotItem = id;
if (uiState->activeItem == 0)
{
if (common_isSet(input.keys[keycode_mouseLeft].flags,
keystateflag_ended_down))
{
uiState->activeItem = id;
}
}
}
/* If no widget has keyboard focus, take it */
if (uiState->kbdItem == 0) uiState->kbdItem = id;
v2 rectSize = math_rectGetSize(rect);
/* If we have keyboard focus, show it */
if (uiState->kbdItem == id)
{
// Draw outline
renderer_rectFixed(renderer, v2_add(V2(-2, -2), rect.min),
v2_add(V2(4, 4), rectSize), V2(0, 0), 0, NULL,
V4(1.0f, 0, 0, 1), zDepth, 0);
}
// Render text field
renderer_rectFixed(renderer, rect.min, rectSize, V2(0, 0), 0, NULL,
V4(0.75f, 0.5f, 0.5f, 1), zDepth, 0);
if (uiState->activeItem == id || uiState->hotItem == id)
{
renderer_rectFixed(renderer, rect.min, rectSize, V2(0, 0), 0, NULL,
V4(0.75f, 0.75f, 0.0f, 1), zDepth, 0);
}
else
{
renderer_rectFixed(renderer, rect.min, rectSize, V2(0, 0), 0, NULL,
V4(0.5f, 0.5f, 0.5f, 1), zDepth, 0);
}
v2 strPos = rect.min;
renderer_stringFixed(renderer, arena, font, string, strPos, V2(0, 0), 0,
V4(0, 0, 0, 1), zDepth, 0);
if (uiState->kbdItem == id)
{
switch (uiState->keyEntered)
{
case keycode_tab:
uiState->kbdItem = 0;
if (uiState->keyMod == keycode_leftShift)
uiState->kbdItem = uiState->lastWidget;
uiState->keyEntered = keycode_null;
break;
case keycode_backspace:
if (strLen > 0)
{
string[--strLen] = 0;
changed = TRUE;
}
break;
default:
break;
}
if (uiState->keyChar >= keycode_space &&
uiState->keyChar <= keycode_tilda && strLen < 30)
{
string[strLen++] = uiState->keyChar + ' ';
string[strLen] = 0;
changed = TRUE;
}
}
if (!common_isSet(input.keys[keycode_mouseLeft].flags,
keystateflag_ended_down) &&
uiState->hotItem == id && uiState->activeItem == id)
{
uiState->kbdItem = id;
}
uiState->lastWidget = id;
if (changed) return id;
return 0;
}

470
src/UserInterface.c Normal file
View File

@ -0,0 +1,470 @@
#include "Dengine/UserInterface.h"
#include "Dengine/AssetManager.h"
#include "Dengine/Assets.h"
#include "Dengine/Debug.h"
#include "Dengine/Renderer.h"
i32 userInterface_button(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager,
Renderer *const renderer, Font *const font,
const KeyInput input, const i32 id, const Rect rect,
const char *const label)
{
if (math_pointInRect(rect, input.mouseP))
{
uiState->hotItem = id;
// NOTE(doyle): UI windows are drawn first, they steal focus on mouse
// click since window logic is paired with the window rendering. If
// a UI element resides over our mouse, we allow the element to override
// the windows focus
// TODO(doyle): Make a window list to iterate over window ids
if (uiState->activeItem == uiState->statWindow.id ||
uiState->activeItem == uiState->debugWindow.id ||
uiState->activeItem == 0)
{
if (input.keys[keycode_mouseLeft].endedDown)
{
uiState->activeItem = id;
}
}
}
RenderTex renderTex = renderer_createNullRenderTex(assetManager);
#if 0
// Draw shadow
renderer_staticRect(renderer, v2_add(V2(1, 1), rect.pos), rect.size,
V2(0, 0), 0, renderTex, V4(0, 0, 0, 1));
#endif
v2 buttonOffset = V2(0, 0);
v4 buttonColor = {0};
if (uiState->hotItem == id)
{
if (uiState->activeItem == id)
{
buttonOffset = V2(1, 1);
buttonColor = V4(1, 1, 1, 1);
}
else
{
// TODO(doyle): Optional add effect on button hover
buttonColor = V4(1, 1, 1, 1);
}
}
else
{
buttonColor = V4(0.5f, 0.5f, 0.5f, 1);
}
/* If no widget has keyboard focus, take it */
if (uiState->kbdItem == 0)
uiState->kbdItem = id;
/* If we have keyboard focus, show it */
if (uiState->kbdItem == id)
{
// Draw outline
renderer_staticRect(
renderer, v2_add(V2(-2, -2), v2_add(buttonOffset, rect.pos)),
v2_add(V2(4, 4), rect.size), V2(0, 0), 0, renderTex, buttonColor);
}
renderer_staticRect(renderer, v2_add(buttonOffset, rect.pos), rect.size,
V2(0, 0), 0, renderTex, buttonColor);
if (label)
{
v2 labelDim = asset_stringDimInPixels(font, label);
v2 labelPos = rect.pos;
// Initially position the label to half the width of the button
labelPos.x += (rect.size.w * 0.5f);
// Move the label pos back half the length of the string (i.e.
// center it)
labelPos.x -= (CAST(f32) labelDim.w * 0.5f);
if (labelDim.h < rect.size.h)
{
labelPos.y += (rect.size.h * 0.5f);
labelPos.y -= (CAST(f32)labelDim.h * 0.5f);
}
labelPos = v2_add(labelPos, buttonOffset);
renderer_staticString(renderer, arena, font, label, labelPos, V2(0, 0),
0, V4(0, 0, 0, 1));
}
// After renderering before click check, see if we need to process keys
if (uiState->kbdItem == id)
{
switch (uiState->keyEntered)
{
case keycode_tab:
// Set focus to nothing and let next widget get focus
uiState->kbdItem = 0;
if (uiState->keyMod == keycode_leftShift)
uiState->kbdItem = uiState->lastWidget;
// Clear key state so next widget doesn't auto grab
uiState->keyEntered = keycode_null;
break;
case keycode_enter:
return 1;
default:
break;
}
}
uiState->lastWidget = id;
// If button is hot and active, but mouse button is not
// down, the user must have clicked the button.
if (!input.keys[keycode_mouseLeft].endedDown &&
uiState->hotItem == id &&
uiState->activeItem == id)
{
return id;
}
return 0;
}
i32 userInterface_scrollbar(UiState *const uiState,
AssetManager *const assetManager,
Renderer *const renderer, const KeyInput input,
const i32 id, const Rect scrollBarRect,
i32 *const value, const i32 maxValue)
{
#ifdef DENGINE_DEBUG
ASSERT(*value <= maxValue);
#endif
if (math_pointInRect(scrollBarRect, input.mouseP))
{
uiState->hotItem = id;
if (uiState->activeItem == uiState->statWindow.id ||
uiState->activeItem == uiState->debugWindow.id ||
uiState->activeItem == 0)
{
if (input.keys[keycode_mouseLeft].endedDown)
{
uiState->activeItem = id;
}
}
}
RenderTex renderTex = renderer_createNullRenderTex(assetManager);
/* If no widget has keyboard focus, take it */
if (uiState->kbdItem == 0)
uiState->kbdItem = id;
/* If we have keyboard focus, show it */
if (uiState->kbdItem == id)
{
// Draw outline
renderer_staticRect(renderer, v2_add(V2(-2, -2), scrollBarRect.pos),
v2_add(V2(4, 4), scrollBarRect.size), V2(0, 0), 0,
renderTex, V4(1, 0, 0, 1));
}
// Render scroll bar background
renderer_staticRect(renderer, scrollBarRect.pos, scrollBarRect.size,
V2(0, 0), 0, renderTex, V4(0.75f, 0.5f, 0.5f, 1));
// Render scroll bar slider
v2 sliderSize = V2(16, 16);
v4 sliderColor = V4(0, 0, 0, 1);
f32 sliderPercentageOffset = (CAST(f32) *value / CAST(f32) maxValue);
f32 sliderYOffsetToBar =
(scrollBarRect.size.h - sliderSize.h) * sliderPercentageOffset;
v2 sliderPos = v2_add(scrollBarRect.pos, V2(0, sliderYOffsetToBar));
if (uiState->hotItem == id || uiState->activeItem == id)
sliderColor = V4(1.0f, 0, 0, 1);
else
sliderColor = V4(0.0f, 1.0f, 0, 1);
renderer_staticRect(renderer, sliderPos, sliderSize, V2(0, 0), 0, renderTex,
sliderColor);
if (uiState->kbdItem == id)
{
switch (uiState->keyEntered)
{
case keycode_tab:
uiState->kbdItem = 0;
if (uiState->keyMod == keycode_leftShift)
uiState->kbdItem = uiState->lastWidget;
// Clear key state so next widget doesn't auto grab
uiState->keyEntered = keycode_null;
break;
case keycode_up:
// TODO(doyle): Fix input for this to work, i.e. proper rate limited input poll
if (*value < maxValue)
{
(*value)++;
return id;
}
case keycode_down:
if (*value > 0)
{
(*value)--;
return id;
}
default:
break;
}
}
uiState->lastWidget = id;
if (uiState->activeItem == id)
{
f32 mouseYRelToRect = input.mouseP.y - scrollBarRect.pos.y;
// Bounds check
if (mouseYRelToRect < 0)
mouseYRelToRect = 0;
else if (mouseYRelToRect > scrollBarRect.size.h)
mouseYRelToRect = scrollBarRect.size.h;
f32 newSliderPercentOffset =
(CAST(f32) mouseYRelToRect / scrollBarRect.size.h);
i32 newValue = CAST(i32)(newSliderPercentOffset * CAST(f32)maxValue);
if (newValue != *value)
{
*value = newValue;
return id;
}
}
return 0;
}
i32 userInterface_textField(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager,
Renderer *const renderer, Font *const font,
KeyInput input, const i32 id, const Rect rect,
char *const string)
{
i32 strLen = common_strlen(string);
b32 changed = FALSE;
if (math_pointInRect(rect, input.mouseP))
{
uiState->hotItem = id;
if (uiState->activeItem == uiState->statWindow.id ||
uiState->activeItem == uiState->debugWindow.id ||
uiState->activeItem == 0)
{
if (input.keys[keycode_mouseLeft].endedDown)
{
uiState->activeItem = id;
}
}
}
/* If no widget has keyboard focus, take it */
if (uiState->kbdItem == 0)
uiState->kbdItem = id;
RenderTex renderTex = renderer_createNullRenderTex(assetManager);
/* If we have keyboard focus, show it */
if (uiState->kbdItem == id)
{
// Draw outline
renderer_staticRect(renderer, v2_add(V2(-2, -2), rect.pos),
v2_add(V2(4, 4), rect.size), V2(0, 0), 0,
renderTex, V4(1.0f, 0, 0, 1));
}
// Render text field
renderer_staticRect(renderer, rect.pos, rect.size, V2(0, 0), 0,
renderTex, V4(0.75f, 0.5f, 0.5f, 1));
if (uiState->activeItem == id || uiState->hotItem == id)
{
renderer_staticRect(renderer, rect.pos, rect.size, V2(0, 0), 0,
renderTex, V4(0.75f, 0.75f, 0.0f, 1));
}
else
{
renderer_staticRect(renderer, rect.pos, rect.size, V2(0, 0), 0,
renderTex, V4(0.5f, 0.5f, 0.5f, 1));
}
v2 strPos = rect.pos;
renderer_staticString(renderer, arena, font, string, strPos, V2(0, 0), 0,
V4(0, 0, 0, 1));
if (uiState->kbdItem == id)
{
switch (uiState->keyEntered)
{
case keycode_tab:
uiState->kbdItem = 0;
if (uiState->keyMod == keycode_leftShift)
uiState->kbdItem = uiState->lastWidget;
uiState->keyEntered = keycode_null;
break;
case keycode_backspace:
if (strLen > 0)
{
string[--strLen] = 0;
changed = TRUE;
}
break;
default:
break;
}
if (uiState->keyChar >= keycode_space &&
uiState->keyChar <= keycode_tilda && strLen < 30)
{
string[strLen++] = uiState->keyChar + ' ';
string[strLen] = 0;
changed = TRUE;
}
}
if (!input.keys[keycode_mouseLeft].endedDown &&
uiState->hotItem == id &&
uiState->activeItem == id)
{
uiState->kbdItem = id;
}
uiState->lastWidget = id;
if (changed) return id;
return 0;
}
i32 userInterface_window(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager,
Renderer *const renderer, Font *const font,
const KeyInput input, WindowState *window)
{
if (math_pointInRect(window->rect, input.mouseP))
{
uiState->hotItem = window->id;
if (uiState->activeItem == 0 && input.keys[keycode_mouseLeft].endedDown)
uiState->activeItem = window->id;
}
Rect rect = window->rect;
RenderTex nullRenderTex = renderer_createNullRenderTex(assetManager);
renderer_staticRect(renderer, rect.pos, rect.size, V2(0, 0), 0,
nullRenderTex, V4(0.25f, 0.25f, 0.5f, 0.5f));
v2 menuTitleP = v2_add(rect.pos, V2(0, rect.size.h - 10));
renderer_staticString(renderer, arena, font, window->title, menuTitleP,
V2(0, 0), 0, V4(0, 0, 0, 1));
/* Draw window elements */
i32 firstActiveChildId = -1;
for (i32 i = 0; i < window->numChildUiItems; i++)
{
UiItem *childUi = &window->childUiItems[i];
// TODO(doyle): Redundant? If we can only have 1 active child at a time
// What about overlapping elements?
i32 getChildActiveState = -1;
switch(childUi->type)
{
case uitype_button:
// TODO(doyle): Bug in font rendering once button reaches 700-800+
// pixels
getChildActiveState = userInterface_button(
uiState, arena, assetManager, renderer, font, input,
childUi->id, childUi->rect, childUi->label);
break;
case uitype_scrollbar:
getChildActiveState = userInterface_scrollbar(
uiState, assetManager, renderer, input, childUi->id,
childUi->rect, &childUi->value, childUi->maxValue);
break;
case uitype_textField:
getChildActiveState = userInterface_textField(
uiState, arena, assetManager, renderer, font, input,
childUi->id, childUi->rect, childUi->string);
break;
default:
DEBUG_LOG(
"userInterface_window() warning: Enum uitype unrecognised");
break;
}
// NOTE(doyle): Capture only the first active id, but keep loop going to
// render the rest of the window ui
if (firstActiveChildId == -1 && getChildActiveState > 0)
firstActiveChildId = getChildActiveState;
}
if (firstActiveChildId != -1)
return firstActiveChildId;
// NOTE(doyle): activeItem captures mouse click within the UI bounds, but if
// the user drags the mouse outside the bounds quicker than the game updates
// then we use a second flag which only "unclicks" when the mouse is let go
if (uiState->activeItem == window->id) window->windowHeld = TRUE;
if (window->windowHeld)
{
if (!input.keys[keycode_mouseLeft].endedDown)
{
window->windowHeld = FALSE;
window->prevFrameWindowHeld = FALSE;
}
}
if (window->windowHeld)
{
// NOTE(doyle): If this is the first window click we don't process
// movement and store the current position to delta from next cycle
if (window->prevFrameWindowHeld)
{
// NOTE(doyle): Window clicked and held
v2 deltaP = v2_sub(input.mouseP, window->prevMouseP);
for (i32 i = 0; i < window->numChildUiItems; i++)
{
UiItem *childUi = &window->childUiItems[i];
childUi->rect.pos = v2_add(deltaP, childUi->rect.pos);
}
DEBUG_PUSH_VAR("Delta Pos %4.2f, %4.2f", deltaP, "v2");
window->rect.pos = v2_add(deltaP, window->rect.pos);
}
else
{
window->prevFrameWindowHeld = TRUE;
}
window->prevMouseP = input.mouseP;
}
if (!input.keys[keycode_mouseLeft].endedDown &&
uiState->hotItem == window->id &&
uiState->activeItem == window->id)
{
return window->id;
}
return 0;
}

2477
src/WorldTraveller.c Normal file

File diff suppressed because it is too large Load Diff

View File

@ -1,13 +1,4 @@
@echo OFF
REM Build tags file
ctags -R
REM Check if build tool is on path
REM >nul, 2>nul will remove the output text from the where command
where cl.exe >nul 2>nul
if %errorlevel%==1 call msvc86.bat
REM Drop compilation files into build folder
IF NOT EXIST ..\bin mkdir ..\bin
pushd ..\bin

View File

@ -1,10 +1,9 @@
#include "Dengine/AssetManager.h"
#include "Dengine/Asteroid.h"
#include "Dengine/Common.h"
#include "Dengine/Debug.h"
#include "Dengine/Math.h"
#include "Dengine/MemoryArena.h"
#include "Dengine/OpenGL.h"
#include "Dengine/WorldTraveller.h"
INTERNAL inline void processKey(KeyState *state, int action)
{
@ -19,12 +18,10 @@ INTERNAL void keyCallback(GLFWwindow *window, int key, int scancode, int action,
{
GameState *game = CAST(GameState *)(glfwGetWindowUserPointer(window));
#if 1
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS)
{
glfwSetWindowShouldClose(window, GL_TRUE);
}
#endif
switch (key)
{
@ -58,8 +55,7 @@ INTERNAL void keyCallback(GLFWwindow *window, int key, int scancode, int action,
i32 offset = 0;
if (key >= 'A' && key <= 'Z')
{
KeyState *leftShiftKey = &game->input.keys[keycode_leftShift];
if (!common_isSet(leftShiftKey->flags, keystateflag_ended_down))
if (!game->input.keys[keycode_leftShift].endedDown)
offset = 'a' - 'A';
}
@ -85,7 +81,7 @@ INTERNAL void mouseButtonCallback(GLFWwindow *window, int button, int action,
{
GameState *game = CAST(GameState *)(glfwGetWindowUserPointer(window));
switch (button)
switch(button)
{
case GLFW_MOUSE_BUTTON_LEFT:
processKey(&game->input.keys[keycode_mouseLeft], action);
@ -99,96 +95,21 @@ INTERNAL void scrollCallback(GLFWwindow *window, double xOffset, double yOffset)
{
}
INTERNAL void setGlfwWindowHints()
{
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
}
i32 main(void)
{
#ifdef DENGINE_DEBUG
common_unitTest();
#endif
/*
**************************
* INIT APPLICATION WINDOW
**************************
*/
glfwInit();
setGlfwWindowHints();
glfwWindowHint(GLFW_CONTEXT_VERSION_MAJOR, 3);
glfwWindowHint(GLFW_CONTEXT_VERSION_MINOR, 3);
glfwWindowHint(GLFW_OPENGL_PROFILE, GLFW_OPENGL_CORE_PROFILE);
glfwWindowHint(GLFW_RESIZABLE, GL_FALSE);
OptimalArrayV2 vidList = {0};
common_optimalArrayV2Create(&vidList);
i32 windowWidth = 0;
i32 windowHeight = 0;
{ // Query Computer Video Resolutions
i32 numMonitors;
GLFWmonitor **monitors = glfwGetMonitors(&numMonitors);
GLFWmonitor *primaryMonitor = monitors[0];
i32 numModes;
const GLFWvidmode *modes = glfwGetVideoModes(primaryMonitor, &numModes);
i32 targetRefreshHz = 60;
f32 targetWindowRatio = 16.0f / 9.0f;
i32 targetPixelDensity = 1280 * 720;
i32 minPixelDensityDelta = 100000000;
printf("== Supported video modes ==\n");
for (i32 i = 0; i < numModes; i++)
{
GLFWvidmode mode = modes[i];
printf("width: %d, height: %d, rgb: %d, %d, %d, refresh: %d\n",
mode.width, mode.height, mode.redBits, mode.greenBits,
mode.blueBits, mode.refreshRate);
if (mode.refreshRate == targetRefreshHz)
{
i32 result = common_optimalArrayV2Push(
&vidList, V2i(mode.width, mode.height));
if (result)
{
printf(
"common_optimalArrayV2Push(): Failed error code %d\n",
result);
ASSERT(INVALID_CODE_PATH);
}
f32 sizeRatio = (f32)mode.width / (f32)mode.height;
f32 delta = targetWindowRatio - sizeRatio;
if (delta < 0.1f)
{
i32 pixelDensity = mode.width * mode.height;
i32 densityDelta = ABS((pixelDensity - targetPixelDensity));
if (densityDelta < minPixelDensityDelta)
{
minPixelDensityDelta = densityDelta;
windowWidth = mode.width;
windowHeight = mode.height;
}
}
}
}
printf("== ==\n");
ASSERT(vidList.index > 0);
}
if (windowWidth == 0 || windowHeight == 0)
{
// NOTE(doyle): In this case just fallback to some value we hope is safe
windowWidth = 800;
windowHeight = 600;
}
i32 windowWidth = 1600;
i32 windowHeight = 900;
GLFWwindow *window =
glfwCreateWindow(windowWidth, windowHeight, "Dengine", NULL, NULL);
@ -221,7 +142,6 @@ i32 main(void)
i32 frameBufferWidth, frameBufferHeight;
glfwGetFramebufferSize(window, &frameBufferWidth, &frameBufferHeight);
glViewport(0, 0, frameBufferWidth, frameBufferHeight);
v2 windowSize = V2i(frameBufferWidth, frameBufferHeight);
glfwSetKeyCallback(window, keyCallback);
glfwSetCursorPosCallback(window, mouseCallback);
@ -240,9 +160,10 @@ i32 main(void)
* INITIALISE GAME
*******************
*/
Memory memory = {0};
MemoryIndex persistentSize = MEGABYTES(32);
MemoryIndex transientSize = MEGABYTES(64);
Memory memory = {0};
size_t persistentSize = MEGABYTES(128);
size_t transientSize = MEGABYTES(128);
memory.persistentSize = persistentSize;
memory.persistent = PLATFORM_MEM_ALLOC_(NULL, persistentSize, u8);
@ -250,27 +171,15 @@ i32 main(void)
memory.transientSize = transientSize;
memory.transient = PLATFORM_MEM_ALLOC_(NULL, transientSize, u8);
MemoryArena_ gameArena = {0};
memory_arenaInit(&gameArena, memory.persistent, memory.persistentSize);
GameState worldTraveller = {0};
worldTraveller_gameInit(&worldTraveller,
V2i(frameBufferWidth, frameBufferHeight), &memory);
#ifdef DENGINE_DEBUG
debug_init(&worldTraveller.arena_, V2i(windowWidth, windowHeight),
worldTraveller.assetManager.font);
#endif
GameState *gameState = MEMORY_PUSH_STRUCT(&gameArena, GameState);
gameState->persistentArena = gameArena;
glfwSetWindowUserPointer(window, CAST(void *)(gameState));
{ // Load game icon
i32 width, height;
char *iconPath = "data/textures/Asteroids/icon.png";
u8 *pixels = asset_imageLoad(&width, &height, NULL, iconPath, FALSE);
if (pixels)
{
GLFWimage image = {width, height, pixels};
glfwSetWindowIcon(window, 1, &image);
asset_imageFree(pixels);
}
}
gameState->input.resolutionList = &vidList;
glfwSetWindowUserPointer(window, CAST(void *)(&worldTraveller));
/*
*******************
@ -282,6 +191,7 @@ i32 main(void)
#if 0
// TODO(doyle): Get actual monitor refresh rate
i32 monitorRefreshHz = 60;
f32 targetSecondsPerFrame = 1.0f / CAST(f32)(monitorRefreshHz);
#else
// TODO(doyle): http://gafferongames.com/game-physics/fix-your-timestep/
@ -296,17 +206,16 @@ i32 main(void)
glfwPollEvents();
/* Rendering commands here*/
glClearColor(0.0f, 0.0f, 0.0f, 1.0f);
glClearColor(0.2f, 0.3f, 0.3f, 1.0f);
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
asteroid_gameUpdateAndRender(gameState, &memory, windowSize,
secondsElapsed);
worldTraveller_gameUpdateAndRender(&worldTraveller, secondsElapsed);
GL_CHECK_ERROR();
/* Swap the buffers */
glfwSwapBuffers(window);
f32 endTime = CAST(f32) glfwGetTime();
f32 endTime = CAST(f32)glfwGetTime();
secondsElapsed = endTime - startTime;
#if 0
@ -326,11 +235,9 @@ i32 main(void)
f32 msPerFrame = secondsElapsed * 1000.0f;
f32 framesPerSecond = 1.0f / secondsElapsed;
i32 entityCount = 0;
GameWorldState *world =
ASTEROID_GET_STATE_DATA(gameState, GameWorldState);
if (world) entityCount = world->entityIndex;
i32 entityCount =
worldTraveller.world[worldTraveller.currWorldIndex]
.freeEntityIndex;
char textBuffer[256];
snprintf(textBuffer, ARRAY_COUNT(textBuffer),
@ -342,22 +249,6 @@ i32 main(void)
}
startTime = endTime;
StartMenuState *menuState =
ASTEROID_GET_STATE_DATA(gameState, StartMenuState);
if (menuState)
{
if (menuState->newResolutionRequest)
{
i32 index = menuState->resStringDisplayIndex;
windowSize = gameState->input.resolutionList->ptr[index];
glfwSetWindowSize(window, (i32)windowSize.w, (i32)windowSize.h);
glViewport(0, 0, (i32)windowSize.w, (i32)windowSize.h);
menuState->newResolutionRequest = FALSE;
}
}
}
glfwTerminate();

View File

@ -2,6 +2,8 @@
#define DENGINE_ASSET_MANAGER_H
#include "Dengine/Assets.h"
#include "Dengine/Shader.h"
#include "Dengine/Texture.h"
/* Forward declaration */
typedef struct MemoryArena MemoryArena_;
@ -16,80 +18,70 @@ typedef struct AssetManager
HashTable audio;
/* Primitive Array */
u32 shaders[shaderlist_count];
FontPack fontPack[4];
i32 fontPackIndex;
Shader shaders[2];
Font font;
} AssetManager;
#define MAX_TEXTURE_SIZE 1024
void asset_init(AssetManager *assetManager, MemoryArena_ *arena);
////////////////////////////////////////////////////////////////////////////////
// Texture Managing
////////////////////////////////////////////////////////////////////////////////
const SubTexture asset_atlasGetSubTex(TexAtlas *const atlas,
/*
*********************************
* Texture Operations
*********************************
*/
const SubTexture asset_getAtlasSubTex(TexAtlas *const atlas,
const char *const key);
Texture *asset_texGet(AssetManager *const assetManager, const char *const key);
TexAtlas *asset_atlasGetFreeSlot(AssetManager *const assetManager,
MemoryArena_ *arena, const char *const key,
i32 numSubTex);
TexAtlas *asset_atlasGet(AssetManager *const assetManager,
const char *const key);
Texture *asset_texGetFreeSlot(AssetManager *const assetManager,
Texture *asset_getTex(AssetManager *const assetManager, const char *const key);
TexAtlas *asset_getFreeTexAtlasSlot(AssetManager *const assetManager,
MemoryArena_ *arena, const char *const key,
i32 numSubTex);
TexAtlas *asset_getTexAtlas(AssetManager *const assetManager,
const char *const key);
Texture *asset_getFreeTexSlot(AssetManager *const assetManager,
MemoryArena_ *const arena, const char *const key);
Texture *asset_texLoadImage(AssetManager *assetManager, MemoryArena_ *arena,
const char *const path, const char *const key);
Texture *asset_loadTextureImage(AssetManager *assetManager, MemoryArena_ *arena,
const char *const path, const char *const key);
u8 *asset_imageLoad(i32 *width, i32 *height, i32 *bpp, const char *const path,
b32 flipVertically);
void asset_imageFree(u8 *image);
////////////////////////////////////////////////////////////////////////////////
// Animation Asset Managing
////////////////////////////////////////////////////////////////////////////////
void asset_animAdd(AssetManager *const assetManager, MemoryArena_ *const arena,
const char *const animName, TexAtlas *const atlas,
char **const subTextureNames, const i32 numSubTextures,
const f32 frameDuration);
Animation *asset_animGet(AssetManager *const assetManager,
/*
*********************************
* Animation Asset Managing
*********************************
*/
void asset_addAnimation(AssetManager *const assetManager,
MemoryArena_ *const arena, const char *const animName,
TexAtlas *const atlas, char **const subTextureNames,
const i32 numSubTextures, const f32 frameDuration);
Animation *asset_getAnim(AssetManager *const assetManager,
const char *const key);
////////////////////////////////////////////////////////////////////////////////
// Audio
////////////////////////////////////////////////////////////////////////////////
AudioVorbis *const asset_vorbisGet(AssetManager *const assetManager,
/*
*********************************
* Audio
*********************************
*/
AudioVorbis *const asset_getVorbis(AssetManager *const assetManager,
const char *const key);
const i32 asset_vorbisLoad(AssetManager *assetManager, MemoryArena_ *arena,
const i32 asset_loadVorbis(AssetManager *assetManager, MemoryArena_ *arena,
const char *const path, const char *const key);
////////////////////////////////////////////////////////////////////////////////
// Everything else
////////////////////////////////////////////////////////////////////////////////
const i32 asset_xmlLoad(AssetManager *const assetManager,
MemoryArena_ *const arena,
const PlatformFileRead *const fileRead);
/*
*********************************
* Everything else
*********************************
*/
const i32 asset_loadXmlFile(AssetManager *const assetManager,
MemoryArena_ *const arena,
const PlatformFileRead *const fileRead);
u32 asset_shaderGet(AssetManager *assetManager, const enum ShaderList type);
const i32 asset_shaderLoad(AssetManager *assetManager, MemoryArena_ *arena,
const char *const vertexPath,
const char *const fragmentPath,
const enum ShaderList type);
Shader *const asset_getShader(AssetManager *assetManager, const enum ShaderList type);
const i32 asset_loadShaderFiles(AssetManager *assetManager, MemoryArena_ *arena,
const char *const vertexPath,
const char *const fragmentPath,
const enum ShaderList type);
Font *asset_fontGetOrCreateOnDemand(AssetManager *assetManager,
MemoryArena_ *persistentArena,
MemoryArena_ *transientArena, char *name,
i32 size);
Font *asset_fontGet(AssetManager *assetManager, char *name, i32 size);
const i32 asset_fontLoadTTF(AssetManager *assetManager,
MemoryArena_ *persistentArena,
MemoryArena_ *transientArena, char *filePath,
char *name, i32 targetFontHeight);
const v2 asset_fontStringDimInPixels(const Font *const font,
const char *const string);
const i32 asset_loadTTFont(AssetManager *assetManager, MemoryArena_ *arena,
const char *filePath);
const v2 asset_stringDimInPixels(const Font *const font,
const char *const string);
void asset_unitTest(MemoryArena_ *arena);

View File

@ -44,8 +44,7 @@ typedef struct XmlNode
enum ShaderList
{
shaderlist_default,
shaderlist_default_no_tex,
shaderlist_sprite,
shaderlist_count,
};
@ -95,6 +94,7 @@ typedef struct AudioVorbis
* Texture Assets
*********************************
*/
typedef struct SubTexture
{
Rect rect;
@ -123,34 +123,6 @@ typedef struct Animation
f32 frameDuration;
} Animation;
#define TARGET_TEXTURE_SIZE 1024
#define TARGET_BYTES_PER_PIXEL 4
// TODO(doyle): Look into merging into assets.h file ..
typedef struct Texture
{
// Holds the ID of the texture object, used for all texture operations to
// reference to this particlar texture
u32 id;
// Texture image dimensions
u32 width;
u32 height;
// Texture Format
u32 internalFormat; // Format of texture object
u32 imageFormat; // Format of loaded image
// Texture configuration
u32 wrapS; // Wrapping mode on S axis
u32 wrapT; // Wrapping mode on T axis
// Filtering mode if texture pixels < screen pixels
u32 filterMinification;
// Filtering mode if texture pixels > screen pixels
u32 filterMagnification;
} Texture;
/*
*********************************
* Font
@ -176,13 +148,6 @@ typedef struct CharMetrics
typedef struct Font
{
// NOTE(doyle): In stb_font we scale the loaded font to this target height
// and so this is used as a general font "size" notion
union {
i32 fontHeight;
i32 size;
};
TexAtlas *atlas;
FontMetrics metrics;
@ -194,14 +159,6 @@ typedef struct Font
v2 codepointRange;
v2 maxSize;
} Font;
// NOTE(doyle): A font pack is a singular font at different sizes
typedef struct FontPack
{
char *name;
char *filePath;
Font font[4];
i32 fontIndex;
} FontPack;
} Font;
#endif

View File

@ -1,114 +0,0 @@
#ifndef ASTEROID_H
#define ASTEROID_H
#include "Dengine/AssetManager.h"
#include "Dengine/Audio.h"
#include "Dengine/Common.h"
#include "Dengine/Entity.h"
#include "Dengine/MemoryArena.h"
#include "Dengine/Platform.h"
#include "Dengine/Renderer.h"
#include "Dengine/Ui.h"
enum AppState
{
appstate_StartMenuState,
appstate_GameWorldState,
appstate_count,
};
enum GameWorldStateFlags
{
gameworldstateflags_init = (1 << 0),
gameworldstateflags_level_started = (1 << 1),
gameworldstateflags_player_lost = (1 << 2),
gameworldstateflags_create_player = (1 << 3),
};
typedef struct GameWorldState
{
enum GameWorldStateFlags flags;
MemoryArena_ entityArena;
v2 *entityVertexListCache[entitytype_count];
Entity *entityList;
i32 entityListSize;
i32 entityIndex;
u32 entityIdCounter;
u32 asteroidCounter;
u32 numAsteroids;
v2 *asteroidSmallVertexCache[3];
v2 *asteroidMediumVertexCache[3];
v2 *asteroidLargeVertexCache[3];
v2 *bulletVertexCache;
v2 *particleVertexCache;
v2 *starPList;
f32 starOpacity;
f32 starMinOpacity;
b32 starFadeAway;
i32 numStarP;
// TODO(doyle): Audio mixing instead of multiple renderers
AudioRenderer *audioRenderer;
i32 numAudioRenderers;
f32 pixelsPerMeter;
Rect camera;
v2 size;
// TODO(doyle): Ensure we change this if it gets too big
b32 collisionTable[entitytype_count][entitytype_count];
i32 score;
i32 scoreMultiplier;
f32 scoreMultiplierBarTimer;
f32 scoreMultiplierBarThresholdInS;
f32 timeSinceLastShot;
} GameWorldState;
typedef struct StartMenuState
{
b32 init;
f32 startPromptBlinkTimer;
b32 startPromptShow;
char **resStrings;
i32 numResStrings;
i32 resStringDisplayIndex;
b32 newResolutionRequest;
b32 optionsShow;
} StartMenuState;
typedef struct GameState
{
b32 init;
enum AppState currState;
void *appStateData[appstate_count];
MemoryArena_ transientArena;
MemoryArena_ persistentArena;
AudioManager audioManager;
AssetManager assetManager;
InputBuffer input;
Renderer renderer;
UiState uiState;
} GameState;
#define ASTEROID_GET_STATE_DATA(state, type) (type *)asteroid_getStateData_(state, appstate_##type)
void *asteroid_getStateData_(GameState *state, enum AppState appState);
void asteroid_gameUpdateAndRender(GameState *state, Memory *memory,
v2 windowSize, f32 dt);
#endif

View File

@ -49,17 +49,17 @@ typedef struct AudioRenderer
const i32 audio_init(AudioManager *audioManager);
const i32 audio_vorbisPlay(MemoryArena_ *arena, AudioManager *audioManager,
const i32 audio_playVorbis(MemoryArena_ *arena, AudioManager *audioManager,
AudioRenderer *audioRenderer, AudioVorbis *vorbis,
i32 numPlays);
const i32 audio_vorbisStream(MemoryArena_ *arena, AudioManager *audioManager,
const i32 audio_streamPlayVorbis(MemoryArena_ *arena, AudioManager *audioManager,
AudioRenderer *audioRenderer,
AudioVorbis *vorbis, i32 numPlays);
const i32 audio_vorbisStop(MemoryArena_ *arena, AudioManager *audioManager,
const i32 audio_stopVorbis(MemoryArena_ *arena, AudioManager *audioManager,
AudioRenderer *audioRenderer);
const i32 audio_vorbisPause(AudioManager *audioManager,
const i32 audio_pauseVorbis(AudioManager *audioManager,
AudioRenderer *audioRenderer);
const i32 audio_vorbisResume(AudioManager *audioManager,
const i32 audio_resumeVorbis(AudioManager *audioManager,
AudioRenderer *audioRenderer);
const i32 audio_updateAndPlay(MemoryArena_ *arena, AudioManager *audioManager,
AudioRenderer *audioRenderer);

View File

@ -14,11 +14,6 @@ typedef i32 b32;
typedef float f32;
typedef double f64;
typedef size_t MemoryIndex;
typedef char String;
typedef struct MemoryArena MemoryArena_;
#define TRUE 1
#define FALSE 0
@ -37,99 +32,16 @@ typedef struct MemoryArena MemoryArena_;
#define DENGINE_DEBUG
#include "Dengine/Math.h"
/*
NOTE(doyle): Small sized optimised dynamic array that grows as required. The
array uses the stack first, only if it runs out of space does it rely on
memory allocated from the machine.
The array->ptr is initially set to fast storage. Once we are out of space
we allocate space on the heap for the ptr and copy over the elements in
fast storage.
The default behaviour expands the array storage by the size of fastStorage.
*/
enum OptimalArrayError
{
optimalarrayerror_out_of_memory = 1,
optimalarrayerror_count,
};
typedef struct OptimalArrayV2
{
v2 fastStorage[16];
v2 *ptr;
i32 index;
i32 size;
} OptimalArrayV2;
void common_optimalArrayV2Create(OptimalArrayV2 *array);
i32 common_optimalArrayV2Push(OptimalArrayV2 *array, v2 data);
void common_optimalArrayV2Destroy(OptimalArrayV2 *array);
i32 common_stringLen(String *const string);
String *const common_stringAppend(MemoryArena_ *const arena, String *oldString,
String *appendString, i32 appendLen);
void common_stringFree(MemoryArena_ *arena, String *string);
String *const common_stringMake(MemoryArena_ *const arena, char *string);
String *const common_stringMakeLen(MemoryArena_ *const arena, i32 len);
i32 common_strlen(const char *const string);
i32 common_strcmp(const char *a, const char *b);
void common_strncat(char *dest, const char *src, i32 numChars);
char *common_strncpy(char *dest, const char *src, i32 numChars);
u8 *common_memset(u8 *const ptr, const i32 value, const i32 numBytes);
// Max buffer size should be 11 for 32 bit integers
#define COMMON_ITOA_MAX_BUFFER_32BIT 11
void common_itoa(i32 value, char *buf, i32 bufSize);
i32 common_atoi(const char *string, const i32 len);
inline b32 common_isSet(u32 bitfield, u32 flags)
{
b32 result = FALSE;
if ((bitfield & flags) == flags) result = TRUE;
return result;
}
inline void common_unitTest()
{
{
char itoa[COMMON_ITOA_MAX_BUFFER_32BIT] = {0};
common_itoa(0, itoa, ARRAY_COUNT(itoa));
ASSERT(itoa[0] == '0');
ASSERT(itoa[1] == 0);
common_memset(itoa, 1, ARRAY_COUNT(itoa));
for (i32 i = 0; i < ARRAY_COUNT(itoa); i++) ASSERT(itoa[i] == 1);
common_memset(itoa, 0, ARRAY_COUNT(itoa));
for (i32 i = 0; i < ARRAY_COUNT(itoa); i++) ASSERT(itoa[i] == 0);
common_itoa(-123, itoa, ARRAY_COUNT(itoa));
ASSERT(itoa[0] == '-');
ASSERT(itoa[1] == '1');
ASSERT(itoa[2] == '2');
ASSERT(itoa[3] == '3');
ASSERT(itoa[4] == 0);
common_memset(itoa, 0, ARRAY_COUNT(itoa));
for (i32 i = 0; i < ARRAY_COUNT(itoa); i++) ASSERT(itoa[i] == 0);
common_itoa(93, itoa, ARRAY_COUNT(itoa));
ASSERT(itoa[0] == '9');
ASSERT(itoa[1] == '3');
ASSERT(itoa[2] == 0);
common_memset(itoa, 0, ARRAY_COUNT(itoa));
for (i32 i = 0; i < ARRAY_COUNT(itoa); i++) ASSERT(itoa[i] == 0);
common_itoa(-0, itoa, ARRAY_COUNT(itoa));
ASSERT(itoa[0] == '0');
ASSERT(itoa[1] == 0);
}
}
//-----------------------------------------------------------------------------
// MurmurHash2, by Austin Appleby

View File

@ -20,13 +20,13 @@ enum DebugCount
debugcount_num,
};
void debug_init(v2 windowSize, Font font);
void debug_init(MemoryArena_ *arena, v2 windowSize, Font font);
#define DEBUG_RECURSIVE_PRINT_XML_TREE(sig) debug_recursivePrintXmlTree(sig, 1)
void debug_recursivePrintXmlTree(XmlNode *root, i32 levelsDeep);
void debug_countIncrement(enum DebugCount id);
void debug_clearCounter();
#define DEBUG_LOG(string) debug_consoleLog(string, __FILE__, __LINE__);
void debug_consoleLog(char *string, char *file, int lineNum);

View File

@ -1,69 +1,81 @@
#ifndef ENTITY_H
#define ENTITY_H
#ifndef DENGINE_ENTITY_H
#define DENGINE_ENTITY_H
#include "Dengine/Assets.h"
#include "Dengine/Common.h"
#include "Dengine/Renderer.h"
#include "Dengine/Math.h"
#include "Dengine/Assets.h"
typedef struct AssetManager AssetManager;
typedef struct AudioRenderer AudioRenderer;
typedef struct MemoryArena_ MemoryArena;
typedef struct MemoryArena MemoryArena_;
typedef struct World World;
typedef struct EventQueue EventQueue;
typedef struct Entity Entity;
enum Direction
{
direction_north,
direction_northwest,
direction_west,
direction_southwest,
direction_south,
direction_southeast,
direction_east,
direction_northeast,
direction_count,
direction_null,
direction_num,
direction_null,
};
INTERNAL inline enum Direction invertDirection(enum Direction direction)
{
enum Direction result = (direction + 4) % direction_count;
return result;
}
INTERNAL inline enum Direction rotateDirectionWest90(enum Direction direction)
{
enum Direction result = (direction + 2) % direction_count;
return result;
}
INTERNAL inline enum Direction rotateDirectionEast90(enum Direction direction)
{
enum Direction result = (direction + 2) % direction_count;
return result;
}
#define NULL_ENTITY_ID 0
enum EntityType
{
entitytype_invalid,
entitytype_null,
entitytype_ship,
// NOTE(doyle): Asteroids must be grouped since we use >= and <= logic to
// combine asteroid logic together
entitytype_asteroid_small,
entitytype_asteroid_medium,
entitytype_asteroid_large,
entitytype_particle,
entitytype_bullet,
entitytype_hero,
entitytype_weapon,
entitytype_projectile,
entitytype_attackObject,
entitytype_npc,
entitytype_mob,
entitytype_tile,
entitytype_soundscape,
entitytype_count,
};
enum EntityState
{
entitystate_idle,
entitystate_battle,
entitystate_attack,
entitystate_dead,
entitystate_count,
entitystate_invalid,
};
enum EntityAttack
{
entityattack_claudeAttack,
entityattack_claudeAttackUp,
entityattack_claudeAttackDown,
entityattack_claudeDragonHowl,
entityattack_claudeEnergySword,
entityattack_claudeRipperBlast,
entityattack_claudeAirSlash,
entityattack_count,
entityattack_invalid,
};
typedef struct EntityStats
{
f32 maxHealth;
f32 health;
f32 actionRate;
f32 actionTimer;
f32 actionSpdMul;
f32 busyDuration;
i32 entityIdToAttack;
enum EntityAttack queuedAttack;
Entity *weapon;
} EntityStats;
typedef struct EntityAnim
{
Animation *anim;
@ -73,49 +85,63 @@ typedef struct EntityAnim
u32 timesCompleted;
} EntityAnim;
typedef struct Entity
struct Entity
{
u32 id;
i32 id;
v2 pos;
v2 dP;
v2 particleInitDp;
i32 childIds[8];
i32 numChilds;
v2 pos; // Position
v2 dPos; // Velocity
v2 hitbox;
v2 size;
/*
NOTE(doyle): Offset is the vector to shift the polygons "origin" to the
world origin. In our case this should strictly be negative as we have
a requirement that all vertex points must be strictly positive.
*/
v2 offset;
enum RenderMode renderMode;
i32 numVertexPoints;
v2 *vertexPoints;
f32 scale;
Degrees rotation;
f32 rotation;
b32 invisible;
enum EntityState state;
enum EntityType type;
enum Direction direction;
v4 color;
Texture *tex;
b32 flipX;
b32 flipY;
// TODO(doyle): Two collision flags, we want certain entities to collide
// with certain types of entities only (i.e. projectile from hero to enemy,
// only collides with enemy). Having two flags is redundant, but! it does
// allow for early-exit in collision check if the entity doesn't collide at
// all
b32 collides;
enum EntityType collidesWith[entitytype_count];
EntityAnim animList[16];
i32 animListIndex;
} Entity;
i32 currAnimId;
SubTexture entity_subTexGetCurr(Entity *const entity);
void entity_animSet(Entity *const entity, const char *const animName);
void entity_animUpdate(Entity *const entity, const f32 dt);
void entity_animAdd(AssetManager *const assetManager, Entity *const entity,
EntityStats *stats;
// TODO(doyle): Audio mixing instead of multiple renderers
AudioRenderer *audioRenderer;
i32 numAudioRenderers;
};
SubTexture entity_getActiveSubTexture(Entity *const entity);
void entity_setActiveAnim(EventQueue *eventQueue, Entity *const entity,
const char *const animName);
void entity_updateAnim(EventQueue *eventQueue, Entity *const entity,
const f32 dt);
void entity_addAnim(AssetManager *const assetManager, Entity *const entity,
const char *const animName);
v2 *entity_generateUpdatedVertexList(MemoryArena_ *transientArena,
Entity *entity);
Entity *const entity_add(MemoryArena_ *const arena, World *const world,
const v2 pos, const v2 size, const f32 scale,
const enum EntityType type,
const enum Direction direction, Texture *const tex,
const b32 collides);
void entity_clearData(MemoryArena_ *const arena, World *const world,
Entity *const entity);
Entity *entity_get(World *const world, const i32 entityId);
i32 entity_getIndex(World *const world, const i32 entityId);
#endif

View File

@ -2,58 +2,32 @@
#define DENGINE_MATH_H
#include <math.h>
#include "Dengine/Common.h"
#define MATH_PI 3.14159265359f
#define SQUARED(x) (x * x)
#define ABS(x) ((x) > 0 ? (x) : -(x))
#define DEGREES_TO_RADIANS(x) ((x * (MATH_PI / 180.0f)))
#define RADIANS_TO_DEGREES(x) ((x * (180.0f / MATH_PI)))
#define MAX(a, b) ((a) < (b) ? (b) : (a))
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#define SQUARED(x) ((x) * (x))
#define DEGREES_TO_RADIANS(x) (x * (MATH_PI / 180.0f))
#define RADIANS_TO_DEGREES(x) (x * (180.0f / MATH_PI))
#define SQRT(x) (sqrtf(x))
typedef f32 Radians;
typedef f32 Degrees;
INTERNAL inline f32 math_acosf(f32 a)
{
f32 result = acosf(a);
return result;
}
INTERNAL inline Radians math_cosf(Radians a)
{
Radians result = cosf(a);
return result;
}
INTERNAL inline Radians math_sinf(Radians a)
{
Radians result = sinf(a);
return result;
}
INTERNAL inline f32 math_atan2f(f32 y, f32 x)
{
f32 result = atan2f(y, x);
return result;
}
INTERNAL inline f32 math_tanf(Radians angle)
{
f32 result = tanf(angle);
return result;
}
/* VECTORS */
typedef union v2
{
struct { f32 x, y; };
struct { f32 w, h; };
struct { f32 min, max; };
f32 e[2];
} v2;
@ -74,8 +48,8 @@ typedef union v4
typedef struct Rect
{
v2 min;
v2 max;
v2 pos;
v2 size;
} Rect;
INTERNAL inline v2 V2i(const i32 x, const i32 y)
@ -140,7 +114,7 @@ INTERNAL inline v##num v##num##_scale(const v##num a, const f32 b) \
for (i32 i = 0; i < ##num; i++) { result.e[i] = a.e[i] * b; } \
return result; \
} \
INTERNAL inline v##num v##num##_hadamard(const v##num a, const v##num b) \
INTERNAL inline v##num v##num##_mul(const v##num a, const v##num b) \
{ \
v##num result; \
for (i32 i = 0; i < ##num; i++) { result.e[i] = a.e[i] * b.e[i]; } \
@ -194,34 +168,6 @@ INTERNAL inline v2 v2_normalise(const v2 a)
return result;
}
INTERNAL inline b32 v2_intervalsOverlap(v2 a, v2 b)
{
b32 result = FALSE;
f32 lenOfA = a.max - a.min;
f32 lenOfB = b.max - b.min;
if (lenOfA > lenOfB)
{
v2 tmp = a;
a = b;
b = tmp;
}
if ((a.min >= b.min && a.min <= b.max) ||
(a.max >= b.min && a.max <= b.max))
{
result = TRUE;
}
return result;
}
INTERNAL inline v2 v2_perpendicular(const v2 a) {
v2 result = {a.y, -a.x};
return result;
}
INTERNAL inline v3 v3_cross(const v3 a, const v3 b)
{
/*
@ -332,7 +278,7 @@ INTERNAL inline mat4 mat4_mul(const mat4 a, const mat4 b)
}
INTERNAL inline v4 mat4_mulV4(const mat4 a, const v4 b)
INTERNAL inline v4 mat4_mul_v4(const mat4 a, const v4 b)
{
v4 result = {0};
@ -348,14 +294,14 @@ INTERNAL inline v4 mat4_mulV4(const mat4 a, const v4 b)
return result;
}
INTERNAL inline b32 math_rectContainsP(Rect rect, v2 p)
INTERNAL inline b32 math_pointInRect(Rect rect, v2 point)
{
b32 outsideOfRectX = FALSE;
if (p.x < rect.min.x || p.x > rect.max.w)
if (point.x < rect.pos.x || point.x > (rect.pos.x + rect.size.w))
outsideOfRectX = TRUE;
b32 outsideOfRectY = FALSE;
if (p.y < rect.min.y || p.y > rect.max.h)
if (point.y < rect.pos.y || point.y > (rect.pos.y + rect.size.h))
outsideOfRectY = TRUE;
if (outsideOfRectX || outsideOfRectY) return FALSE;
@ -363,92 +309,22 @@ INTERNAL inline b32 math_rectContainsP(Rect rect, v2 p)
}
INTERNAL inline Rect math_rectCreate(v2 origin, v2 size)
INTERNAL inline v4 math_getRect(v2 origin, v2 size)
{
Rect result = {0};
result.min = origin;
result.max = v2_add(origin, size);
v2 upperRightBound = v2_add(origin, V2(size.x, size.y));
v4 result = V4(origin.x, origin.y, upperRightBound.x, upperRightBound.y);
return result;
}
INTERNAL inline v2 math_rectGetSize(Rect rect)
INTERNAL inline v2 math_getRectSize(v4 rect)
{
f32 width = ABS(rect.max.x - rect.min.x);
f32 height = ABS(rect.max.y - rect.min.y);
f32 width = ABS(rect.x - rect.z);
f32 height = ABS(rect.y - rect.w);
v2 result = V2(width, height);
return result;
}
INTERNAL inline v2 math_rectGetCentre(Rect rect)
{
f32 sumX = rect.min.x + rect.max.x;
f32 sumY = rect.min.y + rect.max.y;
v2 result = v2_scale(V2(sumX, sumY), 0.5f);
return result;
}
INTERNAL inline Rect math_rectShift(Rect rect, v2 shift)
{
Rect result = {0};
result.min = v2_add(rect.min, shift);
result.max = v2_add(rect.max, shift);
return result;
}
INTERNAL inline void math_applyRotationToVertexes(v2 pos, v2 pivotPoint,
Radians rotate,
v2 *vertexList,
i32 vertexListSize)
{
if (rotate == 0) return;
// NOTE(doyle): Move the world origin to the base position of the object.
// Then move the origin to the pivot point (e.g. center of object) and
// rotate from that point.
v2 pointOfRotation = v2_add(pivotPoint, pos);
mat4 rotateMat = mat4_translate(pointOfRotation.x, pointOfRotation.y, 0.0f);
rotateMat = mat4_mul(rotateMat, mat4_rotate(rotate, 0.0f, 0.0f, 1.0f));
rotateMat = mat4_mul(rotateMat, mat4_translate(-pointOfRotation.x,
-pointOfRotation.y, 0.0f));
// TODO(doyle): Make it nice for 2d vector, stop using mat4
for (i32 i = 0; i < vertexListSize; i++)
{
// NOTE(doyle): Manual matrix multiplication since vertex pos is 2D and
// matrix is 4D
v2 oldP = vertexList[i];
v2 newP = {0};
newP.x = (oldP.x * rotateMat.e[0][0]) + (oldP.y * rotateMat.e[1][0]) +
(rotateMat.e[3][0]);
newP.y = (oldP.x * rotateMat.e[0][1]) + (oldP.y * rotateMat.e[1][1]) +
(rotateMat.e[3][1]);
vertexList[i] = newP;
}
}
INTERNAL inline f32 math_lerp(f32 a, f32 t, f32 b)
{
/*
Linear blend between two values. We having a starting point "a", and
the distance to "b" is defined as (b - a). Then we can say
a + t(b - a)
As our linear blend fn. We start from "a" and choosing a t from 0->1
will vary the value of (b - a) towards b. If we expand this, this
becomes
a + (t * b) - (a * t) == (1 - t)a + t*b
*/
f32 result = ((1 - t) * a) + (t * b);
return result;
}
#endif

View File

@ -5,36 +5,25 @@
typedef struct MemoryArena
{
MemoryIndex size;
MemoryIndex used;
size_t size;
size_t used;
u8 *base;
i32 tempMemoryCount;
} MemoryArena_;
typedef struct Memory
{
void *persistent;
MemoryIndex persistentSize;
size_t persistentSize;
void *transient;
MemoryIndex transientSize;
size_t transientSize;
b32 init;
} Memory;
typedef struct TempMemory
{
MemoryArena_ *arena;
MemoryIndex used;
} TempMemory;
TempMemory memory_beginTempRegion(MemoryArena_ *arena);
void memory_endTempRegion(TempMemory tempMemory);
#define MEMORY_PUSH_STRUCT(arena, type) (type *)memory_pushBytes(arena, sizeof(type))
#define MEMORY_PUSH_ARRAY(arena, count, type) (type *)memory_pushBytes(arena, (count)*sizeof(type))
inline void *memory_pushBytes(MemoryArena_ *arena, MemoryIndex size)
inline void *memory_pushBytes(MemoryArena_ *arena, size_t size)
{
ASSERT((arena->used + size) <= arena->size);
void *result = arena->base + arena->used;
@ -43,6 +32,6 @@ inline void *memory_pushBytes(MemoryArena_ *arena, MemoryIndex size)
return result;
}
void memory_arenaInit(MemoryArena_ *arena, void *base, MemoryIndex size);
void memory_arenaInit(MemoryArena_ *arena, void *base, size_t size);
#endif

View File

@ -119,35 +119,19 @@ enum KeyCode
keycode_null,
};
/*
NOTE(doyle): EndedDown describes the last state the key was in and is
_NOT_ the same as pressed on current frame. They key may of have been
held down over multiple frames, so endedDown will remain true.
Pressed on current frame captures if it was just pressed on this frame
only.
*/
enum KeyStateFlag
{
keystateflag_ended_down = (1 << 0),
keystateflag_pressed_on_curr_frame = (1 << 1),
};
typedef struct KeyState
{
f32 delayInterval;
u32 oldHalfTransitionCount;
u32 newHalfTransitionCount;
u32 flags;
b32 endedDown;
} KeyState;
typedef struct InputBuffer
typedef struct KeyInput
{
OptimalArrayV2 *resolutionList;
v2 mouseP;
KeyState keys[keycode_count];
} InputBuffer;
} KeyInput;
typedef struct PlatformFileRead
{
@ -156,37 +140,17 @@ typedef struct PlatformFileRead
} PlatformFileRead;
// TODO(doyle): Create own custom memory allocator
#define PLATFORM_MEM_FREE_(arena, ptr, bytes) \
platform_memoryFree(arena, CAST(void *) ptr, bytes)
#define PLATFORM_MEM_FREE_(arena, ptr, bytes) platform_memoryFree(arena, CAST(void *) ptr, bytes)
// TODO(doyle): numBytes in mem free is temporary until we create custom
// allocator since we haven't put in a system to track memory usage per
// allocation
void platform_memoryFree(MemoryArena_ *arena, void *data, MemoryIndex numBytes);
void platform_memoryFree(MemoryArena_ *arena, void *data, i32 numBytes);
#define PLATFORM_MEM_ALLOC_(arena, num, type) \
CAST(type *) platform_memoryAlloc(arena, num * sizeof(type))
void *platform_memoryAlloc(MemoryArena_ *arena, MemoryIndex numBytes);
#define PLATFORM_MEM_ALLOC_(arena, num, type) CAST(type *) platform_memoryAlloc(arena, num * sizeof(type))
void *platform_memoryAlloc(MemoryArena_ *arena, i32 numBytes);
void platform_closeFileRead(MemoryArena_ *arena, PlatformFileRead *file);
i32 platform_readFileToBuffer(MemoryArena_ *arena, const char *const filePath,
PlatformFileRead *file);
/*
NOTE(doyle): The keyinput functions are technically not for "communicating to
the platform layer", but I've decided to group it here alongside the input
data definitions. Since we require that the platform layer writes directly
into our input buffer located in the game state.
*/
#define KEY_DELAY_NONE 0.0f
enum ReadKeyType
{
readkeytype_one_shot,
readkeytype_delay_repeat,
readkeytype_repeat,
readkeytype_count,
};
void platform_inputBufferProcess(InputBuffer *inputBuffer, f32 dt);
b32 platform_queryKey(KeyState *key, enum ReadKeyType readType,
f32 delayInterval);
#endif

View File

@ -3,20 +3,20 @@
#include "Dengine/Common.h"
#include "Dengine/Math.h"
#include "Dengine/AssetManager.h"
/* Forward Declaration */
typedef struct Font Font;
typedef struct AssetManager AssetManager;
typedef struct Entity Entity;
typedef struct Font Font;
typedef struct MemoryArena MemoryArena_;
typedef struct Shader Shader;
typedef struct Texture Texture;
typedef struct RenderVertex
typedef struct Vertex
{
v2 pos;
v2 texCoord;
} RenderVertex;
} Vertex;
typedef struct RenderTex
{
@ -24,177 +24,66 @@ typedef struct RenderTex
v4 texRect;
} RenderTex;
typedef u32 RenderFlags;
enum RenderFlag {
renderflag_wireframe = 0x1,
renderflag_no_texture = 0x2,
};
// TODO(doyle): Since all vertexes are built with degenerate vertices and
// in a triangle strip format, we should not split render groups by render mode
// nor either generate buffers vao/vbo based on rendermode count
enum RenderMode
typedef struct RenderQuad
{
rendermode_quad,
rendermode_polygon,
rendermode_count,
rendermode_invalid,
};
Vertex vertex[4];
} RenderQuad_;
typedef struct RenderGroup
{
b32 init;
RenderFlags flags;
enum RenderMode mode;
i32 zDepth;
// NOTE(doyle): Only for when adding singular triangles in triangle strip
// mode
b32 clockwiseWinding;
Texture *tex;
v4 color;
RenderVertex *vertexList;
Vertex *vertexList;
i32 vertexIndex;
} RenderGroup;
enum VertexBatchState {
vertexbatchstate_off,
vertexbatchstate_initial_add,
vertexbatchstate_active,
};
typedef struct Renderer
{
// rendererf
u32 shaderList[shaderlist_count];
u32 activeShaderId;
u32 vao[rendermode_count];
u32 vbo[rendermode_count];
enum VertexBatchState vertexBatchState;
i32 groupIndexForVertexBatch;
Shader *shader;
u32 vao;
u32 vbo;
i32 numVertexesInVbo;
v2 vertexNdcFactor;
v2 size;
// NOTE(doyle): Reference scale is the size chosen to have the best
// playability based on the sizes of the entity given to the system. Any
// other resoluions will be scaled through the renderer so that objects
// remain the same size in different resolutions.
f32 displayScale;
v2 referenceScale;
RenderGroup groups[128];
i32 groupsInUse;
i32 groupCapacity;
} Renderer;
void renderer_updateSize(Renderer *renderer, AssetManager *assetManager, v2 windowSize);
void renderer_init(Renderer *renderer, AssetManager *assetManager,
MemoryArena_ *persistentArena, v2 windowSize);
// TODO(doyle): Use z-index occluding for rendering
RenderTex renderer_createNullRenderTex(AssetManager *const assetManager);
// TODO(doyle): Rectangles with gradient alphas/gradient colours
// TODO(doyle): Clean up lines
// Renderer::~Renderer() { glDeleteVertexArrays(1, &this->quadVAO); }
void renderer_rect(Renderer *const renderer, Rect camera, v2 pos, v2 size,
v2 pivotPoint, Radians rotate, RenderTex *renderTex,
v4 color, i32 zDepth, RenderFlags flags);
v2 pivotPoint, f32 rotate, RenderTex renderTex, v4 color);
inline void renderer_rectOutline(Renderer *const renderer, Rect camera, v2 pos,
v2 size, f32 thickness, v2 pivotPoint,
Radians rotate, RenderTex *renderTex, v4 color,
i32 zDepth, RenderFlags flags)
{
// TODO(doyle): Pivot point is probably not correct!
// TODO(doyle): Rotation doesn't work!
ASSERT(rotate == 0);
/* Bottom line */
renderer_rect(renderer, camera, pos, V2(size.w, thickness), pivotPoint,
rotate, renderTex, color, zDepth, flags);
/* Top line */
v2 topP = v2_add(pos, V2(0, size.h - thickness));
renderer_rect(renderer, camera, topP, V2(size.w, thickness), pivotPoint,
rotate, renderTex, color, zDepth, flags);
/* Left line */
renderer_rect(renderer, camera, pos, V2(thickness, size.h), pivotPoint,
rotate, renderTex, color, zDepth, flags);
/* Right line */
v2 rightP = v2_add(pos, V2(size.w - thickness, 0));
renderer_rect(renderer, camera, rightP, V2(thickness, size.h), pivotPoint,
rotate, renderTex, color, zDepth, flags);
}
inline void renderer_rectFixed(Renderer *const renderer, v2 pos, v2 size,
v2 pivotPoint, Radians rotate,
RenderTex *renderTex, v4 color, i32 zDepth,
RenderFlags flags)
inline void renderer_staticRect(Renderer *const renderer, v2 pos, v2 size,
v2 pivotPoint, f32 rotate, RenderTex renderTex,
v4 color)
{
Rect staticCamera = {V2(0, 0), renderer->size};
renderer_rect(renderer, staticCamera, pos, size, pivotPoint, rotate,
renderTex, color, zDepth, flags);
renderTex, color);
}
inline void renderer_rectFixedOutline(Renderer *const renderer, v2 pos, v2 size,
v2 pivotPoint, f32 thickness,
Radians rotate, RenderTex *renderTex,
v4 color, i32 zDepth, RenderFlags flags)
{
Rect staticCamera = {V2(0, 0), renderer->size};
renderer_rectOutline(renderer, staticCamera, pos, size, thickness,
pivotPoint, rotate, renderTex, color, zDepth, flags);
}
void renderer_string(Renderer *const renderer, MemoryArena_ *arena,
Rect camera, Font *const font,
const char *const string, v2 pos, v2 pivotPoint,
f32 rotate, v4 color);
void renderer_polygon(Renderer *const renderer, Rect camera, v2 *polygonPoints,
i32 numPoints, v2 pivotPoint, Radians rotate,
RenderTex *renderTex, v4 color, i32 zDepth,
RenderFlags flags);
void renderer_string(Renderer *const renderer, MemoryArena_ *arena, Rect camera,
Font *const font, const char *const string, v2 pos,
v2 pivotPoint, Radians rotate, v4 color, i32 zDepth,
RenderFlags flags);
inline void renderer_stringFixed(Renderer *const renderer, MemoryArena_ *arena,
Font *const font, const char *const string,
v2 pos, v2 pivotPoint, Radians rotate,
v4 color, i32 zDepth, RenderFlags flags)
inline void renderer_staticString(Renderer *const renderer, MemoryArena_ *arena,
Font *const font, const char *const string,
v2 pos, v2 pivotPoint, f32 rotate, v4 color)
{
Rect staticCamera = {V2(0, 0), renderer->size};
renderer_string(renderer, arena, staticCamera, font, string, pos,
pivotPoint, rotate, color, zDepth, flags);
pivotPoint, rotate, color);
}
inline void renderer_stringFixedCentered(Renderer *const renderer,
MemoryArena_ *arena, Font *const font,
const char *const string, v2 pos,
v2 pivotPoint, Radians rotate,
v4 color, i32 zDepth,
enum RenderFlags flags)
{
Rect staticCamera = {V2(0, 0), renderer->size};
v2 dim = asset_fontStringDimInPixels(font, string);
v2 halfDim = v2_scale(dim, 0.5f);
pos = v2_sub(pos, halfDim);
renderer_string(renderer, arena, staticCamera, font, string, pos,
pivotPoint, rotate, color, zDepth, flags);
}
void renderer_entity(Renderer *renderer, MemoryArena_ *transientArena,
Rect camera, Entity *entity, v2 pivotPoint, Degrees rotate,
v4 color, i32 zDepth, RenderFlags flags);
void renderer_entity(Renderer *renderer, Rect camera, Entity *entity,
v2 pivotPoint, f32 rotate, v4 color);
void renderer_renderGroups(Renderer *renderer);

View File

@ -0,0 +1,21 @@
#ifndef DENGINE_SHADER_H
#define DENGINE_SHADER_H
#include "Dengine/Math.h"
#include "Dengine/OpenGL.h"
typedef struct Shader
{
GLuint id;
} Shader;
void shader_uniformSet1i(Shader *const shader, const GLchar *name,
const GLuint data);
void shader_uniformSetMat4fv(Shader *const shader, const GLchar *name,
mat4 data);
void shader_uniformSetVec4f(Shader *const shader, const GLchar *name,
v4 data);
void shader_use(const Shader *const shader);
#endif

View File

@ -0,0 +1,16 @@
#ifndef DENGINE_STRING_H
#define DENGINE_STRING_H
#include "Dengine/Common.h"
typedef struct MemoryArena MemoryArena_;
typedef char String;
i32 string_len(String *const string);
String *const string_append(MemoryArena_ *const arena, String *oldString,
String *appendString, i32 appendLen);
void string_free(MemoryArena_ *arena, String *string);
String *const string_make(MemoryArena_ *const arena, char *string);
String *const string_makeLen(MemoryArena_ *const arena, i32 len);
#endif

View File

@ -0,0 +1,39 @@
#ifndef DENGINE_TEXTURE_H
#define DENGINE_TEXTURE_H
#include "Dengine/Common.h"
#include "Dengine/OpenGL.h"
#define TARGET_TEXTURE_SIZE 1024
#define TARGET_BYTES_PER_PIXEL 4
// TODO(doyle): Look into merging into assets.h file ..
typedef struct Texture
{
// Holds the ID of the texture object, used for all texture operations to
// reference to this particlar texture
GLuint id;
// Texture image dimensions
GLuint width;
GLuint height;
// Texture Format
GLuint internalFormat; // Format of texture object
GLuint imageFormat; // Format of loaded image
// Texture configuration
GLuint wrapS; // Wrapping mode on S axis
GLuint wrapT; // Wrapping mode on T axis
// Filtering mode if texture pixels < screen pixels
GLuint filterMinification;
// Filtering mode if texture pixels > screen pixels
GLuint filterMagnification;
} Texture;
// Generates texture from image data
Texture texture_gen(const GLuint width, const GLuint height,
const GLint bytesPerPixel, const u8 *const image);
#endif

View File

@ -1,78 +0,0 @@
#ifndef DENGINE_USER_INTERFACE_H
#define DENGINE_USER_INTERFACE_H
#include "Dengine/Common.h"
#include "Dengine/Math.h"
#include "Dengine/Platform.h"
/* Forward Declaration */
typedef struct AssetManager AssetManager;
typedef struct Font Font;
typedef struct MemoryArena MemoryArena_;
typedef struct Renderer Renderer;
typedef struct GameState GameState;
enum UiType
{
uitype_button,
uitype_scrollbar,
uitype_textField,
uitype_string,
uitype_count,
};
typedef struct UiItem
{
i32 id;
char label[64];
enum UiType type;
Rect rect;
// TODO(doyle): ECS this? Not all elements need
i32 value;
i32 maxValue;
char string[80];
} UiItem;
typedef struct UiState
{
i32 uniqueId;
UiItem uiList[128];
i32 numItems;
i32 hotItem;
i32 activeItem;
i32 lastWidget;
i32 kbdItem;
enum KeyCode keyEntered;
enum KeyCode keyMod;
enum KeyCode keyChar;
} UiState;
inline i32 ui_generateId(UiState *const uiState)
{
i32 result = uiState->uniqueId++;
return result;
}
void ui_beginState(UiState *state);
void ui_endState(UiState *state, InputBuffer *input);
i32 ui_button(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager, Renderer *const renderer,
Font *const font, const InputBuffer input, const i32 id,
const Rect rect, const char *const label, i32 zDepth);
i32 ui_textfield(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager, Renderer *const renderer,
Font *const font, InputBuffer input, const i32 id,
const Rect rect, char *const string, i32 zDepth);
i32 ui_scrollbar(UiState *const uiState, AssetManager *const assetManager,
Renderer *const renderer, const InputBuffer input,
const i32 id, const Rect scrollBarRect, i32 *const value,
const i32 maxValue, i32 zDepth);
#endif

View File

@ -0,0 +1,101 @@
#ifndef DENGINE_USER_INTERFACE_H
#define DENGINE_USER_INTERFACE_H
#include "Dengine/Common.h"
#include "Dengine/Math.h"
#include "Dengine/Platform.h"
/* Forward Declaration */
typedef struct AssetManager AssetManager;
typedef struct Font Font;
typedef struct MemoryArena MemoryArena_;
typedef struct Renderer Renderer;
enum UiType
{
uitype_button,
uitype_scrollbar,
uitype_textField,
uitype_string,
uitype_count,
};
typedef struct UiItem
{
i32 id;
char label[64];
enum UiType type;
Rect rect;
// TODO(doyle): ECS this? Not all elements need
i32 value;
i32 maxValue;
char string[80];
} UiItem;
typedef struct WindowState
{
char title[64];
i32 id;
UiItem childUiItems[16];
i32 numChildUiItems;
Rect rect;
// TODO(doyle): Store this in the input data not window?
v2 prevMouseP;
b32 prevFrameWindowHeld;
b32 windowHeld;
} WindowState;
typedef struct UiState
{
i32 uniqueId;
UiItem uiList[128];
i32 numItems;
i32 hotItem;
i32 activeItem;
i32 lastWidget;
i32 kbdItem;
enum KeyCode keyEntered;
enum KeyCode keyMod;
enum KeyCode keyChar;
WindowState statWindow;
WindowState debugWindow;
} UiState;
inline i32 userInterface_generateId(UiState *const uiState)
{
i32 result = uiState->uniqueId++;
return result;
}
i32 userInterface_button(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager,
Renderer *const renderer, Font *const font,
const KeyInput input, const i32 id, const Rect rect,
const char *const label);
i32 userInterface_textField(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager,
Renderer *const renderer, Font *const font,
KeyInput input, const i32 id, const Rect rect,
char *const string);
i32 userInterface_scrollbar(UiState *const uiState,
AssetManager *const assetManager,
Renderer *const renderer, const KeyInput input,
const i32 id, const Rect scrollBarRect,
i32 *const value, const i32 maxValue);
i32 userInterface_window(UiState *const uiState, MemoryArena_ *const arena,
AssetManager *const assetManager,
Renderer *const renderer, Font *const font,
const KeyInput input, WindowState *window);
#endif

View File

@ -0,0 +1,109 @@
#ifndef WORLDTRAVELLER_GAME_H
#define WORLDTRAVELLER_GAME_H
#include "Dengine/AssetManager.h"
#include "Dengine/Audio.h"
#include "Dengine/Common.h"
#include "Dengine/Math.h"
#include "Dengine/MemoryArena.h"
#include "Dengine/Platform.h"
#include "Dengine/Renderer.h"
#include "Dengine/UserInterface.h"
#define NUM_KEYS 1024
#define METERS_TO_PIXEL 240
/* Forward declaration */
typedef struct Entity Entity;
enum EventType
{
eventtype_null = 0,
eventtype_start_attack,
eventtype_end_attack,
eventtype_start_anim,
eventtype_end_anim,
eventtype_entity_died,
eventtype_count,
};
typedef struct Event
{
enum EventType type;
void *data;
} Event;
typedef struct EventQueue
{
Event queue[1024];
i32 numEvents;
} EventQueue;
enum RectBaseline
{
rectbaseline_top,
rectbaseline_topLeft,
rectbaseline_topRight,
rectbaseline_bottom,
rectbaseline_bottomRight,
rectbaseline_bottomLeft,
rectbaseline_left,
rectbaseline_right,
rectbaseline_center,
rectbaseline_count,
};
typedef struct Config
{
b32 playWorldAudio;
b32 showStatMenu;
b32 showDebugDisplay;
} Config;
typedef struct World
{
Entity *entities;
i32 maxEntities;
b32 *entityIdInBattle;
i32 numEntitiesInBattle;
i32 cameraFollowingId;
v2 cameraPos; // In pixels
v4 bounds; // In pixels
i32 heroId;
i32 freeEntityIndex;
u32 uniqueIdAccumulator;
Entity *soundscape;
} World;
typedef struct GameState
{
Memory *memory;
MemoryArena_ arena_;
MemoryArena_ transientArena;
enum State state;
KeyInput input;
v2 mouse;
Renderer renderer;
World world[4];
i32 currWorldIndex;
i32 tileSize;
AssetManager assetManager;
AudioManager audioManager;
Config config;
UiState uiState;
EventQueue eventQueue;
} GameState;
void worldTraveller_gameInit(GameState *state, v2 windowSize, Memory *memory);
void worldTraveller_gameUpdateAndRender(GameState *state, f32 dt);
void worldTraveller_registerEvent(EventQueue *eventQueue, enum EventType type,
void *data);
#endif