summary refs log tree commit diff
path: root/src/SelectionBox.c
blob: c950ecd63a4d9fdbcc070c55b209c3b944f208f6 (plain)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
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 */
};