From 46d36cf5c78bf55751dc8c9e58eb11ecf1320e27 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Ricardo=20Lu=C3=ADs=20Vaz=20Silva?= Date: Fri, 9 Dec 2022 16:46:38 -0300 Subject: [PATCH] add BobWeapon3D --- src/common/engine/namedef.h | 1 + src/common/scripting/vm/vm.h | 6 + src/playsim/p_pspr.cpp | 20 ++ src/playsim/p_pspr.h | 1 + src/r_data/models.cpp | 18 +- src/r_data/models.h | 2 +- src/rendering/hwrenderer/scene/hw_drawinfo.h | 3 + src/rendering/hwrenderer/scene/hw_weapon.cpp | 256 +++++++++++++----- src/rendering/hwrenderer/scene/hw_weapon.h | 16 +- .../zscript/actors/inventory/weapons.zs | 4 + wadsrc/static/zscript/actors/player/player.zs | 5 + 11 files changed, 262 insertions(+), 70 deletions(-) diff --git a/src/common/engine/namedef.h b/src/common/engine/namedef.h index 86543ca37..8fc201385 100644 --- a/src/common/engine/namedef.h +++ b/src/common/engine/namedef.h @@ -79,6 +79,7 @@ xx(__decorate_internal_float__) // Per-actor sound channels (for deprecated PlaySoundEx function) Do not separate this block!!! xx(Auto) xx(Weapon) +xx(BobPivot3D) xx(Voice) xx(Item) xx(Body) diff --git a/src/common/scripting/vm/vm.h b/src/common/scripting/vm/vm.h index ef089eba5..8a19d5f77 100644 --- a/src/common/scripting/vm/vm.h +++ b/src/common/scripting/vm/vm.h @@ -215,6 +215,11 @@ struct VMReturn Location = loc; RegType = REGT_FLOAT | REGT_MULTIREG2; } + void Vec3At(DVector3 *loc) + { + Location = loc; + RegType = REGT_FLOAT | REGT_MULTIREG3; + } void StringAt(FString *loc) { Location = loc; @@ -229,6 +234,7 @@ struct VMReturn VMReturn(int *loc) { IntAt(loc); } VMReturn(double *loc) { FloatAt(loc); } VMReturn(DVector2 *loc) { Vec2At(loc); } + VMReturn(DVector3 *loc) { Vec3At(loc); } VMReturn(FString *loc) { StringAt(loc); } VMReturn(void **loc) { PointerAt(loc); } }; diff --git a/src/playsim/p_pspr.cpp b/src/playsim/p_pspr.cpp index 38560fba2..b32ce67e6 100644 --- a/src/playsim/p_pspr.cpp +++ b/src/playsim/p_pspr.cpp @@ -641,6 +641,26 @@ void P_BobWeapon (player_t *player, float *x, float *y, double ticfrac) *x = *y = 0; } +void P_BobWeapon3D (player_t *player, FVector3 *translation, FVector3 *rotation, double ticfrac) { + IFVIRTUALPTRNAME(player->mo, NAME_PlayerPawn, BobWeapon3D) + { + VMValue param[] = { player->mo, ticfrac }; + DVector3 t, r; + VMReturn returns[2]; + returns[0].Vec3At(&t); + returns[1].Vec3At(&r); + VMCall(func, param, 2, returns, 2); + translation->X = (float)t.X; + translation->Y = (float)t.Y; + translation->Z = (float)t.Z; + rotation->X = (float)r.X; + rotation->Y = (float)r.Y; + rotation->Z = (float)r.Z; + return; + } + *translation = *rotation = {}; +} + //--------------------------------------------------------------------------- // // PROC P_CheckWeaponButtons diff --git a/src/playsim/p_pspr.h b/src/playsim/p_pspr.h index a992e90d8..85add7b6b 100644 --- a/src/playsim/p_pspr.h +++ b/src/playsim/p_pspr.h @@ -156,6 +156,7 @@ void P_SetPsprite(player_t *player, PSPLayers id, FState *state, bool pending = void P_BringUpWeapon (player_t *player); void P_FireWeapon (player_t *player); void P_BobWeapon (player_t *player, float *x, float *y, double ticfrac); +void P_BobWeapon3D (player_t *player, FVector3 *translation, FVector3 *rotation, double ticfrac); DAngle P_BulletSlope (AActor *mo, FTranslatedLineTarget *pLineTarget = NULL, int aimflags = 0); AActor *P_AimTarget(AActor *mo); diff --git a/src/r_data/models.cpp b/src/r_data/models.cpp index b58dfaa47..fd7d0c06c 100644 --- a/src/r_data/models.cpp +++ b/src/r_data/models.cpp @@ -193,10 +193,9 @@ void RenderModel(FModelRenderer *renderer, float x, float y, float z, FSpriteMod renderer->EndDrawModel(actor->RenderStyle, smf); } -void RenderHUDModel(FModelRenderer *renderer, DPSprite *psp, float ofsX, float ofsY) +void RenderHUDModel(FModelRenderer *renderer, DPSprite *psp, FVector3 translation, FVector3 rotation, FVector3 rotation_pivot, FSpriteModelFrame *smf) { AActor * playermo = players[consoleplayer].camera; - FSpriteModelFrame *smf = psp->Caller != nullptr ? FindModelFrame(psp->Caller->modelData != nullptr ? psp->Caller->modelData->modelDef != NAME_None ? PClass::FindActor(psp->Caller->modelData->modelDef) : psp->Caller->GetClass() : psp->Caller->GetClass(), psp->GetSprite(), psp->GetFrame(), false) : nullptr; // [BB] No model found for this sprite, so we can't render anything. if (smf == nullptr) @@ -221,8 +220,19 @@ void RenderHUDModel(FModelRenderer *renderer, DPSprite *psp, float ofsX, float o objectToWorldMatrix.translate(smf->xoffset / smf->xscale, smf->zoffset / smf->zscale, smf->yoffset / smf->yscale); // [BB] Weapon bob, very similar to the normal Doom weapon bob. - objectToWorldMatrix.rotate(ofsX / 4, 0, 1, 0); - objectToWorldMatrix.rotate((ofsY - WEAPONTOP) / -4., 1, 0, 0); + + + + objectToWorldMatrix.translate(rotation_pivot.X, rotation_pivot.Y, rotation_pivot.Z); + + objectToWorldMatrix.rotate(rotation.X, 0, 1, 0); + objectToWorldMatrix.rotate(rotation.Y, 1, 0, 0); + objectToWorldMatrix.rotate(rotation.Z, 0, 0, 1); + + objectToWorldMatrix.translate(-rotation_pivot.X, -rotation_pivot.Y, -rotation_pivot.Z); + + objectToWorldMatrix.translate(translation.X, translation.Y, translation.Z); + // [BB] For some reason the jDoom models need to be rotated. objectToWorldMatrix.rotate(90.f, 0, 1, 0); diff --git a/src/r_data/models.h b/src/r_data/models.h index d347c0ba6..568ad5399 100644 --- a/src/r_data/models.h +++ b/src/r_data/models.h @@ -110,7 +110,7 @@ void BSPWalkCircle(FLevelLocals *Level, float x, float y, float radiusSquared, c } void RenderModel(FModelRenderer* renderer, float x, float y, float z, FSpriteModelFrame* smf, AActor* actor, double ticFrac); -void RenderHUDModel(FModelRenderer* renderer, DPSprite* psp, float ofsX, float ofsY); +void RenderHUDModel(FModelRenderer* renderer, DPSprite* psp, FVector3 translation, FVector3 rotation, FVector3 rotation_pivot, FSpriteModelFrame *smf); EXTERN_CVAR(Float, cl_scaleweaponfov) diff --git a/src/rendering/hwrenderer/scene/hw_drawinfo.h b/src/rendering/hwrenderer/scene/hw_drawinfo.h index 5ed4a09ff..68afcb246 100644 --- a/src/rendering/hwrenderer/scene/hw_drawinfo.h +++ b/src/rendering/hwrenderer/scene/hw_drawinfo.h @@ -216,6 +216,9 @@ private: float GetFogDensity(int lightlevel, PalEntry fogcolor, int sectorfogdensity, int blendfactor); bool CheckFog(sector_t *frontsector, sector_t *backsector); WeaponLighting GetWeaponLighting(sector_t *viewsector, const DVector3 &pos, int cm, area_t in_area, const DVector3 &playerpos); + + void PreparePlayerSprites2D(sector_t * viewsector, area_t in_area); + void PreparePlayerSprites3D(sector_t * viewsector, area_t in_area); public: void SetCameraPos(const DVector3 &pos) diff --git a/src/rendering/hwrenderer/scene/hw_weapon.cpp b/src/rendering/hwrenderer/scene/hw_weapon.cpp index 01cbf5328..0d7d67e58 100644 --- a/src/rendering/hwrenderer/scene/hw_weapon.cpp +++ b/src/rendering/hwrenderer/scene/hw_weapon.cpp @@ -89,7 +89,7 @@ void HWDrawInfo::DrawPSprite(HUDSprite *huds, FRenderState &state) state.AlphaFunc(Alpha_GEqual, 0); FHWModelRenderer renderer(this, state, huds->lightindex); - RenderHUDModel(&renderer, huds->weapon, huds->mx, huds->my); + RenderHUDModel(&renderer, huds->weapon, huds->translation, huds->rotation + FVector3(huds->mx / 4., (huds->my - WEAPONTOP) / 4., 0), FVector3(0, 0, 0), huds->mframe); state.SetVertexBuffer(screen->mVertexData); } else @@ -157,9 +157,9 @@ static bool isBright(DPSprite *psp) // //========================================================================== -static WeaponPosition GetWeaponPosition(player_t *player, double ticFrac) +static WeaponPosition2D GetWeaponPosition2D(player_t *player, double ticFrac) { - WeaponPosition w; + WeaponPosition2D w; P_BobWeapon(player, &w.bobx, &w.boby, ticFrac); // Interpolate the main weapon layer once so as to be able to add it to other layers. @@ -184,13 +184,53 @@ static WeaponPosition GetWeaponPosition(player_t *player, double ticFrac) return w; } +static WeaponPosition3D GetWeaponPosition3D(player_t *player, double ticFrac) +{ + WeaponPosition3D w; + P_BobWeapon3D(player, &w.translation, &w.rotation, ticFrac); + + // Interpolate the main weapon layer once so as to be able to add it to other layers. + if ((w.weapon = player->FindPSprite(PSP_WEAPON)) != nullptr) + { + if (w.weapon->firstTic) + { + w.wx = (float)w.weapon->x; + w.wy = (float)w.weapon->y; + } + else + { + w.wx = (float)(w.weapon->oldx + (w.weapon->x - w.weapon->oldx) * ticFrac); + w.wy = (float)(w.weapon->oldy + (w.weapon->y - w.weapon->oldy) * ticFrac); + } + auto weaponActor = w.weapon->GetCaller(); + if(weaponActor && weaponActor->IsKindOf(NAME_Weapon)) + { + DVector3 *dPivot = (DVector3*) weaponActor->ScriptVar(NAME_BobPivot3D, nullptr); + w.pivot.X = (float) dPivot->X; + w.pivot.Y = (float) dPivot->Y; + w.pivot.Z = (float) dPivot->Z; + } + else + { + w.pivot = FVector3(0,0,0); + } + } + else + { + w.wx = 0; + w.wy = 0; + w.pivot = FVector3(0,0,0); + } + return w; +} + //========================================================================== // // Bobbing // //========================================================================== -static FVector2 BobWeapon(WeaponPosition &weap, DPSprite *psp, double ticFrac) +static FVector2 BobWeapon2D(WeaponPosition2D &weap, DPSprite *psp, double ticFrac) { if (psp->firstTic) { // Can't interpolate the first tic. @@ -215,6 +255,37 @@ static FVector2 BobWeapon(WeaponPosition &weap, DPSprite *psp, double ticFrac) return { sx, sy }; } +static FVector2 BobWeapon3D(WeaponPosition3D &weap, DPSprite *psp, FVector3 &translation, FVector3 &rotation, FVector3 &pivot, double ticFrac) +{ + if (psp->firstTic) + { // Can't interpolate the first tic. + psp->firstTic = false; + psp->ResetInterpolation(); + } + + float sx = float(psp->oldx + (psp->x - psp->oldx) * ticFrac); + float sy = float(psp->oldy + (psp->y - psp->oldy) * ticFrac); + float sz = 0; + + if (psp->Flags & PSPF_ADDBOB) + { + translation = (psp->Flags & PSPF_MIRROR) ? FVector3(-weap.translation.X, weap.translation.Y, weap.translation.Z) : weap.translation ; // TODO handle PSPF_MIRROR? + rotation = weap.rotation; + pivot = weap.pivot; + } + else + { + translation = rotation = pivot = FVector3(0,0,0); + } + + if (psp->Flags & PSPF_ADDWEAPON && psp->GetID() != PSP_WEAPON) + { + sx += weap.wx; + sy += weap.wy; + } + return { sx, sy }; +} + //========================================================================== // // Lighting @@ -587,11 +658,119 @@ bool HUDSprite::GetWeaponRect(HWDrawInfo *di, DPSprite *psp, float sx, float sy, // R_DrawPlayerSprites // //========================================================================== +void HWDrawInfo::PreparePlayerSprites2D(sector_t * viewsector, area_t in_area) +{ + AActor * playermo = players[consoleplayer].camera; + player_t * player = playermo->player; + + const auto &vp = Viewpoint; + + AActor *camera = vp.camera; + + WeaponPosition2D weap = GetWeaponPosition2D(camera->player, vp.TicFrac); + WeaponLighting light = GetWeaponLighting(viewsector, vp.Pos, isFullbrightScene(), in_area, camera->Pos()); + + // hack alert! Rather than changing everything in the underlying lighting code let's just temporarily change + // light mode here to draw the weapon sprite. + auto oldlightmode = lightmode; + if (isSoftwareLighting()) SetFallbackLightMode(); + + for (DPSprite *psp = player->psprites; psp != nullptr && psp->GetID() < PSP_TARGETCENTER; psp = psp->GetNext()) + { + if (!psp->GetState()) continue; + + FSpriteModelFrame *smf = psp->Caller != nullptr ? FindModelFrame(psp->Caller->modelData != nullptr ? psp->Caller->modelData->modelDef != NAME_None ? PClass::FindActor(psp->Caller->modelData->modelDef) : psp->Caller->GetClass() : psp->Caller->GetClass(), psp->GetSprite(), psp->GetFrame(), false) : nullptr; + + // This is an 'either-or' proposition. This maybe needs some work to allow overlays with weapon models but as originally implemented this just won't work. + if (smf) continue; + + HUDSprite hudsprite; + hudsprite.owner = playermo; + hudsprite.mframe = smf; + hudsprite.weapon = psp; + + if (!hudsprite.GetWeaponRenderStyle(psp, camera, viewsector, light)) continue; + + FVector2 spos = BobWeapon2D(weap, psp, vp.TicFrac); + + hudsprite.dynrgb[0] = hudsprite.dynrgb[1] = hudsprite.dynrgb[2] = 0; + hudsprite.lightindex = -1; + // set the lighting parameters + if (hudsprite.RenderStyle.BlendOp != STYLEOP_Shadow && Level->HasDynamicLights && !isFullbrightScene() && gl_light_sprites) + { + GetDynSpriteLight(playermo, nullptr, hudsprite.dynrgb); + } + + if (!hudsprite.GetWeaponRect(this, psp, spos.X, spos.Y, player, vp.TicFrac)) continue; + hudsprites.Push(hudsprite); + } + lightmode = oldlightmode; +} + +void HWDrawInfo::PreparePlayerSprites3D(sector_t * viewsector, area_t in_area) +{ + AActor * playermo = players[consoleplayer].camera; + player_t * player = playermo->player; + + const auto &vp = Viewpoint; + + AActor *camera = vp.camera; + + WeaponPosition3D weap = GetWeaponPosition3D(camera->player, vp.TicFrac); + WeaponLighting light = GetWeaponLighting(viewsector, vp.Pos, isFullbrightScene(), in_area, camera->Pos()); + + // hack alert! Rather than changing everything in the underlying lighting code let's just temporarily change + // light mode here to draw the weapon sprite. + auto oldlightmode = lightmode; + if (isSoftwareLighting()) SetFallbackLightMode(); + + for (DPSprite *psp = player->psprites; psp != nullptr && psp->GetID() < PSP_TARGETCENTER; psp = psp->GetNext()) + { + if (!psp->GetState()) continue; + FSpriteModelFrame *smf = psp->Caller != nullptr ? FindModelFrame(psp->Caller->modelData != nullptr ? psp->Caller->modelData->modelDef != NAME_None ? PClass::FindActor(psp->Caller->modelData->modelDef) : psp->Caller->GetClass() : psp->Caller->GetClass(), psp->GetSprite(), psp->GetFrame(), false) : nullptr; + + // This is an 'either-or' proposition. This maybe needs some work to allow overlays with weapon models but as originally implemented this just won't work. + if (!smf) continue; + + HUDSprite hudsprite; + hudsprite.owner = playermo; + hudsprite.mframe = smf; + hudsprite.weapon = psp; + + if (!hudsprite.GetWeaponRenderStyle(psp, camera, viewsector, light)) continue; + + //FVector2 spos = BobWeapon3D(weap, psp, hudsprite.translation, hudsprite.rotation, hudsprite.pivot, vp.TicFrac); + + FVector2 spos = BobWeapon3D(weap, psp, hudsprite.translation, hudsprite.rotation, hudsprite.pivot, vp.TicFrac); + + hudsprite.dynrgb[0] = hudsprite.dynrgb[1] = hudsprite.dynrgb[2] = 0; + hudsprite.lightindex = -1; + // set the lighting parameters + if (hudsprite.RenderStyle.BlendOp != STYLEOP_Shadow && Level->HasDynamicLights && !isFullbrightScene() && gl_light_sprites) + { + hw_GetDynModelLight(playermo, lightdata); + hudsprite.lightindex = screen->mLights->UploadLights(lightdata); + LightProbe* probe = FindLightProbe(playermo->Level, playermo->X(), playermo->Y(), playermo->Center()); + if (probe) + { + hudsprite.dynrgb[0] = probe->Red; + hudsprite.dynrgb[1] = probe->Green; + hudsprite.dynrgb[2] = probe->Blue; + } + } + + // [BB] In the HUD model step we just render the model and break out. + hudsprite.mx = spos.X; + hudsprite.my = spos.Y; + + hudsprites.Push(hudsprite); + } + lightmode = oldlightmode; +} void HWDrawInfo::PreparePlayerSprites(sector_t * viewsector, area_t in_area) { - bool brightflash = false; AActor * playermo = players[consoleplayer].camera; player_t * player = playermo->player; @@ -608,67 +787,16 @@ void HWDrawInfo::PreparePlayerSprites(sector_t * viewsector, area_t in_area) return; const bool hudModelStep = IsHUDModelForPlayerAvailable(camera->player); - WeaponPosition weap = GetWeaponPosition(camera->player, vp.TicFrac); - WeaponLighting light = GetWeaponLighting(viewsector, vp.Pos, isFullbrightScene(), in_area, camera->Pos()); - - // hack alert! Rather than changing everything in the underlying lighting code let's just temporarily change - // light mode here to draw the weapon sprite. - auto oldlightmode = lightmode; - if (isSoftwareLighting()) SetFallbackLightMode(); - - for (DPSprite *psp = player->psprites; psp != nullptr && psp->GetID() < PSP_TARGETCENTER; psp = psp->GetNext()) + + if(hudModelStep) { - if (!psp->GetState()) continue; - FSpriteModelFrame *smf = psp->Caller != nullptr ? FindModelFrame(psp->Caller->modelData != nullptr ? psp->Caller->modelData->modelDef != NAME_None ? PClass::FindActor(psp->Caller->modelData->modelDef) : psp->Caller->GetClass() : psp->Caller->GetClass(), psp->GetSprite(), psp->GetFrame(), false) : nullptr; - // This is an 'either-or' proposition. This maybe needs some work to allow overlays with weapon models but as originally implemented this just won't work. - if (smf && !hudModelStep) continue; - if (!smf && hudModelStep) continue; - - HUDSprite hudsprite; - hudsprite.owner = playermo; - hudsprite.mframe = smf; - hudsprite.weapon = psp; - - if (!hudsprite.GetWeaponRenderStyle(psp, camera, viewsector, light)) continue; - - FVector2 spos = BobWeapon(weap, psp, vp.TicFrac); - - hudsprite.dynrgb[0] = hudsprite.dynrgb[1] = hudsprite.dynrgb[2] = 0; - hudsprite.lightindex = -1; - // set the lighting parameters - if (hudsprite.RenderStyle.BlendOp != STYLEOP_Shadow && Level->HasDynamicLights && !isFullbrightScene() && gl_light_sprites) - { - if (!hudModelStep) - { - GetDynSpriteLight(playermo, nullptr, hudsprite.dynrgb); - } - else - { - hw_GetDynModelLight(playermo, lightdata); - hudsprite.lightindex = screen->mLights->UploadLights(lightdata); - LightProbe* probe = FindLightProbe(playermo->Level, playermo->X(), playermo->Y(), playermo->Center()); - if (probe) - { - hudsprite.dynrgb[0] = probe->Red; - hudsprite.dynrgb[1] = probe->Green; - hudsprite.dynrgb[2] = probe->Blue; - } - } - } - - // [BB] In the HUD model step we just render the model and break out. - if (hudModelStep) - { - hudsprite.mx = spos.X; - hudsprite.my = spos.Y; - } - else - { - if (!hudsprite.GetWeaponRect(this, psp, spos.X, spos.Y, player, vp.TicFrac)) continue; - } - hudsprites.Push(hudsprite); + PreparePlayerSprites3D(viewsector,in_area); } - lightmode = oldlightmode; + else + { + PreparePlayerSprites2D(viewsector,in_area); + } + PrepareTargeterSprites(vp.TicFrac); } diff --git a/src/rendering/hwrenderer/scene/hw_weapon.h b/src/rendering/hwrenderer/scene/hw_weapon.h index 76df17d6c..97f39ad8e 100644 --- a/src/rendering/hwrenderer/scene/hw_weapon.h +++ b/src/rendering/hwrenderer/scene/hw_weapon.h @@ -11,13 +11,25 @@ struct HWDrawInfo; class FGameTexture; -struct WeaponPosition +struct WeaponPosition2D { float wx, wy; float bobx, boby; DPSprite *weapon; }; +struct WeaponPosition3D +{ + float wx, + wy; + + FVector3 translation, + rotation, + pivot; + + DPSprite *weapon; +}; + struct WeaponLighting { FColormap cm; @@ -43,6 +55,8 @@ struct HUDSprite float mx, my; float dynrgb[3]; + FVector3 rotation, translation, pivot; + int lightindex; void SetBright(bool isbelow); diff --git a/wadsrc/static/zscript/actors/inventory/weapons.zs b/wadsrc/static/zscript/actors/inventory/weapons.zs index 288ae8f8f..f40b7facb 100644 --- a/wadsrc/static/zscript/actors/inventory/weapons.zs +++ b/wadsrc/static/zscript/actors/inventory/weapons.zs @@ -37,6 +37,8 @@ class Weapon : StateProvider // AmmoUse1 will be set to the first attack's ammo use so that checking for empty weapons still works meta int SlotNumber; meta double SlotPriority; + + Vector3 BobPivot3D; // Pivot used for BobWeapon3D property AmmoGive: AmmoGive1; property AmmoGive1: AmmoGive1; @@ -63,6 +65,7 @@ class Weapon : StateProvider property SlotNumber: SlotNumber; property SlotPriority: SlotPriority; property LookScale: LookScale; + property BobPivot3D : BobPivot3D; flagdef NoAutoFire: WeaponFlags, 0; // weapon does not autofire flagdef ReadySndHalf: WeaponFlags, 1; // ready sound is played ~1/2 the time @@ -102,6 +105,7 @@ class Weapon : StateProvider Weapon.WeaponScaleY 1.2; Weapon.SlotNumber -1; Weapon.SlotPriority 32767; + Weapon.BobPivot3D (0.0, 0.0, 0.0); +WEAPONSPAWN DefaultStateUsage SUF_ACTOR|SUF_OVERLAY|SUF_WEAPON; } diff --git a/wadsrc/static/zscript/actors/player/player.zs b/wadsrc/static/zscript/actors/player/player.zs index e72bef838..6c9d8680e 100644 --- a/wadsrc/static/zscript/actors/player/player.zs +++ b/wadsrc/static/zscript/actors/player/player.zs @@ -2437,6 +2437,11 @@ class PlayerPawn : Actor } return p1 * (1. - ticfrac) + p2 * ticfrac; } + + virtual Vector3 /*translation*/ , Vector3 /*rotation*/ BobWeapon3D (double ticfrac) + { + return (0, 0, 0) , (BobWeapon(ticfrac) / 4, 0); + } //---------------------------------------------------------------------------- //