1 /**
2  * @file
3  * @brief This node allow to include a 3D-model into the GUI.
4  * It provide a way to create composite models, check
5  * [[How to script UI#How to create a composite model]]. We call it "main model"
6  * when a model is a child node of a non model node, and "submodel" when the node
7  * is a child node of a model node.
8  */
9 
10 /*
11 Copyright (C) 2002-2013 UFO: Alien Invasion.
12 
13 This program is free software; you can redistribute it and/or
14 modify it under the terms of the GNU General Public License
15 as published by the Free Software Foundation; either version 2
16 of the License, or (at your option) any later version.
17 
18 This program is distributed in the hope that it will be useful,
19 but WITHOUT ANY WARRANTY; without even the implied warranty of
20 MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.
21 
22 See the GNU General Public License for more details.
23 
24 You should have received a copy of the GNU General Public License
25 along with this program; if not, write to the Free Software
26 Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA  02111-1307, USA.
27 
28 */
29 
30 #include "../ui_main.h"
31 #include "../ui_internal.h"
32 #include "../ui_nodes.h"
33 #include "../ui_parse.h"
34 #include "../ui_input.h"
35 #include "../ui_render.h"
36 #include "../ui_internal.h"
37 #include "ui_node_model.h"
38 #include "ui_node_abstractnode.h"
39 
40 #include "../../client.h"
41 #include "../../renderer/r_draw.h"
42 #include "../../renderer/r_mesh.h"
43 #include "../../renderer/r_mesh_anim.h"
44 #include "../../renderer/r_model.h"
45 
46 #define EXTRADATA_TYPE modelExtraData_t
47 #define EXTRADATA(node) UI_EXTRADATA(node, EXTRADATA_TYPE)
48 
49 #define ROTATE_SPEED	0.5
50 #define MAX_OLDREFVALUE MAX_VAR
51 
52 static const uiBehaviour_t* localBehaviour;
53 
54 /**
55  * @brief Returns pointer to UI model
56  * @param[in] modelName model id from script files
57  * @return uiModel_t pointer
58  */
UI_GetUIModel(const char * modelName)59 uiModel_t* UI_GetUIModel (const char* modelName)
60 {
61 	int i;
62 
63 	for (i = 0; i < ui_global.numModels; i++) {
64 		uiModel_t* m = &ui_global.models[i];
65 		if (Q_streq(m->id, modelName))
66 			return m;
67 	}
68 	return nullptr;
69 }
70 
UI_ListUIModels_f(void)71 static void UI_ListUIModels_f (void)
72 {
73 	int i;
74 
75 	/* search for UI models with same name */
76 	Com_Printf("UI models: %i\n", ui_global.numModels);
77 	for (i = 0; i < ui_global.numModels; i++) {
78 		const uiModel_t* m = &ui_global.models[i];
79 		const char* need = m->next != nullptr ? m->next->id : "none";
80 		Com_Printf("id: %s\n...model: %s\n...need: %s\n\n", m->id, m->model, need);
81 	}
82 }
83 
draw(uiNode_t * node)84 void uiModelNode::draw (uiNode_t* node)
85 {
86 	const char* ref = UI_GetReferenceString(node, EXTRADATA(node).model);
87 	char source[MAX_VAR];
88 	if (Q_strnull(ref))
89 		source[0] = '\0';
90 	else
91 		Q_strncpyz(source, ref, sizeof(source));
92 	UI_DrawModelNode(node, source);
93 }
94 
95 static vec3_t nullVector = {0, 0, 0};
96 
97 /**
98  * @brief Set the Model info view (angle,origin,scale) according to the node definition
99  * @todo the param "model" is not used !?
100  */
UI_InitModelInfoView(uiNode_t * node,modelInfo_t * mi,uiModel_t * model)101 static inline void UI_InitModelInfoView (uiNode_t* node, modelInfo_t* mi, uiModel_t* model)
102 {
103 	vec3_t nodeorigin;
104 	UI_GetNodeAbsPos(node, nodeorigin);
105 	nodeorigin[0] += node->box.size[0] / 2 + EXTRADATA(node).origin[0];
106 	nodeorigin[1] += node->box.size[1] / 2 + EXTRADATA(node).origin[1];
107 	nodeorigin[2] = EXTRADATA(node).origin[2];
108 
109 	VectorCopy(EXTRADATA(node).scale, mi->scale);
110 	VectorCopy(EXTRADATA(node).angles, mi->angles);
111 	VectorCopy(nodeorigin, mi->origin);
112 
113 	VectorCopy(nullVector, mi->center);
114 }
115 
116 /**
117  * @brief Draw a model using UI model definition
118  */
UI_DrawModelNodeWithUIModel(uiNode_t * node,const char * source,modelInfo_t * mi,uiModel_t * model)119 static void UI_DrawModelNodeWithUIModel (uiNode_t* node, const char* source, modelInfo_t* mi, uiModel_t* model)
120 {
121 	bool autoScaleComputed = false;
122 	vec3_t autoScale;
123 	vec3_t autoCenter;
124 
125 	while (model) {
126 		/* no animation */
127 		mi->frame = 0;
128 		mi->oldframe = 0;
129 		mi->backlerp = 0;
130 
131 		assert(model->model);
132 		mi->model = R_FindModel(model->model);
133 		if (!mi->model) {
134 			model = model->next;
135 			continue;
136 		}
137 
138 		mi->skin = model->skin;
139 		mi->name = model->model;
140 
141 		/* set mi pointers to model */
142 		mi->origin = model->origin;
143 		mi->angles = model->angles;
144 		mi->center = model->center;
145 		mi->color = model->color;
146 		mi->scale = model->scale;
147 
148 		if (model->tag && model->parent) {
149 			/* tag and parent defined */
150 			uiModel_t* parentModel;
151 			modelInfo_t pmi;
152 			vec3_t pmiorigin;
153 			animState_t* as;
154 			/* place this model part on an already existing model tag */
155 			parentModel = UI_GetUIModel(model->parent);
156 			if (!parentModel) {
157 				Com_Printf("UI Model: Could not get the model '%s'\n", model->parent);
158 				break;
159 			}
160 			pmi.model = R_FindModel(parentModel->model);
161 			if (!pmi.model) {
162 				Com_Printf("UI Model: Could not get the model '%s'\n", parentModel->model);
163 				break;
164 			}
165 
166 			pmi.name = parentModel->model;
167 
168 			pmi.origin = pmiorigin;
169 			pmi.angles = parentModel->angles;
170 			pmi.scale = parentModel->scale;
171 			pmi.center = parentModel->center;
172 			pmi.color = parentModel->color;
173 
174 			pmi.origin[0] = parentModel->origin[0] + mi->origin[0];
175 			pmi.origin[1] = parentModel->origin[1] + mi->origin[1];
176 			pmi.origin[2] = parentModel->origin[2];
177 			/* don't count window offset twice for tagged models */
178 			mi->origin[0] -= node->root->box.pos[0];
179 			mi->origin[1] -= node->root->box.pos[1];
180 
181 			/* autoscale? */
182 			if (EXTRADATA(node).autoscale) {
183 				if (!autoScaleComputed)
184 					Sys_Error("Wrong order of model nodes - the tag and parent model node must be after the base model node");
185 				pmi.scale = autoScale;
186 				pmi.center = autoCenter;
187 			}
188 
189 			as = &parentModel->animState;
190 			pmi.frame = as->frame;
191 			pmi.oldframe = as->oldframe;
192 			pmi.backlerp = as->backlerp;
193 
194 			R_DrawModelDirect(mi, &pmi, model->tag);
195 		} else {
196 			/* no tag and no parent means - base model or single model */
197 			const char* ref;
198 			UI_InitModelInfoView(node, mi, model);
199 			Vector4Copy(node->color, mi->color);
200 
201 			/* compute the scale and center for the first model.
202 			 * it think its the bigger of composite models.
203 			 * All next elements use the same result
204 			 */
205 			if (EXTRADATA(node).autoscale) {
206 				if (!autoScaleComputed) {
207 					vec2_t size;
208 					size[0] = node->box.size[0] - node->padding;
209 					size[1] = node->box.size[1] - node->padding;
210 					R_ModelAutoScale(size, mi, autoScale, autoCenter);
211 					autoScaleComputed = true;
212 				} else {
213 					mi->scale = autoScale;
214 					mi->center = autoCenter;
215 				}
216 			}
217 
218 			/* get the animation given by node properties */
219 			if (EXTRADATA(node).animation && *EXTRADATA(node).animation) {
220 				ref = UI_GetReferenceString(node, EXTRADATA(node).animation);
221 			/* otherwise use the standard animation from UI model definition */
222 			} else
223 				ref = model->anim;
224 
225 			/* only base models have animations */
226 			if (ref && *ref) {
227 				animState_t* as = &model->animState;
228 				const char* anim = R_AnimGetName(as, mi->model);
229 				/* initial animation or animation change */
230 				if (anim == nullptr || !Q_streq(anim, ref))
231 					R_AnimChange(as, mi->model, ref);
232 				else
233 					R_AnimRun(as, mi->model, cls.frametime * 1000);
234 
235 				mi->frame = as->frame;
236 				mi->oldframe = as->oldframe;
237 				mi->backlerp = as->backlerp;
238 			}
239 			R_DrawModelDirect(mi, nullptr, nullptr);
240 		}
241 
242 		/* next */
243 		model = model->next;
244 	}
245 }
246 
247 /**
248  * @todo need to merge UI model case, and the common case (looks to be a copy-pasted code)
249  */
UI_DrawModelNode(uiNode_t * node,const char * source)250 void UI_DrawModelNode (uiNode_t* node, const char* source)
251 {
252 	modelInfo_t mi;
253 	uiModel_t* model;
254 	vec3_t nodeorigin;
255 	vec2_t screenPos;
256 
257 	assert(UI_NodeInstanceOf(node, "model"));			/**< We use model extradata */
258 
259 	if (!source || source[0] == '\0')
260 		return;
261 
262 	model = UI_GetUIModel(source);
263 	/* direct model name - no UI model definition */
264 	if (!model) {
265 		/* prevent the searching for a model def in the next frame */
266 		mi.model = R_FindModel(source);
267 		mi.name = source;
268 		if (!mi.model) {
269 			Com_Printf("Could not find model '%s'\n", source);
270 			return;
271 		}
272 	}
273 
274 	/* compute the absolute origin ('origin' property is relative to the node center) */
275 	UI_GetNodeScreenPos(node, screenPos);
276 	UI_GetNodeAbsPos(node, nodeorigin);
277 	R_CleanupDepthBuffer(nodeorigin[0], nodeorigin[1], node->box.size[0], node->box.size[1]);
278 	if (EXTRADATA(node).clipOverflow) {
279 		UI_PushClipRect(screenPos[0], screenPos[1], node->box.size[0], node->box.size[1]);
280 	}
281 	nodeorigin[0] += node->box.size[0] / 2 + EXTRADATA(node).origin[0];
282 	nodeorigin[1] += node->box.size[1] / 2 + EXTRADATA(node).origin[1];
283 	nodeorigin[2] = EXTRADATA(node).origin[2];
284 
285 	VectorMA(EXTRADATA(node).angles, cls.frametime, EXTRADATA(node).omega, EXTRADATA(node).angles);
286 	mi.origin = nodeorigin;
287 	mi.angles = EXTRADATA(node).angles;
288 	mi.scale = EXTRADATA(node).scale;
289 	mi.center = nullVector;
290 	mi.color = node->color;
291 	mi.mesh = 0;
292 
293 	/* special case to draw models with UI model */
294 	if (model) {
295 		UI_DrawModelNodeWithUIModel(node, source, &mi, model);
296 		if (EXTRADATA(node).clipOverflow)
297 			UI_PopClipRect();
298 		return;
299 	}
300 
301 	/* if the node is linked to a parent, the parent will display it */
302 	if (EXTRADATA(node).tag) {
303 		if (EXTRADATA(node).clipOverflow)
304 			UI_PopClipRect();
305 		return;
306 	}
307 
308 	/* autoscale? */
309 	if (EXTRADATA(node).autoscale) {
310 		vec3_t autoScale;
311 		vec3_t autoCenter;
312 		const vec2_t size = {node->box.size[0] - node->padding, node->box.size[1] - node->padding};
313 		R_ModelAutoScale(size, &mi, autoScale, autoCenter);
314 	}
315 
316 	/* no animation */
317 	mi.frame = 0;
318 	mi.oldframe = 0;
319 	mi.backlerp = 0;
320 
321 	/* get skin */
322 	if (EXTRADATA(node).skin && *EXTRADATA(node).skin)
323 		mi.skin = atoi(UI_GetReferenceString(node, EXTRADATA(node).skin));
324 	else
325 		mi.skin = 0;
326 
327 	/* do animations */
328 	if (EXTRADATA(node).animation && *EXTRADATA(node).animation) {
329 		const char* ref;
330 		ref = UI_GetReferenceString(node, EXTRADATA(node).animation);
331 
332 		/* check whether the cvar value changed */
333 		if (strncmp(EXTRADATA(node).oldRefValue, source, MAX_OLDREFVALUE)) {
334 			Q_strncpyz(EXTRADATA(node).oldRefValue, source, MAX_OLDREFVALUE);
335 			/* model has changed but mem is already reserved in pool */
336 			Mem_Free(EXTRADATA(node).animationState);
337 			EXTRADATA(node).animationState = nullptr;
338 		}
339 		animState_t* as = EXTRADATA(node).animationState;
340 		if (!as) {
341 			as = Mem_PoolAllocType(animState_t, cl_genericPool);
342 			if (!as)
343 				Com_Error(ERR_DROP, "Model %s should have animState_t for animation %s - but doesn't\n", mi.name, ref);
344 			R_AnimChange(as, mi.model, ref);
345 			EXTRADATA(node).animationState = as;
346 		} else {
347 			const char* anim;
348 			/* change anim if needed */
349 			anim = R_AnimGetName(as, mi.model);
350 			if (anim && !Q_streq(anim, ref))
351 				R_AnimChange(as, mi.model, ref);
352 			R_AnimRun(as, mi.model, cls.frametime * 1000);
353 		}
354 
355 		mi.frame = as->frame;
356 		mi.oldframe = as->oldframe;
357 		mi.backlerp = as->backlerp;
358 	}
359 
360 	/* draw the main model on the node */
361 	R_DrawModelDirect(&mi, nullptr, nullptr);
362 
363 	/* draw all children */
364 	if (node->firstChild) {
365 		uiNode_t* child;
366 		modelInfo_t pmi = mi;
367 		for (child = node->firstChild; child; child = child->next) {
368 			const char* tag;
369 			char childSource[MAX_VAR];
370 			const char* childRef;
371 
372 			/* skip non "model" nodes */
373 			if (child->behaviour != node->behaviour)
374 				continue;
375 
376 			/* skip invisible child */
377 			if (child->invis || !UI_CheckVisibility(child))
378 				continue;
379 
380 			OBJZERO(mi);
381 			mi.angles = EXTRADATA(child).angles;
382 			mi.scale = EXTRADATA(child).scale;
383 			mi.center = nullVector;
384 			mi.origin = EXTRADATA(child).origin;
385 			mi.color = pmi.color;
386 
387 			/* get the anchor name to link the model into the parent */
388 			tag = EXTRADATA(child).tag;
389 
390 			/* init model name */
391 			childRef = UI_GetReferenceString(child, EXTRADATA(child).model);
392 			if (Q_strnull(childRef))
393 				childSource[0] = '\0';
394 			else
395 				Q_strncpyz(childSource, childRef, sizeof(childSource));
396 			mi.model = R_FindModel(childSource);
397 			mi.name = childSource;
398 
399 			/* init skin */
400 			if (EXTRADATA(child).skin && *EXTRADATA(child).skin)
401 				mi.skin = atoi(UI_GetReferenceString(child, EXTRADATA(child).skin));
402 			else
403 				mi.skin = 0;
404 
405 			R_DrawModelDirect(&mi, &pmi, tag);
406 		}
407 	}
408 
409 	if (EXTRADATA(node).clipOverflow)
410 		UI_PopClipRect();
411 }
412 
413 static int oldMousePosX = 0;
414 static int oldMousePosY = 0;
415 
onCapturedMouseMove(uiNode_t * node,int x,int y)416 void uiModelNode::onCapturedMouseMove (uiNode_t* node, int x, int y)
417 {
418 	float* rotateAngles = EXTRADATA(node).angles;
419 
420 	/* rotate a model */
421 	rotateAngles[YAW] -= ROTATE_SPEED * (x - oldMousePosX);
422 	rotateAngles[ROLL] += ROTATE_SPEED * (y - oldMousePosY);
423 
424 	/* clamp the angles */
425 	rotateAngles[YAW] -= floor(rotateAngles[YAW] / 360.0) * 360.0;
426 
427 	if (rotateAngles[ROLL] < 0.0)
428 		rotateAngles[ROLL] = 0.0;
429 	else if (rotateAngles[ROLL] > 180.0)
430 		rotateAngles[ROLL] = 180.0;
431 
432 	oldMousePosX = x;
433 	oldMousePosY = y;
434 }
435 
onMouseDown(uiNode_t * node,int x,int y,int button)436 void uiModelNode::onMouseDown (uiNode_t* node, int x, int y, int button)
437 {
438 	if (button != K_MOUSE1)
439 		return;
440 	if (!EXTRADATA(node).rotateWithMouse)
441 		return;
442 	UI_SetMouseCapture(node);
443 	oldMousePosX = x;
444 	oldMousePosY = y;
445 }
446 
onMouseUp(uiNode_t * node,int x,int y,int button)447 void uiModelNode::onMouseUp (uiNode_t* node, int x, int y, int button)
448 {
449 	if (button != K_MOUSE1)
450 		return;
451 	if (UI_GetMouseCapture() != node)
452 		return;
453 	UI_MouseRelease();
454 }
455 
456 /**
457  * @brief Called before loading. Used to set default attribute values
458  */
onLoading(uiNode_t * node)459 void uiModelNode::onLoading (uiNode_t* node)
460 {
461 	Vector4Set(node->color, 1, 1, 1, 1);
462 	VectorSet(EXTRADATA(node).scale, 1, 1, 1);
463 	EXTRADATA(node).clipOverflow = true;
464 }
465 
466 /**
467  * @brief Call to update a cloned node
468  */
clone(const uiNode_t * source,uiNode_t * clone)469 void uiModelNode::clone (const uiNode_t* source, uiNode_t* clone)
470 {
471 	uiLocatedNode::clone(source, clone);
472 	if (!clone->dynamic)
473 		EXTRADATA(clone).oldRefValue = UI_AllocStaticString("", MAX_OLDREFVALUE);
474 }
475 
newNode(uiNode_t * node)476 void uiModelNode::newNode (uiNode_t* node)
477 {
478 	EXTRADATA(node).oldRefValue = Mem_PoolAllocTypeN(char, MAX_OLDREFVALUE, ui_dynPool);
479 	EXTRADATA(node).oldRefValue[0] = '\0';
480 }
481 
deleteNode(uiNode_t * node)482 void uiModelNode::deleteNode (uiNode_t* node)
483 {
484 	Mem_Free(EXTRADATA(node).oldRefValue);
485 	EXTRADATA(node).oldRefValue = nullptr;
486 }
487 
onLoaded(uiNode_t * node)488 void uiModelNode::onLoaded (uiNode_t* node)
489 {
490 	/* a tag without but not a submodel */
491 	if (EXTRADATA(node).tag != nullptr && node->behaviour != node->parent->behaviour) {
492 		Com_Printf("UI_ModelNodeLoaded: '%s' use a tag but is not a submodel. Tag removed.\n", UI_GetPath(node));
493 		EXTRADATA(node).tag = nullptr;
494 	}
495 
496 	if (EXTRADATA(node).oldRefValue == nullptr)
497 		EXTRADATA(node).oldRefValue = UI_AllocStaticString("", MAX_OLDREFVALUE);
498 
499 	/* no tag but no size */
500 	if (EXTRADATA(node).tag == nullptr && (node->box.size[0] == 0 || node->box.size[1] == 0)) {
501 		Com_Printf("UI_ModelNodeLoaded: Please set a pos and size to the node '%s'. Note: 'origin' is a relative value to the center of the node\n", UI_GetPath(node));
502 	}
503 }
504 
UI_RegisterModelNode(uiBehaviour_t * behaviour)505 void UI_RegisterModelNode (uiBehaviour_t* behaviour)
506 {
507 	localBehaviour = behaviour;
508 	behaviour->name = "model";
509 	behaviour->drawItselfChild = true;
510 	behaviour->manager = UINodePtr(new uiModelNode());
511 	behaviour->extraDataSize = sizeof(EXTRADATA_TYPE);
512 
513 	/* Both. Name of the animation for the model */
514 	UI_RegisterExtradataNodeProperty(behaviour, "anim", V_CVAR_OR_STRING, modelExtraData_t, animation);
515 	/* Main model only. Point of view. */
516 	UI_RegisterExtradataNodeProperty(behaviour, "angles", V_VECTOR, modelExtraData_t, angles);
517 	/* Main model only. Position of the model relative to the center of the node. */
518 	UI_RegisterExtradataNodeProperty(behaviour, "origin", V_VECTOR, modelExtraData_t, origin);
519 	/* Main model only. Rotation vector of the model. */
520 	UI_RegisterExtradataNodeProperty(behaviour, "omega", V_VECTOR, modelExtraData_t, omega);
521 	/* Both. Scale the model */
522 	UI_RegisterExtradataNodeProperty(behaviour, "scale", V_VECTOR, modelExtraData_t, scale);
523 	/* Submodel only. A tag name to link the model to the parent model. */
524 	UI_RegisterExtradataNodeProperty(behaviour, "tag", V_CVAR_OR_STRING, modelExtraData_t, tag);
525 	/* Main model only. Auto compute the "better" scale for the model. The function dont work
526 	 * very well at the moment because it dont check the angle and no more submodel bounding box.
527 	 */
528 	UI_RegisterExtradataNodeProperty(behaviour, "autoscale", V_BOOL, modelExtraData_t, autoscale);
529 	/* Main model only. Allow to change the POV of the model with the mouse (only for main model) */
530 	UI_RegisterExtradataNodeProperty(behaviour, "rotatewithmouse", V_BOOL, modelExtraData_t, rotateWithMouse);
531 	/* Main model only. Clip the model with the node rect */
532 	UI_RegisterExtradataNodeProperty(behaviour, "clipoverflow", V_BOOL, modelExtraData_t, clipOverflow);
533 	/* Source of the model. The path to the model, relative to <code>base/models</code> */
534 	UI_RegisterExtradataNodeProperty(behaviour, "src", V_CVAR_OR_STRING, modelExtraData_t, model);
535 	/* Both. Name of the skin for the model. */
536 	UI_RegisterExtradataNodeProperty(behaviour, "skin", V_CVAR_OR_STRING, modelExtraData_t, skin);
537 
538 	Cmd_AddCommand("uimodelslist", UI_ListUIModels_f);
539 }
540