1 /** @file p_mobj.cpp  World map objects.
2  *
3  * Various routines for moving mobjs, collision and Z checking.
4  *
5  * @authors Copyright © 2003-2017 Jaakko Keränen <jaakko.keranen@iki.fi>
6  * @authors Copyright © 2006-2015 Daniel Swanson <danij@dengine.net>
7  * @authors Copyright © 1993-1996 by id Software, Inc.
8  *
9  * @par License
10  * GPL: http://www.gnu.org/licenses/gpl.html
11  *
12  * <small>This program is free software; you can redistribute it and/or modify
13  * it under the terms of the GNU General Public License as published by the
14  * Free Software Foundation; either version 2 of the License, or (at your
15  * option) any later version. This program is distributed in the hope that it
16  * will be useful, but WITHOUT ANY WARRANTY; without even the implied warranty
17  * of MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU General
18  * Public License for more details. You should have received a copy of the GNU
19  * General Public License along with this program; if not, write to the Free
20  * Software Foundation, Inc., 51 Franklin St, Fifth Floor, Boston, MA
21  * 02110-1301 USA</small>
22  */
23 
24 #include "de_base.h"
25 #include "world/p_object.h"
26 
27 #include <cmath>
28 #include <de/vector1.h>
29 #include <de/Error>
30 #include <de/LogBuffer>
31 #include <doomsday/console/cmd.h>
32 #include <doomsday/console/exec.h>
33 #include <doomsday/console/var.h>
34 #include <doomsday/defs/sprite.h>
35 #include <doomsday/res/Textures>
36 #include <doomsday/res/Sprites>
37 #include <doomsday/world/mobjthinkerdata.h>
38 #include <doomsday/world/Materials>
39 
40 #include "def_main.h"
41 #include "api_sound.h"
42 #include "network/net_main.h"
43 
44 #ifdef __CLIENT__
45 #  include "client/cl_mobj.h"
46 #  include "client/clientsubsector.h"
47 #  include "gl/gl_tex.h"
48 #  include "network/net_demo.h"
49 #  include "render/viewports.h"
50 #  include "render/rend_main.h"
51 #  include "render/rend_model.h"
52 #  include "render/rend_halo.h"
53 #  include "render/billboard.h"
54 #endif
55 
56 #include "world/clientserverworld.h" // validCount
57 #include "world/p_object.h"
58 #include "world/p_players.h"
59 #include "world/thinkers.h"
60 #include "BspLeaf"
61 #include "ConvexSubspace"
62 #include "Subsector"
63 
64 #ifdef __CLIENT__
65 #  include "Generator"
66 #  include "Lumobj"
67 #endif
68 
69 using namespace de;
70 using namespace world;
71 
72 static String const VAR_MATERIAL("material");
73 
74 static mobj_t *unusedMobjs;
75 
76 /*
77  * Console variables:
78  */
79 dint useSRVO      = 2;  ///< @c 1= models only, @c 2= sprites + models
80 dint useSRVOAngle = 1;
81 
82 #ifdef __CLIENT__
83 static byte mobjAutoLights = true;
84 #endif
85 
86 /**
87  * Called during map loading.
88  */
P_InitUnusedMobjList()89 void P_InitUnusedMobjList()
90 {
91     // Any zone memory allocated for the mobjs will have already been purged.
92     ::unusedMobjs = nullptr;
93 }
94 
95 /**
96  * All mobjs must be allocated through this routine. Part of the public API.
97  */
P_MobjCreate(thinkfunc_t function,Vector3d const & origin,angle_t angle,coord_t radius,coord_t height,dint ddflags)98 mobj_t *P_MobjCreate(thinkfunc_t function, Vector3d const &origin, angle_t angle,
99     coord_t radius, coord_t height, dint ddflags)
100 {
101     if (!function)
102         App_Error("P_MobjCreate: Think function invalid, cannot create mobj.");
103 
104 #ifdef DENG2_DEBUG
105     if (::isClient)
106     {
107         LOG_VERBOSE("P_MobjCreate: Client creating mobj at %s")
108             << origin.asText();
109     }
110 #endif
111 
112     // Do we have any unused mobjs we can reuse?
113     mobj_t *mob;
114     if (::unusedMobjs)
115     {
116         mob = ::unusedMobjs;
117         ::unusedMobjs = ::unusedMobjs->sNext;
118     }
119     else
120     {
121         // No, we need to allocate another.
122         mob = MobjThinker(Thinker::AllocateMemoryZone).take();
123     }
124 
125     V3d_Set(mob->origin, origin.x, origin.y, origin.z);
126     mob->angle    = angle;
127     mob->visAngle = mob->angle >> 16; // "angle-servo"; smooth actor turning.
128     mob->radius   = radius;
129     mob->height   = height;
130     mob->ddFlags  = ddflags;
131     mob->lumIdx   = -1;
132     mob->thinker.function = function;
133     Mobj_Map(*mob).thinkers().add(mob->thinker);
134 
135     return mob;
136 }
137 
138 /**
139  * All mobjs must be destroyed through this routine. Part of the public API.
140  *
141  * @note Does not actually destroy the mobj. Instead, mobj is marked as
142  * awaiting removal (which occurs when its turn for thinking comes around).
143  */
144 #undef Mobj_Destroy
Mobj_Destroy(mobj_t * mo)145 DENG_EXTERN_C void Mobj_Destroy(mobj_t *mo)
146 {
147 #ifdef _DEBUG
148     if (mo->ddFlags & DDMF_MISSILE)
149     {
150         LOG_AS("Mobj_Destroy");
151         LOG_MAP_XVERBOSE("Destroying missile %i", mo->thinker.id);
152     }
153 #endif
154 
155     // Unlink from sector and block lists.
156     Mobj_Unlink(mo);
157 
158     S_StopSound(0, mo);
159 
160     Mobj_Map(*mo).thinkers().remove(reinterpret_cast<thinker_t &>(*mo));
161 }
162 
163 /**
164  * Called when a mobj is actually removed (when it's thinking turn comes around).
165  * The mobj is moved to the unused list to be reused later.
166  */
P_MobjRecycle(mobj_t * mo)167 void P_MobjRecycle(mobj_t* mo)
168 {
169     // Release the private data.
170     MobjThinker::zap(*mo);
171 
172     // The sector next link is used as the unused mobj list links.
173     mo->sNext = unusedMobjs;
174     unusedMobjs = mo;
175 }
176 
Mobj_IsSectorLinked(mobj_t const & mob)177 bool Mobj_IsSectorLinked(mobj_t const &mob)
178 {
179     return (mob._bspLeaf != nullptr && mob.sPrev != nullptr);
180 }
181 
182 #undef Mobj_SetState
Mobj_SetState(mobj_t * mob,int statenum)183 DENG_EXTERN_C void Mobj_SetState(mobj_t *mob, int statenum)
184 {
185     if (!mob) return;
186 
187     state_t const *oldState = mob->state;
188 
189     DENG2_ASSERT(statenum >= 0 && statenum < DED_Definitions()->states.size());
190 
191     mob->state  = &runtimeDefs.states[statenum];
192     mob->tics   = mob->state->tics;
193     mob->sprite = mob->state->sprite;
194     mob->frame  = mob->state->frame;
195 
196     if (!(mob->ddFlags & DDMF_REMOTE))
197     {
198         String const exec = DED_Definitions()->states[statenum].gets("execute");
199         if (!exec.isEmpty())
200         {
201             Con_Execute(CMDS_SCRIPT, exec.toUtf8(), true, false);
202         }
203     }
204 
205     // Notify private data about the changed state.
206     if (!mob->thinker.d)
207     {
208         Thinker_InitPrivateData(&mob->thinker);
209     }
210     if (MobjThinkerData *data = THINKER_DATA_MAYBE(mob->thinker, MobjThinkerData))
211     {
212         data->stateChanged(oldState);
213     }
214 }
215 
Mobj_Origin(mobj_t const & mob)216 Vector3d Mobj_Origin(mobj_t const &mob)
217 {
218     return Vector3d(mob.origin);
219 }
220 
Mobj_Center(mobj_t & mob)221 Vector3d Mobj_Center(mobj_t &mob)
222 {
223     return Vector3d(mob.origin[0], mob.origin[1], mob.origin[2] + mob.height / 2);
224 }
225 
Mobj_SetOrigin(struct mobj_s * mob,coord_t x,coord_t y,coord_t z)226 dd_bool Mobj_SetOrigin(struct mobj_s *mob, coord_t x, coord_t y, coord_t z)
227 {
228     if(!gx.MobjTryMoveXYZ)
229     {
230         return false;
231     }
232     return gx.MobjTryMoveXYZ(mob, x, y, z);
233 }
234 
235 #undef Mobj_OriginSmoothed
Mobj_OriginSmoothed(mobj_t * mob,coord_t origin[3])236 DENG_EXTERN_C void Mobj_OriginSmoothed(mobj_t *mob, coord_t origin[3])
237 {
238     if (!origin) return;
239 
240     V3d_Set(origin, 0, 0, 0);
241     if (!mob) return;
242 
243     V3d_Copy(origin, mob->origin);
244 
245     // Apply a Short Range Visual Offset?
246     if (useSRVO && mob->state && mob->tics >= 0)
247     {
248         ddouble const mul = mob->tics / dfloat( mob->state->tics );
249         vec3d_t srvo;
250 
251         V3d_Copy(srvo, mob->srvo);
252         V3d_Scale(srvo, mul);
253         V3d_Sum(origin, origin, srvo);
254     }
255 
256 #ifdef __CLIENT__
257     if (mob->dPlayer)
258     {
259         /// @todo What about splitscreen? We have smoothed origins for all local players.
260         if (P_GetDDPlayerIdx(mob->dPlayer) == consolePlayer
261             // $voodoodolls: Must be a real player to use the smoothed origin.
262             && mob->dPlayer->mo == mob)
263         {
264             viewdata_t const *vd = &DD_Player(consolePlayer)->viewport();
265             V3d_Set(origin, vd->current.origin.x, vd->current.origin.y, vd->current.origin.z);
266         }
267         // The client may have a Smoother for this object.
268         else if (isClient)
269         {
270             Smoother_Evaluate(DD_Player(P_GetDDPlayerIdx(mob->dPlayer))->smoother(), origin);
271         }
272     }
273 #endif
274 }
275 
Mobj_Map(mobj_t const & mob)276 world::Map &Mobj_Map(mobj_t const &mob)
277 {
278     return Thinker_Map(mob.thinker);
279 }
280 
Mobj_IsLinked(mobj_t const & mob)281 bool Mobj_IsLinked(mobj_t const &mob)
282 {
283     return mob._bspLeaf != 0;
284 }
285 
Mobj_BspLeafAtOrigin(mobj_t const & mob)286 BspLeaf &Mobj_BspLeafAtOrigin(mobj_t const &mob)
287 {
288     if (Mobj_IsLinked(mob))
289     {
290         return *(BspLeaf *)mob._bspLeaf;
291     }
292     throw Error("Mobj_BspLeafAtOrigin", "Mobj is not yet linked");
293 }
294 
Mobj_HasSubsector(mobj_t const & mob)295 bool Mobj_HasSubsector(mobj_t const &mob)
296 {
297     if (!Mobj_IsLinked(mob)) return false;
298     BspLeaf const &bspLeaf = Mobj_BspLeafAtOrigin(mob);
299     if (!bspLeaf.hasSubspace()) return false;
300     return bspLeaf.subspace().hasSubsector();
301 }
302 
Mobj_Subsector(mobj_t const & mob)303 Subsector &Mobj_Subsector(mobj_t const &mob)
304 {
305     return Mobj_BspLeafAtOrigin(mob).subspace().subsector();
306 }
307 
Mobj_SubsectorPtr(mobj_t const & mob)308 Subsector *Mobj_SubsectorPtr(mobj_t const &mob)
309 {
310     return Mobj_HasSubsector(mob) ? &Mobj_Subsector(mob) : nullptr;
311 }
312 
313 #undef Mobj_Sector
Mobj_Sector(mobj_t const * mob)314 DENG_EXTERN_C Sector *Mobj_Sector(mobj_t const *mob)
315 {
316     if (!mob || !Mobj_IsLinked(*mob)) return nullptr;
317     return Mobj_BspLeafAtOrigin(*mob).sectorPtr();
318 }
319 
Mobj_SpawnParticleGen(mobj_t * mob,ded_ptcgen_t const * def)320 void Mobj_SpawnParticleGen(mobj_t *mob, ded_ptcgen_t const *def)
321 {
322 #ifdef __CLIENT__
323     DENG2_ASSERT(mob && def);
324 
325     //if (!useParticles) return;
326 
327     Generator *gen = Mobj_Map(*mob).newGenerator();
328     if (!gen) return;
329 
330     /*LOG_INFO("SpawnPtcGen: %s/%i (src:%s typ:%s mo:%p)")
331         << def->state << (def - defs.ptcgens) << defs.states[mob->state-states].id
332         << defs.mobjs[mob->type].id << source;*/
333 
334     // Initialize the particle generator.
335     gen->count = def->particles;
336     // Size of source sector might determine count.
337     if (def->flags & Generator::ScaledRate)
338     {
339         gen->spawnRateMultiplier = Mobj_BspLeafAtOrigin(*mob).sectorPtr()->roughArea() / (128 * 128);
340     }
341     else
342     {
343         gen->spawnRateMultiplier = 1;
344     }
345 
346     gen->configureFromDef(def);
347     gen->source = mob;
348     gen->srcid  = mob->thinker.id;
349 
350     // Is there a need to pre-simulate?
351     gen->presimulate(def->preSim);
352 #else
353     DENG2_UNUSED2(mob, def);
354 #endif
355 }
356 
357 #undef Mobj_SpawnDamageParticleGen
Mobj_SpawnDamageParticleGen(mobj_t const * mob,mobj_t const * inflictor,int amount)358 DENG_EXTERN_C void Mobj_SpawnDamageParticleGen(mobj_t const *mob, mobj_t const *inflictor, int amount)
359 {
360 #ifdef __CLIENT__
361     if (!mob || !inflictor || amount <= 0) return;
362 
363     // Are particles allowed?
364     //if (!useParticles) return;
365 
366     ded_ptcgen_t const *def = Def_GetDamageGenerator(mob->type);
367     if (def)
368     {
369         Generator *gen = Mobj_Map(*mob).newGenerator();
370         if (!gen) return; // No more generators.
371 
372         gen->count = def->particles;
373         gen->configureFromDef(def);
374         gen->setUntriggered();
375 
376         gen->spawnRateMultiplier = de::max(amount, 1);
377 
378         // Calculate appropriate center coordinates.
379         gen->originAtSpawn[0] += FLT2FIX(mob->origin[0]);
380         gen->originAtSpawn[1] += FLT2FIX(mob->origin[1]);
381         gen->originAtSpawn[2] += FLT2FIX(mob->origin[2] + mob->height / 2);
382 
383         // Calculate launch vector.
384         vec3f_t vecDelta;
385         V3f_Set(vecDelta, inflictor->origin[0] - mob->origin[0],
386                 inflictor->origin[1] - mob->origin[1],
387                 (inflictor->origin[2] - inflictor->height / 2) - (mob->origin[2] + mob->height / 2));
388 
389         vec3f_t vector;
390         V3f_SetFixed(vector, gen->vector[0], gen->vector[1], gen->vector[2]);
391         V3f_Sum(vector, vector, vecDelta);
392         V3f_Normalize(vector);
393 
394         gen->vector[0] = FLT2FIX(vector[0]);
395         gen->vector[1] = FLT2FIX(vector[1]);
396         gen->vector[2] = FLT2FIX(vector[2]);
397 
398         // Is there a need to pre-simulate?
399         gen->presimulate(def->preSim);
400     }
401 #else
402     DENG2_UNUSED3(mob, inflictor, amount);
403 #endif
404 }
405 
406 #ifdef __CLIENT__
407 
Mobj_OriginBehindVisPlane(mobj_t * mob)408 dd_bool Mobj_OriginBehindVisPlane(mobj_t *mob)
409 {
410     if (!mob || !Mobj_HasSubsector(*mob)) return false;
411 
412     auto &subsec = Mobj_Subsector(*mob).as<ClientSubsector>();
413 
414     if (&subsec.sector().floor() != &subsec.visFloor()
415         && mob->origin[2] < subsec.visFloor().heightSmoothed())
416         return true;
417 
418     if (&subsec.sector().ceiling() != &subsec.visCeiling()
419         && mob->origin[2] > subsec.visCeiling().heightSmoothed())
420         return true;
421 
422     return false;
423 }
424 
Mobj_UnlinkLumobjs(mobj_t * mob)425 void Mobj_UnlinkLumobjs(mobj_t *mob)
426 {
427     if (!mob) return;
428     mob->lumIdx = Lumobj::NoIndex;
429 }
430 
lightDefByMobjState(state_t const * state)431 static ded_light_t *lightDefByMobjState(state_t const *state)
432 {
433     if (state)
434     {
435         return runtimeDefs.stateInfo[runtimeDefs.states.indexOf(state)].light;
436     }
437     return nullptr;
438 }
439 
lightmap(de::Uri const * textureUri)440 static inline ClientTexture *lightmap(de::Uri const *textureUri)
441 {
442     if(!textureUri) return nullptr;
443     return static_cast<ClientTexture *>
444             (res::Textures::get().tryFindTextureByResourceUri(QStringLiteral("Lightmaps"), *textureUri));
445 }
446 
Mobj_GenerateLumobjs(mobj_t * mob)447 void Mobj_GenerateLumobjs(mobj_t *mob)
448 {
449     if (!mob) return;
450 
451     Mobj_UnlinkLumobjs(mob);
452 
453     if (!Mobj_HasSubsector(*mob)) return;
454     auto &subsec = Mobj_Subsector(*mob).as<ClientSubsector>();
455 
456     if (!(((mob->state && (mob->state->flags & STF_FULLBRIGHT))
457             && !(mob->ddFlags & DDMF_DONTDRAW))
458           || (mob->ddFlags & DDMF_ALWAYSLIT)))
459     {
460         return;
461     }
462 
463     // Are the automatically calculated light values for fullbright sprite frames in use?
464     if (mob->state
465         && (!mobjAutoLights || (mob->state->flags & STF_NOAUTOLIGHT))
466         && !runtimeDefs.stateInfo[runtimeDefs.states.indexOf(mob->state)].light)
467     {
468        return;
469     }
470 
471     // If the mobj's origin is outside the BSP leaf it is linked within, then
472     // this means it is outside the playable map (and no light should be emitted).
473     /// @todo Optimize: Mobj_Link() should do this and flag the mobj accordingly.
474     if (!Mobj_BspLeafAtOrigin(*mob).subspace().contains(mob->origin))
475         return;
476 
477     // Always use the front view of the Sprite when determining light properties.
478     Record const *spriteRec = Mobj_SpritePtr(*mob);
479     if (!spriteRec) return;
480 
481     // Lookup the Material for the Sprite and prepare the animator.
482     MaterialAnimator *matAnimator = Rend_SpriteMaterialAnimator(*spriteRec);
483     if (!matAnimator) return;
484     matAnimator->prepare();  // Ensure we have up-to-date info.
485 
486     TextureVariant *tex = matAnimator->texUnit(MaterialAnimator::TU_LAYER0).texture;
487     if (!tex) return;  // Unloadable texture?
488     Vector2i const &texOrigin = tex->base().origin();
489 
490     // Will the visual be allowed to go inside the floor?
491     /// @todo Handle this as occlusion so that the halo fades smoothly.
492     coord_t impacted = mob->origin[2] + -texOrigin.y - matAnimator->dimensions().y
493                      - subsec.visFloor().heightSmoothed();
494 
495     // If the floor is a visual plane then no light should be emitted.
496     if (impacted < 0 && &subsec.visFloor() != &subsec.sector().floor())
497         return;
498 
499     // Attempt to generate luminous object from the sprite.
500     std::unique_ptr<Lumobj> lum(Rend_MakeLumobj(*spriteRec));
501     if (!lum) return;
502 
503     lum->setSourceMobj(mob);
504 
505     // A light definition may override the (auto-calculated) defaults.
506     if (ded_light_t *def = lightDefByMobjState(mob->state))
507     {
508         if (!de::fequal(def->size, 0))
509         {
510             lum->setRadius(de::max(def->size, 32.f / (40 * lum->radiusFactor())));
511         }
512 
513         if (!de::fequal(def->offset[1], 0))
514         {
515             lum->setZOffset(-texOrigin.y - def->offset[1]);
516         }
517 
518         if (Vector3f(def->color) != Vector3f(0, 0, 0))
519         {
520             lum->setColor(def->color);
521         }
522 
523         lum->setLightmap(Lumobj::Side, lightmap(def->sides))
524             .setLightmap(Lumobj::Down, lightmap(def->down))
525             .setLightmap(Lumobj::Up,   lightmap(def->up));
526     }
527 
528     // Translate to the mobj's origin in map space.
529     lum->move(mob->origin);
530 
531     // Does the mobj need a Z origin offset?
532     coord_t zOffset = -mob->floorClip - Mobj_BobOffset(*mob);
533     if (!(mob->ddFlags & DDMF_NOFITBOTTOM) && impacted < 0)
534     {
535         // Raise the light out of the impacted surface.
536         zOffset -= impacted;
537     }
538     lum->setZOffset(lum->zOffset() + zOffset);
539 
540     // Insert a copy of the temporary lumobj in the map and remember it's unique
541     // index in the mobj (this'll allow a halo to be rendered).
542     mob->lumIdx = subsec.sector().map().addLumobj(lum.release()).indexInMap();
543 }
544 
Mobj_AnimateHaloOcclussion(mobj_t & mob)545 void Mobj_AnimateHaloOcclussion(mobj_t &mob)
546 {
547     for (dint i = 0; i < DDMAXPLAYERS; ++i)
548     {
549         dbyte *haloFactor = &mob.haloFactors[i];
550 
551         // Set the high bit of halofactor if the light is clipped. This will
552         // make P_Ticker diminish the factor to zero. Take the first step here
553         // and now, though.
554         if (mob.lumIdx == Lumobj::NoIndex || R_ViewerLumobjIsClipped(mob.lumIdx))
555         {
556             if (*haloFactor & 0x80)
557             {
558                 dint f = (*haloFactor & 0x7f);  // - haloOccludeSpeed;
559                 if (f < 0) f = 0;
560                 *haloFactor = f;
561             }
562         }
563         else
564         {
565             if (!(*haloFactor & 0x80))
566             {
567                 dint f = (*haloFactor & 0x7f);  // + haloOccludeSpeed;
568                 if (f > 127) f = 127;
569                 *haloFactor = 0x80 | f;
570             }
571         }
572 
573         // Handle halofactor.
574         dint f = *haloFactor & 0x7f;
575         if (*haloFactor & 0x80)
576         {
577             // Going up.
578             f += ::haloOccludeSpeed;
579             if (f > 127)
580                 f = 127;
581         }
582         else
583         {
584             // Going down.
585             f -= ::haloOccludeSpeed;
586             if (f < 0)
587                 f = 0;
588         }
589 
590         *haloFactor &= ~0x7f;
591         *haloFactor |= f;
592     }
593 }
594 
Mobj_ShadowStrength(mobj_t const & mob)595 dfloat Mobj_ShadowStrength(mobj_t const &mob)
596 {
597     static dfloat const minSpriteAlphaLimit = .1f;
598 
599     // A shadow is not cast if the map-object is not linked in the map.
600     if (!Mobj_HasSubsector(mob)) return 0;
601     // ...or the current state is invalid or full-bright.
602     if (!mob.state || (mob.state->flags & STF_FULLBRIGHT)) return 0;
603     // ...or it won't be drawn at all.
604     if (mob.ddFlags & DDMF_DONTDRAW) return 0;
605     // ...or is "always lit" (?).
606     if (mob.ddFlags & DDMF_ALWAYSLIT) return 0;
607 
608     // Evaluate the ambient light level at our map origin.
609     auto const &subsec = Mobj_Subsector(mob).as<ClientSubsector>();
610     dfloat ambientLightLevel;
611 #if 0
612     if (::useBias && subsec.sector().map().hasLightGrid())
613     {
614         ambientLightLevel = subsec.sector().map().lightGrid().evaluateIntensity(mob.origin);
615     }
616     else
617 #endif
618     {
619         ambientLightLevel = subsec.lightSourceIntensity();
620     }
621     Rend_ApplyLightAdaptation(ambientLightLevel);
622 
623     // Sprites have their own shadow strength factor.
624     dfloat strength = .65f;  ///< Default.
625     if (!::useModels || !Mobj_ModelDef(mob))
626     {
627         if (Record const *spriteRec = Mobj_SpritePtr(mob))
628         {
629             auto &matAnimator = *Rend_SpriteMaterialAnimator(*spriteRec); // world::Materials::get().materialPtr(sprite.viewMaterial(0)))
630             matAnimator.prepare();  // Ensure we have up-to-date info.
631 
632             if (TextureVariant const *texture = matAnimator.texUnit(MaterialAnimator::TU_LAYER0).texture)
633             {
634                 auto const *aa = (averagealpha_analysis_t const *)texture->base().analysisDataPointer(res::Texture::AverageAlphaAnalysis);
635                 DENG2_ASSERT(aa);
636 
637                 // We use an average which factors in the coverage ratio of
638                 // alpha:non-alpha pixels.
639                 /// @todo Constant weights could stand some tweaking...
640                 dfloat weightedSpriteAlpha = aa->alpha * (0.4f + (1 - aa->coverage) * 0.6f);
641 
642                 // Almost entirely translucent sprite? => no shadow.
643                 if(weightedSpriteAlpha < minSpriteAlphaLimit) return 0;
644 
645                 // Apply this factor.
646                 strength *= de::min(1.f, .2f + weightedSpriteAlpha);
647             }
648         }
649     }
650 
651     // Factor in Mobj alpha.
652     strength *= Mobj_Alpha(mob);
653 
654     /// @note This equation is the same as that used for fakeradio.
655     return (0.6f - ambientLightLevel * 0.4f) * strength;
656 }
657 
Mobj_SpritePtr(mobj_t const & mob)658 Record const *Mobj_SpritePtr(mobj_t const &mob)
659 {
660     return res::Sprites::get().spritePtr(mob.sprite, mob.frame);
661 }
662 
Mobj_ModelDef(mobj_t const & mo,FrameModelDef ** retNextModef,float * retInter)663 FrameModelDef *Mobj_ModelDef(mobj_t const &mo, FrameModelDef **retNextModef, float *retInter)
664 {
665     // By default there are no models.
666     if (retNextModef) *retNextModef = 0;
667     if (retInter)     *retInter = -1;
668 
669     // On the client it is possible that we don't know the mobj's state.
670     if (!mo.state) return 0;
671 
672     state_t &st = *mo.state;
673     FrameModelDef *modef = App_Resources().modelDefForState(runtimeDefs.states.indexOf(&st), mo.selector);
674     if (!modef) return 0; // No model available.
675 
676     dfloat interp = -1;
677 
678     // World time animation?
679     bool worldTime = false;
680     if (modef->flags & MFF_WORLD_TIME_ANIM)
681     {
682         dfloat duration = modef->interRange[0];
683         dfloat offset   = modef->interRange[1];
684 
685         // Validate/modify the values.
686         if (duration == 0) duration = 1;
687 
688         if (offset == -1)
689         {
690             offset = M_CycleIntoRange(MOBJ_TO_ID(&mo), duration);
691         }
692 
693         interp = M_CycleIntoRange(App_World().time() / duration + offset, 1);
694         worldTime = true;
695     }
696     else
697     {
698         // Calculate the currently applicable intermark.
699         interp = 1.0f - (mo.tics - frameTimePos) / dfloat( st.tics );
700     }
701 
702 /*#if _DEBUG
703     if (mo.dPlayer)
704     {
705         qDebug() << "itp:" << interp << " mot:" << mo.tics << " stt:" << st.tics;
706     }
707 #endif*/
708 
709     // First find the modef for the interpoint. Intermark is 'stronger' than interrange.
710 
711     // Scan interlinks.
712     while (modef->interNext && modef->interNext->interMark <= interp)
713     {
714         modef = modef->interNext;
715     }
716 
717     if (!worldTime)
718     {
719         // Scale to the modeldef's interpolation range.
720         interp = modef->interRange[0] + interp
721                * (modef->interRange[1] - modef->interRange[0]);
722     }
723 
724     // What would be the next model? Check interlinks first.
725     if (retNextModef)
726     {
727         if (modef->interNext)
728         {
729             *retNextModef = modef->interNext;
730         }
731         else if (worldTime)
732         {
733             *retNextModef = App_Resources().modelDefForState(runtimeDefs.states.indexOf(&st), mo.selector);
734         }
735         else if (st.nextState > 0) // Check next state.
736         {
737             // Find the appropriate state based on interrange.
738             state_t *it = &runtimeDefs.states[st.nextState];
739             bool foundNext = false;
740             if (modef->interRange[1] < 1)
741             {
742                 // Current modef doesn't interpolate to the end, find the proper destination
743                 // modef (it isn't just the next one). Scan the states that follow (and
744                 // interlinks of each).
745                 bool stopScan = false;
746                 dint max = 20; // Let's not be here forever...
747                 while (!stopScan)
748                 {
749                     if(!((  !App_Resources().modelDefForState(runtimeDefs.states.indexOf(it))
750                           || App_Resources().modelDefForState(runtimeDefs.states.indexOf(it), mo.selector)->interRange[0] > 0)
751                          && it->nextState > 0))
752                     {
753                         stopScan = true;
754                     }
755                     else
756                     {
757                         // Scan interlinks, then go to the next state.
758                         FrameModelDef *mdit = App_Resources().modelDefForState(runtimeDefs.states.indexOf(it), mo.selector);
759                         if (mdit && mdit->interNext)
760                         {
761                             forever
762                             {
763                                 mdit = mdit->interNext;
764                                 if (mdit)
765                                 {
766                                     if (mdit->interRange[0] <= 0) // A new beginning?
767                                     {
768                                         *retNextModef = mdit;
769                                         foundNext = true;
770                                     }
771                                 }
772 
773                                 if (!mdit || foundNext)
774                                 {
775                                     break;
776                                 }
777                             }
778                         }
779 
780                         if (foundNext)
781                         {
782                             stopScan = true;
783                         }
784                         else
785                         {
786                             it = &runtimeDefs.states[it->nextState];
787                         }
788                     }
789 
790                     if (max-- <= 0)
791                         stopScan = true;
792                 }
793                 // @todo What about max == -1? What should 'it' be then?
794             }
795 
796             if (!foundNext)
797             {
798                 *retNextModef = App_Resources().modelDefForState(runtimeDefs.states.indexOf(it), mo.selector);
799             }
800         }
801     }
802 
803     if (retInter) *retInter = interp;
804 
805     return modef;
806 }
807 
808 #endif // __CLIENT__
809 
810 #undef Mobj_AngleSmoothed
Mobj_AngleSmoothed(mobj_t * mob)811 DENG_EXTERN_C angle_t Mobj_AngleSmoothed(mobj_t *mob)
812 {
813     if (!mob) return 0;
814 
815 #ifdef __CLIENT__
816     if (mob->dPlayer)
817     {
818         /// @todo What about splitscreen? We have smoothed angles for all local players.
819         if (P_GetDDPlayerIdx(mob->dPlayer) == ::consolePlayer
820             // $voodoodolls: Must be a real player to use the smoothed angle.
821             && mob->dPlayer->mo == mob)
822         {
823             viewdata_t const *vd = &DD_Player(::consolePlayer)->viewport();
824             return vd->current.angle();
825         }
826     }
827 
828     // Apply a Short Range Visual Offset?
829     if (::useSRVOAngle && !::netGame && !::playback)
830     {
831         return mob->visAngle << 16;
832     }
833 #endif
834 
835     return mob->angle;
836 }
837 
Mobj_ApproxPointDistance(mobj_t const * mob,coord_t const * point)838 coord_t Mobj_ApproxPointDistance(mobj_t const *mob, coord_t const *point)
839 {
840     if (!mob || !point) return 0;
841     return M_ApproxDistance(point[2] - mob->origin[2],
842                             M_ApproxDistance(point[0] - mob->origin[0],
843                                              point[1] - mob->origin[1]));
844 }
845 
Mobj_BobOffset(mobj_t const & mob)846 coord_t Mobj_BobOffset(mobj_t const &mob)
847 {
848     if (mob.ddFlags & DDMF_BOB)
849     {
850         return (sin(MOBJ_TO_ID(&mob) + App_World().time() / 1.8286 * 2 * PI) * 8);
851     }
852     return 0;
853 }
854 
Mobj_Alpha(mobj_t const & mob)855 dfloat Mobj_Alpha(mobj_t const &mob)
856 {
857     dfloat alpha = (mob.ddFlags & DDMF_BRIGHTSHADOW)? .80f :
858                    (mob.ddFlags & DDMF_SHADOW      )? .33f :
859                    (mob.ddFlags & DDMF_ALTSHADOW   )? .66f : 1;
860 
861     // The three highest bits of the selector are used for alpha.
862     // 0 = opaque (alpha -1)
863     // 1 = 1/8 transparent
864     // 4 = 1/2 transparent
865     // 7 = 7/8 transparent
866     dint selAlpha = mob.selector >> DDMOBJ_SELECTOR_SHIFT;
867     if (selAlpha & 0xe0)
868     {
869         alpha *= 1 - ((selAlpha & 0xe0) >> 5) / 8.0f;
870     }
871     else if (mob.translucency)
872     {
873         alpha *= 1 - mob.translucency * reciprocal255;
874     }
875     return alpha;
876 }
877 
Mobj_Radius(mobj_t const & mobj)878 coord_t Mobj_Radius(mobj_t const &mobj)
879 {
880     return mobj.radius;
881 }
882 
883 #ifdef __CLIENT__
Mobj_ShadowRadius(mobj_t const & mobj)884 coord_t Mobj_ShadowRadius(mobj_t const &mobj)
885 {
886     if (useModels)
887     {
888         if (FrameModelDef *modef = Mobj_ModelDef(mobj))
889         {
890             if (modef->shadowRadius > 0)
891             {
892                 return modef->shadowRadius;
893             }
894         }
895     }
896     // Fall back to the visual radius.
897     return Mobj_VisualRadius(mobj);
898 }
899 #endif
900 
Mobj_VisualRadius(mobj_t const & mob)901 coord_t Mobj_VisualRadius(mobj_t const &mob)
902 {
903 #ifdef __CLIENT__
904     // Is a model in effect?
905     if (useModels)
906     {
907         if (FrameModelDef *modef = Mobj_ModelDef(mob))
908         {
909             return modef->visualRadius;
910         }
911     }
912 
913     // Is a sprite in effect?
914     if (Record const *sprite = Mobj_SpritePtr(mob))
915     {
916         return Rend_VisualRadius(*sprite);
917     }
918 #endif
919 
920     // Use the physical radius.
921     return Mobj_Radius(mob);
922 }
923 
Mobj_Bounds(mobj_t const & mobj)924 AABoxd Mobj_Bounds(mobj_t const &mobj)
925 {
926     Vector2d const origin = Mobj_Origin(mobj);
927     ddouble const radius  = Mobj_Radius(mobj);
928     return AABoxd(origin.x - radius, origin.y - radius,
929                   origin.x + radius, origin.y + radius);
930 }
931 
D_CMD(InspectMobj)932 D_CMD(InspectMobj)
933 {
934     DENG2_UNUSED(src);
935 
936     if (argc != 2)
937     {
938         LOG_SCR_NOTE("Usage: %s (mobj-id)") << argv[0];
939         return true;
940     }
941 
942     // Get the ID.
943     auto const id = thid_t( String(argv[1]).toInt() );
944     // Find the map-object.
945     mobj_t *mob   = App_World().map().thinkers().mobjById(id);
946     if (!mob)
947     {
948         LOG_MAP_ERROR("Mobj with id %i not found") << id;
949         return false;
950     }
951 
952     char const *mobType = "Mobj";
953 #ifdef __CLIENT__
954     ClientMobjThinkerData::RemoteSync *info = ClMobj_GetInfo(mob);
955     if (info) mobType = "CLMOBJ";
956 #endif
957 
958     LOG_MAP_MSG("%s %i [%p] State:%s (%i)")
959             << mobType << id << mob << Def_GetStateName(mob->state) << ::runtimeDefs.states.indexOf(mob->state);
960     LOG_MAP_MSG("Type:%s (%i) Info:[%p] %s")
961             << DED_Definitions()->getMobjName(mob->type) << mob->type << mob->info
962             << (mob->info ? QString(" (%1)").arg(::runtimeDefs.mobjInfo.indexOf(mob->info)) : "");
963     LOG_MAP_MSG("Tics:%i ddFlags:%08x") << mob->tics << mob->ddFlags;
964 #ifdef __CLIENT__
965     if (info)
966     {
967         LOG_MAP_MSG("Cltime:%i (now:%i) Flags:%04x") << info->time << Timer_RealMilliseconds() << info->flags;
968     }
969 #endif
970     LOG_MAP_MSG("Flags:%08x Flags2:%08x Flags3:%08x") << mob->flags << mob->flags2 << mob->flags3;
971     LOG_MAP_MSG("Height:%f Radius:%f") << mob->height << mob->radius;
972     LOG_MAP_MSG("Angle:%x Pos:%s Mom:%s")
973             << mob->angle
974             << Vector3d(mob->origin).asText()
975             << Vector3d(mob->mom).asText();
976 #ifdef __CLIENT__
977     LOG_MAP_MSG("VisAngle:%x") << mob->visAngle;
978 #endif
979     LOG_MAP_MSG("%sZ:%f %sZ:%f")
980         << Sector::planeIdAsText(Sector::Floor  ).upperFirstChar() << mob->floorZ
981         << Sector::planeIdAsText(Sector::Ceiling).upperFirstChar() << mob->ceilingZ;
982 
983     if (Subsector *subsec = Mobj_SubsectorPtr(*mob))
984     {
985         LOG_MAP_MSG("Sector:%i (%sZ:%f %sZ:%f)")
986                 << subsec->sector().indexInMap()
987                 << Sector::planeIdAsText(Sector::Floor  ) << subsec->sector().floor  ().height()
988                 << Sector::planeIdAsText(Sector::Ceiling) << subsec->sector().ceiling().height();
989     }
990 
991     if (mob->onMobj)
992     {
993         LOG_MAP_MSG("onMobj:%i") << mob->onMobj->thinker.id;
994     }
995 
996     return true;
997 }
998 
Mobj_ConsoleRegister()999 void Mobj_ConsoleRegister()
1000 {
1001     C_CMD("inspectmobj",    "i",    InspectMobj);
1002 
1003 #ifdef __CLIENT__
1004     C_VAR_BYTE("rend-mobj-light-auto", &mobjAutoLights, 0, 0, 1);
1005 #endif
1006 }
1007