mirror of
https://github.com/ddnet/ddnet.git
synced 2024-11-10 10:08:18 +00:00
Support bezier envelope curves in maps and editor
Port map and editor support for `CURVETYPE_BEZIER` from upstream, i.e. support bezier curves with configurable in- and out-tangents for every envelope point. The in- and out-tangents are represented by triangles and can be dragged in the envelope editor like the envelope points. Support reading and writing the bezier information as a separate UUID-based map item. If the bezier information is not found, bezier will default to linear behavior. Old clients will still be able to read the new maps and ignore the unknown map item. The unknown curvetype will also be handled as linear by old clients. Allow reading upstream maps that use `CMapItemEnvelope` version 3. On upstream, a different struct is used to store all envelope points including bezier information, which broke compatibility to old clients. Fix holding Ctrl for slow envelope point editing not working for vertical movement. Highlight the currently selected element (envelope point or bezier tangent marker) which is being used with the value/time edit boxes. Hide the value/time edit boxes when no element is selected.
This commit is contained in:
parent
785f03e73a
commit
4ae0928b47
|
@ -27,6 +27,20 @@ constexpr inline T mix(const T a, const T b, TB amount)
|
||||||
return a + (b - a) * amount;
|
return a + (b - a) * amount;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
template<typename T, typename TB>
|
||||||
|
inline T bezier(const T p0, const T p1, const T p2, const T p3, TB amount)
|
||||||
|
{
|
||||||
|
// De-Casteljau Algorithm
|
||||||
|
const T c10 = mix(p0, p1, amount);
|
||||||
|
const T c11 = mix(p1, p2, amount);
|
||||||
|
const T c12 = mix(p2, p3, amount);
|
||||||
|
|
||||||
|
const T c20 = mix(c10, c11, amount);
|
||||||
|
const T c21 = mix(c11, c12, amount);
|
||||||
|
|
||||||
|
return mix(c20, c21, amount); // c30
|
||||||
|
}
|
||||||
|
|
||||||
inline float random_float()
|
inline float random_float()
|
||||||
{
|
{
|
||||||
return rand() / (float)(RAND_MAX);
|
return rand() / (float)(RAND_MAX);
|
||||||
|
|
|
@ -15,6 +15,8 @@ class CMap : public IEngineMap
|
||||||
public:
|
public:
|
||||||
CMap();
|
CMap();
|
||||||
|
|
||||||
|
CDataFileReader *GetReader() { return &m_DataFile; }
|
||||||
|
|
||||||
void *GetData(int Index) override;
|
void *GetData(int Index) override;
|
||||||
int GetDataSize(int Index) const override;
|
int GetDataSize(int Index) const override;
|
||||||
void *GetDataSwapped(int Index) override;
|
void *GetDataSwapped(int Index) override;
|
||||||
|
|
|
@ -12,6 +12,7 @@
|
||||||
|
|
||||||
#include <game/layers.h>
|
#include <game/layers.h>
|
||||||
#include <game/mapitems.h>
|
#include <game/mapitems.h>
|
||||||
|
#include <game/mapitems_ex.h>
|
||||||
|
|
||||||
#include <game/client/components/camera.h>
|
#include <game/client/components/camera.h>
|
||||||
#include <game/client/components/mapimages.h>
|
#include <game/client/components/mapimages.h>
|
||||||
|
@ -60,22 +61,15 @@ void CMapLayers::EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Channels
|
||||||
CMapLayers *pThis = (CMapLayers *)pUser;
|
CMapLayers *pThis = (CMapLayers *)pUser;
|
||||||
Channels = ColorRGBA();
|
Channels = ColorRGBA();
|
||||||
|
|
||||||
CEnvPoint *pPoints = 0;
|
const CMapBasedEnvelopePointAccess EnvelopePoints(pThis->m_pLayers->Map());
|
||||||
|
|
||||||
{
|
int EnvStart, EnvNum;
|
||||||
int Start, Num;
|
pThis->m_pLayers->Map()->GetType(MAPITEMTYPE_ENVELOPE, &EnvStart, &EnvNum);
|
||||||
pThis->m_pLayers->Map()->GetType(MAPITEMTYPE_ENVPOINTS, &Start, &Num);
|
|
||||||
if(Num)
|
|
||||||
pPoints = (CEnvPoint *)pThis->m_pLayers->Map()->GetItem(Start);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Start, Num;
|
if(EnvelopePoints.NumPoints() == 0 || Env < 0 || Env >= EnvNum)
|
||||||
pThis->m_pLayers->Map()->GetType(MAPITEMTYPE_ENVELOPE, &Start, &Num);
|
|
||||||
|
|
||||||
if(Env >= Num)
|
|
||||||
return;
|
return;
|
||||||
|
|
||||||
CMapItemEnvelope *pItem = (CMapItemEnvelope *)pThis->m_pLayers->Map()->GetItem(Start + Env);
|
const CMapItemEnvelope *pItem = (CMapItemEnvelope *)pThis->m_pLayers->Map()->GetItem(EnvStart + Env);
|
||||||
|
|
||||||
const auto TickToNanoSeconds = std::chrono::nanoseconds(1s) / (int64_t)pThis->Client()->GameTickSpeed();
|
const auto TickToNanoSeconds = std::chrono::nanoseconds(1s) / (int64_t)pThis->Client()->GameTickSpeed();
|
||||||
|
|
||||||
|
@ -117,7 +111,7 @@ void CMapLayers::EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Channels
|
||||||
MinTick * TickToNanoSeconds;
|
MinTick * TickToNanoSeconds;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
CRenderTools::RenderEvalEnvelope(pPoints + pItem->m_StartPoint, pItem->m_NumPoints, 4, s_Time + (int64_t)TimeOffsetMillis * std::chrono::nanoseconds(1ms), Channels);
|
CRenderTools::RenderEvalEnvelope(&EnvelopePoints, 4, s_Time + (int64_t)TimeOffsetMillis * std::chrono::nanoseconds(1ms), Channels);
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
|
@ -142,7 +136,7 @@ void CMapLayers::EnvelopeEval(int TimeOffsetMillis, int Env, ColorRGBA &Channels
|
||||||
s_Time += CurTime - s_LastLocalTime;
|
s_Time += CurTime - s_LastLocalTime;
|
||||||
s_LastLocalTime = CurTime;
|
s_LastLocalTime = CurTime;
|
||||||
}
|
}
|
||||||
CRenderTools::RenderEvalEnvelope(pPoints + pItem->m_StartPoint, pItem->m_NumPoints, 4, s_Time + std::chrono::nanoseconds(std::chrono::milliseconds(TimeOffsetMillis)), Channels);
|
CRenderTools::RenderEvalEnvelope(&EnvelopePoints, 4, s_Time + std::chrono::nanoseconds(std::chrono::milliseconds(TimeOffsetMillis)), Channels);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
@ -20,6 +20,8 @@ struct CDataSprite;
|
||||||
}
|
}
|
||||||
struct CDataSprite;
|
struct CDataSprite;
|
||||||
struct CEnvPoint;
|
struct CEnvPoint;
|
||||||
|
struct CEnvPointBezier;
|
||||||
|
struct CEnvPointBezier_upstream;
|
||||||
struct CMapItemGroup;
|
struct CMapItemGroup;
|
||||||
struct CMapItemGroupEx;
|
struct CMapItemGroupEx;
|
||||||
struct CQuad;
|
struct CQuad;
|
||||||
|
@ -71,6 +73,29 @@ enum
|
||||||
TILERENDERFLAG_EXTEND = 4,
|
TILERENDERFLAG_EXTEND = 4,
|
||||||
};
|
};
|
||||||
|
|
||||||
|
class IEnvelopePointAccess
|
||||||
|
{
|
||||||
|
public:
|
||||||
|
virtual int NumPoints() const = 0;
|
||||||
|
virtual const CEnvPoint *GetPoint(int Index) const = 0;
|
||||||
|
virtual const CEnvPointBezier *GetBezier(int Index) const = 0;
|
||||||
|
};
|
||||||
|
|
||||||
|
class CMapBasedEnvelopePointAccess : public IEnvelopePointAccess
|
||||||
|
{
|
||||||
|
int m_NumPoints;
|
||||||
|
CEnvPoint *m_pPoints;
|
||||||
|
CEnvPointBezier *m_pPointsBezier;
|
||||||
|
CEnvPointBezier_upstream *m_pPointsBezierUpstream;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CMapBasedEnvelopePointAccess(class CDataFileReader *pReader);
|
||||||
|
CMapBasedEnvelopePointAccess(class IMap *pMap);
|
||||||
|
int NumPoints() const override;
|
||||||
|
const CEnvPoint *GetPoint(int Index) const override;
|
||||||
|
const CEnvPointBezier *GetBezier(int Index) const override;
|
||||||
|
};
|
||||||
|
|
||||||
typedef void (*ENVELOPE_EVAL)(int TimeOffsetMillis, int Env, ColorRGBA &Channels, void *pUser);
|
typedef void (*ENVELOPE_EVAL)(int TimeOffsetMillis, int Env, ColorRGBA &Channels, void *pUser);
|
||||||
|
|
||||||
class CRenderTools
|
class CRenderTools
|
||||||
|
@ -117,7 +142,7 @@ public:
|
||||||
void RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha = 1.0f);
|
void RenderTee(const CAnimState *pAnim, const CTeeRenderInfo *pInfo, int Emote, vec2 Dir, vec2 Pos, float Alpha = 1.0f);
|
||||||
|
|
||||||
// map render methods (render_map.cpp)
|
// map render methods (render_map.cpp)
|
||||||
static void RenderEvalEnvelope(CEnvPoint *pPoints, int NumPoints, int Channels, std::chrono::nanoseconds TimeNanos, ColorRGBA &Result);
|
static void RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, int Channels, std::chrono::nanoseconds TimeNanos, ColorRGBA &Result);
|
||||||
void RenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser);
|
void RenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser);
|
||||||
void ForceRenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser, float Alpha = 1.0f);
|
void ForceRenderQuads(CQuad *pQuads, int NumQuads, int Flags, ENVELOPE_EVAL pfnEval, void *pUser, float Alpha = 1.0f);
|
||||||
void RenderTilemap(CTile *pTiles, int w, int h, float Scale, ColorRGBA Color, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset);
|
void RenderTilemap(CTile *pTiles, int w, int h, float Scale, ColorRGBA Color, int RenderFlags, ENVELOPE_EVAL pfnEval, void *pUser, int ColorEnv, int ColorEnvOffset);
|
||||||
|
|
|
@ -3,23 +3,225 @@
|
||||||
#include <base/math.h>
|
#include <base/math.h>
|
||||||
|
|
||||||
#include <engine/graphics.h>
|
#include <engine/graphics.h>
|
||||||
|
#include <engine/map.h>
|
||||||
#include <engine/textrender.h>
|
#include <engine/textrender.h>
|
||||||
|
|
||||||
#include <engine/shared/config.h>
|
#include <engine/shared/config.h>
|
||||||
|
#include <engine/shared/datafile.h>
|
||||||
|
#include <engine/shared/map.h>
|
||||||
|
|
||||||
#include "render.h"
|
#include "render.h"
|
||||||
|
|
||||||
#include <game/generated/client_data.h>
|
#include <game/generated/client_data.h>
|
||||||
|
|
||||||
#include <game/mapitems.h>
|
#include <game/mapitems.h>
|
||||||
|
#include <game/mapitems_ex.h>
|
||||||
|
|
||||||
#include <chrono>
|
#include <chrono>
|
||||||
#include <cmath>
|
#include <cmath>
|
||||||
|
|
||||||
using namespace std::chrono_literals;
|
using namespace std::chrono_literals;
|
||||||
|
|
||||||
void CRenderTools::RenderEvalEnvelope(CEnvPoint *pPoints, int NumPoints, int Channels, std::chrono::nanoseconds TimeNanos, ColorRGBA &Result)
|
CMapBasedEnvelopePointAccess::CMapBasedEnvelopePointAccess(CDataFileReader *pReader)
|
||||||
{
|
{
|
||||||
|
bool FoundBezierEnvelope = false;
|
||||||
|
int EnvStart, EnvNum;
|
||||||
|
pReader->GetType(MAPITEMTYPE_ENVELOPE, &EnvStart, &EnvNum);
|
||||||
|
for(int EnvIndex = 0; EnvIndex < EnvNum; EnvIndex++)
|
||||||
|
{
|
||||||
|
CMapItemEnvelope *pEnvelope = static_cast<CMapItemEnvelope *>(pReader->GetItem(EnvStart + EnvIndex));
|
||||||
|
if(pEnvelope->m_Version >= CMapItemEnvelope_v3::CURRENT_VERSION)
|
||||||
|
{
|
||||||
|
FoundBezierEnvelope = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if(FoundBezierEnvelope)
|
||||||
|
{
|
||||||
|
m_pPoints = nullptr;
|
||||||
|
m_pPointsBezier = nullptr;
|
||||||
|
|
||||||
|
int EnvPointStart, FakeEnvPointNum;
|
||||||
|
pReader->GetType(MAPITEMTYPE_ENVPOINTS, &EnvPointStart, &FakeEnvPointNum);
|
||||||
|
if(FakeEnvPointNum > 0)
|
||||||
|
m_pPointsBezierUpstream = static_cast<CEnvPointBezier_upstream *>(pReader->GetItem(EnvPointStart));
|
||||||
|
else
|
||||||
|
m_pPointsBezierUpstream = nullptr;
|
||||||
|
|
||||||
|
m_NumPoints = pReader->GetItemSize(EnvPointStart) / sizeof(CEnvPointBezier_upstream);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
int EnvPointStart, FakeEnvPointNum;
|
||||||
|
pReader->GetType(MAPITEMTYPE_ENVPOINTS, &EnvPointStart, &FakeEnvPointNum);
|
||||||
|
if(FakeEnvPointNum > 0)
|
||||||
|
m_pPoints = static_cast<CEnvPoint *>(pReader->GetItem(EnvPointStart));
|
||||||
|
else
|
||||||
|
m_pPoints = nullptr;
|
||||||
|
|
||||||
|
m_NumPoints = pReader->GetItemSize(EnvPointStart) / sizeof(CEnvPoint);
|
||||||
|
|
||||||
|
int EnvPointBezierStart, FakeEnvPointBezierNum;
|
||||||
|
pReader->GetType(MAPITEMTYPE_ENVPOINTS_BEZIER, &EnvPointBezierStart, &FakeEnvPointBezierNum);
|
||||||
|
const int NumPointsBezier = pReader->GetItemSize(EnvPointBezierStart) / sizeof(CEnvPointBezier);
|
||||||
|
if(FakeEnvPointBezierNum > 0 && m_NumPoints == NumPointsBezier)
|
||||||
|
m_pPointsBezier = static_cast<CEnvPointBezier *>(pReader->GetItem(EnvPointBezierStart));
|
||||||
|
else
|
||||||
|
m_pPointsBezier = nullptr;
|
||||||
|
|
||||||
|
m_pPointsBezierUpstream = nullptr;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
CMapBasedEnvelopePointAccess::CMapBasedEnvelopePointAccess(IMap *pMap) :
|
||||||
|
CMapBasedEnvelopePointAccess(static_cast<CMap *>(pMap)->GetReader())
|
||||||
|
{
|
||||||
|
}
|
||||||
|
|
||||||
|
int CMapBasedEnvelopePointAccess::NumPoints() const
|
||||||
|
{
|
||||||
|
return m_NumPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CEnvPoint *CMapBasedEnvelopePointAccess::GetPoint(int Index) const
|
||||||
|
{
|
||||||
|
if(Index < 0 || Index >= m_NumPoints)
|
||||||
|
return nullptr;
|
||||||
|
if(m_pPoints != nullptr)
|
||||||
|
return &m_pPoints[Index];
|
||||||
|
if(m_pPointsBezierUpstream != nullptr)
|
||||||
|
return &m_pPointsBezierUpstream[Index];
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
const CEnvPointBezier *CMapBasedEnvelopePointAccess::GetBezier(int Index) const
|
||||||
|
{
|
||||||
|
if(Index < 0 || Index >= m_NumPoints)
|
||||||
|
return nullptr;
|
||||||
|
if(m_pPointsBezier != nullptr)
|
||||||
|
return &m_pPointsBezier[Index];
|
||||||
|
if(m_pPointsBezierUpstream != nullptr)
|
||||||
|
return &m_pPointsBezierUpstream[Index].m_Bezier;
|
||||||
|
return nullptr;
|
||||||
|
}
|
||||||
|
|
||||||
|
static void ValidateFCurve(const vec2 &p0, vec2 &p1, vec2 &p2, const vec2 &p3)
|
||||||
|
{
|
||||||
|
// validate the bezier curve
|
||||||
|
p1.x = clamp(p1.x, p0.x, p3.x);
|
||||||
|
p2.x = clamp(p2.x, p0.x, p3.x);
|
||||||
|
}
|
||||||
|
|
||||||
|
static double CubicRoot(double x)
|
||||||
|
{
|
||||||
|
if(x == 0.0)
|
||||||
|
return 0.0;
|
||||||
|
else if(x < 0.0)
|
||||||
|
return -std::exp(std::log(-x) / 3.0);
|
||||||
|
else
|
||||||
|
return std::exp(std::log(x) / 3.0);
|
||||||
|
}
|
||||||
|
|
||||||
|
static float SolveBezier(float x, float p0, float p1, float p2, float p3)
|
||||||
|
{
|
||||||
|
// check for valid f-curve
|
||||||
|
// we only take care of monotonic bezier curves, so there has to be exactly 1 real solution
|
||||||
|
if(!(p0 <= x && x <= p3) || !(p0 <= p1 && p1 <= p3) || !(p0 <= p2 && p2 <= p3))
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
const double x3 = -p0 + 3.0 * p1 - 3.0 * p2 + p3;
|
||||||
|
const double x2 = 3.0 * p0 - 6.0 * p1 + 3.0 * p2;
|
||||||
|
const double x1 = -3.0 * p0 + 3.0 * p1;
|
||||||
|
const double x0 = p0 - x;
|
||||||
|
|
||||||
|
if(x3 == 0.0 && x2 == 0.0)
|
||||||
|
{
|
||||||
|
// linear
|
||||||
|
// a * t + b = 0
|
||||||
|
const double a = x1;
|
||||||
|
const double b = x0;
|
||||||
|
|
||||||
|
if(a == 0.0)
|
||||||
|
return 0.0f;
|
||||||
|
return -b / a;
|
||||||
|
}
|
||||||
|
else if(x3 == 0.0)
|
||||||
|
{
|
||||||
|
// quadratic
|
||||||
|
// t * t + b * t +c = 0
|
||||||
|
const double b = x1 / x2;
|
||||||
|
const double c = x0 / x2;
|
||||||
|
|
||||||
|
if(c == 0.0)
|
||||||
|
return 0.0f;
|
||||||
|
|
||||||
|
const double D = b * b - 4.0 * c;
|
||||||
|
const double SqrtD = std::sqrt(D);
|
||||||
|
|
||||||
|
const double t = (-b + SqrtD) / 2.0;
|
||||||
|
|
||||||
|
if(0.0 <= t && t <= 1.0001f)
|
||||||
|
return t;
|
||||||
|
return (-b - SqrtD) / 2.0;
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// cubic
|
||||||
|
// t * t * t + a * t * t + b * t * t + c = 0
|
||||||
|
const double a = x2 / x3;
|
||||||
|
const double b = x1 / x3;
|
||||||
|
const double c = x0 / x3;
|
||||||
|
|
||||||
|
// substitute t = y - a / 3
|
||||||
|
const double sub = a / 3.0;
|
||||||
|
|
||||||
|
// depressed form x^3 + px + q = 0
|
||||||
|
// cardano's method
|
||||||
|
const double p = b / 3.0 - a * a / 9.0;
|
||||||
|
const double q = (2.0 * a * a * a / 27.0 - a * b / 3.0 + c) / 2.0;
|
||||||
|
|
||||||
|
const double D = q * q + p * p * p;
|
||||||
|
|
||||||
|
if(D > 0.0)
|
||||||
|
{
|
||||||
|
// only one 'real' solution
|
||||||
|
const double s = std::sqrt(D);
|
||||||
|
return CubicRoot(s - q) - CubicRoot(s + q) - sub;
|
||||||
|
}
|
||||||
|
else if(D == 0.0)
|
||||||
|
{
|
||||||
|
// one single, one double solution or triple solution
|
||||||
|
const double s = CubicRoot(-q);
|
||||||
|
const double t = 2.0 * s - sub;
|
||||||
|
|
||||||
|
if(0.0 <= t && t <= 1.0001f)
|
||||||
|
return t;
|
||||||
|
return (-s - sub);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
// Casus irreductibilis ... ,_,
|
||||||
|
const double phi = std::acos(-q / std::sqrt(-(p * p * p))) / 3.0;
|
||||||
|
const double s = 2.0 * std::sqrt(-p);
|
||||||
|
|
||||||
|
const double t1 = s * std::cos(phi) - sub;
|
||||||
|
|
||||||
|
if(0.0 <= t1 && t1 <= 1.0001f)
|
||||||
|
return t1;
|
||||||
|
|
||||||
|
const double t2 = -s * std::cos(phi + pi / 3.0) - sub;
|
||||||
|
|
||||||
|
if(0.0 <= t2 && t2 <= 1.0001f)
|
||||||
|
return t2;
|
||||||
|
return -s * std::cos(phi - pi / 3.0) - sub;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
void CRenderTools::RenderEvalEnvelope(const IEnvelopePointAccess *pPoints, int Channels, std::chrono::nanoseconds TimeNanos, ColorRGBA &Result)
|
||||||
|
{
|
||||||
|
const int NumPoints = pPoints->NumPoints();
|
||||||
if(NumPoints == 0)
|
if(NumPoints == 0)
|
||||||
{
|
{
|
||||||
Result = ColorRGBA();
|
Result = ColorRGBA();
|
||||||
|
@ -28,14 +230,16 @@ void CRenderTools::RenderEvalEnvelope(CEnvPoint *pPoints, int NumPoints, int Cha
|
||||||
|
|
||||||
if(NumPoints == 1)
|
if(NumPoints == 1)
|
||||||
{
|
{
|
||||||
Result.r = fx2f(pPoints[0].m_aValues[0]);
|
const CEnvPoint *pFirstPoint = pPoints->GetPoint(0);
|
||||||
Result.g = fx2f(pPoints[0].m_aValues[1]);
|
Result.r = fx2f(pFirstPoint->m_aValues[0]);
|
||||||
Result.b = fx2f(pPoints[0].m_aValues[2]);
|
Result.g = fx2f(pFirstPoint->m_aValues[1]);
|
||||||
Result.a = fx2f(pPoints[0].m_aValues[3]);
|
Result.b = fx2f(pFirstPoint->m_aValues[2]);
|
||||||
|
Result.a = fx2f(pFirstPoint->m_aValues[3]);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int64_t MaxPointTime = (int64_t)pPoints[NumPoints - 1].m_Time * std::chrono::nanoseconds(1ms).count();
|
const CEnvPoint *pLastPoint = pPoints->GetPoint(NumPoints - 1);
|
||||||
|
const int64_t MaxPointTime = (int64_t)pLastPoint->m_Time * std::chrono::nanoseconds(1ms).count();
|
||||||
if(MaxPointTime > 0) // TODO: remove this check when implementing a IO check for maps(in this case broken envelopes)
|
if(MaxPointTime > 0) // TODO: remove this check when implementing a IO check for maps(in this case broken envelopes)
|
||||||
TimeNanos = std::chrono::nanoseconds(TimeNanos.count() % MaxPointTime);
|
TimeNanos = std::chrono::nanoseconds(TimeNanos.count() % MaxPointTime);
|
||||||
else
|
else
|
||||||
|
@ -44,31 +248,70 @@ void CRenderTools::RenderEvalEnvelope(CEnvPoint *pPoints, int NumPoints, int Cha
|
||||||
int TimeMillis = (int)(TimeNanos / std::chrono::nanoseconds(1ms).count()).count();
|
int TimeMillis = (int)(TimeNanos / std::chrono::nanoseconds(1ms).count()).count();
|
||||||
for(int i = 0; i < NumPoints - 1; i++)
|
for(int i = 0; i < NumPoints - 1; i++)
|
||||||
{
|
{
|
||||||
if(TimeMillis >= pPoints[i].m_Time && TimeMillis <= pPoints[i + 1].m_Time)
|
const CEnvPoint *pCurrentPoint = pPoints->GetPoint(i);
|
||||||
|
const CEnvPoint *pNextPoint = pPoints->GetPoint(i + 1);
|
||||||
|
if(TimeMillis >= pCurrentPoint->m_Time && TimeMillis <= pNextPoint->m_Time)
|
||||||
{
|
{
|
||||||
float Delta = pPoints[i + 1].m_Time - pPoints[i].m_Time;
|
const float Delta = pNextPoint->m_Time - pCurrentPoint->m_Time;
|
||||||
float a = (float)(((double)TimeNanos.count() / (double)std::chrono::nanoseconds(1ms).count()) - pPoints[i].m_Time) / Delta;
|
float a = (float)(((double)TimeNanos.count() / (double)std::chrono::nanoseconds(1ms).count()) - pCurrentPoint->m_Time) / Delta;
|
||||||
|
|
||||||
if(pPoints[i].m_Curvetype == CURVETYPE_SMOOTH)
|
switch(pCurrentPoint->m_Curvetype)
|
||||||
a = -2 * a * a * a + 3 * a * a; // second hermite basis
|
{
|
||||||
else if(pPoints[i].m_Curvetype == CURVETYPE_SLOW)
|
case CURVETYPE_STEP:
|
||||||
|
a = 0.0f;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CURVETYPE_SLOW:
|
||||||
a = a * a * a;
|
a = a * a * a;
|
||||||
else if(pPoints[i].m_Curvetype == CURVETYPE_FAST)
|
break;
|
||||||
|
|
||||||
|
case CURVETYPE_FAST:
|
||||||
|
a = 1.0f - a;
|
||||||
|
a = 1.0f - a * a * a;
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CURVETYPE_SMOOTH:
|
||||||
|
a = -2.0f * a * a * a + 3.0f * a * a; // second hermite basis
|
||||||
|
break;
|
||||||
|
|
||||||
|
case CURVETYPE_BEZIER:
|
||||||
{
|
{
|
||||||
a = 1 - a;
|
const CEnvPointBezier *pCurrentPointBezier = pPoints->GetBezier(i);
|
||||||
a = 1 - a * a * a;
|
const CEnvPointBezier *pNextPointBezier = pPoints->GetBezier(i + 1);
|
||||||
|
if(pCurrentPointBezier == nullptr || pNextPointBezier == nullptr)
|
||||||
|
break; // fallback to linear
|
||||||
|
for(int c = 0; c < Channels; c++)
|
||||||
|
{
|
||||||
|
// monotonic 2d cubic bezier curve
|
||||||
|
const vec2 p0 = vec2(pCurrentPoint->m_Time / 1000.0f, fx2f(pCurrentPoint->m_aValues[c]));
|
||||||
|
const vec2 p3 = vec2(pNextPoint->m_Time / 1000.0f, fx2f(pNextPoint->m_aValues[c]));
|
||||||
|
|
||||||
|
const vec2 OutTang = vec2(pCurrentPointBezier->m_aOutTangentDeltaX[c] / 1000.0f, fx2f(pCurrentPointBezier->m_aOutTangentDeltaY[c]));
|
||||||
|
const vec2 InTang = -vec2(pNextPointBezier->m_aInTangentDeltaX[c] / 1000.0f, fx2f(pNextPointBezier->m_aInTangentDeltaY[c]));
|
||||||
|
vec2 p1 = p0 + OutTang;
|
||||||
|
vec2 p2 = p3 - InTang;
|
||||||
|
|
||||||
|
// validate bezier curve
|
||||||
|
ValidateFCurve(p0, p1, p2, p3);
|
||||||
|
|
||||||
|
// solve x(a) = time for a
|
||||||
|
a = clamp(SolveBezier(TimeMillis / 1000.0f, p0.x, p1.x, p2.x, p3.x), 0.0f, 1.0f);
|
||||||
|
|
||||||
|
// value = y(t)
|
||||||
|
Result[c] = bezier(p0.y, p1.y, p2.y, p3.y, a);
|
||||||
}
|
}
|
||||||
else if(pPoints[i].m_Curvetype == CURVETYPE_STEP)
|
return;
|
||||||
a = 0;
|
}
|
||||||
else
|
|
||||||
{
|
case CURVETYPE_LINEAR: [[fallthrough]];
|
||||||
// linear
|
default:
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
|
|
||||||
for(int c = 0; c < Channels; c++)
|
for(int c = 0; c < Channels; c++)
|
||||||
{
|
{
|
||||||
float v0 = fx2f(pPoints[i].m_aValues[c]);
|
const float v0 = fx2f(pCurrentPoint->m_aValues[c]);
|
||||||
float v1 = fx2f(pPoints[i + 1].m_aValues[c]);
|
const float v1 = fx2f(pNextPoint->m_aValues[c]);
|
||||||
Result[c] = v0 + (v1 - v0) * a;
|
Result[c] = v0 + (v1 - v0) * a;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -76,10 +319,10 @@ void CRenderTools::RenderEvalEnvelope(CEnvPoint *pPoints, int NumPoints, int Cha
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
Result.r = fx2f(pPoints[NumPoints - 1].m_aValues[0]);
|
Result.r = fx2f(pLastPoint->m_aValues[0]);
|
||||||
Result.g = fx2f(pPoints[NumPoints - 1].m_aValues[1]);
|
Result.g = fx2f(pLastPoint->m_aValues[1]);
|
||||||
Result.b = fx2f(pPoints[NumPoints - 1].m_aValues[2]);
|
Result.b = fx2f(pLastPoint->m_aValues[2]);
|
||||||
Result.a = fx2f(pPoints[NumPoints - 1].m_aValues[3]);
|
Result.a = fx2f(pLastPoint->m_aValues[3]);
|
||||||
}
|
}
|
||||||
|
|
||||||
static void Rotate(CPoint *pCenter, CPoint *pPoint, float Rotation)
|
static void Rotate(CPoint *pCenter, CPoint *pPoint, float Rotation)
|
||||||
|
|
|
@ -2116,19 +2116,26 @@ void CEditor::DoQuadEnvelopes(const std::vector<CQuad> &vQuads, IGraphics::CText
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
//QuadParams
|
//QuadParams
|
||||||
const CPoint *pPoints = vQuads[j].m_aPoints;
|
const CPoint *pPivotPoint = &vQuads[j].m_aPoints[4];
|
||||||
for(size_t i = 0; i < apEnvelope[j]->m_vPoints.size() - 1; i++)
|
for(size_t i = 0; i < apEnvelope[j]->m_vPoints.size() - 1; i++)
|
||||||
{
|
{
|
||||||
float OffsetX = fx2f(apEnvelope[j]->m_vPoints[i].m_aValues[0]);
|
ColorRGBA Result;
|
||||||
float OffsetY = fx2f(apEnvelope[j]->m_vPoints[i].m_aValues[1]);
|
apEnvelope[j]->Eval(apEnvelope[j]->m_vPoints[i].m_Time / 1000.0f + 0.000001f, Result);
|
||||||
vec2 Pos0 = vec2(fx2f(pPoints[4].x) + OffsetX, fx2f(pPoints[4].y) + OffsetY);
|
vec2 Pos0 = vec2(fx2f(pPivotPoint->x) + Result.r, fx2f(pPivotPoint->y) + Result.g);
|
||||||
|
|
||||||
OffsetX = fx2f(apEnvelope[j]->m_vPoints[i + 1].m_aValues[0]);
|
const int Steps = 15;
|
||||||
OffsetY = fx2f(apEnvelope[j]->m_vPoints[i + 1].m_aValues[1]);
|
for(int n = 1; n <= Steps; n++)
|
||||||
vec2 Pos1 = vec2(fx2f(pPoints[4].x) + OffsetX, fx2f(pPoints[4].y) + OffsetY);
|
{
|
||||||
|
const float Time = mix(apEnvelope[j]->m_vPoints[i].m_Time, apEnvelope[j]->m_vPoints[i + 1].m_Time, (float)n / Steps);
|
||||||
|
apEnvelope[j]->Eval(Time / 1000.0f - 0.000001f, Result);
|
||||||
|
|
||||||
|
vec2 Pos1 = vec2(fx2f(pPivotPoint->x) + Result.r, fx2f(pPivotPoint->y) + Result.g);
|
||||||
|
|
||||||
IGraphics::CLineItem Line = IGraphics::CLineItem(Pos0.x, Pos0.y, Pos1.x, Pos1.y);
|
IGraphics::CLineItem Line = IGraphics::CLineItem(Pos0.x, Pos0.y, Pos1.x, Pos1.y);
|
||||||
Graphics()->LinesDraw(&Line, 1);
|
Graphics()->LinesDraw(&Line, 1);
|
||||||
|
|
||||||
|
Pos0 = Pos1;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
|
@ -5528,7 +5535,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
|
||||||
|
|
||||||
if(pNewEnv) // add the default points
|
if(pNewEnv) // add the default points
|
||||||
{
|
{
|
||||||
if(pNewEnv->m_Channels == 4)
|
if(pNewEnv->GetChannels() == 4)
|
||||||
{
|
{
|
||||||
pNewEnv->AddPoint(0, f2fx(1.0f), f2fx(1.0f), f2fx(1.0f), f2fx(1.0f));
|
pNewEnv->AddPoint(0, f2fx(1.0f), f2fx(1.0f), f2fx(1.0f), f2fx(1.0f));
|
||||||
pNewEnv->AddPoint(1000, f2fx(1.0f), f2fx(1.0f), f2fx(1.0f), f2fx(1.0f));
|
pNewEnv->AddPoint(1000, f2fx(1.0f), f2fx(1.0f), f2fx(1.0f), f2fx(1.0f));
|
||||||
|
@ -5600,7 +5607,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
|
||||||
}
|
}
|
||||||
|
|
||||||
bool ShowColorBar = false;
|
bool ShowColorBar = false;
|
||||||
if(pEnvelope && pEnvelope->m_Channels == 4)
|
if(pEnvelope && pEnvelope->GetChannels() == 4)
|
||||||
{
|
{
|
||||||
ShowColorBar = true;
|
ShowColorBar = true;
|
||||||
View.HSplitTop(20.0f, &ColorBar, &View);
|
View.HSplitTop(20.0f, &ColorBar, &View);
|
||||||
|
@ -5612,7 +5619,6 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
|
||||||
|
|
||||||
if(pEnvelope)
|
if(pEnvelope)
|
||||||
{
|
{
|
||||||
static std::vector<int> s_vSelection;
|
|
||||||
static int s_EnvelopeEditorID = 0;
|
static int s_EnvelopeEditorID = 0;
|
||||||
static int s_ActiveChannels = 0xf;
|
static int s_ActiveChannels = 0xf;
|
||||||
|
|
||||||
|
@ -5642,17 +5648,17 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
|
||||||
for(int i = 0; i < CEnvPoint::MAX_CHANNELS; i++, Bit <<= 1)
|
for(int i = 0; i < CEnvPoint::MAX_CHANNELS; i++, Bit <<= 1)
|
||||||
{
|
{
|
||||||
ToolBar.VSplitLeft(15.0f, &Button, &ToolBar);
|
ToolBar.VSplitLeft(15.0f, &Button, &ToolBar);
|
||||||
if(i < pEnvelope->m_Channels)
|
if(i < pEnvelope->GetChannels())
|
||||||
{
|
{
|
||||||
int Corners = IGraphics::CORNER_NONE;
|
int Corners = IGraphics::CORNER_NONE;
|
||||||
if(pEnvelope->m_Channels == 1)
|
if(pEnvelope->GetChannels() == 1)
|
||||||
Corners = IGraphics::CORNER_ALL;
|
Corners = IGraphics::CORNER_ALL;
|
||||||
else if(i == 0)
|
else if(i == 0)
|
||||||
Corners = IGraphics::CORNER_L;
|
Corners = IGraphics::CORNER_L;
|
||||||
else if(i == pEnvelope->m_Channels - 1)
|
else if(i == pEnvelope->GetChannels() - 1)
|
||||||
Corners = IGraphics::CORNER_R;
|
Corners = IGraphics::CORNER_R;
|
||||||
|
|
||||||
if(DoButton_Env(&s_aChannelButtons[i], s_aapNames[pEnvelope->m_Channels - 1][i], s_ActiveChannels & Bit, &Button, s_aapDescriptions[pEnvelope->m_Channels - 1][i], aColors[i], Corners))
|
if(DoButton_Env(&s_aChannelButtons[i], s_aapNames[pEnvelope->GetChannels() - 1][i], s_ActiveChannels & Bit, &Button, s_aapDescriptions[pEnvelope->GetChannels() - 1][i], aColors[i], Corners))
|
||||||
s_ActiveChannels ^= Bit;
|
s_ActiveChannels ^= Bit;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5706,12 +5712,65 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
|
||||||
m_pTooltip = "Press right mouse button to create a new point";
|
m_pTooltip = "Press right mouse button to create a new point";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// keep track of selected point to handle value/time text input
|
||||||
|
static const void *s_pSelectedPoint = nullptr;
|
||||||
|
|
||||||
|
// render tangents for bezier curves
|
||||||
|
{
|
||||||
|
UI()->ClipEnable(&View);
|
||||||
|
Graphics()->TextureClear();
|
||||||
|
Graphics()->LinesBegin();
|
||||||
|
for(int c = 0; c < pEnvelope->GetChannels(); c++)
|
||||||
|
{
|
||||||
|
if(!(s_ActiveChannels & (1 << c)))
|
||||||
|
continue;
|
||||||
|
|
||||||
|
for(int i = 0; i < (int)pEnvelope->m_vPoints.size(); i++)
|
||||||
|
{
|
||||||
|
const float PosX = pEnvelope->m_vPoints[i].m_Time / 1000.0f / EndTime;
|
||||||
|
const float PosY = (fx2f(pEnvelope->m_vPoints[i].m_aValues[c]) - Bottom) / (Top - Bottom);
|
||||||
|
|
||||||
|
// Out-Tangent
|
||||||
|
if(pEnvelope->m_vPoints[i].m_Curvetype == CURVETYPE_BEZIER)
|
||||||
|
{
|
||||||
|
const float OutTangentX = PosX + pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c] / 1000.0f / EndTime;
|
||||||
|
const float OutTangentY = PosY + fx2f(pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY[c]) / (Top - Bottom);
|
||||||
|
|
||||||
|
if(s_pSelectedPoint == &pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c] || (m_SelectedQuadEnvelope == m_SelectedEnvelope && m_SelectedEnvelopePoint == i))
|
||||||
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f);
|
||||||
|
else
|
||||||
|
Graphics()->SetColor(aColors[c].r, aColors[c].g, aColors[c].b, 0.4f);
|
||||||
|
|
||||||
|
IGraphics::CLineItem LineItem(View.x + PosX * View.w, View.y + View.h - PosY * View.h, View.x + OutTangentX * View.w, View.y + View.h - OutTangentY * View.h);
|
||||||
|
Graphics()->LinesDraw(&LineItem, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In-Tangent
|
||||||
|
if(i > 0 && pEnvelope->m_vPoints[i - 1].m_Curvetype == CURVETYPE_BEZIER)
|
||||||
|
{
|
||||||
|
const float InTangentX = PosX + pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c] / 1000.0f / EndTime;
|
||||||
|
const float InTangentY = PosY + fx2f(pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaY[c]) / (Top - Bottom);
|
||||||
|
|
||||||
|
if(s_pSelectedPoint == &pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c] || (m_SelectedQuadEnvelope == m_SelectedEnvelope && m_SelectedEnvelopePoint == i))
|
||||||
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.4f);
|
||||||
|
else
|
||||||
|
Graphics()->SetColor(aColors[c].r, aColors[c].g, aColors[c].b, 0.4f);
|
||||||
|
|
||||||
|
IGraphics::CLineItem LineItem(View.x + PosX * View.w, View.y + View.h - PosY * View.h, View.x + InTangentX * View.w, View.y + View.h - InTangentY * View.h);
|
||||||
|
Graphics()->LinesDraw(&LineItem, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
Graphics()->LinesEnd();
|
||||||
|
UI()->ClipDisable();
|
||||||
|
}
|
||||||
|
|
||||||
// render lines
|
// render lines
|
||||||
{
|
{
|
||||||
UI()->ClipEnable(&View);
|
UI()->ClipEnable(&View);
|
||||||
Graphics()->TextureClear();
|
Graphics()->TextureClear();
|
||||||
Graphics()->LinesBegin();
|
Graphics()->LinesBegin();
|
||||||
for(int c = 0; c < pEnvelope->m_Channels; c++)
|
for(int c = 0; c < pEnvelope->GetChannels(); c++)
|
||||||
{
|
{
|
||||||
if(s_ActiveChannels & (1 << c))
|
if(s_ActiveChannels & (1 << c))
|
||||||
Graphics()->SetColor(aColors[c].r, aColors[c].g, aColors[c].b, 1);
|
Graphics()->SetColor(aColors[c].r, aColors[c].g, aColors[c].b, 1);
|
||||||
|
@ -5748,19 +5807,18 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
|
||||||
float t0 = pEnvelope->m_vPoints[i].m_Time / 1000.0f / EndTime;
|
float t0 = pEnvelope->m_vPoints[i].m_Time / 1000.0f / EndTime;
|
||||||
float t1 = pEnvelope->m_vPoints[i + 1].m_Time / 1000.0f / EndTime;
|
float t1 = pEnvelope->m_vPoints[i + 1].m_Time / 1000.0f / EndTime;
|
||||||
|
|
||||||
CUIRect v;
|
CUIRect CurveButton;
|
||||||
v.x = CurveBar.x + (t0 + (t1 - t0) * 0.5f) * CurveBar.w;
|
CurveButton.x = CurveBar.x + (t0 + (t1 - t0) * 0.5f) * CurveBar.w;
|
||||||
v.y = CurveBar.y;
|
CurveButton.y = CurveBar.y;
|
||||||
v.h = CurveBar.h;
|
CurveButton.h = CurveBar.h;
|
||||||
v.w = CurveBar.h;
|
CurveButton.w = CurveBar.h;
|
||||||
v.x -= v.w / 2;
|
CurveButton.x -= CurveButton.w / 2.0f;
|
||||||
void *pID = &pEnvelope->m_vPoints[i].m_Curvetype;
|
const void *pID = &pEnvelope->m_vPoints[i].m_Curvetype;
|
||||||
const char *apTypeName[] = {
|
const char *apTypeName[] = {"N", "L", "S", "F", "M", "B"};
|
||||||
"N", "L", "S", "F", "M"};
|
const char *pTypeName = "!?";
|
||||||
const char *pTypeName = "Invalid";
|
|
||||||
if(0 <= pEnvelope->m_vPoints[i].m_Curvetype && pEnvelope->m_vPoints[i].m_Curvetype < (int)std::size(apTypeName))
|
if(0 <= pEnvelope->m_vPoints[i].m_Curvetype && pEnvelope->m_vPoints[i].m_Curvetype < (int)std::size(apTypeName))
|
||||||
pTypeName = apTypeName[pEnvelope->m_vPoints[i].m_Curvetype];
|
pTypeName = apTypeName[pEnvelope->m_vPoints[i].m_Curvetype];
|
||||||
if(DoButton_Editor(pID, pTypeName, 0, &v, 0, "Switch curve type"))
|
if(DoButton_Editor(pID, pTypeName, 0, &CurveButton, 0, "Switch curve type (N = step, L = linear, S = slow, F = fast, M = smooth, B = bezier)"))
|
||||||
pEnvelope->m_vPoints[i].m_Curvetype = (pEnvelope->m_vPoints[i].m_Curvetype + 1) % NUM_CURVETYPES;
|
pEnvelope->m_vPoints[i].m_Curvetype = (pEnvelope->m_vPoints[i].m_Curvetype + 1) % NUM_CURVETYPES;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -5798,15 +5856,12 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
|
||||||
|
|
||||||
// render handles
|
// render handles
|
||||||
|
|
||||||
// keep track of last Env
|
|
||||||
static void *s_pID = nullptr;
|
|
||||||
|
|
||||||
static CLineInputNumber s_CurValueInput;
|
static CLineInputNumber s_CurValueInput;
|
||||||
static CLineInputNumber s_CurTimeInput;
|
static CLineInputNumber s_CurTimeInput;
|
||||||
|
|
||||||
if(CurrentEnvelopeSwitched)
|
if(CurrentEnvelopeSwitched)
|
||||||
{
|
{
|
||||||
s_pID = nullptr;
|
s_pSelectedPoint = nullptr;
|
||||||
|
|
||||||
// update displayed text
|
// update displayed text
|
||||||
s_CurValueInput.SetFloat(0.0f);
|
s_CurValueInput.SetFloat(0.0f);
|
||||||
|
@ -5814,28 +5869,28 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
int CurrentValue = 0, CurrentTime = 0;
|
|
||||||
|
|
||||||
Graphics()->TextureClear();
|
Graphics()->TextureClear();
|
||||||
Graphics()->QuadsBegin();
|
Graphics()->QuadsBegin();
|
||||||
for(int c = 0; c < pEnvelope->m_Channels; c++)
|
for(int c = 0; c < pEnvelope->GetChannels(); c++)
|
||||||
{
|
{
|
||||||
if(!(s_ActiveChannels & (1 << c)))
|
if(!(s_ActiveChannels & (1 << c)))
|
||||||
continue;
|
continue;
|
||||||
|
|
||||||
for(size_t i = 0; i < pEnvelope->m_vPoints.size(); i++)
|
for(size_t i = 0; i < pEnvelope->m_vPoints.size(); i++)
|
||||||
{
|
{
|
||||||
float x0 = pEnvelope->m_vPoints[i].m_Time / 1000.0f / EndTime;
|
// point handle
|
||||||
float y0 = (fx2f(pEnvelope->m_vPoints[i].m_aValues[c]) - Bottom) / (Top - Bottom);
|
{
|
||||||
|
const float PosX = pEnvelope->m_vPoints[i].m_Time / 1000.0f / EndTime;
|
||||||
|
const float PosY = (fx2f(pEnvelope->m_vPoints[i].m_aValues[c]) - Bottom) / (Top - Bottom);
|
||||||
CUIRect Final;
|
CUIRect Final;
|
||||||
Final.x = View.x + x0 * View.w;
|
Final.x = View.x + PosX * View.w;
|
||||||
Final.y = View.y + View.h - y0 * View.h;
|
Final.y = View.y + View.h - PosY * View.h;
|
||||||
Final.x -= 2.0f;
|
Final.x -= 2.0f;
|
||||||
Final.y -= 2.0f;
|
Final.y -= 2.0f;
|
||||||
Final.w = 4.0f;
|
Final.w = 4.0f;
|
||||||
Final.h = 4.0f;
|
Final.h = 4.0f;
|
||||||
|
|
||||||
void *pID = &pEnvelope->m_vPoints[i].m_aValues[c];
|
const void *pID = &pEnvelope->m_vPoints[i].m_aValues[c];
|
||||||
|
|
||||||
if(UI()->MouseInside(&Final))
|
if(UI()->MouseInside(&Final))
|
||||||
UI()->SetHotItem(pID);
|
UI()->SetHotItem(pID);
|
||||||
|
@ -5857,10 +5912,10 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
|
||||||
{
|
{
|
||||||
if(i != 0)
|
if(i != 0)
|
||||||
{
|
{
|
||||||
if(Input()->ModifierIsPressed())
|
float DeltaX = m_MouseDeltaX * TimeScale * (Input()->ModifierIsPressed() ? 1.0f : 1000.0f);
|
||||||
pEnvelope->m_vPoints[i].m_Time += (int)((m_MouseDeltaX));
|
DeltaX = DeltaX < 0 ? -std::ceil(-DeltaX) : std::ceil(DeltaX);
|
||||||
else
|
pEnvelope->m_vPoints[i].m_Time += (int)DeltaX;
|
||||||
pEnvelope->m_vPoints[i].m_Time += (int)((m_MouseDeltaX * TimeScale) * 1000.0f);
|
|
||||||
if(pEnvelope->m_vPoints[i].m_Time < pEnvelope->m_vPoints[i - 1].m_Time)
|
if(pEnvelope->m_vPoints[i].m_Time < pEnvelope->m_vPoints[i - 1].m_Time)
|
||||||
pEnvelope->m_vPoints[i].m_Time = pEnvelope->m_vPoints[i - 1].m_Time + 1;
|
pEnvelope->m_vPoints[i].m_Time = pEnvelope->m_vPoints[i - 1].m_Time + 1;
|
||||||
if(i + 1 != pEnvelope->m_vPoints.size() && pEnvelope->m_vPoints[i].m_Time > pEnvelope->m_vPoints[i + 1].m_Time)
|
if(i + 1 != pEnvelope->m_vPoints.size() && pEnvelope->m_vPoints[i].m_Time > pEnvelope->m_vPoints[i + 1].m_Time)
|
||||||
|
@ -5869,10 +5924,7 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
|
||||||
}
|
}
|
||||||
else
|
else
|
||||||
{
|
{
|
||||||
if(Input()->ModifierIsPressed())
|
pEnvelope->m_vPoints[i].m_aValues[c] -= f2fx(m_MouseDeltaY * (Input()->ModifierIsPressed() ? 0.001f : ValueScale));
|
||||||
pEnvelope->m_vPoints[i].m_aValues[c] -= f2fx(m_MouseDeltaY * 0.001f);
|
|
||||||
else
|
|
||||||
pEnvelope->m_vPoints[i].m_aValues[c] -= f2fx(m_MouseDeltaY * ValueScale);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
m_SelectedQuadEnvelope = m_SelectedEnvelope;
|
m_SelectedQuadEnvelope = m_SelectedEnvelope;
|
||||||
|
@ -5888,19 +5940,16 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
|
||||||
{
|
{
|
||||||
if(UI()->MouseButton(0))
|
if(UI()->MouseButton(0))
|
||||||
{
|
{
|
||||||
s_vSelection.clear();
|
|
||||||
s_vSelection.push_back(i);
|
|
||||||
UI()->SetActiveItem(pID);
|
UI()->SetActiveItem(pID);
|
||||||
// track it
|
s_pSelectedPoint = pID;
|
||||||
s_pID = pID;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// remove point
|
// remove point
|
||||||
if(UI()->MouseButtonClicked(1))
|
if(UI()->MouseButtonClicked(1))
|
||||||
{
|
{
|
||||||
if(s_pID == pID)
|
if(s_pSelectedPoint == pID)
|
||||||
{
|
{
|
||||||
s_pID = nullptr;
|
s_pSelectedPoint = nullptr;
|
||||||
|
|
||||||
// update displayed text
|
// update displayed text
|
||||||
s_CurValueInput.SetFloat(0.0f);
|
s_CurValueInput.SetFloat(0.0f);
|
||||||
|
@ -5914,10 +5963,10 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
|
||||||
m_ShowEnvelopePreview = SHOWENV_SELECTED;
|
m_ShowEnvelopePreview = SHOWENV_SELECTED;
|
||||||
ColorMod = 100.0f;
|
ColorMod = 100.0f;
|
||||||
Graphics()->SetColor(1, 0.75f, 0.75f, 1);
|
Graphics()->SetColor(1, 0.75f, 0.75f, 1);
|
||||||
m_pTooltip = "Left mouse to drag. Hold ctrl to be more precise. Hold shift to alter time point as well. Right click to delete.";
|
m_pTooltip = "Envelope point. Left mouse to drag. Hold ctrl to be more precise. Hold shift to alter time point instead of value. Right click to delete.";
|
||||||
}
|
}
|
||||||
|
|
||||||
if(pID == s_pID && (Input()->KeyIsPressed(KEY_RETURN) || Input()->KeyIsPressed(KEY_KP_ENTER)))
|
if(pID == s_pSelectedPoint && UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
|
||||||
{
|
{
|
||||||
if(i != 0)
|
if(i != 0)
|
||||||
{
|
{
|
||||||
|
@ -5939,24 +5988,232 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
|
||||||
|
|
||||||
if(UI()->CheckActiveItem(pID))
|
if(UI()->CheckActiveItem(pID))
|
||||||
{
|
{
|
||||||
CurrentTime = pEnvelope->m_vPoints[i].m_Time;
|
const int CurrentTime = pEnvelope->m_vPoints[i].m_Time;
|
||||||
CurrentValue = pEnvelope->m_vPoints[i].m_aValues[c];
|
const int CurrentValue = pEnvelope->m_vPoints[i].m_aValues[c];
|
||||||
|
|
||||||
// update displayed text
|
// update displayed text
|
||||||
s_CurValueInput.SetFloat(fx2f(CurrentValue));
|
s_CurValueInput.SetFloat(fx2f(CurrentValue));
|
||||||
s_CurTimeInput.SetFloat(CurrentTime / 1000.0f);
|
s_CurTimeInput.SetFloat(CurrentTime / 1000.0f);
|
||||||
}
|
}
|
||||||
|
|
||||||
if(m_SelectedQuadEnvelope == m_SelectedEnvelope && m_SelectedEnvelopePoint == (int)i)
|
if(pID == s_pSelectedPoint || (m_SelectedQuadEnvelope == m_SelectedEnvelope && m_SelectedEnvelopePoint == (int)i))
|
||||||
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 1.0f);
|
||||||
else
|
else
|
||||||
Graphics()->SetColor(aColors[c].r * ColorMod, aColors[c].g * ColorMod, aColors[c].b * ColorMod, 1.0f);
|
Graphics()->SetColor(aColors[c].r * ColorMod, aColors[c].g * ColorMod, aColors[c].b * ColorMod, 1.0f);
|
||||||
IGraphics::CQuadItem QuadItem(Final.x, Final.y, Final.w, Final.h);
|
IGraphics::CQuadItem QuadItem(Final.x, Final.y, Final.w, Final.h);
|
||||||
Graphics()->QuadsDrawTL(&QuadItem, 1);
|
Graphics()->QuadsDrawTL(&QuadItem, 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// tangent handles for bezier curves
|
||||||
|
{
|
||||||
|
const float PosX = pEnvelope->m_vPoints[i].m_Time / 1000.0f / EndTime;
|
||||||
|
const float PosY = (fx2f(pEnvelope->m_vPoints[i].m_aValues[c]) - Bottom) / (Top - Bottom);
|
||||||
|
|
||||||
|
// Out-Tangent handle
|
||||||
|
if(pEnvelope->m_vPoints[i].m_Curvetype == CURVETYPE_BEZIER)
|
||||||
|
{
|
||||||
|
const float OutTangentX = PosX + (pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c] / 1000.0f / EndTime);
|
||||||
|
const float OutTangentY = PosY + fx2f(pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY[c]) / (Top - Bottom);
|
||||||
|
|
||||||
|
CUIRect Final;
|
||||||
|
Final.x = View.x + OutTangentX * View.w;
|
||||||
|
Final.y = View.y + View.h - OutTangentY * View.h;
|
||||||
|
Final.x -= 2.0f;
|
||||||
|
Final.y -= 2.0f;
|
||||||
|
Final.w = 4.0f;
|
||||||
|
Final.h = 4.0f;
|
||||||
|
|
||||||
|
// handle logic
|
||||||
|
bool Updated = false;
|
||||||
|
const void *pID = &pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c];
|
||||||
|
|
||||||
|
float ColorMod = 1.0f;
|
||||||
|
if(UI()->MouseInside(&Final))
|
||||||
|
UI()->SetHotItem(pID);
|
||||||
|
|
||||||
|
if(UI()->CheckActiveItem(pID))
|
||||||
|
{
|
||||||
|
if(!UI()->MouseButton(0))
|
||||||
|
{
|
||||||
|
m_SelectedQuadEnvelope = -1;
|
||||||
|
m_SelectedEnvelopePoint = -1;
|
||||||
|
|
||||||
|
UI()->SetActiveItem(nullptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float DeltaX = m_MouseDeltaX * TimeScale * (Input()->ModifierIsPressed() ? 1.0f : 1000.0f);
|
||||||
|
DeltaX = DeltaX < 0 ? -std::ceil(-DeltaX) : std::ceil(DeltaX);
|
||||||
|
pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c] += (int)DeltaX;
|
||||||
|
pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY[c] -= f2fx(m_MouseDeltaY * (Input()->ModifierIsPressed() ? 0.005f : ValueScale));
|
||||||
|
|
||||||
|
// clamp time value
|
||||||
|
pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c] = clamp<int>(pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c], 0, EndTime * 1000.0f - pEnvelope->m_vPoints[i].m_Time);
|
||||||
|
|
||||||
|
m_SelectedQuadEnvelope = m_SelectedEnvelope;
|
||||||
|
m_ShowEnvelopePreview = SHOWENV_SELECTED;
|
||||||
|
m_SelectedEnvelopePoint = i;
|
||||||
|
m_Map.OnModify();
|
||||||
|
}
|
||||||
|
ColorMod = 100.0f;
|
||||||
|
}
|
||||||
|
else if(UI()->HotItem() == pID)
|
||||||
|
{
|
||||||
|
if(UI()->MouseButton(0))
|
||||||
|
{
|
||||||
|
UI()->SetActiveItem(pID);
|
||||||
|
s_pSelectedPoint = pID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset
|
||||||
|
if(UI()->MouseButtonClicked(1))
|
||||||
|
{
|
||||||
|
UI()->SetActiveItem(pID);
|
||||||
|
s_pSelectedPoint = pID;
|
||||||
|
mem_zero(pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX, sizeof(pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX));
|
||||||
|
mem_zero(pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY, sizeof(pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY));
|
||||||
|
m_Map.OnModify();
|
||||||
|
Updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ShowEnvelopePreview = SHOWENV_SELECTED;
|
||||||
|
ColorMod = 100.0f;
|
||||||
|
m_pTooltip = "Bezier out-tangent. Left mouse to drag. Hold ctrl to be more precise. Right click to reset.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pID == s_pSelectedPoint && UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
|
||||||
|
{
|
||||||
|
pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c] = clamp<int>(s_CurTimeInput.GetFloat() * 1000.0f - pEnvelope->m_vPoints[i].m_Time, 0, EndTime * 1000.0f - pEnvelope->m_vPoints[i].m_Time);
|
||||||
|
pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY[c] = f2fx(s_CurValueInput.GetFloat()) - pEnvelope->m_vPoints[i].m_aValues[c];
|
||||||
|
Updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(UI()->CheckActiveItem(pID) || Updated)
|
||||||
|
{
|
||||||
|
const int CurrentTime = pEnvelope->m_vPoints[i].m_Time + pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaX[c];
|
||||||
|
const int CurrentValue = pEnvelope->m_vPoints[i].m_aValues[c] + pEnvelope->m_vPoints[i].m_Bezier.m_aOutTangentDeltaY[c];
|
||||||
|
|
||||||
|
// update displayed text
|
||||||
|
s_CurValueInput.SetFloat(fx2f(CurrentValue));
|
||||||
|
s_CurTimeInput.SetFloat(CurrentTime / 1000.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pID == s_pSelectedPoint || (m_SelectedQuadEnvelope == m_SelectedEnvelope && m_SelectedEnvelopePoint == (int)i))
|
||||||
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f);
|
||||||
|
else
|
||||||
|
Graphics()->SetColor(aColors[c].r * ColorMod, aColors[c].g * ColorMod, aColors[c].b * ColorMod, 0.5f);
|
||||||
|
|
||||||
|
// draw triangle
|
||||||
|
IGraphics::CFreeformItem FreeformItem(Final.x + Final.w / 2.0f, Final.y, Final.x + Final.w / 2.0f, Final.y, Final.x + Final.w, Final.y + Final.h, Final.x, Final.y + Final.h);
|
||||||
|
Graphics()->QuadsDrawFreeform(&FreeformItem, 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
// In-Tangent handle
|
||||||
|
if(i > 0 && pEnvelope->m_vPoints[i - 1].m_Curvetype == CURVETYPE_BEZIER)
|
||||||
|
{
|
||||||
|
const float InTangentX = PosX + pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c] / 1000.0f / EndTime;
|
||||||
|
const float InTangentY = PosY + fx2f(pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaY[c]) / (Top - Bottom);
|
||||||
|
|
||||||
|
CUIRect Final;
|
||||||
|
Final.x = View.x + InTangentX * View.w;
|
||||||
|
Final.y = View.y + View.h - InTangentY * View.h;
|
||||||
|
Final.x -= 2.0f;
|
||||||
|
Final.y -= 2.0f;
|
||||||
|
Final.w = 4.0f;
|
||||||
|
Final.h = 4.0f;
|
||||||
|
|
||||||
|
// handle logic
|
||||||
|
bool Updated = false;
|
||||||
|
const void *pID = &pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c];
|
||||||
|
|
||||||
|
float ColorMod = 1.0f;
|
||||||
|
if(UI()->MouseInside(&Final))
|
||||||
|
UI()->SetHotItem(pID);
|
||||||
|
|
||||||
|
if(UI()->CheckActiveItem(pID))
|
||||||
|
{
|
||||||
|
if(!UI()->MouseButton(0))
|
||||||
|
{
|
||||||
|
m_SelectedQuadEnvelope = -1;
|
||||||
|
m_SelectedEnvelopePoint = -1;
|
||||||
|
|
||||||
|
UI()->SetActiveItem(nullptr);
|
||||||
|
}
|
||||||
|
else
|
||||||
|
{
|
||||||
|
float DeltaX = m_MouseDeltaX * TimeScale * (Input()->ModifierIsPressed() ? 1.0f : 1000.0f);
|
||||||
|
DeltaX = DeltaX < 0 ? -std::ceil(-DeltaX) : std::ceil(DeltaX);
|
||||||
|
pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c] += (int)DeltaX;
|
||||||
|
pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaY[c] -= f2fx(m_MouseDeltaY * (Input()->ModifierIsPressed() ? 0.005f : ValueScale));
|
||||||
|
|
||||||
|
// clamp time value
|
||||||
|
pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c] = clamp(pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c], -pEnvelope->m_vPoints[i].m_Time, 0);
|
||||||
|
|
||||||
|
m_SelectedQuadEnvelope = m_SelectedEnvelope;
|
||||||
|
m_ShowEnvelopePreview = SHOWENV_SELECTED;
|
||||||
|
m_SelectedEnvelopePoint = i;
|
||||||
|
m_Map.OnModify();
|
||||||
|
}
|
||||||
|
ColorMod = 100.0f;
|
||||||
|
}
|
||||||
|
else if(UI()->HotItem() == pID)
|
||||||
|
{
|
||||||
|
if(UI()->MouseButton(0))
|
||||||
|
{
|
||||||
|
UI()->SetActiveItem(pID);
|
||||||
|
s_pSelectedPoint = pID;
|
||||||
|
}
|
||||||
|
|
||||||
|
// reset
|
||||||
|
if(UI()->MouseButtonClicked(1))
|
||||||
|
{
|
||||||
|
UI()->SetActiveItem(pID);
|
||||||
|
s_pSelectedPoint = pID;
|
||||||
|
mem_zero(pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX, sizeof(pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX));
|
||||||
|
mem_zero(pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaY, sizeof(pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaY));
|
||||||
|
m_Map.OnModify();
|
||||||
|
Updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
m_ShowEnvelopePreview = SHOWENV_SELECTED;
|
||||||
|
ColorMod = 100.0f;
|
||||||
|
m_pTooltip = "Bezier in-tangent. Left mouse to drag. Hold ctrl to be more precise. Right click to reset.";
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pID == s_pSelectedPoint && UI()->ConsumeHotkey(CUI::HOTKEY_ENTER))
|
||||||
|
{
|
||||||
|
pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c] = clamp<int>(s_CurTimeInput.GetFloat() * 1000.0f - pEnvelope->m_vPoints[i].m_Time, -pEnvelope->m_vPoints[i].m_Time, 0);
|
||||||
|
pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaY[c] = f2fx(s_CurValueInput.GetFloat()) - pEnvelope->m_vPoints[i].m_aValues[c];
|
||||||
|
Updated = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
if(UI()->CheckActiveItem(pID) || Updated)
|
||||||
|
{
|
||||||
|
const int CurrentTime = pEnvelope->m_vPoints[i].m_Time + pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaX[c];
|
||||||
|
const int CurrentValue = pEnvelope->m_vPoints[i].m_aValues[c] + pEnvelope->m_vPoints[i].m_Bezier.m_aInTangentDeltaY[c];
|
||||||
|
|
||||||
|
// update displayed text
|
||||||
|
s_CurValueInput.SetFloat(fx2f(CurrentValue));
|
||||||
|
s_CurTimeInput.SetFloat(CurrentTime / 1000.0f);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pID == s_pSelectedPoint || (m_SelectedQuadEnvelope == m_SelectedEnvelope && m_SelectedEnvelopePoint == (int)i))
|
||||||
|
Graphics()->SetColor(1.0f, 1.0f, 1.0f, 0.5f);
|
||||||
|
else
|
||||||
|
Graphics()->SetColor(aColors[c].r * ColorMod, aColors[c].g * ColorMod, aColors[c].b * ColorMod, 0.5f);
|
||||||
|
|
||||||
|
// draw triangle
|
||||||
|
IGraphics::CFreeformItem FreeformItem(Final.x + Final.w / 2.0f, Final.y, Final.x + Final.w / 2.0f, Final.y, Final.x + Final.w, Final.y + Final.h, Final.x, Final.y + Final.h);
|
||||||
|
Graphics()->QuadsDrawFreeform(&FreeformItem, 1);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
Graphics()->QuadsEnd();
|
Graphics()->QuadsEnd();
|
||||||
|
}
|
||||||
|
|
||||||
|
if(s_pSelectedPoint != nullptr)
|
||||||
|
{
|
||||||
CUIRect ToolBar1;
|
CUIRect ToolBar1;
|
||||||
CUIRect ToolBar2;
|
CUIRect ToolBar2;
|
||||||
ToolBar.VSplitMid(&ToolBar1, &ToolBar2);
|
ToolBar.VSplitMid(&ToolBar1, &ToolBar2);
|
||||||
|
@ -5976,8 +6233,8 @@ void CEditor::RenderEnvelopeEditor(CUIRect View)
|
||||||
UI()->DoLabel(&Label2, "Time (in s):", 10.0f, TEXTALIGN_MR);
|
UI()->DoLabel(&Label2, "Time (in s):", 10.0f, TEXTALIGN_MR);
|
||||||
}
|
}
|
||||||
|
|
||||||
DoEditBox(&s_CurValueInput, &ToolBar1, 10.0f, IGraphics::CORNER_ALL, "The value of the selected envelope point");
|
DoEditBox(&s_CurValueInput, &ToolBar1, 10.0f, IGraphics::CORNER_ALL, "The value of the selected element");
|
||||||
DoEditBox(&s_CurTimeInput, &ToolBar2, 10.0f, IGraphics::CORNER_ALL, "The time of the selected envelope point");
|
DoEditBox(&s_CurTimeInput, &ToolBar2, 10.0f, IGraphics::CORNER_ALL, "The time of the selected element");
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
@ -45,17 +45,50 @@ enum
|
||||||
|
|
||||||
class CEnvelope
|
class CEnvelope
|
||||||
{
|
{
|
||||||
public:
|
class CEnvelopePointAccess : public IEnvelopePointAccess
|
||||||
|
{
|
||||||
|
std::vector<CEnvPoint_runtime> *m_pvPoints;
|
||||||
|
|
||||||
|
public:
|
||||||
|
CEnvelopePointAccess(std::vector<CEnvPoint_runtime> *pvPoints)
|
||||||
|
{
|
||||||
|
m_pvPoints = pvPoints;
|
||||||
|
}
|
||||||
|
|
||||||
|
int NumPoints() const override
|
||||||
|
{
|
||||||
|
return m_pvPoints->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
const CEnvPoint *GetPoint(int Index) const override
|
||||||
|
{
|
||||||
|
if(Index < 0 || (size_t)Index >= m_pvPoints->size())
|
||||||
|
return nullptr;
|
||||||
|
return &m_pvPoints->at(Index);
|
||||||
|
}
|
||||||
|
|
||||||
|
const CEnvPointBezier *GetBezier(int Index) const override
|
||||||
|
{
|
||||||
|
if(Index < 0 || (size_t)Index >= m_pvPoints->size())
|
||||||
|
return nullptr;
|
||||||
|
return &m_pvPoints->at(Index).m_Bezier;
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
int m_Channels;
|
int m_Channels;
|
||||||
std::vector<CEnvPoint> m_vPoints;
|
|
||||||
|
public:
|
||||||
|
std::vector<CEnvPoint_runtime> m_vPoints;
|
||||||
|
CEnvelopePointAccess m_PointsAccess;
|
||||||
char m_aName[32];
|
char m_aName[32];
|
||||||
float m_Bottom, m_Top;
|
float m_Bottom, m_Top;
|
||||||
bool m_Synchronized;
|
bool m_Synchronized;
|
||||||
|
|
||||||
CEnvelope(int Chan)
|
CEnvelope(int Channels) :
|
||||||
|
m_PointsAccess(&m_vPoints)
|
||||||
{
|
{
|
||||||
m_Channels = Chan;
|
SetChannels(Channels);
|
||||||
m_aName[0] = 0;
|
m_aName[0] = '\0';
|
||||||
m_Bottom = 0;
|
m_Bottom = 0;
|
||||||
m_Top = 0;
|
m_Top = 0;
|
||||||
m_Synchronized = false;
|
m_Synchronized = false;
|
||||||
|
@ -71,46 +104,82 @@ public:
|
||||||
{
|
{
|
||||||
m_Top = -1000000000.0f;
|
m_Top = -1000000000.0f;
|
||||||
m_Bottom = 1000000000.0f;
|
m_Bottom = 1000000000.0f;
|
||||||
|
CEnvPoint_runtime *pPrevPoint = nullptr;
|
||||||
for(auto &Point : m_vPoints)
|
for(auto &Point : m_vPoints)
|
||||||
{
|
{
|
||||||
for(int c = 0; c < m_Channels; c++)
|
for(int c = 0; c < m_Channels; c++)
|
||||||
{
|
{
|
||||||
if(ChannelMask & (1 << c))
|
if(ChannelMask & (1 << c))
|
||||||
{
|
{
|
||||||
float v = fx2f(Point.m_aValues[c]);
|
{
|
||||||
if(v > m_Top)
|
// value handle
|
||||||
m_Top = v;
|
const float v = fx2f(Point.m_aValues[c]);
|
||||||
if(v < m_Bottom)
|
m_Top = maximum(m_Top, v);
|
||||||
m_Bottom = v;
|
m_Bottom = minimum(m_Bottom, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(Point.m_Curvetype == CURVETYPE_BEZIER)
|
||||||
|
{
|
||||||
|
// out-tangent handle
|
||||||
|
const float v = fx2f(Point.m_aValues[c] + Point.m_Bezier.m_aOutTangentDeltaY[c]);
|
||||||
|
m_Top = maximum(m_Top, v);
|
||||||
|
m_Bottom = minimum(m_Bottom, v);
|
||||||
|
}
|
||||||
|
|
||||||
|
if(pPrevPoint != nullptr && pPrevPoint->m_Curvetype == CURVETYPE_BEZIER)
|
||||||
|
{
|
||||||
|
// in-tangent handle
|
||||||
|
const float v = fx2f(Point.m_aValues[c] + Point.m_Bezier.m_aInTangentDeltaY[c]);
|
||||||
|
m_Top = maximum(m_Top, v);
|
||||||
|
m_Bottom = minimum(m_Bottom, v);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pPrevPoint = &Point;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
int Eval(float Time, ColorRGBA &Color)
|
int Eval(float Time, ColorRGBA &Color)
|
||||||
{
|
{
|
||||||
CRenderTools::RenderEvalEnvelope(&m_vPoints[0], m_vPoints.size(), m_Channels, std::chrono::nanoseconds((int64_t)((double)Time * (double)std::chrono::nanoseconds(1s).count())), Color);
|
CRenderTools::RenderEvalEnvelope(&m_PointsAccess, m_Channels, std::chrono::nanoseconds((int64_t)((double)Time * (double)std::chrono::nanoseconds(1s).count())), Color);
|
||||||
return m_Channels;
|
return m_Channels;
|
||||||
}
|
}
|
||||||
|
|
||||||
void AddPoint(int Time, int v0, int v1 = 0, int v2 = 0, int v3 = 0)
|
void AddPoint(int Time, int v0, int v1 = 0, int v2 = 0, int v3 = 0)
|
||||||
{
|
{
|
||||||
CEnvPoint p;
|
CEnvPoint_runtime p;
|
||||||
p.m_Time = Time;
|
p.m_Time = Time;
|
||||||
p.m_aValues[0] = v0;
|
p.m_aValues[0] = v0;
|
||||||
p.m_aValues[1] = v1;
|
p.m_aValues[1] = v1;
|
||||||
p.m_aValues[2] = v2;
|
p.m_aValues[2] = v2;
|
||||||
p.m_aValues[3] = v3;
|
p.m_aValues[3] = v3;
|
||||||
p.m_Curvetype = CURVETYPE_LINEAR;
|
p.m_Curvetype = CURVETYPE_LINEAR;
|
||||||
|
for(int c = 0; c < CEnvPoint::MAX_CHANNELS; c++)
|
||||||
|
{
|
||||||
|
p.m_Bezier.m_aInTangentDeltaX[c] = 0;
|
||||||
|
p.m_Bezier.m_aInTangentDeltaY[c] = 0;
|
||||||
|
p.m_Bezier.m_aOutTangentDeltaX[c] = 0;
|
||||||
|
p.m_Bezier.m_aOutTangentDeltaY[c] = 0;
|
||||||
|
}
|
||||||
m_vPoints.push_back(p);
|
m_vPoints.push_back(p);
|
||||||
Resort();
|
Resort();
|
||||||
}
|
}
|
||||||
|
|
||||||
float EndTime() const
|
float EndTime() const
|
||||||
{
|
{
|
||||||
if(!m_vPoints.empty())
|
if(m_vPoints.empty())
|
||||||
return m_vPoints[m_vPoints.size() - 1].m_Time * (1.0f / 1000.0f);
|
return 0.0f;
|
||||||
return 0;
|
return m_vPoints.back().m_Time / 1000.0f;
|
||||||
|
}
|
||||||
|
|
||||||
|
int GetChannels() const
|
||||||
|
{
|
||||||
|
return m_Channels;
|
||||||
|
}
|
||||||
|
|
||||||
|
void SetChannels(int Channels)
|
||||||
|
{
|
||||||
|
m_Channels = clamp<int>(Channels, 1, CEnvPoint::MAX_CHANNELS);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
|
@ -345,12 +345,13 @@ bool CEditorMap::Save(const char *pFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// save envelopes
|
// save envelopes
|
||||||
|
m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "editor", "saving envelopes");
|
||||||
int PointCount = 0;
|
int PointCount = 0;
|
||||||
for(size_t e = 0; e < m_vpEnvelopes.size(); e++)
|
for(size_t e = 0; e < m_vpEnvelopes.size(); e++)
|
||||||
{
|
{
|
||||||
CMapItemEnvelope Item;
|
CMapItemEnvelope Item;
|
||||||
Item.m_Version = CMapItemEnvelope::CURRENT_VERSION;
|
Item.m_Version = CMapItemEnvelope::CURRENT_VERSION;
|
||||||
Item.m_Channels = m_vpEnvelopes[e]->m_Channels;
|
Item.m_Channels = m_vpEnvelopes[e]->GetChannels();
|
||||||
Item.m_StartPoint = PointCount;
|
Item.m_StartPoint = PointCount;
|
||||||
Item.m_NumPoints = m_vpEnvelopes[e]->m_vPoints.size();
|
Item.m_NumPoints = m_vpEnvelopes[e]->m_vPoints.size();
|
||||||
Item.m_Synchronized = m_vpEnvelopes[e]->m_Synchronized;
|
Item.m_Synchronized = m_vpEnvelopes[e]->m_Synchronized;
|
||||||
|
@ -361,20 +362,61 @@ bool CEditorMap::Save(const char *pFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
// save points
|
// save points
|
||||||
int TotalSize = sizeof(CEnvPoint) * PointCount;
|
m_pEditor->Console()->Print(IConsole::OUTPUT_LEVEL_ADDINFO, "editor", "saving envelope points");
|
||||||
CEnvPoint *pPoints = (CEnvPoint *)calloc(maximum(PointCount, 1), sizeof(*pPoints));
|
bool BezierUsed = true;
|
||||||
|
for(const auto &pEnvelope : m_vpEnvelopes)
|
||||||
|
{
|
||||||
|
for(const auto &Point : pEnvelope->m_vPoints)
|
||||||
|
{
|
||||||
|
if(Point.m_Curvetype == CURVETYPE_BEZIER)
|
||||||
|
{
|
||||||
|
BezierUsed = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if(BezierUsed)
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
CEnvPoint *pPoints = (CEnvPoint *)calloc(maximum(PointCount, 1), sizeof(CEnvPoint));
|
||||||
|
CEnvPointBezier *pPointsBezier = nullptr;
|
||||||
|
if(BezierUsed)
|
||||||
|
pPointsBezier = (CEnvPointBezier *)calloc(maximum(PointCount, 1), sizeof(CEnvPointBezier));
|
||||||
PointCount = 0;
|
PointCount = 0;
|
||||||
|
|
||||||
for(const auto &pEnvelope : m_vpEnvelopes)
|
for(const auto &pEnvelope : m_vpEnvelopes)
|
||||||
{
|
{
|
||||||
int Count = pEnvelope->m_vPoints.size();
|
const CEnvPoint_runtime *pPrevPoint = nullptr;
|
||||||
mem_copy(&pPoints[PointCount], pEnvelope->m_vPoints.data(), sizeof(CEnvPoint) * Count);
|
for(const auto &Point : pEnvelope->m_vPoints)
|
||||||
PointCount += Count;
|
{
|
||||||
|
mem_copy(&pPoints[PointCount], &Point, sizeof(CEnvPoint));
|
||||||
|
if(pPointsBezier != nullptr)
|
||||||
|
{
|
||||||
|
if(Point.m_Curvetype == CURVETYPE_BEZIER)
|
||||||
|
{
|
||||||
|
mem_copy(&pPointsBezier[PointCount].m_aOutTangentDeltaX, &Point.m_Bezier.m_aOutTangentDeltaX, sizeof(Point.m_Bezier.m_aOutTangentDeltaX));
|
||||||
|
mem_copy(&pPointsBezier[PointCount].m_aOutTangentDeltaY, &Point.m_Bezier.m_aOutTangentDeltaY, sizeof(Point.m_Bezier.m_aOutTangentDeltaY));
|
||||||
|
}
|
||||||
|
if(pPrevPoint != nullptr && pPrevPoint->m_Curvetype == CURVETYPE_BEZIER)
|
||||||
|
{
|
||||||
|
mem_copy(&pPointsBezier[PointCount].m_aInTangentDeltaX, &Point.m_Bezier.m_aInTangentDeltaX, sizeof(Point.m_Bezier.m_aInTangentDeltaX));
|
||||||
|
mem_copy(&pPointsBezier[PointCount].m_aInTangentDeltaY, &Point.m_Bezier.m_aInTangentDeltaY, sizeof(Point.m_Bezier.m_aInTangentDeltaY));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
PointCount++;
|
||||||
|
pPrevPoint = &Point;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
df.AddItem(MAPITEMTYPE_ENVPOINTS, 0, TotalSize, pPoints);
|
df.AddItem(MAPITEMTYPE_ENVPOINTS, 0, sizeof(CEnvPoint) * PointCount, pPoints);
|
||||||
free(pPoints);
|
free(pPoints);
|
||||||
|
|
||||||
|
if(pPointsBezier != nullptr)
|
||||||
|
{
|
||||||
|
df.AddItem(MAPITEMTYPE_ENVPOINTS_BEZIER, 0, sizeof(CEnvPointBezier) * PointCount, pPointsBezier);
|
||||||
|
free(pPointsBezier);
|
||||||
|
}
|
||||||
|
|
||||||
// finish the data file
|
// finish the data file
|
||||||
std::shared_ptr<CDataFileWriterFinishJob> pWriterFinishJob = std::make_shared<CDataFileWriterFinishJob>(pFileName, std::move(df));
|
std::shared_ptr<CDataFileWriterFinishJob> pWriterFinishJob = std::make_shared<CDataFileWriterFinishJob>(pFileName, std::move(df));
|
||||||
m_pEditor->Engine()->AddJob(pWriterFinishJob);
|
m_pEditor->Engine()->AddJob(pWriterFinishJob);
|
||||||
|
@ -904,37 +946,38 @@ bool CEditorMap::Load(const char *pFileName, int StorageType, const std::functio
|
||||||
|
|
||||||
// load envelopes
|
// load envelopes
|
||||||
{
|
{
|
||||||
CEnvPoint *pPoints = nullptr;
|
const CMapBasedEnvelopePointAccess EnvelopePoints(&DataFile);
|
||||||
|
|
||||||
|
int EnvStart, EnvNum;
|
||||||
|
DataFile.GetType(MAPITEMTYPE_ENVELOPE, &EnvStart, &EnvNum);
|
||||||
|
for(int e = 0; e < EnvNum; e++)
|
||||||
{
|
{
|
||||||
int Start, Num;
|
CMapItemEnvelope *pItem = (CMapItemEnvelope *)DataFile.GetItem(EnvStart + e);
|
||||||
DataFile.GetType(MAPITEMTYPE_ENVPOINTS, &Start, &Num);
|
|
||||||
if(Num)
|
|
||||||
pPoints = (CEnvPoint *)DataFile.GetItem(Start);
|
|
||||||
}
|
|
||||||
|
|
||||||
int Start, Num;
|
|
||||||
DataFile.GetType(MAPITEMTYPE_ENVELOPE, &Start, &Num);
|
|
||||||
for(int e = 0; e < Num; e++)
|
|
||||||
{
|
|
||||||
CMapItemEnvelope *pItem = (CMapItemEnvelope *)DataFile.GetItem(Start + e);
|
|
||||||
CEnvelope *pEnv = new CEnvelope(pItem->m_Channels);
|
CEnvelope *pEnv = new CEnvelope(pItem->m_Channels);
|
||||||
pEnv->m_vPoints.resize(pItem->m_NumPoints);
|
pEnv->m_vPoints.resize(pItem->m_NumPoints);
|
||||||
mem_copy(pEnv->m_vPoints.data(), &pPoints[pItem->m_StartPoint], sizeof(CEnvPoint) * pItem->m_NumPoints);
|
for(int p = 0; p < pItem->m_NumPoints; p++)
|
||||||
|
{
|
||||||
|
const CEnvPoint *pPoint = EnvelopePoints.GetPoint(pItem->m_StartPoint + p);
|
||||||
|
if(pPoint != nullptr)
|
||||||
|
mem_copy(&pEnv->m_vPoints[p], pPoint, sizeof(CEnvPoint));
|
||||||
|
const CEnvPointBezier *pPointBezier = EnvelopePoints.GetBezier(pItem->m_StartPoint + p);
|
||||||
|
if(pPointBezier != nullptr)
|
||||||
|
mem_copy(&pEnv->m_vPoints[p].m_Bezier, pPointBezier, sizeof(CEnvPointBezier));
|
||||||
|
}
|
||||||
if(pItem->m_aName[0] != -1) // compatibility with old maps
|
if(pItem->m_aName[0] != -1) // compatibility with old maps
|
||||||
IntsToStr(pItem->m_aName, sizeof(pItem->m_aName) / sizeof(int), pEnv->m_aName);
|
IntsToStr(pItem->m_aName, sizeof(pItem->m_aName) / sizeof(int), pEnv->m_aName);
|
||||||
m_vpEnvelopes.push_back(pEnv);
|
m_vpEnvelopes.push_back(pEnv);
|
||||||
if(pItem->m_Version >= 2)
|
if(pItem->m_Version >= CMapItemEnvelope_v2::CURRENT_VERSION)
|
||||||
pEnv->m_Synchronized = pItem->m_Synchronized;
|
pEnv->m_Synchronized = pItem->m_Synchronized;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
{
|
{
|
||||||
int Start, Num;
|
int AutomapperConfigStart, AutomapperConfigNum;
|
||||||
DataFile.GetType(MAPITEMTYPE_AUTOMAPPER_CONFIG, &Start, &Num);
|
DataFile.GetType(MAPITEMTYPE_AUTOMAPPER_CONFIG, &AutomapperConfigStart, &AutomapperConfigNum);
|
||||||
for(int i = 0; i < Num; i++)
|
for(int i = 0; i < AutomapperConfigNum; i++)
|
||||||
{
|
{
|
||||||
CMapItemAutoMapperConfig *pItem = (CMapItemAutoMapperConfig *)DataFile.GetItem(Start + i);
|
CMapItemAutoMapperConfig *pItem = (CMapItemAutoMapperConfig *)DataFile.GetItem(AutomapperConfigStart + i);
|
||||||
if(pItem->m_Version == CMapItemAutoMapperConfig::CURRENT_VERSION)
|
if(pItem->m_Version == CMapItemAutoMapperConfig::CURRENT_VERSION)
|
||||||
{
|
{
|
||||||
if(pItem->m_GroupId >= 0 && (size_t)pItem->m_GroupId < m_vpGroups.size() &&
|
if(pItem->m_GroupId >= 0 && (size_t)pItem->m_GroupId < m_vpGroups.size() &&
|
||||||
|
|
|
@ -930,7 +930,7 @@ CUI::EPopupMenuFunctionResult CLayerTiles::RenderProperties(CUIRect *pToolBox)
|
||||||
{
|
{
|
||||||
for(; Index >= -1 && Index < (int)m_pEditor->m_Map.m_vpEnvelopes.size(); Index += Step)
|
for(; Index >= -1 && Index < (int)m_pEditor->m_Map.m_vpEnvelopes.size(); Index += Step)
|
||||||
{
|
{
|
||||||
if(Index == -1 || m_pEditor->m_Map.m_vpEnvelopes[Index]->m_Channels == 4)
|
if(Index == -1 || m_pEditor->m_Map.m_vpEnvelopes[Index]->GetChannels() == 4)
|
||||||
{
|
{
|
||||||
m_ColorEnv = Index;
|
m_ColorEnv = Index;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -936,7 +936,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b
|
||||||
{
|
{
|
||||||
for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
|
for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
|
||||||
{
|
{
|
||||||
if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->m_Channels == 3)
|
if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->GetChannels() == 3)
|
||||||
{
|
{
|
||||||
pQuad->m_PosEnv = Index;
|
pQuad->m_PosEnv = Index;
|
||||||
break;
|
break;
|
||||||
|
@ -956,7 +956,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupQuad(void *pContext, CUIRect View, b
|
||||||
{
|
{
|
||||||
for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
|
for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
|
||||||
{
|
{
|
||||||
if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->m_Channels == 4)
|
if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->GetChannels() == 4)
|
||||||
{
|
{
|
||||||
pQuad->m_ColorEnv = Index;
|
pQuad->m_ColorEnv = Index;
|
||||||
break;
|
break;
|
||||||
|
@ -1095,7 +1095,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View,
|
||||||
const int StepDirection = Index < pSource->m_PosEnv ? -1 : 1;
|
const int StepDirection = Index < pSource->m_PosEnv ? -1 : 1;
|
||||||
for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
|
for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
|
||||||
{
|
{
|
||||||
if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->m_Channels == 3)
|
if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->GetChannels() == 3)
|
||||||
{
|
{
|
||||||
pSource->m_PosEnv = Index;
|
pSource->m_PosEnv = Index;
|
||||||
break;
|
break;
|
||||||
|
@ -1112,7 +1112,7 @@ CUI::EPopupMenuFunctionResult CEditor::PopupSource(void *pContext, CUIRect View,
|
||||||
const int StepDirection = Index < pSource->m_SoundEnv ? -1 : 1;
|
const int StepDirection = Index < pSource->m_SoundEnv ? -1 : 1;
|
||||||
for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
|
for(; Index >= -1 && Index < (int)pEditor->m_Map.m_vpEnvelopes.size(); Index += StepDirection)
|
||||||
{
|
{
|
||||||
if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->m_Channels == 1)
|
if(Index == -1 || pEditor->m_Map.m_vpEnvelopes[Index]->GetChannels() == 1)
|
||||||
{
|
{
|
||||||
pSource->m_SoundEnv = Index;
|
pSource->m_SoundEnv = Index;
|
||||||
break;
|
break;
|
||||||
|
|
|
@ -37,6 +37,7 @@ enum
|
||||||
CURVETYPE_SLOW,
|
CURVETYPE_SLOW,
|
||||||
CURVETYPE_FAST,
|
CURVETYPE_FAST,
|
||||||
CURVETYPE_SMOOTH,
|
CURVETYPE_SMOOTH,
|
||||||
|
CURVETYPE_BEZIER,
|
||||||
NUM_CURVETYPES,
|
NUM_CURVETYPES,
|
||||||
|
|
||||||
// game layer tiles
|
// game layer tiles
|
||||||
|
@ -351,6 +352,8 @@ struct CMapItemVersion
|
||||||
int m_Version;
|
int m_Version;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Represents basic information about envelope points.
|
||||||
|
// In upstream Teeworlds, this is only used if all CMapItemEnvelope are version 1 or 2.
|
||||||
struct CEnvPoint
|
struct CEnvPoint
|
||||||
{
|
{
|
||||||
enum
|
enum
|
||||||
|
@ -359,14 +362,45 @@ struct CEnvPoint
|
||||||
};
|
};
|
||||||
|
|
||||||
int m_Time; // in ms
|
int m_Time; // in ms
|
||||||
int m_Curvetype;
|
int m_Curvetype; // CURVETYPE_* constants, any unknown value behaves like CURVETYPE_LINEAR
|
||||||
int m_aValues[MAX_CHANNELS]; // 1-4 depending on envelope (22.10 fixed point)
|
int m_aValues[MAX_CHANNELS]; // 1-4 depending on envelope (22.10 fixed point)
|
||||||
|
|
||||||
bool operator<(const CEnvPoint &Other) const { return m_Time < Other.m_Time; }
|
bool operator<(const CEnvPoint &Other) const { return m_Time < Other.m_Time; }
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Represents additional envelope point information for CURVETYPE_BEZIER.
|
||||||
|
// In DDNet, these are stored separately in an UUID-based map item.
|
||||||
|
// In upstream Teeworlds, CEnvPointBezier_upstream is used instead.
|
||||||
|
struct CEnvPointBezier
|
||||||
|
{
|
||||||
|
// DeltaX in ms and DeltaY as 22.10 fxp
|
||||||
|
int m_aInTangentDeltaX[CEnvPoint::MAX_CHANNELS];
|
||||||
|
int m_aInTangentDeltaY[CEnvPoint::MAX_CHANNELS];
|
||||||
|
int m_aOutTangentDeltaX[CEnvPoint::MAX_CHANNELS];
|
||||||
|
int m_aOutTangentDeltaY[CEnvPoint::MAX_CHANNELS];
|
||||||
|
};
|
||||||
|
|
||||||
|
// Written to maps on upstream Teeworlds for envelope points including bezier information instead of the basic
|
||||||
|
// CEnvPoint items, if at least one CMapItemEnvelope with version 3 or higher exists in the map.
|
||||||
|
struct CEnvPointBezier_upstream : public CEnvPoint
|
||||||
|
{
|
||||||
|
CEnvPointBezier m_Bezier;
|
||||||
|
};
|
||||||
|
|
||||||
|
// Used to represent all envelope point information at runtime in editor.
|
||||||
|
// (Can eventually be different than CEnvPointBezier_upstream)
|
||||||
|
struct CEnvPoint_runtime : public CEnvPoint
|
||||||
|
{
|
||||||
|
CEnvPointBezier m_Bezier;
|
||||||
|
};
|
||||||
|
|
||||||
struct CMapItemEnvelope_v1
|
struct CMapItemEnvelope_v1
|
||||||
{
|
{
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
CURRENT_VERSION = 1,
|
||||||
|
};
|
||||||
|
|
||||||
int m_Version;
|
int m_Version;
|
||||||
int m_Channels;
|
int m_Channels;
|
||||||
int m_StartPoint;
|
int m_StartPoint;
|
||||||
|
@ -374,15 +408,29 @@ struct CMapItemEnvelope_v1
|
||||||
int m_aName[8];
|
int m_aName[8];
|
||||||
};
|
};
|
||||||
|
|
||||||
struct CMapItemEnvelope : public CMapItemEnvelope_v1
|
struct CMapItemEnvelope_v2 : public CMapItemEnvelope_v1
|
||||||
{
|
{
|
||||||
enum
|
enum
|
||||||
{
|
{
|
||||||
CURRENT_VERSION = 2
|
CURRENT_VERSION = 2,
|
||||||
};
|
};
|
||||||
|
|
||||||
int m_Synchronized;
|
int m_Synchronized;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Only written to maps in upstream Teeworlds.
|
||||||
|
// If at least one of these exists in a map, the envelope points
|
||||||
|
// are represented by CEnvPointBezier_upstream instead of CEnvPoint.
|
||||||
|
struct CMapItemEnvelope_v3 : public CMapItemEnvelope_v2
|
||||||
|
{
|
||||||
|
enum
|
||||||
|
{
|
||||||
|
CURRENT_VERSION = 3,
|
||||||
|
};
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef CMapItemEnvelope_v2 CMapItemEnvelope;
|
||||||
|
|
||||||
struct CSoundShape
|
struct CSoundShape
|
||||||
{
|
{
|
||||||
enum
|
enum
|
||||||
|
|
|
@ -3,3 +3,4 @@
|
||||||
UUID(MAPITEMTYPE_TEST, "mapitemtype-test@ddnet.tw")
|
UUID(MAPITEMTYPE_TEST, "mapitemtype-test@ddnet.tw")
|
||||||
UUID(MAPITEMTYPE_AUTOMAPPER_CONFIG, "mapitemtype-automapper-config@ddnet.tw")
|
UUID(MAPITEMTYPE_AUTOMAPPER_CONFIG, "mapitemtype-automapper-config@ddnet.tw")
|
||||||
UUID(MAPITEMTYPE_GROUP_EX, "mapitemtype-group@ddnet.tw")
|
UUID(MAPITEMTYPE_GROUP_EX, "mapitemtype-group@ddnet.tw")
|
||||||
|
UUID(MAPITEMTYPE_ENVPOINTS_BEZIER, "mapitemtype-envpoints-bezier@ddnet.tw")
|
||||||
|
|
Loading…
Reference in a new issue