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