Parallax-aware zoom

How this works: parallax values configure perceived distance from camera
when it's moving along x and y axes. Assume that zoom is moving the
camera away and scale layers accordingly, with background layers
(furtherst away) changing the least.

New per-ItemGroup (LayerGroup) setting allows to set the new parallax
value independently from the other two. This can be used to do tricks
like on Time Shop zoom correctly or make it feel like the camera is
changing the field of view at the same time as moving in space.
This commit is contained in:
Fireball 2022-08-05 00:16:44 +01:00
parent b4914d5d27
commit a90c86e9a5
12 changed files with 55 additions and 19 deletions

View file

@ -322,7 +322,6 @@ MACRO_CONFIG_INT(ClShowOthers, cl_show_others, 0, 0, 2, CFGFLAG_CLIENT | CFGFLAG
MACRO_CONFIG_INT(ClShowOthersAlpha, cl_show_others_alpha, 40, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show players in other teams (alpha value, 0 invisible, 100 fully visible)")
MACRO_CONFIG_INT(ClOverlayEntities, cl_overlay_entities, 0, 0, 100, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Overlay game tiles with a percentage of opacity")
MACRO_CONFIG_INT(ClShowQuads, cl_showquads, 1, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Show quads (only interesting for mappers, or if your system has extremely bad performance)")
MACRO_CONFIG_INT(ClZoomBackgroundLayers, cl_zoom_background_layers, 0, 0, 1, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Zoom background layers")
MACRO_CONFIG_COL(ClBackgroundColor, cl_background_color, 128, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Background color") // 0 0 128
MACRO_CONFIG_COL(ClBackgroundEntitiesColor, cl_background_entities_color, 128, CFGFLAG_CLIENT | CFGFLAG_SAVE, "Background (entities) color") // 0 0 128
MACRO_CONFIG_STR(ClBackgroundEntities, cl_background_entities, 100, "", CFGFLAG_CLIENT | CFGFLAG_SAVE, "Background (entities)")

View file

@ -621,7 +621,7 @@ void CHud::RenderCursor()
if(!m_pClient->m_Snap.m_pLocalCharacter || Client()->State() == IClient::STATE_DEMOPLAYBACK)
return;
RenderTools()->MapScreenToGroup(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y, Layers()->GameGroup());
RenderTools()->MapScreenToInterface(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y);
// render cursor
int CurWeapon = m_pClient->m_Snap.m_pLocalCharacter->m_Weapon % NUM_WEAPONS;

View file

@ -1592,12 +1592,7 @@ void CMapLayers::OnRender()
(int)((x1 - x0) * Graphics()->ScreenWidth()), (int)((y1 - y0) * Graphics()->ScreenHeight()));
}
if((!g_Config.m_ClZoomBackgroundLayers || m_Type == TYPE_FULL_DESIGN) && !pGroup->m_ParallaxX && !pGroup->m_ParallaxY)
{
RenderTools()->MapScreenToGroup(Center.x, Center.y, pGroup, 1.0f);
}
else
RenderTools()->MapScreenToGroup(Center.x, Center.y, pGroup, GetCurCamera()->m_Zoom);
RenderTools()->MapScreenToGroup(Center.x, Center.y, pGroup, GetCurCamera()->m_Zoom);
for(int l = 0; l < pGroup->m_NumLayers; l++)
{

View file

@ -101,7 +101,7 @@ void CNamePlates::RenderNameplatePos(vec2 Position, const CNetObj_PlayerInfo *pP
// create nameplates at standard zoom
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
RenderTools()->MapScreenToGroup(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y, Layers()->GameGroup());
RenderTools()->MapScreenToInterface(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y);
m_aNamePlates[ClientID].m_NameTextWidth = TextRender()->TextWidth(0, FontSize, pName, -1, -1.0f);
@ -126,7 +126,7 @@ void CNamePlates::RenderNameplatePos(vec2 Position, const CNetObj_PlayerInfo *pP
// create nameplates at standard zoom
float ScreenX0, ScreenY0, ScreenX1, ScreenY1;
Graphics()->GetScreen(&ScreenX0, &ScreenY0, &ScreenX1, &ScreenY1);
RenderTools()->MapScreenToGroup(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y, Layers()->GameGroup());
RenderTools()->MapScreenToInterface(m_pClient->m_Camera.m_Center.x, m_pClient->m_Camera.m_Center.y);
m_aNamePlates[ClientID].m_ClanNameTextWidth = TextRender()->TextWidth(0, FontSizeClan, pClan, -1, -1.0f);

View file

@ -783,10 +783,15 @@ void CRenderTools::CalcScreenParams(float Aspect, float Zoom, float *pWidth, flo
}
void CRenderTools::MapScreenToWorld(float CenterX, float CenterY, float ParallaxX, float ParallaxY,
float OffsetX, float OffsetY, float Aspect, float Zoom, float *pPoints)
float ParallaxZoom, float OffsetX, float OffsetY, float Aspect, float Zoom, float *pPoints)
{
float Width, Height;
CalcScreenParams(Aspect, Zoom, &Width, &Height);
float Scale = (ParallaxZoom * (Zoom - 1.0f) + 100.0f) / 100.0f / Zoom;
Width *= Scale;
Height *= Scale;
CenterX *= ParallaxX / 100.0f;
CenterY *= ParallaxY / 100.0f;
pPoints[0] = OffsetX + CenterX - Width / 2;
@ -798,7 +803,15 @@ void CRenderTools::MapScreenToWorld(float CenterX, float CenterY, float Parallax
void CRenderTools::MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, float Zoom)
{
float aPoints[4];
MapScreenToWorld(CenterX, CenterY, pGroup->m_ParallaxX, pGroup->m_ParallaxY,
MapScreenToWorld(CenterX, CenterY, pGroup->m_ParallaxX, pGroup->m_ParallaxY, pGroup->GetParallaxZoom(),
pGroup->m_OffsetX, pGroup->m_OffsetY, Graphics()->ScreenAspect(), Zoom, aPoints);
Graphics()->MapScreen(aPoints[0], aPoints[1], aPoints[2], aPoints[3]);
}
void CRenderTools::MapScreenToInterface(float CenterX, float CenterY)
{
float aPoints[4];
MapScreenToWorld(CenterX, CenterY, 100.0f, 100.0f, 100.0f,
0, 0, Graphics()->ScreenAspect(), 1.0f, aPoints);
Graphics()->MapScreen(aPoints[0], aPoints[1], aPoints[2], aPoints[3]);
}

View file

@ -142,8 +142,9 @@ public:
// helpers
void CalcScreenParams(float Aspect, float Zoom, float *pWidth, float *pHeight);
void MapScreenToWorld(float CenterX, float CenterY, float ParallaxX, float ParallaxY,
float OffsetX, float OffsetY, float Aspect, float Zoom, float *pPoints);
void MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, float Zoom = 1.0f);
float ParallaxZoom, float OffsetX, float OffsetY, float Aspect, float Zoom, float *pPoints);
void MapScreenToGroup(float CenterX, float CenterY, CMapItemGroup *pGroup, float Zoom);
void MapScreenToInterface(float CenterX, float CenterY);
// DDRace

View file

@ -102,6 +102,7 @@ CLayerGroup::CLayerGroup()
m_OffsetY = 0;
m_ParallaxX = 100;
m_ParallaxY = 100;
m_ParallaxZoom = 100;
m_UseClipping = 0;
m_ClipX = 0;
@ -133,7 +134,7 @@ void CLayerGroup::Mapping(float *pPoints)
{
m_pMap->m_pEditor->RenderTools()->MapScreenToWorld(
m_pMap->m_pEditor->m_WorldOffsetX, m_pMap->m_pEditor->m_WorldOffsetY,
m_ParallaxX, m_ParallaxY, m_OffsetX, m_OffsetY,
m_ParallaxX, m_ParallaxY, 100.0f, m_OffsetX, m_OffsetY,
m_pMap->m_pEditor->Graphics()->ScreenAspect(), m_pMap->m_pEditor->m_WorldZoom, pPoints);
pPoints[0] += m_pMap->m_pEditor->m_EditorOffsetX;
@ -2870,7 +2871,7 @@ void CEditor::DoMapEditor(CUIRect View)
RenderTools()->MapScreenToWorld(
m_WorldOffsetX, m_WorldOffsetY,
100.0f, 100.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints);
100.0f, 100.0f, 100.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints);
if(i == 0)
{
@ -2912,7 +2913,7 @@ void CEditor::DoMapEditor(CUIRect View)
RenderTools()->MapScreenToWorld(
m_WorldOffsetX, m_WorldOffsetY,
100.0f, 100.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints);
100.0f, 100.0f, 100.0f, 0.0f, 0.0f, Aspect, 1.0f, aPoints);
CUIRect r;
r.x = aPoints[0];
@ -6118,7 +6119,7 @@ void CEditor::ZoomMouseTarget(float ZoomFactor)
float aPoints[4];
RenderTools()->MapScreenToWorld(
m_WorldOffsetX, m_WorldOffsetY,
100.0f, 100.0f, 0.0f, 0.0f, Graphics()->ScreenAspect(), m_WorldZoom, aPoints);
100.0f, 100.0f, 100.0f, 0.0f, 0.0f, Graphics()->ScreenAspect(), m_WorldZoom, aPoints);
float WorldWidth = aPoints[2] - aPoints[0];
float WorldHeight = aPoints[3] - aPoints[1];
@ -6230,6 +6231,7 @@ void CEditorMap::CreateDefault(IGraphics::CTextureHandle EntitiesTexture)
CLayerGroup *pGroup = NewGroup();
pGroup->m_ParallaxX = 0;
pGroup->m_ParallaxY = 0;
pGroup->m_ParallaxZoom = 0;
CLayerQuads *pLayer = new CLayerQuads;
pLayer->m_pEditor = m_pEditor;
CQuad *pQuad = pLayer->NewQuad(0, 0, 1600, 1200);

View file

@ -176,6 +176,7 @@ public:
int m_ParallaxX;
int m_ParallaxY;
int m_ParallaxZoom;
int m_UseClipping;
int m_ClipX;

View file

@ -184,6 +184,8 @@ int CEditorMap::Save(class IStorage *pStorage, const char *pFileName)
// save group name
StrToInts(GItem.m_aName, sizeof(GItem.m_aName) / sizeof(int), pGroup->m_aName);
GItem.m_ParallaxZoom = pGroup->m_ParallaxZoom;
for(const auto &pLayer : pGroup->m_vpLayers)
{
if(pLayer->m_Type == LAYERTYPE_TILES)
@ -607,6 +609,8 @@ int CEditorMap::Load(class IStorage *pStorage, const char *pFileName, int Storag
if(pGItem->m_Version >= 3)
IntsToStr(pGItem->m_aName, sizeof(pGroup->m_aName) / sizeof(int), pGroup->m_aName);
pGroup->m_ParallaxZoom = pGItem->GetParallaxZoom();
for(int l = 0; l < pGItem->m_NumLayers; l++)
{
CLayer *pLayer = nullptr;

View file

@ -320,6 +320,7 @@ int CEditor::PopupGroup(CEditor *pEditor, CUIRect View, void *pContext)
PROP_POS_Y,
PROP_PARA_X,
PROP_PARA_Y,
PROP_PARA_ZOOM,
PROP_USE_CLIPPING,
PROP_CLIP_X,
PROP_CLIP_Y,
@ -334,6 +335,7 @@ int CEditor::PopupGroup(CEditor *pEditor, CUIRect View, void *pContext)
{"Pos Y", -pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetY, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Para X", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxX, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Para Y", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxY, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Para Zoom", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxZoom, PROPTYPE_INT_SCROLL, -1000000, 1000000},
{"Use Clipping", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_UseClipping, PROPTYPE_BOOL, 0, 1},
{"Clip X", pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ClipX, PROPTYPE_INT_SCROLL, -1000000, 1000000},
@ -364,6 +366,8 @@ int CEditor::PopupGroup(CEditor *pEditor, CUIRect View, void *pContext)
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxX = NewVal;
else if(Prop == PROP_PARA_Y)
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxY = NewVal;
else if(Prop == PROP_PARA_ZOOM)
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_ParallaxZoom = NewVal;
else if(Prop == PROP_POS_X)
pEditor->m_Map.m_vpGroups[pEditor->m_SelectedGroup]->m_OffsetX = -NewVal;
else if(Prop == PROP_POS_Y)

View file

@ -66,6 +66,8 @@ void CLayers::Init(class IKernel *pKernel)
m_pGameGroup->m_ClipH = 0;
}
if(m_pGameGroup->m_Version >= 4)
m_pGameGroup->m_ParallaxZoom = 100;
//break;
}
if(pTilemap->m_Flags & TILESLAYERFLAG_TELE)
@ -158,6 +160,9 @@ void CLayers::InitBackground(class IMap *pMap)
m_pGameGroup->m_ClipW = 0;
m_pGameGroup->m_ClipH = 0;
}
if(m_pGameGroup->m_Version >= 4)
m_pGameGroup->m_ParallaxZoom = 100;
//We don't care about tile layers.
}
}

View file

@ -278,7 +278,7 @@ struct CMapItemGroup : public CMapItemGroup_v1
{
enum
{
CURRENT_VERSION = 3
CURRENT_VERSION = 4
};
int m_UseClipping;
@ -288,6 +288,18 @@ struct CMapItemGroup : public CMapItemGroup_v1
int m_ClipH;
int m_aName[3];
// ItemGroup's perceived distance from camera when zooming. Similar to how
// Parallax{X,Y} works when camera is moving along the X and Y axes,
// this setting applies to camera moving closer or away (zooming in or out).
int m_ParallaxZoom;
int GetParallaxZoom() const
{
if(m_Version >= 4)
return m_ParallaxZoom;
else
return maximum(m_ParallaxX, m_ParallaxY);
}
};
struct CMapItemLayer