diff --git a/doomsday/client/include/resource/resourcesystem.h b/doomsday/client/include/resource/resourcesystem.h index d40abbf698..63e81e62ed 100644 --- a/doomsday/client/include/resource/resourcesystem.h +++ b/doomsday/client/include/resource/resourcesystem.h @@ -121,6 +121,10 @@ class ResourceSystem : public de::System SpriteSet &spriteSet(int spriteId); +#ifdef __CLIENT__ + void cacheSpriteSet(int spriteId, de::MaterialVariantSpec const &spec); +#endif + /** * Provides access to the Textures collection. */ diff --git a/doomsday/client/include/resource/sprite.h b/doomsday/client/include/resource/sprite.h index 24c989b4ec..363faaaee4 100644 --- a/doomsday/client/include/resource/sprite.h +++ b/doomsday/client/include/resource/sprite.h @@ -23,7 +23,7 @@ #ifndef DENG_RESOURCE_SPRITE_H #define DENG_RESOURCE_SPRITE_H -#include "dd_types.h" +#include #include #include @@ -46,46 +46,57 @@ class Lumobj; class Sprite { public: + /// Required view angle is missing. @ingroup errors + DENG2_ERROR(MissingViewAngleError); + static int const max_angles = 8; + struct ViewAngle { + Material *material; + bool mirrorX; + + ViewAngle() : material(0), mirrorX(false) + {} + }; + public: Sprite(); Sprite(Sprite const &other); Sprite &operator = (Sprite const &other); - void newViewAngle(Material *material, uint rotation, bool flipped); + void newViewAngle(Material *material, uint rotation, bool mirrorX); /** - * Select an appropriate material for visualizing the sprite given a mobj's - * angle and relative angle with the viewer (the 'eye'). - * - * @param mobjAngle Angle of the mobj in the map coordinate space. - * @param angleToEye Relative angle of the mobj from the view position. - * @param noRotation @c true= Ignore rotations and always use the "front". + * Returns @c true iff a view angle is defined for the specified @a rotation. * - * Return values: - * @param flipX @c true= chosen material should be flipped on the X axis. - * @param flipY @c true= chosen material should be flipped on the Y axis. - * - * @return The chosen material otherwise @c 0. + * @param rotation Rotation index/identifier to lookup the material for. The + * valid range is [0..max_angles) */ - Material *material(angle_t mobjAngle, angle_t angleToEye, bool noRotation = false, - bool *flipX = 0, bool *flipY = 0) const; + bool hasViewAngle(int rotation) const; /** - * Returns the material attributed to the specified rotation. + * Returns the view angle for the specified @a rotation. * * @param rotation Rotation index/identifier to lookup the material for. The - * valid range is [0...SPRITEFRAME_MAX_ANGLES) + * valid range is [0..max_angles) * - * Return values: - * @param flipX @c true= chosen material should be flipped on the X axis. - * @param flipY @c true= chosen material should be flipped on the Y axis. + * @return The viewAngle associated with the specified rotation. + */ + ViewAngle const &viewAngle(int rotation) const; + + /** + * Select an appropriate view angle for visualizing the sprite given a mobj's + * angle and relative angle with the viewer (the 'eye'). * - * @return The attributed material otherwise @c 0. + * @param mobjAngle Angle of the mobj in the map coordinate space. + * @param angleToEye Relative angle of the mobj from the view position. + * @param noRotation @c true= Ignore rotations and always use the "front". + * + * @return The viewAngle associated with the chosen rotation. */ - Material *material(int rotation = 0, bool *flipX = 0, bool *flipY = 0) const; + ViewAngle const &closestViewAngle(angle_t mobjAngle, angle_t angleToEye, + bool noRotation = false) const; #ifdef __CLIENT__ /** @@ -104,4 +115,6 @@ class Sprite DENG2_PRIVATE(d) }; +typedef Sprite::ViewAngle SpriteViewAngle; + #endif // DENG_RESOURCE_SPRITE_H diff --git a/doomsday/client/src/render/api_render.cpp b/doomsday/client/src/render/api_render.cpp index aa15cd9f24..cef73bf27a 100644 --- a/doomsday/client/src/render/api_render.cpp +++ b/doomsday/client/src/render/api_render.cpp @@ -55,9 +55,8 @@ DENG_EXTERN_C boolean R_GetSpriteInfo(int spriteId, int frame, spriteinfo_t *inf } de::zapPtr(info); - bool flip; - info->material = sprite->material(0, &flip); - info->flip = flip; + info->material = sprite->viewAngle(0).material; + info->flip = sprite->viewAngle(0).mirrorX; if(novideo) { diff --git a/doomsday/client/src/render/billboard.cpp b/doomsday/client/src/render/billboard.cpp index b5c80cab30..9cacef529f 100644 --- a/doomsday/client/src/render/billboard.cpp +++ b/doomsday/client/src/render/billboard.cpp @@ -210,8 +210,8 @@ static void setupPSpriteParams(rendpspriteparams_t *params, vispsprite_t *spr) Sprite const *sprite = &App_ResourceSystem().sprite(spriteIdx, frameIdx); - bool flip; - Material *material = sprite->material(0, &flip); + Material *material = sprite->viewAngle(0).material; + bool flip = sprite->viewAngle(0).mirrorX; MaterialVariantSpec const &spec = App_Materials().variantSpec(PSpriteContext, 0, 1, 0, 0, GL_CLAMP_TO_EDGE, GL_CLAMP_TO_EDGE, diff --git a/doomsday/client/src/render/r_main.cpp b/doomsday/client/src/render/r_main.cpp index 0b8a4106a9..75148cfc5e 100644 --- a/doomsday/client/src/render/r_main.cpp +++ b/doomsday/client/src/render/r_main.cpp @@ -1553,19 +1553,6 @@ static int findSpriteOwner(thinker_t *th, void *context) return false; // Continue iteration. } -static void cacheSpriteSet(int spriteId, MaterialVariantSpec const &spec) -{ - ResourceSystem::SpriteSet const &sprites = App_ResourceSystem().spriteSet(spriteId); - foreach(Sprite *sprite, sprites) - for(int i = 0; i < Sprite::max_angles; ++i) - { - if(Material *material = sprite->material(i)) - { - App_Materials().cache(*material, spec); - } - } -} - #undef Rend_CacheForMobjType DENG_EXTERN_C void Rend_CacheForMobjType(int num) { @@ -1586,7 +1573,7 @@ DENG_EXTERN_C void Rend_CacheForMobjType(int num) state_t *state = toState(i); DENG2_ASSERT(state != 0); - cacheSpriteSet(state->sprite, spec); + App_ResourceSystem().cacheSpriteSet(state->sprite, spec); } /// @todo What about sounds? } @@ -1647,7 +1634,7 @@ void Rend_CacheForMap() findSpriteOwner, &i)) { // This sprite is used by some state of at least one mobj. - cacheSpriteSet(i, spec); + App_ResourceSystem().cacheSpriteSet(i, spec); } } } diff --git a/doomsday/client/src/render/r_things.cpp b/doomsday/client/src/render/r_things.cpp index 5a677c3f4f..2d14ded062 100644 --- a/doomsday/client/src/render/r_things.cpp +++ b/doomsday/client/src/render/r_things.cpp @@ -206,9 +206,25 @@ void R_ProjectSprite(mobj_t *mo) // Decide which material to use according to the sprite's angle and position // relative to that of the viewer. - bool matFlipS, matFlipT; - Material *mat = sprite->material(mo->angle, R_ViewPointToAngle(mo->origin), - mf != 0, &matFlipS, &matFlipT); + Material *mat = 0; + bool matFlipS = false; + bool matFlipT = false; + + try + { + SpriteViewAngle const &sprViewAngle = + sprite->closestViewAngle(mo->angle, R_ViewPointToAngle(mo->origin), mf != 0); + + mat = sprViewAngle.material; + matFlipS = sprViewAngle.mirrorX; + } + catch(Sprite::MissingViewAngleError const &er) + { + // Log but otherwise ignore this error. + LOG_WARNING(er.asText() + ". Projecting sprite '%i' frame '%i', ignoring.") + << mo->sprite << mo->frame; + } + if(!mat) return; // A valid sprite texture in the "Sprites" scheme is required. @@ -428,68 +444,78 @@ void R_ProjectSprite(mobj_t *mo) // Do we need to project a flare source too? if(mo->lumIdx != Lumobj::NoIndex) { - Material *mat = sprite->material(mo->angle, R_ViewPointToAngle(mo->origin)); - if(!mat) return; + try + { + SpriteViewAngle const &sprViewAngle = + sprite->closestViewAngle(mo->angle, R_ViewPointToAngle(mo->origin)); - // A valid sprite texture in the "Sprites" scheme is required. - MaterialSnapshot const &ms = mat->prepare(Rend_SpriteMaterialSpec(mo->tclass, mo->tmap)); - if(!ms.hasTexture(MTU_PRIMARY)) - return; - Texture &tex = ms.texture(MTU_PRIMARY).generalCase(); - if(tex.manifest().schemeName().compareWithoutCase("Sprites")) - return; + Material *mat = sprViewAngle.material; - pointlight_analysis_t const *pl = (pointlight_analysis_t const *) - ms.texture(MTU_PRIMARY).generalCase().analysisDataPointer(Texture::BrightPointAnalysis); - DENG_ASSERT(pl != 0); + // A valid sprite texture in the "Sprites" scheme is required. + MaterialSnapshot const &ms = mat->prepare(Rend_SpriteMaterialSpec(mo->tclass, mo->tmap)); + if(!ms.hasTexture(MTU_PRIMARY)) + return; + Texture &tex = ms.texture(MTU_PRIMARY).generalCase(); + if(tex.manifest().schemeName().compareWithoutCase("Sprites")) + return;; + pointlight_analysis_t const *pl = (pointlight_analysis_t const *) + ms.texture(MTU_PRIMARY).generalCase().analysisDataPointer(Texture::BrightPointAnalysis); + DENG2_ASSERT(pl != 0); - Lumobj const *lum = cluster.sector().map().lumobj(mo->lumIdx); + Lumobj const *lum = cluster.sector().map().lumobj(mo->lumIdx); - vissprite_t *vis = R_NewVisSprite(VSPR_FLARE); + vissprite_t *vis = R_NewVisSprite(VSPR_FLARE); - vis->distance = distFromEye; + vis->distance = distFromEye; - // Determine the exact center of the flare. - vis->origin = moPos + visOff; - vis->origin.z += lum->zOffset(); + // Determine the exact center of the flare. + vis->origin = moPos + visOff; + vis->origin.z += lum->zOffset(); - float flareSize = pl->brightMul; - // X offset to the flare position. - float xOffset = ms.width() * pl->originX - -tex.origin().x; + float flareSize = pl->brightMul; + // X offset to the flare position. + float xOffset = ms.width() * pl->originX - -tex.origin().x; - // Does the mobj have an active light definition? - ded_light_t const *def = (mo->state? stateLights[mo->state - states] : 0); - if(def) - { - if(def->size) - flareSize = def->size; - if(def->haloRadius) - flareSize = def->haloRadius; - if(def->offset[VX]) - xOffset = def->offset[VX]; - - vis->data.flare.flags = def->flags; - } + // Does the mobj have an active light definition? + ded_light_t const *def = (mo->state? stateLights[mo->state - states] : 0); + if(def) + { + if(def->size) + flareSize = def->size; + if(def->haloRadius) + flareSize = def->haloRadius; + if(def->offset[VX]) + xOffset = def->offset[VX]; + + vis->data.flare.flags = def->flags; + } - vis->data.flare.size = flareSize * 60 * (50 + haloSize) / 100.0f; - if(vis->data.flare.size < 8) - vis->data.flare.size = 8; + vis->data.flare.size = flareSize * 60 * (50 + haloSize) / 100.0f; + if(vis->data.flare.size < 8) + vis->data.flare.size = 8; - // Color is taken from the associated lumobj. - V3f_Set(vis->data.flare.color, lum->color().x, lum->color().y, lum->color().z); + // Color is taken from the associated lumobj. + V3f_Set(vis->data.flare.color, lum->color().x, lum->color().y, lum->color().z); - vis->data.flare.factor = mo->haloFactors[viewPlayer - ddPlayers]; - vis->data.flare.xOff = xOffset; - vis->data.flare.mul = 1; - vis->data.flare.tex = 0; + vis->data.flare.factor = mo->haloFactors[viewPlayer - ddPlayers]; + vis->data.flare.xOff = xOffset; + vis->data.flare.mul = 1; + vis->data.flare.tex = 0; - if(def && def->flare) - { - de::Uri const &flaremapResourceUri = *reinterpret_cast(def->flare); - if(flaremapResourceUri.path().toStringRef().compareWithoutCase("-")) + if(def && def->flare) { - vis->data.flare.tex = GL_PrepareFlaremap(flaremapResourceUri); + de::Uri const &flaremapResourceUri = *reinterpret_cast(def->flare); + if(flaremapResourceUri.path().toStringRef().compareWithoutCase("-")) + { + vis->data.flare.tex = GL_PrepareFlaremap(flaremapResourceUri); + } } } + catch(Sprite::MissingViewAngleError const &er) + { + // Log but otherwise ignore this error. + LOG_WARNING(er.asText() + ". Projecting flare source for sprite '%i' frame '%i', ignoring.") + << mo->sprite << mo->frame; + } } } diff --git a/doomsday/client/src/render/rend_main.cpp b/doomsday/client/src/render/rend_main.cpp index 311d500f48..bf912ac400 100644 --- a/doomsday/client/src/render/rend_main.cpp +++ b/doomsday/client/src/render/rend_main.cpp @@ -2670,8 +2670,9 @@ static int projectSpriteWorker(mobj_t &mo, void * /*context*/) if(cluster.visCeiling().surface().hasSkyMaskedMaterial()) { Sprite *sprite = App_ResourceSystem().spritePtr(mo.sprite, mo.frame); - if(Material *material = sprite->material()) + if(sprite->hasViewAngle(0)) { + Material *material = sprite->viewAngle(0).material; if(!(mo.dPlayer && (mo.dPlayer->flags & DDPF_CAMERA)) && mo.origin[VZ] <= cluster.visCeiling().heightSmoothed() && mo.origin[VZ] >= cluster.visFloor().heightSmoothed()) diff --git a/doomsday/client/src/resource/models.cpp b/doomsday/client/src/resource/models.cpp index 4a664f2ab1..1505e9ab1a 100644 --- a/doomsday/client/src/resource/models.cpp +++ b/doomsday/client/src/resource/models.cpp @@ -950,8 +950,9 @@ static void scaleModelToSprite(modeldef_t &mf, int spriteIdx, int frameIdx) { Sprite *sprite = App_ResourceSystem().spritePtr(spriteIdx, frameIdx); if(!sprite) return; + if(!sprite->hasViewAngle(0)) return; - MaterialSnapshot const &ms = sprite->material(0)->prepare(Rend_SpriteMaterialSpec()); + MaterialSnapshot const &ms = sprite->viewAngle(0).material->prepare(Rend_SpriteMaterialSpec()); Texture const &tex = ms.texture(MTU_PRIMARY).generalCase(); int off = de::max(0, -tex.origin().y - ms.height()); scaleModel(mf, ms.height(), off); diff --git a/doomsday/client/src/resource/resourcesystem.cpp b/doomsday/client/src/resource/resourcesystem.cpp index 61dff6c7f3..4e39bc66e5 100644 --- a/doomsday/client/src/resource/resourcesystem.cpp +++ b/doomsday/client/src/resource/resourcesystem.cpp @@ -1514,6 +1514,21 @@ void ResourceSystem::initSprites() LOG_INFO(String("Completed in %1 seconds.").arg(begunAt.since(), 0, 'g', 2)); } +#ifdef __CLIENT__ +void ResourceSystem::cacheSpriteSet(int spriteId, MaterialVariantSpec const &spec) +{ + SpriteSet const &set = spriteSet(spriteId); + foreach(Sprite *sprite, set) + for(int i = 0; i < Sprite::max_angles; ++i) + { + if(sprite->hasViewAngle(i)) + { + d->materials.cache(*sprite->viewAngle(0).material, spec); + } + } +} +#endif + void ResourceSystem::clearAllColorPalettes() { d->colorPaletteNames.clear(); diff --git a/doomsday/client/src/resource/sprite.cpp b/doomsday/client/src/resource/sprite.cpp index ed4c370725..2823f7f210 100644 --- a/doomsday/client/src/resource/sprite.cpp +++ b/doomsday/client/src/resource/sprite.cpp @@ -36,22 +36,18 @@ using namespace de; DENG2_PIMPL_NOREF(Sprite) { - byte rotate; ///< 0= no rotations, 1= only front, 2= more... - Material *mats[8]; ///< Material to use for view angles 0-7 - byte flip[8]; ///< Flip (1 = flip) to use for view angles 0-7 + byte rotate; ///< 0= no rotations, 1= only front, 2= more... + ViewAngle viewAngles[max_angles]; - Instance() - : rotate(0) - { - zap(mats); - zap(flip); - } + Instance() : rotate(0) + {} - Instance(Instance const &other) - : rotate(other.rotate) + Instance(Instance const &other) : rotate(other.rotate) { - std::memcpy(mats, other.mats, sizeof(mats)); - std::memcpy(flip, other.flip, sizeof(flip)); + for(int i = 0; i < max_angles; ++i) + { + viewAngles[i] = other.viewAngles[i]; + } } }; @@ -67,7 +63,16 @@ Sprite &Sprite::operator = (Sprite const &other) return *this; } -void Sprite::newViewAngle(Material *material, uint rotation, bool flipped) +bool Sprite::hasViewAngle(int rotation) const +{ + if(rotation >= 0 && rotation < max_angles) + { + return d->viewAngles[rotation].material != 0; + } + return false; +} + +void Sprite::newViewAngle(Material *material, uint rotation, bool mirrorX) { if(rotation > 8) return; @@ -75,10 +80,10 @@ void Sprite::newViewAngle(Material *material, uint rotation, bool flipped) { // This frame should be used for all rotations. d->rotate = false; - for(int r = 0; r < 8; ++r) + for(int i = 0; i < max_angles; ++i) { - d->mats[r] = material; - d->flip[r] = byte( flipped ); + d->viewAngles[i].material = material; + d->viewAngles[i].mirrorX = mirrorX; } return; } @@ -86,27 +91,22 @@ void Sprite::newViewAngle(Material *material, uint rotation, bool flipped) rotation--; // Make 0 based. d->rotate = true; - d->mats[rotation] = material; - d->flip[rotation] = byte( flipped ); + d->viewAngles[rotation].material = material; + d->viewAngles[rotation].mirrorX = mirrorX; } -Material *Sprite::material(int rotation, bool *flipX, bool *flipY) const +Sprite::ViewAngle const &Sprite::viewAngle(int rotation) const { - if(flipX) *flipX = false; - if(flipY) *flipY = false; - - if(rotation < 0 || rotation >= max_angles) + if(rotation >= 0 && rotation < max_angles) { - return 0; + return d->viewAngles[rotation]; } - - if(flipX) *flipX = CPP_BOOL(d->flip[rotation]); - - return d->mats[rotation]; + /// @throw MissingViewAngle Specified an invalid rotation. + throw MissingViewAngleError("Sprite::viewAngle", "Invalid rotation" + String::number(rotation)); } -Material *Sprite::material(angle_t mobjAngle, angle_t angleToEye, - bool noRotation, bool *flipX, bool *flipY) const +Sprite::ViewAngle const &Sprite::closestViewAngle(angle_t mobjAngle, angle_t angleToEye, + bool noRotation) const { int rotation = 0; // Use single rotation for all viewing angles (default). @@ -116,7 +116,7 @@ Material *Sprite::material(angle_t mobjAngle, angle_t angleToEye, rotation = (angleToEye - mobjAngle + (unsigned) (ANG45 / 2) * 9) >> 29; } - return material(rotation, flipX, flipY); + return viewAngle(rotation); } #ifdef __CLIENT__ @@ -124,8 +124,8 @@ Lumobj *Sprite::generateLumobj() const { // Always use rotation zero. /// @todo We could do better here... - Material *mat = material(); - if(!mat) return 0; + if(!hasViewAngle(0)) return 0; + Material *mat = viewAngle(0).material; // Ensure we have up-to-date information about the material. MaterialSnapshot const &ms = mat->prepare(Rend_SpriteMaterialSpec()); diff --git a/doomsday/client/src/world/p_mobj.cpp b/doomsday/client/src/world/p_mobj.cpp index c74d89e844..573f3ed03d 100644 --- a/doomsday/client/src/world/p_mobj.cpp +++ b/doomsday/client/src/world/p_mobj.cpp @@ -369,8 +369,9 @@ void Mobj_GenerateLumobjs(mobj_t *mo) if(!sprite) return; // Always use the front rotation when determining light properties. - Material *mat = sprite->material(); - if(!mat) return; + if(!sprite->hasViewAngle(0)) return; + + Material *mat = sprite->viewAngle(0).material; MaterialSnapshot const &ms = mat->prepare(Rend_SpriteMaterialSpec()); if(!ms.hasTexture(MTU_PRIMARY)) return; // Unloadable texture? @@ -462,8 +463,9 @@ float Mobj_ShadowStrength(mobj_t *mo) if(!currentModelDefForMobj(*mo)) { if(Sprite *sprite = App_ResourceSystem().spritePtr(mo->sprite, mo->frame)) - if(Material *mat = sprite->material()) + if(sprite->hasViewAngle(0)) { + Material *mat = sprite->viewAngle(0).material; // Ensure we've prepared this. MaterialSnapshot const &ms = mat->prepare(Rend_SpriteMaterialSpec()); @@ -581,8 +583,9 @@ coord_t Mobj_VisualRadius(mobj_t const &mobj) // Use the sprite frame's width? if(Sprite *sprite = App_ResourceSystem().spritePtr(mobj.sprite, mobj.frame)) - if(Material *material = sprite->material()) + if(sprite->hasViewAngle(0)) { + Material *material = sprite->viewAngle(0).material; MaterialSnapshot const &ms = material->prepare(Rend_SpriteMaterialSpec()); return ms.width() / 2; }