summary refs log tree commit diff
path: root/src/EntityRenderers.c
diff options
context:
space:
mode:
authorWlodekM <[email protected]>2024-06-16 10:35:45 +0300
committerWlodekM <[email protected]>2024-06-16 10:35:45 +0300
commitabef6da56913f1c55528103e60a50451a39628b1 (patch)
treeb3c8092471ecbb73e568cd0d336efa0e7871ee8d /src/EntityRenderers.c
initial commit
Diffstat (limited to 'src/EntityRenderers.c')
-rw-r--r--src/EntityRenderers.c478
1 files changed, 478 insertions, 0 deletions
diff --git a/src/EntityRenderers.c b/src/EntityRenderers.c
new file mode 100644
index 0000000..e45d681
--- /dev/null
+++ b/src/EntityRenderers.c
@@ -0,0 +1,478 @@
+#include "EntityRenderers.h"
+#include "Entity.h"
+#include "Bitmap.h"
+#include "Block.h"
+#include "Event.h"
+#include "ExtMath.h"
+#include "Funcs.h"
+#include "Game.h"
+#include "Graphics.h"
+#include "Model.h"
+#include "World.h"
+#include "Particle.h"
+#include "Drawer2D.h"
+
+/*########################################################################################################################*
+*------------------------------------------------------Entity Shadow------------------------------------------------------*
+*#########################################################################################################################*/
+static cc_bool shadows_boundTex;
+static GfxResourceID shadows_VB;
+static GfxResourceID shadows_tex;
+static float shadow_radius, shadow_uvScale;
+struct ShadowData { float y; BlockID block; cc_uint8 alpha; };
+
+/* Circle shadows extend at most 4 blocks vertically */
+#define SHADOW_MAX_RANGE 4 
+/* Circle shadows on blocks underneath the top block can be chopped up into at most 4 pieces */
+#define SHADOW_MAX_PER_SUB_BLOCK (4 * 4) 
+/* Circle shadows use at most:
+   - 4 vertices for top most block
+   - MAX_PER_SUB_BLOCK for everyblock underneath the top block */
+#define SHADOW_MAX_PER_COLUMN (4 + SHADOW_MAX_PER_SUB_BLOCK * (SHADOW_MAX_RANGE - 1))
+/* Circle shadows may be split across (x,z), (x,z+1), (x+1,z), (x+1,z+1) */
+#define SHADOW_MAX_VERTS 4 * SHADOW_MAX_PER_COLUMN
+
+static cc_bool lequal(float a, float b) { return a < b || Math_AbsF(a - b) < 0.001f; }
+static void EntityShadow_DrawCoords(struct VertexTextured** vertices, struct Entity* e, struct ShadowData* data, float x1, float z1, float x2, float z2) {
+	PackedCol col;
+	struct VertexTextured* v;
+	Vec3 cen;
+	float u1, v1, u2, v2;
+
+	if (lequal(x2, x1) || lequal(z2, z1)) return;
+	cen = e->Position;
+
+	u1 = (x1 - cen.x) * shadow_uvScale + 0.5f;
+	v1 = (z1 - cen.z) * shadow_uvScale + 0.5f;
+	u2 = (x2 - cen.x) * shadow_uvScale + 0.5f;
+	v2 = (z2 - cen.z) * shadow_uvScale + 0.5f;
+	if (u2 <= 0.0f || v2 <= 0.0f || u1 >= 1.0f || v1 >= 1.0f) return;
+
+	x1 = max(x1, cen.x - shadow_radius); u1 = u1 >= 0.0f ? u1 : 0.0f;
+	z1 = max(z1, cen.z - shadow_radius); v1 = v1 >= 0.0f ? v1 : 0.0f;
+	x2 = min(x2, cen.x + shadow_radius); u2 = u2 <= 1.0f ? u2 : 1.0f;
+	z2 = min(z2, cen.z + shadow_radius); v2 = v2 <= 1.0f ? v2 : 1.0f;
+
+	v   = *vertices;
+	col = PackedCol_Make(255, 255, 255, data->alpha);
+
+	v->x = x1; v->y = data->y; v->z = z1; v->Col = col; v->U = u1; v->V = v1; v++;
+	v->x = x2; v->y = data->y; v->z = z1; v->Col = col; v->U = u2; v->V = v1; v++;
+	v->x = x2; v->y = data->y; v->z = z2; v->Col = col; v->U = u2; v->V = v2; v++;
+	v->x = x1; v->y = data->y; v->z = z2; v->Col = col; v->U = u1; v->V = v2; v++;
+
+	*vertices = v;
+}
+
+static void EntityShadow_DrawSquareShadow(struct VertexTextured** vertices, float y, float x, float z) {
+	PackedCol col = PackedCol_Make(255, 255, 255, 220);
+	float     uv1 = 63/128.0f, uv2 = 64/128.0f;
+	struct VertexTextured* v = *vertices;
+
+	v->x = x;     v->y = y; v->z = z;     v->Col = col; v->U = uv1; v->V = uv1; v++;
+	v->x = x + 1; v->y = y; v->z = z;     v->Col = col; v->U = uv2; v->V = uv1; v++;
+	v->x = x + 1; v->y = y; v->z = z + 1; v->Col = col; v->U = uv2; v->V = uv2; v++;
+	v->x = x;     v->y = y; v->z = z + 1; v->Col = col; v->U = uv1; v->V = uv2; v++;
+
+	*vertices = v;
+}
+
+/* Shadow may extend down multiple blocks vertically */
+/* If so, shadow on a block must be 'chopped up' to avoid a shadow underneath block above this one */
+static void EntityShadow_DrawCircle(struct VertexTextured** vertices, struct Entity* e, struct ShadowData* data, float x, float z) {
+	Vec3 min, max, nMin, nMax;
+	int i;
+	x = (float)Math_Floor(x); z = (float)Math_Floor(z);
+	min = Blocks.MinBB[data[0].block]; max = Blocks.MaxBB[data[0].block];
+
+	EntityShadow_DrawCoords(vertices, e, &data[0], x + min.x, z + min.z, x + max.x, z + max.z);
+	for (i = 1; i < 4; i++) 
+	{
+		if (data[i].block == BLOCK_AIR) return;
+		nMin = Blocks.MinBB[data[i].block]; nMax = Blocks.MaxBB[data[i].block];
+
+		EntityShadow_DrawCoords(vertices, e, &data[i], x +  min.x, z + nMin.z, x +  max.x, z +  min.z);
+		EntityShadow_DrawCoords(vertices, e, &data[i], x +  min.x, z +  max.z, x +  max.x, z + nMax.z);
+
+		EntityShadow_DrawCoords(vertices, e, &data[i], x + nMin.x, z + nMin.z, x +  min.x, z + nMax.z);
+		EntityShadow_DrawCoords(vertices, e, &data[i], x +  max.x, z + nMin.z, x + nMax.x, z + nMax.z);
+		min = nMin; max = nMax;
+	}
+}
+
+static void EntityShadow_CalcAlpha(float playerY, struct ShadowData* data) {
+	float height = playerY - data->y;
+	if (height <= 6.0f) {
+		data->alpha = (cc_uint8)(160 - 160 * height / 6.0f);
+		data->y     += 1.0f / 64.0f; 
+		return;
+	}
+
+	data->alpha = 0;
+	if (height <= 16.0f)      data->y += 1.0f / 64.0f;
+	else if (height <= 32.0f) data->y += 1.0f / 16.0f;
+	else if (height <= 96.0f) data->y += 1.0f / 8.0f;
+	else data->y += 1.0f / 4.0f;
+}
+
+static cc_bool EntityShadow_GetBlocks(struct Entity* e, int x, int y, int z, struct ShadowData* data) {
+	struct ShadowData zeroData = { 0 };
+	struct ShadowData* cur;
+	float posY, topY;
+	cc_bool outside;
+	BlockID block; cc_uint8 draw;
+	int i;
+
+	for (i = 0; i < 4; i++) { data[i] = zeroData; }
+	cur     = data;
+	posY    = e->Position.y;
+	outside = !World_ContainsXZ(x, z);
+
+	for (i = 0; y >= 0 && i < 4; y--) 
+	{
+		if (!outside) {
+			block = World_GetBlock(x, y, z);
+		} else if (y == Env.EdgeHeight - 1) {
+			block = Blocks.Draw[Env.EdgeBlock] == DRAW_GAS  ? BLOCK_AIR : BLOCK_BEDROCK;
+		} else if (y == Env_SidesHeight - 1) {
+			block = Blocks.Draw[Env.SidesBlock] == DRAW_GAS ? BLOCK_AIR : BLOCK_BEDROCK;
+		} else {
+			block = BLOCK_AIR;
+		}
+
+		draw = Blocks.Draw[block];
+		if (draw == DRAW_GAS || draw == DRAW_SPRITE || Blocks.IsLiquid[block]) continue;
+		topY = y + Blocks.MaxBB[block].y;
+		if (topY >= posY + 0.01f) continue;
+
+		cur->block = block; cur->y = topY;
+		EntityShadow_CalcAlpha(posY, cur);
+		i++; cur++;
+
+		/* Check if the casted shadow will continue on further down. */
+		if (Blocks.MinBB[block].x == 0.0f && Blocks.MaxBB[block].x == 1.0f &&
+			Blocks.MinBB[block].z == 0.0f && Blocks.MaxBB[block].z == 1.0f) return true;
+	}
+
+	if (i < 4) {
+		cur->block = Env.EdgeBlock; cur->y = 0.0f;
+		EntityShadow_CalcAlpha(posY, cur);
+		i++; cur++;
+	}
+	return true;
+}
+
+static void EntityShadow_Draw(struct Entity* e) {
+	struct VertexTextured vertices[128]; /* TODO this is less than maxVertes */
+	struct VertexTextured* ptr;
+	struct ShadowData data[4];
+	Vec3 pos;
+	float radius;
+	int y, count;
+	int x1, z1, x2, z2;
+
+	pos = e->Position;
+	if (pos.y < 0.0f) return;
+	y = min((int)pos.y, World.MaxY);
+
+	radius = 7.0f * min(e->ModelScale.y, 1.0f) * e->Model->shadowScale;
+	shadow_radius  = radius / 16.0f;
+	shadow_uvScale = 16.0f / (radius * 2.0f);
+
+	ptr = vertices;
+	if (Entities.ShadowsMode == SHADOW_MODE_SNAP_TO_BLOCK) {
+		x1 = Math_Floor(pos.x); z1 = Math_Floor(pos.z);
+		if (!EntityShadow_GetBlocks(e, x1, y, z1, data)) return;
+
+		EntityShadow_DrawSquareShadow(&ptr, data[0].y, x1, z1);
+	} else {
+		x1 = Math_Floor(pos.x - shadow_radius); z1 = Math_Floor(pos.z - shadow_radius);
+		x2 = Math_Floor(pos.x + shadow_radius); z2 = Math_Floor(pos.z + shadow_radius);
+
+		if (EntityShadow_GetBlocks(e, x1, y, z1, data) && data[0].alpha > 0) {
+			EntityShadow_DrawCircle(&ptr, e, data, (float)x1, (float)z1);
+		}
+		if (x1 != x2 && EntityShadow_GetBlocks(e, x2, y, z1, data) && data[0].alpha > 0) {
+			EntityShadow_DrawCircle(&ptr, e, data, (float)x2, (float)z1);
+		}
+		if (z1 != z2 && EntityShadow_GetBlocks(e, x1, y, z2, data) && data[0].alpha > 0) {
+			EntityShadow_DrawCircle(&ptr, e, data, (float)x1, (float)z2);
+		}
+		if (x1 != x2 && z1 != z2 && EntityShadow_GetBlocks(e, x2, y, z2, data) && data[0].alpha > 0) {
+			EntityShadow_DrawCircle(&ptr, e, data, (float)x2, (float)z2);
+		}
+	}
+
+	if (ptr == vertices) return;
+
+	if (!shadows_boundTex) {
+		Gfx_BindTexture(shadows_tex);
+		shadows_boundTex = true;
+	}
+
+	count = (int)(ptr - vertices);
+	Gfx_SetDynamicVbData(shadows_VB, vertices, count);
+	Gfx_DrawVb_IndexedTris(count);
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------Entity Shadows------------------------------------------------------*
+*#########################################################################################################################*/
+#define sh_size 128
+#define sh_half (sh_size / 2)
+
+static void EntityShadows_MakeTexture(void) {
+	BitmapCol pixels[sh_size * sh_size];
+	BitmapCol color = BitmapCol_Make(0, 0, 0, 200);
+	struct Bitmap bmp;
+	cc_uint32 x, y;
+
+	Bitmap_Init(bmp, sh_size, sh_size, pixels);
+	for (y = 0; y < sh_size; y++) {
+		BitmapCol* row = Bitmap_GetRow(&bmp, y);
+
+		for (x = 0; x < sh_size; x++) {
+			float dist =
+				(sh_half - (x + 0.5f)) * (sh_half - (x + 0.5f)) +
+				(sh_half - (y + 0.5f)) * (sh_half - (y + 0.5f));
+			row[x] = dist < sh_half * sh_half ? color : 0;
+		}
+	}
+	shadows_tex = Gfx_CreateTexture(&bmp, 0, false);
+}
+
+void EntityShadows_Render(void) {
+	struct Entity* e;
+	int i;
+	if (Entities.ShadowsMode == SHADOW_MODE_NONE) return;
+
+	shadows_boundTex = false;
+	if (!shadows_tex) 
+		EntityShadows_MakeTexture();
+	if (!shadows_VB)
+		shadows_VB = Gfx_CreateDynamicVb(VERTEX_FORMAT_TEXTURED, SHADOW_MAX_VERTS);
+
+	Gfx_SetAlphaArgBlend(true);
+	Gfx_SetDepthWrite(false);
+	Gfx_SetAlphaBlending(true);
+
+	Gfx_SetVertexFormat(VERTEX_FORMAT_TEXTURED);
+	EntityShadow_Draw(&Entities.CurPlayer->Base);
+
+	if (Entities.ShadowsMode == SHADOW_MODE_CIRCLE_ALL) {	
+		for (i = 0; i < ENTITIES_MAX_COUNT; i++) 
+		{
+			e = Entities.List[i];
+			if (!e || !e->ShouldRender || e == &Entities.CurPlayer->Base) continue;
+			EntityShadow_Draw(e);
+		}
+	}
+
+	Gfx_SetAlphaArgBlend(false);
+	Gfx_SetDepthWrite(true);
+	Gfx_SetAlphaBlending(false);
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------Entity nametag------------------------------------------------------*
+*#########################################################################################################################*/
+static GfxResourceID names_VB;
+#define NAME_IS_EMPTY -30000
+#define NAME_OFFSET 3 /* offset of back layer of name above an entity */
+
+static void MakeNameTexture(struct Entity* e) {
+	cc_string colorlessName; char colorlessBuffer[STRING_SIZE];
+	BitmapCol shadowColor = BitmapCol_Make(80, 80, 80, 255);
+	BitmapCol origWhiteColor;
+
+	struct DrawTextArgs args;
+	struct FontDesc font;
+	struct Context2D ctx;
+	int width, height;
+	cc_string name;
+
+	/* Names are always drawn using default.png font */
+	Font_MakeBitmapped(&font, 24, FONT_FLAGS_NONE);
+	/* Don't want DPI scaling or padding */
+	font.size = 24; font.height = 24;
+
+	name = String_FromRawArray(e->NameRaw);
+	DrawTextArgs_Make(&args, &name, &font, false);
+	width = Drawer2D_TextWidth(&args);
+
+	if (!width) {
+		e->NameTex.ID = 0;
+		e->NameTex.x  = NAME_IS_EMPTY;
+	} else {
+		String_InitArray(colorlessName, colorlessBuffer);
+		width  += NAME_OFFSET; 
+		height = Drawer2D_TextHeight(&args) + NAME_OFFSET;
+
+		Context2D_Alloc(&ctx, width, height);
+		{
+			origWhiteColor = Drawer2D.Colors['f'];
+
+			Drawer2D.Colors['f'] = shadowColor;
+			Drawer2D_WithoutColors(&colorlessName, &name);
+			args.text = colorlessName;
+			Context2D_DrawText(&ctx, &args, NAME_OFFSET, NAME_OFFSET);
+
+			Drawer2D.Colors['f'] = origWhiteColor;
+			args.text = name;
+			Context2D_DrawText(&ctx, &args, 0, 0);
+		}
+		Context2D_MakeTexture(&e->NameTex, &ctx);
+		Context2D_Free(&ctx);
+	}
+}
+
+static void DrawName(struct Entity* e) {
+	struct VertexTextured* vertices;
+	struct Model* model;
+	struct Matrix mat;
+	Vec3 pos;
+	float scale;
+	Vec2 size;
+
+	if (!e->VTABLE->ShouldRenderName(e)) return;
+	if (e->NameTex.x == NAME_IS_EMPTY)   return;
+	if (!e->NameTex.ID) MakeNameTexture(e);
+	Gfx_BindTexture(e->NameTex.ID);
+
+	if (!names_VB)
+		names_VB = Gfx_CreateDynamicVb(VERTEX_FORMAT_TEXTURED, 4);
+
+	model = e->Model;
+	Vec3_TransformY(&pos, model->GetNameY(e), &e->Transform);
+
+	scale  = e->ModelScale.y;
+	scale  = scale > 1.0f ? (1.0f/70.0f) : (scale/70.0f);
+	size.x = e->NameTex.width * scale; size.y = e->NameTex.height * scale;
+
+	if (Entities.NamesMode == NAME_MODE_ALL_UNSCALED && Entities.CurPlayer->Hacks.CanSeeAllNames) {
+		Matrix_Mul(&mat, &Gfx.View, &Gfx.Projection); /* TODO: This mul is slow, avoid it */
+		/* Get W component of transformed position */
+		scale = pos.x * mat.row1.w + pos.y * mat.row2.w + pos.z * mat.row3.w + mat.row4.w;
+		size.x *= scale * 0.2f; size.y *= scale * 0.2f;
+	}
+
+	Gfx_SetVertexFormat(VERTEX_FORMAT_TEXTURED);
+
+	vertices = (struct VertexTextured*)Gfx_LockDynamicVb(names_VB, VERTEX_FORMAT_TEXTURED, 4);
+	Particle_DoRender(&size, &pos, &e->NameTex.uv, PACKEDCOL_WHITE, vertices);
+	Gfx_UnlockDynamicVb(names_VB);
+
+	Gfx_DrawVb_IndexedTris(4);
+}
+
+void EntityNames_Delete(struct Entity* e) {
+	Gfx_DeleteTexture(&e->NameTex.ID);
+	e->NameTex.x = 0; /* X is used as an 'empty name' flag */
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------------Names rendering-----------------------------------------------------*
+*#########################################################################################################################*/
+static int closestEntityId;
+
+void EntityNames_Render(void) {
+	struct LocalPlayer* p = Entities.CurPlayer;
+	cc_bool hadFog;
+	int i;
+
+	if (Entities.NamesMode == NAME_MODE_NONE) return;
+	closestEntityId = Entities_GetClosest(&p->Base);
+	if (!p->Hacks.CanSeeAllNames || Entities.NamesMode != NAME_MODE_ALL) return;
+
+	Gfx_SetAlphaTest(true);
+	hadFog = Gfx_GetFog();
+	if (hadFog) Gfx_SetFog(false);
+
+	for (i = 0; i < ENTITIES_MAX_COUNT; i++) 
+	{
+		if (!Entities.List[i]) continue;
+		if (i != closestEntityId) DrawName(Entities.List[i]);
+	}
+
+	Gfx_SetAlphaTest(false);
+	if (hadFog) Gfx_SetFog(true);
+}
+
+void EntityNames_RenderHovered(void) {
+	struct LocalPlayer* p = Entities.CurPlayer;
+	struct Entity* e;
+	cc_bool allNames, hadFog;
+	cc_bool setupState = false;
+	int i;
+
+	if (Entities.NamesMode == NAME_MODE_NONE) return;
+	allNames = !(Entities.NamesMode == NAME_MODE_HOVERED || Entities.NamesMode == NAME_MODE_ALL) 
+		&& p->Hacks.CanSeeAllNames;
+
+	for (i = 0; i < ENTITIES_MAX_COUNT; i++) 
+	{
+		e = Entities.List[i];
+		if (!e || e == &p->Base) continue;
+		if (!allNames && i != closestEntityId) continue;
+
+		/* Only alter the GPU state when actually necessary */
+		if (!setupState) {
+			Gfx_SetAlphaTest(true);
+			Gfx_SetDepthTest(false);
+			Gfx_SetDepthWrite(false);
+
+			setupState = true;
+			hadFog = Gfx_GetFog();
+			if (hadFog) Gfx_SetFog(false);
+		}
+		DrawName(e);
+	}
+
+	if (!setupState) return;
+	Gfx_SetAlphaTest(false);
+	Gfx_SetDepthTest(true);
+	Gfx_SetDepthWrite(true);
+	if (hadFog) Gfx_SetFog(true);
+}
+
+static void DeleteAllNameTextures(void) {
+	int i;
+	for (i = 0; i < ENTITIES_MAX_COUNT; i++) 
+	{
+		if (!Entities.List[i]) continue;
+		EntityNames_Delete(Entities.List[i]);
+	}
+}
+
+static void EntityNames_ChatFontChanged(void* obj) {
+	DeleteAllNameTextures();
+}
+
+
+/*########################################################################################################################*
+*-----------------------------------------------Entity renderers component------------------------------------------------*
+*#########################################################################################################################*/
+static void EntityRenderers_ContextLost(void* obj) {
+	Gfx_DeleteTexture(&shadows_tex);
+	Gfx_DeleteDynamicVb(&shadows_VB);
+	
+	Gfx_DeleteDynamicVb(&names_VB);
+	DeleteAllNameTextures();
+}
+
+static void EntityRenderers_Init(void) {
+	Event_Register_(&GfxEvents.ContextLost,  NULL, EntityRenderers_ContextLost);
+	Event_Register_(&ChatEvents.FontChanged, NULL, EntityNames_ChatFontChanged);
+}
+
+static void EntityRenderers_Free(void) {
+	EntityRenderers_ContextLost(NULL);
+}
+
+struct IGameComponent EntityRenderers_Component = {
+	EntityRenderers_Init,  /* Init  */
+	EntityRenderers_Free   /* Free  */
+};