summary refs log tree commit diff
path: root/src/SelectionBox.c
diff options
context:
space:
mode:
Diffstat (limited to 'src/SelectionBox.c')
-rw-r--r--src/SelectionBox.c233
1 files changed, 233 insertions, 0 deletions
diff --git a/src/SelectionBox.c b/src/SelectionBox.c
new file mode 100644
index 0000000..c950ecd
--- /dev/null
+++ b/src/SelectionBox.c
@@ -0,0 +1,233 @@
+#include "SelectionBox.h"
+#include "ExtMath.h"
+#include "Graphics.h"
+#include "Event.h"
+#include "Funcs.h"
+#include "Game.h"
+#include "Camera.h"
+
+#ifdef CC_BUILD_NETWORKING
+/* Data for a selection box. */
+struct SelectionBox {
+	Vec3 p0, p1;
+	PackedCol color;
+	float minDist, maxDist;
+};
+
+#define X0 0
+#define X1 1
+#define Y0 0
+#define Y1 2
+#define Z0 0
+#define Z1 4
+
+#define SelectionBox_Y(y) X0|y |Z0, X0|y |Z1, X1|y |Z1, X1|y |Z0,
+#define SelectionBox_Z(z) X0|Y0|z , X0|Y1|z , X1|Y1|z , X1|Y0|z ,
+#define SelectionBox_X(x) x |Y0|Z0, x |Y1|Z0, x |Y1|Z1, x |Y0|Z1,
+
+static void BuildFaces(struct SelectionBox* box, struct VertexColoured* v) {
+	static const cc_uint8 faceIndices[24] = {
+		SelectionBox_Y(Y0) SelectionBox_Y(Y1) /* YMin, YMax */
+		SelectionBox_Z(Z0) SelectionBox_Z(Z1) /* ZMin, ZMax */
+		SelectionBox_X(X0) SelectionBox_X(X1) /* XMin, XMax */
+	};
+	PackedCol color;
+	int i, flags;
+
+	float offset = box->minDist < 32.0f * 32.0f ? (1/32.0f) : (1/16.0f);
+	Vec3 coords[2];
+	Vec3_Add1(&coords[0], &box->p0, -offset);
+	Vec3_Add1(&coords[1], &box->p1,  offset);
+
+	color = box->color;
+	for (i = 0; i < Array_Elems(faceIndices); i++, v++) {
+		flags  = faceIndices[i];
+		v->x   = coords[(flags     ) & 1].x;
+		v->y   = coords[(flags >> 1) & 1].y;
+		v->z   = coords[(flags >> 2)    ].z;
+		v->Col = color;
+	}
+}
+
+static void BuildEdges(struct SelectionBox* box, struct VertexColoured* v) {
+	static const cc_uint8 edgeIndices[24] = {
+		X0|Y0|Z0, X1|Y0|Z0,  X1|Y0|Z0, X1|Y0|Z1,  X1|Y0|Z1, X0|Y0|Z1,  X0|Y0|Z1, X0|Y0|Z0, /* YMin */
+		X0|Y1|Z0, X1|Y1|Z0,  X1|Y1|Z0, X1|Y1|Z1,  X1|Y1|Z1, X0|Y1|Z1,  X0|Y1|Z1, X0|Y1|Z0, /* YMax */
+		X0|Y0|Z0, X0|Y1|Z0,  X1|Y0|Z0, X1|Y1|Z0,  X1|Y0|Z1, X1|Y1|Z1,  X0|Y0|Z1, X0|Y1|Z1, /* X/Z  */
+	};
+	PackedCol color;
+	int i, flags;
+
+	float offset = box->minDist < 32.0f * 32.0f ? (1/32.0f) : (1/16.0f);
+	Vec3 coords[2];
+	Vec3_Add1(&coords[0], &box->p0, -offset);
+	Vec3_Add1(&coords[1], &box->p1,  offset);
+
+	color = box->color;
+	/* invert R/G/B for surrounding line */
+	color = (color & PACKEDCOL_A_MASK) | (~color & PACKEDCOL_RGB_MASK);
+
+	for (i = 0; i < Array_Elems(edgeIndices); i++, v++) {
+		flags  = edgeIndices[i];
+		v->x   = coords[(flags     ) & 1].x;
+		v->y   = coords[(flags >> 1) & 1].y;
+		v->z   = coords[(flags >> 2)    ].z;
+		v->Col = color;
+	}
+}
+
+static int CompareDists(struct SelectionBox* a, struct SelectionBox* b) {
+	float aDist, bDist;
+	if (a->minDist == b->minDist) {
+		aDist = a->maxDist; bDist = b->maxDist;
+	} else {
+		aDist = a->minDist; bDist = b->minDist;
+	}
+
+	/* Reversed comparison order result, because we need to render back to front for alpha blending */
+	if (aDist < bDist) return 1;
+	if (aDist > bDist) return -1;
+	return 0;
+}
+
+static void CalcDists(struct SelectionBox* box, Vec3 P) {
+	float dx0 = (P.x - box->p0.x) * (P.x - box->p0.x), dx1 = (P.x - box->p1.x) * (P.x - box->p1.x);
+	float dy0 = (P.y - box->p0.y) * (P.y - box->p0.y), dy1 = (P.y - box->p1.y) * (P.y - box->p1.y);
+	float dz0 = (P.z - box->p0.z) * (P.z - box->p0.z), dz1 = (P.z - box->p1.z) * (P.z - box->p1.z);
+
+	/* Distance to closest and furthest of the eight box corners */
+	box->minDist = min(dx0, dx1) + min(dy0, dy1) + min(dz0, dz1);
+	box->maxDist = max(dx0, dx1) + max(dy0, dy1) + max(dz0, dz1);
+}
+
+
+#define SELECTIONS_MAX 256
+#define SELECTIONS_VERTICES 24
+#define SELECTIONS_MAX_VERTICES SELECTIONS_MAX * SELECTIONS_VERTICES
+
+static int selections_count;
+static struct SelectionBox selections_list[SELECTIONS_MAX];
+static cc_uint8 selections_ids[SELECTIONS_MAX];
+static GfxResourceID selections_VB, selections_LineVB;
+
+void Selections_Add(cc_uint8 id, const IVec3* p1, const IVec3* p2, PackedCol color) {
+	struct SelectionBox sel;
+	IVec3_ToVec3(&sel.p0, p1);
+	IVec3_ToVec3(&sel.p1, p2);
+	sel.color = color;
+
+	Selections_Remove(id);
+	selections_list[selections_count] = sel;
+	selections_ids[selections_count]  = id;
+	selections_count++;
+}
+
+void Selections_Remove(cc_uint8 id) {
+	int i;
+	for (i = 0; i < selections_count; i++) {
+		if (selections_ids[i] != id) continue;
+
+		for (; i < selections_count - 1; i++) {
+			selections_list[i] = selections_list[i + 1];
+			selections_ids[i]  = selections_ids[i + 1];
+		}
+
+		selections_count--;
+		return;
+	}
+}
+
+static void Selections_ContextLost(void* obj) {
+	Gfx_DeleteDynamicVb(&selections_VB);
+	Gfx_DeleteDynamicVb(&selections_LineVB);
+}
+
+static void AllocateVertexBuffers(void) {
+	selections_VB     = Gfx_CreateDynamicVb(VERTEX_FORMAT_COLOURED, SELECTIONS_MAX_VERTICES);
+	selections_LineVB = Gfx_CreateDynamicVb(VERTEX_FORMAT_COLOURED, SELECTIONS_MAX_VERTICES);
+}
+
+static void Selections_QuickSort(int left, int right) {
+	cc_uint8* values = selections_ids; cc_uint8 value;
+	struct SelectionBox* keys = selections_list; struct SelectionBox key;
+
+	while (left < right) {
+		int i = left, j = right;
+		struct SelectionBox* pivot = &keys[(i + j) >> 1];
+
+		/* partition the list */
+		while (i <= j) {
+			while (CompareDists(pivot, &keys[i]) > 0) i++;
+			while (CompareDists(pivot, &keys[j]) < 0) j--;
+			QuickSort_Swap_KV_Maybe();
+		}
+		/* recurse into the smaller subset */
+		QuickSort_Recurse(Selections_QuickSort)
+	}
+}
+
+void Selections_Render(void) {
+	struct VertexColoured* data;
+	Vec3 cameraPos;
+	int i, count;
+	if (!selections_count) return;
+
+	/* TODO: Proper selection box sorting. But this is very difficult because
+	   we can have boxes within boxes, intersecting boxes, etc. Probably not worth it. */
+	cameraPos = Camera.CurrentPos;
+	for (i = 0; i < selections_count; i++) {
+		CalcDists(&selections_list[i], cameraPos);
+	}
+	Selections_QuickSort(0, selections_count - 1);
+
+	/* lazy init as most servers don't use this */
+	if (!selections_VB) AllocateVertexBuffers();
+
+	count = selections_count * SELECTIONS_VERTICES;
+	Gfx_SetVertexFormat(VERTEX_FORMAT_COLOURED);
+
+	data = (struct VertexColoured*)Gfx_LockDynamicVb(selections_LineVB, 
+										VERTEX_FORMAT_COLOURED, count);
+	for (i = 0; i < selections_count; i++, data += SELECTIONS_VERTICES) {
+		BuildEdges(&selections_list[i], data);
+	}
+	Gfx_UnlockDynamicVb(selections_LineVB);
+	Gfx_DrawVb_Lines(count);
+
+	data = (struct VertexColoured*)Gfx_LockDynamicVb(selections_VB, 
+										VERTEX_FORMAT_COLOURED, count);
+	for (i = 0; i < selections_count; i++, data += SELECTIONS_VERTICES) {
+		BuildFaces(&selections_list[i], data);
+	}
+	Gfx_UnlockDynamicVb(selections_VB);
+
+	Gfx_SetDepthWrite(false);
+	Gfx_SetAlphaBlending(true);
+	Gfx_DrawVb_IndexedTris(count);
+	Gfx_SetDepthWrite(true);
+	Gfx_SetAlphaBlending(false);
+}
+#else
+static int selections_count;
+void Selections_Render(void) { }
+static void Selections_ContextLost(void* obj) { }
+#endif
+
+
+/*########################################################################################################################*
+*--------------------------------------------------Selections component---------------------------------------------------*
+*#########################################################################################################################*/
+static void OnInit(void) {
+	Event_Register_(&GfxEvents.ContextLost, NULL, Selections_ContextLost);
+}
+
+static void OnReset(void) { selections_count = 0; }
+
+static void OnFree(void) { Selections_ContextLost(NULL); }
+
+struct IGameComponent Selections_Component = {
+	OnInit,  /* Init  */
+	OnFree,  /* Free  */
+	OnReset, /* Reset */
+	OnReset  /* OnNewMap */
+};