Files
GTASource/game/debug/DebugModelAnalysis.cpp

1752 lines
58 KiB
C++
Raw Normal View History

2025-02-23 17:40:52 +08:00
// ============================
// debug/DebugModelAnalysis.cpp
// (c) 2012 RockstarNorth
// ============================
#if __BANK
#include "atl/array.h"
#include "grcore/debugdraw.h"
#include "grcore/viewport.h"
#if __PS3
#include <cell/rtc.h>
#include "grcore/edgeExtractgeomspu.h"
#endif // __PS3
#include "grmodel/geometry.h"
#include "vector/color32.h"
#include "vectormath/classes.h"
#include "bank/bkmgr.h"
#include "bank/bank.h"
#include "string/stringutil.h"
#include "system/memory.h"
#include "system/nelem.h"
#include "fwdebug/picker.h"
#include "fwmaths/vectorutil.h"
#include "fwutil/xmacro.h"
#include "camera/viewports/ViewportManager.h"
#include "debug/DebugGeometryUtil.h"
#include "debug/DebugModelAnalysis.h"
#include "debug/DebugVoxelAnalysis.h"
#include "debug/GtaPicker.h"
#include "debug/UiGadget/UiColorScheme.h"
#include "debug/UiGadget/UIGadgetInspector.h"
#include "debug/UiGadget/UiGadgetList.h"
#include "modelinfo/BaseModelInfo.h"
#include "scene/Entity.h"
#include "vector/geometry.h"
#include "Vehicles/vehicle.h"
namespace DMA {
class CDMAHistogramProperties
{
public:
enum eHistogramMode
{
MODE_TRIANGLE_AREA_SQRT,
MODE_TRIANGLE_AREA,
MODE_TRIANGLE_EDGE_MAX,
MODE_TRIANGLE_EDGE_MIN,
MODE_TRIANGLE_HEIGHT_MAX,
MODE_TRIANGLE_HEIGHT_MIN,
MODE_TRIANGLE_ANGLE_MAX,
MODE_TRIANGLE_ANGLE_MIN,
MODE_EDGE_LENGTH,
MODE_COUNT
};
enum eHistogramDistScale
{
SCALE_NONE,
SCALE_BY_MODEL_LOD_DISTANCE,
SCALE_BY_CAMERA_DISTANCE,
SCALE_COUNT
};
CDMAHistogramProperties()
{
m_mode = MODE_TRIANGLE_AREA_SQRT;
m_distScale = SCALE_NONE;
m_bucketCount = 64;
m_screenspace = false;
m_useLogScale = true;
m_rangeLower = 0.0f;
m_rangeUpper = 1.0f;
m_expBiasX = 0.0f;
m_valueRef = 0.0f;
m_ratioRef = 0.0f;
}
bool RequiresUpdate(const CDMAHistogramProperties& rhs) const
{
if (m_mode != rhs.m_mode ) { return true; }
if (m_distScale != rhs.m_distScale ) { return true; }
if (m_bucketCount != rhs.m_bucketCount) { return true; }
if (m_screenspace != rhs.m_screenspace) { return true; }
if (m_useLogScale != rhs.m_useLogScale) { return true; }
if (m_rangeLower != rhs.m_rangeLower ) { return true; }
if (m_rangeUpper != rhs.m_rangeUpper ) { return true; }
if (m_expBiasX != rhs.m_expBiasX ) { return true; }
if (m_valueRef != rhs.m_valueRef ) { return true; }
if (m_ratioRef != rhs.m_ratioRef ) { return true; }
return false;
}
int m_mode; // eHistogramMode
int m_distScale; // eHistogramDistScale
int m_bucketCount;
bool m_screenspace;
bool m_useLogScale;
float m_rangeLower; // outliers
float m_rangeUpper; // outliers
float m_expBiasX;
float m_valueRef; // will compute # of samples whose value is <= this reference in m_valueRefCount
float m_ratioRef; // will compute value of sample @ m_bucketCount times this in m_ratioRefValue
};
class CDMAHistogramWindowSettings
{
public:
enum eWindowMode
{
WINDOW_MODE_LINES,
WINDOW_MODE_BARS,
WINDOW_MODE_COUNT
};
CDMAHistogramWindowSettings()
{
m_windowMode = WINDOW_MODE_BARS;
m_windowX = 40;
m_windowY = 100;
m_windowW = 320;
m_windowH = 128;
m_windowBackgroundColour = Color32(80,80,80,255);
m_windowBarColourOutline = Color32(255,255,255,80);
m_windowBarColourOutlineSelected = Color32(255,0,0,255);
m_windowBarColourFill = Color32(80,80,128,255);
m_windowBarColourFillSelected = Color32(255,0,0,255);
m_windowBarOpacityNotSelected = 1.0f;
m_windowExpBiasXScale = 0.0f;
m_windowExpBiasY = 0.0f;
}
int m_windowMode; // eWindowMode
int m_windowX;
int m_windowY;
int m_windowW;
int m_windowH;
Color32 m_windowBackgroundColour;
Color32 m_windowBarColourOutline;
Color32 m_windowBarColourOutlineSelected;
Color32 m_windowBarColourFill;
Color32 m_windowBarColourFillSelected;
float m_windowBarOpacityNotSelected;
float m_windowExpBiasXScale;
float m_windowExpBiasY;
};
static bool g_CDMAUpdateValueMinMax = false;
static void CDMAUpdateValueMinMax() { g_CDMAUpdateValueMinMax = true; }
class CDMAHistogramSettings : public CDMAHistogramProperties, public CDMAHistogramWindowSettings
{
public:
CDMAHistogramSettings()
{
m_enabled = false;
m_trackPickerWindow = true;
m_valueMinMaxEnabled = false;
m_valueMinMaxLocked = false;
m_valueMin = 0.0f;
m_valueMax = 0.0f;
m_debugBucketIndex = -1;
m_debugDrawMatrix = Mat34V(V_ZERO);
m_debugDrawEnabled = false;
m_debugDrawColour = Color32(255,0,0,255);
m_debugDrawSolid = false;
m_debugDrawProjectToScreen = false;
m_debugDrawShowTriNormals = false;
m_debugDrawTriNormalLength = 0.5f;
m_mouseRayTestEnabled = false;
m_mouseRayTestDepth = 1024.0f;
m_mouseRayTestOrigin = Vec3V(V_ZERO);
m_mouseRayTestEnd = Vec3V(V_ZERO);
m_mouseRayTestFraction = ScalarV(V_ZERO);
m_mouseRayTestHitPos = Vec3V(V_ZERO);
m_mouseRayTestHitNormal = Vec3V(V_ZERO);
m_mouseRayTestHitTri[0] = Vec3V(V_ZERO);
m_mouseRayTestHitTri[1] = Vec3V(V_ZERO);
m_mouseRayTestHitTri[2] = Vec3V(V_ZERO);
}
void AddWidgets(bkBank& bank)
{
const char* modeStrings[] =
{
"MODE_TRIANGLE_AREA_SQRT",
"MODE_TRIANGLE_AREA",
"MODE_TRIANGLE_EDGE_MAX",
"MODE_TRIANGLE_EDGE_MIN",
"MODE_TRIANGLE_HEIGHT_MAX",
"MODE_TRIANGLE_HEIGHT_MIN",
"MODE_TRIANGLE_ANGLE_MAX",
"MODE_TRIANGLE_ANGLE_MIN",
"MODE_EDGE_LENGTH",
};
CompileTimeAssert(NELEM(modeStrings) == MODE_COUNT);
const char* distScaleStrings[] =
{
"",
"SCALE_BY_MODEL_LOD_DISTANCE",
"SCALE_BY_CAMERA_DISTANCE",
};
CompileTimeAssert(NELEM(distScaleStrings) == SCALE_COUNT);
bank.AddToggle("Enabled" , &m_enabled);
bank.AddToggle("Track Picker", &m_trackPickerWindow);
bank.AddSeparator();
bank.AddToggle("Value Min/Max Enabled", &m_valueMinMaxEnabled, CDMAUpdateValueMinMax);
bank.AddToggle("Value Min/Max Locked" , &m_valueMinMaxLocked, CDMAUpdateValueMinMax);
bank.AddSlider("Value Min" , &m_valueMin, 0.0f, 1000.0f, 1.0f/256.0f, CDMAUpdateValueMinMax);
bank.AddSlider("Value Max" , &m_valueMax, 0.0f, 1000.0f, 1.0f/256.0f, CDMAUpdateValueMinMax);
bank.AddSeparator();
bank.AddCombo ("Mode" , &m_mode, NELEM(modeStrings), modeStrings);
bank.AddCombo ("Distance Scale" , &m_distScale, NELEM(distScaleStrings), distScaleStrings);
bank.AddSlider("Bucket Count" , &m_bucketCount, 8, 512, 1);
bank.AddToggle("Screenspace" , &m_screenspace);
bank.AddToggle("Use Log Scale" , &m_useLogScale);
bank.AddSlider("Range Lower" , &m_rangeLower, 0.0f, 1.0f, 1.0f/128.0f);
bank.AddSlider("Range Upper" , &m_rangeUpper, 0.0f, 1.0f, 1.0f/128.0f);
bank.AddSlider("Exp Bias X" , &m_expBiasX, -4.0f, 4.0f, 1.0f/128.0f);
bank.AddSlider("Value Reference", &m_valueRef, 0.0f, 100.0f, 1.0f/512.0f);
bank.AddSlider("Ratio Reference", &m_ratioRef, 0.0f, 1.0f, 1.0f/512.0f);
bank.AddSeparator();
bank.AddSlider("Debug Bucket Index", &m_debugBucketIndex, -1, 512 - 1, 1);
bank.AddSeparator();
bank.AddToggle("Debug Draw Enabled" , &m_debugDrawEnabled);
bank.AddColor ("Debug Draw Bucket Colour" , &m_debugDrawColour);
bank.AddToggle("Debug Draw Solid" , &m_debugDrawSolid);
bank.AddToggle("Debug Draw Project to Screen", &m_debugDrawProjectToScreen);
bank.AddToggle("Debug Draw Show Tri Normals" , &m_debugDrawShowTriNormals);
bank.AddSlider("Debug Draw Tri Normal Length", &m_debugDrawTriNormalLength, 0.1f, 10.0f, 1.0f/32.0f);
bank.AddSeparator();
bank.AddToggle("Mouse Ray Test Enabled (use middle mouse button to track)", &m_mouseRayTestEnabled);
bank.AddSlider("Mouse Ray Test Depth", &m_mouseRayTestDepth, 1.0f, 1024.0f, 1.0f);
bank.AddSeparator();
const char* windowModeStrings[] =
{
"Lines",
"Bars",
};
CompileTimeAssert(NELEM(windowModeStrings) == CDMAHistogramWindowSettings::WINDOW_MODE_COUNT);
bank.AddCombo ("Window Mode" , &m_windowMode, NELEM(windowModeStrings), windowModeStrings);
bank.AddSlider("Window X" , &m_windowX, 0, 1280, 1);
bank.AddSlider("Window Y" , &m_windowY, 0, 720, 1);
bank.AddSlider("Window W" , &m_windowW, 0, 1280, 1);
bank.AddSlider("Window H" , &m_windowH, 0, 720, 1);
bank.AddColor ("Window Background" , &m_windowBackgroundColour);
bank.AddColor ("Outline Colour" , &m_windowBarColourOutline);
bank.AddColor ("Outline Selected" , &m_windowBarColourOutlineSelected);
bank.AddColor ("Fill Colour" , &m_windowBarColourFill);
bank.AddColor ("Fill Selected" , &m_windowBarColourFillSelected);
bank.AddSlider("Bar Opacity Not Selected", &m_windowBarOpacityNotSelected, 0.0f, 1.0f, 1.0f/32.0f);
bank.AddSlider("Exp Bias X Scale" , &m_windowExpBiasXScale, 0.0f, 1.0f, 1.0f/32.0f);
bank.AddSlider("Exp Bias Y" , &m_windowExpBiasY, -4.0f, 4.0f, 1.0f/128.0f);
}
bool m_enabled;
bool m_trackPickerWindow;
bool m_valueMinMaxEnabled;
bool m_valueMinMaxLocked;
float m_valueMin;
float m_valueMax;
int m_debugBucketIndex;
Mat34V m_debugDrawMatrix; // for passing entity transform
bool m_debugDrawEnabled;
Color32 m_debugDrawColour;
bool m_debugDrawSolid;
bool m_debugDrawProjectToScreen;
bool m_debugDrawShowTriNormals;
float m_debugDrawTriNormalLength;
bool m_mouseRayTestEnabled;
float m_mouseRayTestDepth;
Vec3V m_mouseRayTestOrigin;
Vec3V m_mouseRayTestEnd;
ScalarV m_mouseRayTestFraction;
Vec3V m_mouseRayTestHitPos;
Vec3V m_mouseRayTestHitNormal;
Vec3V m_mouseRayTestHitTri[3];
};
class CDMAHistogram : public CDMAHistogramProperties
{
public:
CDMAHistogram()
{
m_modelName[0] = '\0';
m_buckets = NULL;
m_bucketCount = 0; // base-class properties bucket count will be non-zero
m_bucketMin = 0;
m_bucketMax = 0;
m_bucketSum = 0;
m_valueMin = 0.0f;
m_valueMax = 0.0f;
m_valueRefCount = 0;
m_ratioRefValue = 0.0f;
m_numTriangles = 0;
m_numTrianglesDegenerateIdx = 0;
m_numTrianglesDegenerateVtx = 0;
}
void AddTriangle(atArray<float>& values, int pass, float ex, float scale, Vec3V_In v0, Vec3V_In v1, Vec3V_In v2, u16 index0, u16 index1, u16 index2, CDMAHistogramSettings* pDebugDrawSettings = NULL);
float GetTriangleValue(Vec3V_In v0, Vec3V_In v1, Vec3V_In v2) const;
int GetBucketIndex(float value, float ex) const;
float GetBucketMinValue(int bucketIndex) const;
float GetBucketMaxValue(int bucketIndex) const;
float GetBucketMaxValueCoverage(float percent) const;
float GetValueMin() const { return m_useLogScale ? expf(m_valueMin) : m_valueMin; }
float GetValueMax() const { return m_useLogScale ? expf(m_valueMax) : m_valueMax; }
float GetRatioRefValue() const { return m_useLogScale ? expf(m_ratioRefValue) : m_ratioRefValue; }
void Update(const Drawable* pDrawable, const char* modelName, const CDMAHistogramProperties& properties, CDMAHistogramSettings* pDebugDrawSettings = NULL);
void UpdateWindow(const CDMAHistogramWindowSettings& settings, int selectedBucketIndex = -1) const;
char m_modelName[80];
int* m_buckets;
int m_bucketMin; // smallest bucket size that's not zero
int m_bucketMax; // largest bucket size
int m_bucketSum; // sum of all buckets
float m_valueMin;
float m_valueMax;
int m_valueRefCount; // number of samples <= m_valueRef
float m_ratioRefValue;
int m_numTriangles;
int m_numTrianglesDegenerateIdx;
int m_numTrianglesDegenerateVtx;
};
static float g_modelLODDistance = 0.0f;
static Vec3V g_modelWorldCentre = Vec3V(V_ZERO);
static CDMAHistogramSettings g_settings; // rag controls
static CDMAHistogram g_histogram;
void CDMAHistogram::AddTriangle(atArray<float>& values, int pass, float ex, float scale, Vec3V_In v0, Vec3V_In v1, Vec3V_In v2, u16 index0, u16 index1, u16 index2, CDMAHistogramSettings* pDebugDrawSettings)
{
if (index0 == index1 ||
index1 == index2 ||
index2 == index0)
{
if (pass == 0)
{
m_numTrianglesDegenerateIdx++;
}
return;
}
if (IsEqualAll(v0, v1) |
IsEqualAll(v1, v2) |
IsEqualAll(v2, v0))
{
if (pass == 0)
{
m_numTrianglesDegenerateVtx++;
}
return;
}
Vec3V v[3]; // for calculating triangle/edge value
Vec3V p[3]; // for debug draw
if (pDebugDrawSettings && m_screenspace) // this is a bit expensive ..
{
const grcViewport& gameVP = *gVpMan.GetUpdateGameGrcViewport();
const Vec3V camPos = gameVP.GetCameraMtx().GetCol3();
v[0] = Transform(pDebugDrawSettings->m_debugDrawMatrix, v0);
v[1] = Transform(pDebugDrawSettings->m_debugDrawMatrix, v1);
v[2] = Transform(pDebugDrawSettings->m_debugDrawMatrix, v2);
if (Dot(Cross(v[1] - v[0], v[2] - v[0]), v[0] - camPos).Getf() > 0.0f)
{
return; // backfacing triangle
}
v[0] = TransformProjective(gameVP.GetFullCompositeMtx(), v[0]);
v[1] = TransformProjective(gameVP.GetFullCompositeMtx(), v[1]);
v[2] = TransformProjective(gameVP.GetFullCompositeMtx(), v[2]);
}
else
{
v[0] = v0;
v[1] = v1;
v[2] = v2;
}
if (pass == 0)
{
m_numTriangles++;
}
else if (pass == 1 && pDebugDrawSettings)
{
if (pDebugDrawSettings->m_debugDrawEnabled &&
pDebugDrawSettings->m_debugDrawColour.GetAlpha() != 0)
{
p[0] = Transform(pDebugDrawSettings->m_debugDrawMatrix, v0);
p[1] = Transform(pDebugDrawSettings->m_debugDrawMatrix, v1);
p[2] = Transform(pDebugDrawSettings->m_debugDrawMatrix, v2);
if (pDebugDrawSettings->m_debugDrawProjectToScreen)
{
const grcViewport& gameVP = *gVpMan.GetUpdateGameGrcViewport();
const Vec3V camPos = +gameVP.GetCameraMtx().GetCol3();
const Vec3V camDir = -gameVP.GetCameraMtx().GetCol2();
const ScalarV camNearZ = ScalarV(gameVP.GetNearClip() + 0.01f);
p[0] = camPos + (p[0] - camPos)*(camNearZ/Dot(p[0] - camPos, camDir));
p[1] = camPos + (p[1] - camPos)*(camNearZ/Dot(p[1] - camPos, camDir));
p[2] = camPos + (p[2] - camPos)*(camNearZ/Dot(p[2] - camPos, camDir));
}
}
if (pDebugDrawSettings->m_mouseRayTestEnabled && (ioMouse::GetButtons() & ioMouse::MOUSE_MIDDLE))
{
const Vec3V triP0 = Transform(pDebugDrawSettings->m_debugDrawMatrix, v0);
const Vec3V triP1 = Transform(pDebugDrawSettings->m_debugDrawMatrix, v1);
const Vec3V triP2 = Transform(pDebugDrawSettings->m_debugDrawMatrix, v2);
const Vec3V triNorm = NormalizeSafe(Cross(triP1 - triP0, triP2 - triP0), Vec3V(V_ZERO));
if (!IsZeroAll(triNorm))
{
const Vec3V ray = pDebugDrawSettings->m_mouseRayTestEnd - pDebugDrawSettings->m_mouseRayTestOrigin;
float t = 0.0f;
#if 1
ScalarV fraction(V_ZERO);
const bool bHit = geomSegments::SegmentTriangleIntersectDirected(
pDebugDrawSettings->m_mouseRayTestOrigin,
ray,
pDebugDrawSettings->m_mouseRayTestEnd,
triNorm,
triP0,
triP1,
triP2,
fraction
);
t = fraction.Getf();
#else
const bool bHit = geomSegments::CollideRayTriangle(
RCC_VECTOR3(triP0),
RCC_VECTOR3(triP1),
RCC_VECTOR3(triP2),
RCC_VECTOR3(pDebugDrawSettings->m_mouseRayTestOrigin),
RCC_VECTOR3(ray),
&t,
false
);
#endif
if (bHit && t >= 0.0f && t <= 1.0f)
{
if (pDebugDrawSettings->m_mouseRayTestFraction.Getf() >= t)
{
pDebugDrawSettings->m_mouseRayTestFraction = ScalarV(t);
pDebugDrawSettings->m_mouseRayTestHitPos = pDebugDrawSettings->m_mouseRayTestOrigin + ray*fraction;
pDebugDrawSettings->m_mouseRayTestHitNormal = triNorm;
pDebugDrawSettings->m_mouseRayTestHitTri[0] = triP0;
pDebugDrawSettings->m_mouseRayTestHitTri[1] = triP1;
pDebugDrawSettings->m_mouseRayTestHitTri[2] = triP2;
}
}
}
}
}
if (m_mode == MODE_EDGE_LENGTH)
{
for (int j = 0; j < 3; j++)
{
float value = scale*Mag(v[j] - v[(j + 1)%3]).Getf();
if (m_useLogScale)
{
if (value <= 0.000001f)
{
continue;
}
value = logf(value);
}
if (pass == 0)
{
values.PushAndGrow(value);
}
else if (pass == 1)
{
if (pDebugDrawSettings->m_debugBucketIndex == -1 ||
pDebugDrawSettings->m_debugBucketIndex == GetBucketIndex(value, ex))
{
grcDebugDraw::Line(p[j], p[(j + 1)%3], pDebugDrawSettings->m_debugDrawColour);
}
}
}
}
else
{
float value = scale*GetTriangleValue(v[0], v[1], v[2]);
if (m_useLogScale)
{
if (value <= 0.000001f)
{
return;
}
value = logf(value);
}
if (pass == 0)
{
values.PushAndGrow(value);
}
else if (pass == 1)
{
if (pDebugDrawSettings->m_debugBucketIndex == -1 ||
pDebugDrawSettings->m_debugBucketIndex == GetBucketIndex(value, ex))
{
grcDebugDraw::Poly(p[0], p[1], p[2], pDebugDrawSettings->m_debugDrawColour, true, pDebugDrawSettings->m_debugDrawSolid);
if (pDebugDrawSettings->m_debugDrawShowTriNormals)
{
const Vec3V centroid = (p[0] + p[1] + p[2])*ScalarVConstant<FLOAT_TO_INT(1.0f/3.0f)>();
const Vec3V normal = Normalize(Cross(p[1] - p[0], p[2] - p[0]));
grcDebugDraw::Line(centroid, AddScaled(centroid, normal, ScalarV(pDebugDrawSettings->m_debugDrawTriNormalLength)), pDebugDrawSettings->m_debugDrawColour);
}
}
}
}
}
float CDMAHistogram::GetTriangleValue(Vec3V_In v0, Vec3V_In v1, Vec3V_In v2) const
{
float value = 0.0f;
switch (m_mode)
{
case MODE_TRIANGLE_AREA_SQRT:
case MODE_TRIANGLE_AREA:
{
const Vec3V ab = v1 - v0;
const Vec3V ac = v2 - v0;
const ScalarV temp0 = Dot(ab, ab)*Dot(ac, ac);
const ScalarV temp1 = Dot(ab, ac);
value = 0.5f*sqrtf((temp0 - temp1*temp1).Getf());
if (m_mode == MODE_TRIANGLE_AREA_SQRT)
{
value = sqrtf(value);
}
break;
}
case MODE_TRIANGLE_EDGE_MAX:
case MODE_TRIANGLE_EDGE_MIN:
{
const ScalarV e01 = MagSquared(v0 - v1);
const ScalarV e12 = MagSquared(v1 - v2);
const ScalarV e20 = MagSquared(v2 - v0);
if (m_mode == MODE_TRIANGLE_EDGE_MAX)
{
value = sqrtf(Max(e01, e12, e20).Getf());
}
else
{
value = sqrtf(Min(e01, e12, e20).Getf());
}
break;
}
case MODE_TRIANGLE_HEIGHT_MAX:
case MODE_TRIANGLE_HEIGHT_MIN:
{
const ScalarV temp01 = Dot(v2 - v0, v1 - v0);
const ScalarV temp12 = Dot(v0 - v1, v2 - v1);
const ScalarV temp20 = Dot(v1 - v2, v0 - v2);
const ScalarV h01 = MagSquared(v2 - v0) - temp01*temp01/MagSquared(v1 - v0);
const ScalarV h12 = MagSquared(v0 - v1) - temp12*temp12/MagSquared(v2 - v1);
const ScalarV h20 = MagSquared(v1 - v2) - temp20*temp20/MagSquared(v0 - v2);
if (m_mode == MODE_TRIANGLE_HEIGHT_MAX)
{
value = sqrtf(Max(h01, h12, h20).Getf());
}
else
{
value = sqrtf(Min(h01, h12, h20).Getf());
}
break;
}
case MODE_TRIANGLE_ANGLE_MAX:
case MODE_TRIANGLE_ANGLE_MIN:
{
const ScalarV temp0 = Dot(v0 - v1, v1 - v2);
const ScalarV temp1 = Dot(v1 - v2, v2 - v0);
const ScalarV temp2 = Dot(v2 - v0, v0 - v1);
const ScalarV d0 = temp0*temp0/(MagSquared(v0 - v1)*MagSquared(v1 - v2));
const ScalarV d1 = temp1*temp1/(MagSquared(v1 - v2)*MagSquared(v2 - v0));
const ScalarV d2 = temp2*temp2/(MagSquared(v2 - v0)*MagSquared(v0 - v1));
if (m_mode == MODE_TRIANGLE_ANGLE_MAX)
{
value = RtoD*acosf(sqrtf(Min(d0, d1, d2).Getf()));
}
else
{
value = RtoD*acosf(sqrtf(Max(d0, d1, d2).Getf()));
}
break;
}}
if (value > 0.0f) // make sure we don't return -0.0f
{
return value;
}
return 0.0f;
}
int CDMAHistogram::GetBucketIndex(float value, float ex) const
{
Assert(m_valueMin <= m_valueMax);
if (m_valueMin == m_valueMax)
{
return 0;
}
const float x = powf(Clamp<float>((value - m_valueMin)/(m_valueMax - m_valueMin), 0.0f, 1.0f), ex);
return Clamp<int>((int)(x*(float)m_bucketCount), 0, m_bucketCount - 1);
}
float CDMAHistogram::GetBucketMinValue(int bucketIndex) const
{
const float value = m_valueMin + (m_valueMax - m_valueMin)*(float)(bucketIndex + 0)/(float)m_bucketCount;
return m_useLogScale ? expf(value) : value;
}
float CDMAHistogram::GetBucketMaxValue(int bucketIndex) const
{
const float value = m_valueMin + (m_valueMax - m_valueMin)*(float)(bucketIndex + 1)/(float)m_bucketCount;
return m_useLogScale ? expf(value) : value;
}
float CDMAHistogram::GetBucketMaxValueCoverage(float percent) const
{
if (percent <= 0.0f)
{
return m_valueMin;
}
else if (percent < 100.0f)
{
int sum = 0;
for (int i = 0; i < m_bucketCount; i++)
{
if (100.0f*(float)sum >= percent*(float)m_bucketSum)
{
return GetBucketMaxValue(i - 1); // [0..i-1] covers the percentage of values we need
}
sum += m_buckets[i];
}
}
return m_valueMax;
}
static void SortFloats(float* values, int count)
{
class CompareFunc { public: static s32 func(const float* a, const float* b)
{
if (*a > *b) return +1;
else if (*a < *b) return -1;
return 0;
}};
qsort(values, count, sizeof(float), (int(*)(const void*, const void*))CompareFunc::func);
}
#if __PS3
static Vec4V* g_extractVertStreams[CExtractGeomParams::obvIdxMax] ;
static Vec4V* g_extractVerts = NULL;
static u16* g_extractIndices = NULL;
#endif // __PS3
void CDMAHistogram::Update(const Drawable* pDrawable, const char* modelName, const CDMAHistogramProperties& properties, CDMAHistogramSettings* pDebugDrawSettings)
{
USE_DEBUG_MEMORY();
bool bRequiresUpdate = (properties.RequiresUpdate(*this) || strcmp(m_modelName, modelName) != 0);
if (properties.m_screenspace ||
properties.m_distScale == SCALE_BY_CAMERA_DISTANCE)
{
bRequiresUpdate = true; // TODO -- only force update when camera changes
}
if (g_CDMAUpdateValueMinMax)
{
g_CDMAUpdateValueMinMax = false;
bRequiresUpdate = true;
}
if (bRequiresUpdate)
{
strcpy(m_modelName, modelName);
if (m_bucketCount != properties.m_bucketCount)
{
if (m_buckets)
{
delete[] m_buckets;
}
m_buckets = rage_new int[properties.m_bucketCount];
m_bucketCount = properties.m_bucketCount;
}
sysMemSet(m_buckets, 0, m_bucketCount*sizeof(int));
m_mode = properties.m_mode;
m_distScale = properties.m_distScale;
m_screenspace = properties.m_screenspace;
m_useLogScale = properties.m_useLogScale;
m_rangeLower = properties.m_rangeLower;
m_rangeUpper = properties.m_rangeUpper;
m_expBiasX = properties.m_expBiasX;
m_valueRef = properties.m_valueRef;
m_ratioRef = properties.m_ratioRef;
m_bucketMin = 0;
m_bucketMax = 0;
m_bucketSum = 0;
m_valueMin = 0;
m_valueMax = 0.0f;
m_valueRefCount = 0;
m_ratioRefValue = 0.0f;
m_numTriangles = 0;
m_numTrianglesDegenerateIdx = 0;
m_numTrianglesDegenerateVtx = 0;
}
if (pDebugDrawSettings &&
pDebugDrawSettings->m_debugBucketIndex > m_bucketCount - 1)
{
pDebugDrawSettings->m_debugBucketIndex = m_bucketCount - 1; // always clamp this
}
if (!bRequiresUpdate)
{
if (pDebugDrawSettings && pDebugDrawSettings->m_debugDrawEnabled && pDebugDrawSettings->m_debugDrawColour.GetAlpha() != 0)
{
// ok, need to debug draw ..
}
else if (pDebugDrawSettings && pDebugDrawSettings->m_mouseRayTestEnabled)
{
// ok, need to update mouse ray test ..
}
else
{
return;
}
}
if (pDebugDrawSettings == NULL)
{
m_screenspace = false; // don't apply screenspace if we're not using debug draw, we might be processing drawables without a camera
}
if (pDebugDrawSettings && pDebugDrawSettings->m_mouseRayTestEnabled && (ioMouse::GetButtons() & ioMouse::MOUSE_MIDDLE))
{
const grcViewport& gameVP = *gVpMan.GetUpdateGameGrcViewport();
const ScalarV tanHFOV = ScalarV(gameVP.GetTanHFOV());
const ScalarV tanVFOV = ScalarV(gameVP.GetTanVFOV());
const ScalarV screenX = ScalarV(-1.0f + 2.0f*(float)ioMouse::GetX()/(float)GRCDEVICE.GetWidth());
const ScalarV screenY = ScalarV(+1.0f - 2.0f*(float)ioMouse::GetY()/(float)GRCDEVICE.GetHeight()); // flip y
const ScalarV screenZ = ScalarV(pDebugDrawSettings->m_mouseRayTestDepth);
pDebugDrawSettings->m_mouseRayTestOrigin = gameVP.GetCameraMtx().GetCol3();
pDebugDrawSettings->m_mouseRayTestEnd = Transform(gameVP.GetCameraMtx(), Vec3V(tanHFOV*screenX, tanVFOV*screenY, ScalarV(V_NEGONE))*screenZ);
pDebugDrawSettings->m_mouseRayTestFraction = ScalarV(V_FLT_MAX);
pDebugDrawSettings->m_mouseRayTestHitPos = Vec3V(V_ZERO);
pDebugDrawSettings->m_mouseRayTestHitNormal = Vec3V(V_ZERO);
pDebugDrawSettings->m_mouseRayTestHitTri[0] = Vec3V(V_ZERO);
pDebugDrawSettings->m_mouseRayTestHitTri[1] = Vec3V(V_ZERO);
pDebugDrawSettings->m_mouseRayTestHitTri[2] = Vec3V(V_ZERO);
}
const float ex = powf(2.0f, m_expBiasX);
float scale = 1.0f;
if (m_distScale == SCALE_BY_MODEL_LOD_DISTANCE)
{
if (g_modelLODDistance > 0.0f)
{
scale = 1.0f/g_modelLODDistance;
}
}
else if (m_distScale == SCALE_BY_CAMERA_DISTANCE)
{
const grcViewport& gameVP = *gVpMan.GetUpdateGameGrcViewport();
const Vec3V camPos = +gameVP.GetCameraMtx().GetCol3();
const Vec3V camDir = -gameVP.GetCameraMtx().GetCol2();
scale = 1.0f/Abs(Dot(g_modelWorldCentre - camPos, camDir)).Getf();
}
atArray<float> values;
for (int pass = 0; pass < 2; pass++)
{
if (pass == 0)
{
if (!bRequiresUpdate)
{
continue;
}
}
else if (pass == 1)
{
if (bRequiresUpdate)
{
const int count = values.GetCount();
if (count > 0)
{
SortFloats(&values[0], count);
const int index0 = Clamp<int>((int)(m_rangeLower*(float)count), 0, count - 1);
const int index1 = Clamp<int>((int)(m_rangeUpper*(float)count), index0 + 1, count);
// first determine min/max value ..
if (pDebugDrawSettings &&
pDebugDrawSettings->m_valueMinMaxEnabled)
{
m_valueMin = m_useLogScale ? logf(pDebugDrawSettings->m_valueMin) : pDebugDrawSettings->m_valueMin;
m_valueMax = m_useLogScale ? logf(pDebugDrawSettings->m_valueMax) : pDebugDrawSettings->m_valueMax;
}
else
{
m_valueMin = values[index0];
m_valueMax = values[index0];
for (int i = index0 + 1; i < index1; i++)
{
const float value = values[i];
m_valueMin = Min<float>(value, m_valueMin);
m_valueMax = Max<float>(value, m_valueMax);
}
if (pDebugDrawSettings &&
pDebugDrawSettings->m_valueMinMaxLocked == false)
{
pDebugDrawSettings->m_valueMin = m_useLogScale ? expf(m_valueMin) : m_valueMin;
pDebugDrawSettings->m_valueMax = m_useLogScale ? expf(m_valueMax) : m_valueMax;
}
}
// .. and then calculate buckets
{
const float valueRef = m_useLogScale ? logf(m_valueRef) : m_valueRef;
for (int i = index0; i < index1; i++)
{
const float value = values[i];
m_buckets[GetBucketIndex(value, ex)]++;
m_bucketSum++;
if (value <= valueRef)
{
m_valueRefCount++;
}
}
m_ratioRefValue = values[index0 + Clamp<int>((int)(m_ratioRef*(float)m_bucketSum), 0, m_bucketSum - 1)];
}
m_bucketMin = m_buckets[0];
m_bucketMax = m_buckets[0];
for (int i = 1; i < m_bucketCount; i++)
{
if (m_buckets[i] > 0)
{
if (m_bucketMin == 0)
{
m_bucketMin = m_buckets[i];
}
else
{
m_bucketMin = Min<int>(m_buckets[i], m_bucketMin);
}
m_bucketMax = Max<int>(m_buckets[i], m_bucketMax);
}
}
}
else
{
m_valueMin = 0.0f;
m_valueMax = 0.0f;
}
if (1) // not sure why we would have degenerate triangles, but let's report if it happens
{
Assertf(m_numTrianglesDegenerateIdx == 0, "%s: has %d degenerate triangles (by index)", m_modelName, m_numTrianglesDegenerateIdx);
Assertf(m_numTrianglesDegenerateVtx == 0, "%s: has %d degenerate triangles (by vertex)", m_modelName, m_numTrianglesDegenerateVtx);
}
if (pDebugDrawSettings &&
pDebugDrawSettings->m_debugDrawEnabled &&
pDebugDrawSettings->m_debugDrawColour.GetAlpha() != 0)
{
// ok, need to debug draw
}
else
{
break;
}
}
}
const rmcLodGroup& lodGroup = pDrawable->GetLodGroup();
if (lodGroup.ContainsLod(LOD_HIGH))
{
const rmcLod& lod = lodGroup.GetLod(LOD_HIGH);
for (int lodModelIndex = 0; lodModelIndex < lod.GetCount(); lodModelIndex++)
{
const grmModel* pModel = lod.GetModel(lodModelIndex);
if (pModel)
{
const grmModel& model = *pModel;
for (int geomIndex = 0; geomIndex < model.GetGeometryCount(); geomIndex++)
{
const int shaderId = model.GetShaderIndex(geomIndex);
const grmShader& shader = pDrawable->GetShaderGroup().GetShader(shaderId);
if (strcmp(shader.GetName(), "cable") == 0) // don't use cable shaders, since all their triangles are degenerate
{
continue;
}
grmGeometry& geom = model.GetGeometry(geomIndex);
#if __PS3
if (geom.GetType() == grmGeometry::GEOMETRYEDGE)
{
grmGeometryEdge *geomEdge = static_cast<grmGeometryEdge*>(&geom);
#if HACK_GTA4_MODELINFOIDX_ON_SPU && USE_EDGE
CGta4DbgSpuInfoStruct gtaSpuInfoStruct;
gtaSpuInfoStruct.gta4RenderPhaseID = 0x02; // called by Object
gtaSpuInfoStruct.gta4ModelInfoIdx = 0;
gtaSpuInfoStruct.gta4ModelInfoType = 0;
#endif // HACK_GTA4_MODELINFOIDX_ON_SPU && USE_EDGE
// check up front how many verts are in processed geometry and assert if too many
int totalI = 0;
int totalV = 0;
for (int i = 0; i < geomEdge->GetEdgeGeomPpuConfigInfoCount(); i++)
{
totalI += geomEdge->GetEdgeGeomPpuConfigInfos()[i].spuConfigInfo.numIndexes;
totalV += geomEdge->GetEdgeGeomPpuConfigInfos()[i].spuConfigInfo.numVertexes;
}
if (totalI > GeometryUtil::EXTRACT_MAX_INDICES)
{
Assertf(0, "%s: index buffer has more indices (%d) than system can handle (%d)", m_modelName, totalI, GeometryUtil::EXTRACT_MAX_INDICES);
return;
}
if (totalV > GeometryUtil::EXTRACT_MAX_VERTICES)
{
Assertf(0, "%s: vertex buffer has more verts (%d) than system can handle (%d)", m_modelName, totalV, GeometryUtil::EXTRACT_MAX_VERTICES);
return;
}
GeometryUtil::AllocateExtractData(&g_extractVerts, &g_extractIndices);
sysMemSet(&g_extractVertStreams[0], 0, sizeof(g_extractVertStreams));
int numVerts = 0;
const int numIndices = geomEdge->GetVertexAndIndex(
(Vector4*)g_extractVerts,
GeometryUtil::EXTRACT_MAX_VERTICES,
(Vector4**)g_extractVertStreams,
g_extractIndices,
GeometryUtil::EXTRACT_MAX_INDICES,
NULL,//BoneIndexesAndWeights,
0,//sizeof(BoneIndexesAndWeights),
NULL,//&BoneIndexOffset,
NULL,//&BoneIndexStride,
NULL,//&BoneOffset1,
NULL,//&BoneOffset2,
NULL,//&BoneOffsetPoint,
(u32*)&numVerts,
#if HACK_GTA4_MODELINFOIDX_ON_SPU
&gtaSpuInfoStruct,
#endif // HACK_GTA4_MODELINFOIDX_ON_SPU
NULL,
CExtractGeomParams::extractPos
);
for (int i = 0; i < numIndices; i += 3)
{
const u16 index0 = g_extractIndices[i + 0];
const u16 index1 = g_extractIndices[i + 1];
const u16 index2 = g_extractIndices[i + 2];
if (!AssertVerify((int)Max<u16>(index0, index1, index2) < numVerts))
{
continue;
}
const Vec3V v0 = g_extractVerts[index0].GetXYZ();
const Vec3V v1 = g_extractVerts[index1].GetXYZ();
const Vec3V v2 = g_extractVerts[index2].GetXYZ();
AddTriangle(values, pass, ex, scale, v0, v1, v2, index0, index1, index2, pDebugDrawSettings);
}
}
else
#endif // __PS3
{
grcVertexBuffer* vb = geom.GetVertexBuffer(true);
grcIndexBuffer* ib = geom.GetIndexBuffer (true);
Assert(vb && ib);
PS3_ONLY(++g_AllowVertexBufferVramLocks); // PS3: attempting to edit VBs in VRAM
grcVertexBufferEditor vertexBufferEditor(vb, true, true); // lock=true, readOnly=true
Assert(geom.GetPrimitiveType() == drawTris);
Assert(geom.GetPrimitiveCount()*3 == (u32)ib->GetIndexCount());
const int numVerts = vb->GetVertexCount();
const int numTriangles = ib->GetIndexCount()/3;
const u16* indexData = ib->LockRO();
for (int i = 0; i < numTriangles; i++)
{
const u16 index0 = indexData[i*3 + 0];
const u16 index1 = indexData[i*3 + 1];
const u16 index2 = indexData[i*3 + 2];
if (!AssertVerify((int)Max<u16>(index0, index1, index2) < numVerts))
{
continue;
}
const Vec3V v0 = VECTOR3_TO_VEC3V(vertexBufferEditor.GetPosition(index0));
const Vec3V v1 = VECTOR3_TO_VEC3V(vertexBufferEditor.GetPosition(index1));
const Vec3V v2 = VECTOR3_TO_VEC3V(vertexBufferEditor.GetPosition(index2));
AddTriangle(values, pass, ex, scale, v0, v1, v2, index0, index1, index2, pDebugDrawSettings);
}
PS3_ONLY(--g_AllowVertexBufferVramLocks); // PS3: finished with editing VBs in VRAM
ib->UnlockRO();
}
}
}
}
}
}
if (pDebugDrawSettings && pDebugDrawSettings->m_mouseRayTestEnabled)
{
if (pDebugDrawSettings->m_mouseRayTestFraction.Getf() <= 1.0f)
{
const grcViewport& gameVP = *gVpMan.GetUpdateGameGrcViewport();
const Vec3V camPos = +gameVP.GetCameraMtx().GetCol3();
const Vec3V camDir = -gameVP.GetCameraMtx().GetCol2();
const ScalarV camNearZ = ScalarV(gameVP.GetNearClip() + 0.01f);
const Vec3V p = pDebugDrawSettings->m_mouseRayTestHitPos;
const Vec3V n = pDebugDrawSettings->m_mouseRayTestHitNormal;
const ScalarV z = Dot(p - camPos, camDir);
const Vec3V pp[3] =
{
camPos + (pDebugDrawSettings->m_mouseRayTestHitTri[0] - camPos)*(camNearZ/Dot(pDebugDrawSettings->m_mouseRayTestHitTri[0] - camPos, camDir)),
camPos + (pDebugDrawSettings->m_mouseRayTestHitTri[1] - camPos)*(camNearZ/Dot(pDebugDrawSettings->m_mouseRayTestHitTri[1] - camPos, camDir)),
camPos + (pDebugDrawSettings->m_mouseRayTestHitTri[2] - camPos)*(camNearZ/Dot(pDebugDrawSettings->m_mouseRayTestHitTri[2] - camPos, camDir)),
};
grcDebugDraw::Sphere(p, 0.05f, Color32(255,0,0,255), false);
grcDebugDraw::Line(p, p + n, Color32(0,0,255,255));
grcDebugDraw::Poly(pp[0], pp[1], pp[2], Color32(255,0,0,76), true, true); // solid (alpha=30%)
grcDebugDraw::Poly(pp[0], pp[1], pp[2], Color32(255,255,0,255), true, false); // edges
grcDebugDraw::AddDebugOutput("mouse ray test: pos=%f,%f,%f, z=%f, norm:%f,%f,%f", VEC3V_ARGS(p), z.Getf(), VEC3V_ARGS(n));
}
else
{
grcDebugDraw::AddDebugOutput("mouse ray test = NO INTERSECTION");
}
}
}
void CDMAHistogram::UpdateWindow(const CDMAHistogramWindowSettings& settings, int selectedBucketIndex) const
{
const int resW = 1280;
const int resH = 720;
const float wx = (float)settings.m_windowX/(float)resW;
const float wy = (float)settings.m_windowY/(float)resH;
const float ww = (float)settings.m_windowW/(float)resW;
const float wh = (float)settings.m_windowH/(float)resH;
if (settings.m_windowBackgroundColour.GetAlpha() != 0)
{
grcDebugDraw::Quad(Vec2V(wx, wy), Vec2V(wx + ww, wy), Vec2V(wx + ww, wy + wh), Vec2V(wx, wy + wh), settings.m_windowBackgroundColour, true, true);
}
const float ex = powf(2.0f, -settings.m_windowExpBiasXScale*m_expBiasX);
const float ey = powf(2.0f, -settings.m_windowExpBiasY);
for (int pass = 0; pass < 2; pass++) // fill, outline
{
float x0 = wx;
float y1_prev = 0.0f;
if (pass == 0 && settings.m_windowMode == CDMAHistogramWindowSettings::WINDOW_MODE_LINES)
{
continue;
}
for (int i = 0; i < m_bucketCount; i++)
{
const float f1 = (float)(i + 1)/(float)m_bucketCount;
const float x1 = wx + ww*powf(f1, ex);
const float y0 = wy + wh;
const float y1 = y0 - wh*powf((float)m_buckets[i]/(float)m_bucketMax, ey);
Color32 edge = settings.m_windowBarColourOutline;
Color32 fill = settings.m_windowBarColourFill;
if (selectedBucketIndex == i)
{
// selected bucket background
{
const Color32 bkg = settings.m_windowBarColourFillSelected.MultiplyAlpha(48);
grcDebugDraw::Quad(Vec2V(x0, wy), Vec2V(x1, wy), Vec2V(x1, wy + wh), Vec2V(x0, wy + wh), bkg, true, true);
}
if (settings.m_windowMode == CDMAHistogramWindowSettings::WINDOW_MODE_LINES)
{
edge = settings.m_windowBarColourOutlineSelected;
}
else if (settings.m_windowMode == CDMAHistogramWindowSettings::WINDOW_MODE_BARS)
{
fill = settings.m_windowBarColourFillSelected;
}
}
else if (selectedBucketIndex != -1)
{
edge.SetAlpha((u8)(0.5f + 255.0f*settings.m_windowBarOpacityNotSelected*(float)edge.GetAlphaf()));
fill.SetAlpha((u8)(0.5f + 255.0f*settings.m_windowBarOpacityNotSelected*(float)fill.GetAlphaf()));
}
if (settings.m_windowMode == CDMAHistogramWindowSettings::WINDOW_MODE_LINES)
{
const float x = (x1 + x0)*0.5f;
grcDebugDraw::Line(Vec2V(x, y0), Vec2V(x, y1), edge);
}
else if (settings.m_windowMode == CDMAHistogramWindowSettings::WINDOW_MODE_BARS)
{
if (pass == 0)
{
grcDebugDraw::Quad(Vec2V(x0, y0), Vec2V(x1, y0), Vec2V(x1, y1), Vec2V(x0, y1), fill, true, true);
}
else if (pass == 1)
{
grcDebugDraw::Line(Vec2V(x0, y1), Vec2V(x1, y1), edge); // top
if (i == 0)
{
grcDebugDraw::Line(Vec2V(x0, y0), Vec2V(x0, y1), edge);
}
else
{
Color32 edge2 = settings.m_windowBarColourOutline;
if (selectedBucketIndex == i ||
selectedBucketIndex == i - 1)
{
//edge2 = settings.m_windowBarColourOutlineSelected;
}
else if (selectedBucketIndex != -1)
{
edge2.SetAlpha((u8)(0.5f + 255.0f*settings.m_windowBarOpacityNotSelected*(float)edge2.GetAlphaf()));
}
grcDebugDraw::Line(Vec2V(x0, y0), Vec2V(x0, Min<float>(y1, y1_prev)), edge2);
}
if (i == m_bucketCount - 1)
{
grcDebugDraw::Line(Vec2V(x1, y0), Vec2V(x1, y1), edge);
}
}
}
x0 = x1;
y1_prev = y1;
}
}
if (settings.m_windowMode == CDMAHistogramWindowSettings::WINDOW_MODE_BARS)
{
grcDebugDraw::Line(Vec2V(wx, wy + wh), Vec2V(wx + ww, wy + wh), settings.m_windowBarColourOutline);
}
}
} // namespace DMA
__COMMENT(static) void CDebugModelAnalysisInterface::AddWidgets(bkBank& bank)
{
bank.PushGroup("Model Analysis", false);
DMA::g_settings.AddWidgets(bank);
bank.PopGroup();
bank.PushGroup("Dump selected entities to OBJ", false); // TODO -- move this to DebugGeometryUtil.cpp i think
{
static char g_dumpSelectedEntitiesOBJPath[80] = "assets:/non_final/dumpgeom.obj";
static bool g_dumpSelectedEntitiesOBJUseColourPerEntity = false;
static u32 g_dumpSelectedEntitiesOBJEntityLODs = ~0U;
static u32 g_dumpSelectedEntitiesOBJDrawableLODs = ~0U;
static u32 g_dumpSelectedEntitiesOBJRequiredBuckets = 0;
static u32 g_dumpSelectedEntitiesOBJExcludedBuckets = 0;
static bool g_dumpSelectedEntitiesOBJVehicleTest = false;
static char g_dumpSelectedEntitiesOBJVehicleShaderNameFilter[80] = "";
static char g_dumpSelectedEntitiesOBJVehicleBoneName[80] = "";
static bool g_dumpSelectedEntitiesOBJFlip = false;
static bool g_dumpSelectedEntitiesOBJVerbose = false;
class DumpSelectedEntitiesToOBJ_button { public: static void func()
{
const int numEntities = g_PickerManager.GetNumberOfEntities();
if (numEntities > 0)
{
static CDumpGeometryToOBJ* g_dumpGeometry = NULL;
static const grmGeometry* s_geomPtr = NULL;
static Vec3V g_entityBoundsMin(V_ZERO);
static Vec3V g_entityBoundsMax(V_ZERO);
class DumpSelectedEntitiesToOBJ_geometry { public: static bool func(const grmModel& model, const grmGeometry&, const grmShader&, const grmShaderGroup& shaderGroup, int lodIndex, void*)
{
if ((g_dumpSelectedEntitiesOBJDrawableLODs & BIT(lodIndex)) == 0)
{
return false;
}
const u32 maskDefault = CRenderer::GenerateSubBucketMask(CRenderer::RB_MODEL_DEFAULT );
const u32 maskShadow = CRenderer::GenerateSubBucketMask(CRenderer::RB_MODEL_SHADOW );
const u32 maskReflection = CRenderer::GenerateSubBucketMask(CRenderer::RB_MODEL_REFLECTION);
const u32 maskMirror = CRenderer::GenerateSubBucketMask(CRenderer::RB_MODEL_MIRROR );
const u32 maskWater = CRenderer::GenerateSubBucketMask(CRenderer::RB_MODEL_WATER );
u32 maskRequired = 0;
u32 maskExcluded = 0;
if (g_dumpSelectedEntitiesOBJRequiredBuckets & BIT(CRenderer::RB_MODEL_DEFAULT )) { maskRequired |= maskDefault ; }
if (g_dumpSelectedEntitiesOBJRequiredBuckets & BIT(CRenderer::RB_MODEL_SHADOW )) { maskRequired |= maskShadow ; }
if (g_dumpSelectedEntitiesOBJRequiredBuckets & BIT(CRenderer::RB_MODEL_REFLECTION)) { maskRequired |= maskReflection; }
if (g_dumpSelectedEntitiesOBJRequiredBuckets & BIT(CRenderer::RB_MODEL_MIRROR )) { maskRequired |= maskMirror ; }
if (g_dumpSelectedEntitiesOBJRequiredBuckets & BIT(CRenderer::RB_MODEL_WATER )) { maskRequired |= maskWater ; }
if (g_dumpSelectedEntitiesOBJExcludedBuckets & BIT(CRenderer::RB_MODEL_DEFAULT )) { maskExcluded |= maskDefault ; }
if (g_dumpSelectedEntitiesOBJExcludedBuckets & BIT(CRenderer::RB_MODEL_SHADOW )) { maskExcluded |= maskShadow ; }
if (g_dumpSelectedEntitiesOBJExcludedBuckets & BIT(CRenderer::RB_MODEL_REFLECTION)) { maskExcluded |= maskReflection; }
if (g_dumpSelectedEntitiesOBJExcludedBuckets & BIT(CRenderer::RB_MODEL_MIRROR )) { maskExcluded |= maskMirror ; }
if (g_dumpSelectedEntitiesOBJExcludedBuckets & BIT(CRenderer::RB_MODEL_WATER )) { maskExcluded |= maskWater ; }
const u32 mask = CRenderer::GetSubBucketMask(model.ComputeBucketMask(shaderGroup));
if ((maskRequired & ~mask) |
(maskExcluded & mask))
{
return false;
}
return true;
}
static bool func_vehicletest(const grmModel& model, const grmGeometry& geom, const grmShader& shader, const grmShaderGroup& shaderGroup, int lodIndex, void* userData)
{
if (!func(model, geom, shader, shaderGroup, lodIndex, userData))
{
return false;
}
if (s_geomPtr && s_geomPtr != &geom)
{
return false;
}
if (g_dumpSelectedEntitiesOBJVehicleShaderNameFilter[0] != '\0' && stristr(shader.GetName(), g_dumpSelectedEntitiesOBJVehicleShaderNameFilter) == NULL)
{
return false;
}
return true;
}};
class DumpSelectedEntitiesToOBJ_triangle { public: static void func(Vec3V_In v0, Vec3V_In v1, Vec3V_In v2, int, int, int, void*)
{
if (g_dumpSelectedEntitiesOBJFlip)
{
g_dumpGeometry->AddTriangle(v2, v1, v0);
}
else
{
g_dumpGeometry->AddTriangle(v0, v1, v2);
}
g_entityBoundsMin = Min(v0, v1, v2, g_entityBoundsMin);
g_entityBoundsMax = Max(v0, v1, v2, g_entityBoundsMax);
}};
if (g_dumpSelectedEntitiesOBJVehicleTest)
{
const CEntity* pEntity = static_cast<const CEntity*>(g_PickerManager.GetEntity(0));
if (pEntity && pEntity->GetIsTypeVehicle())
{
const Drawable* pDrawable = pEntity->GetDrawable();
if (pDrawable)
{
const rmcLodGroup& lodGroup = pDrawable->GetLodGroup();
if (lodGroup.ContainsLod(LOD_HIGH))
{
const rmcLod& lod = lodGroup.GetLod(LOD_HIGH);
for (int lodModelIndex = 0; lodModelIndex < lod.GetCount(); lodModelIndex++)
{
const grmModel* pModel = lod.GetModel(lodModelIndex);
if (pModel)
{
const grmModel& model = *pModel;
const CVehicle* pVehicle = static_cast<const CVehicle*>(pEntity);
const fragInst* pFragInst = pVehicle->GetFragInst();
const phBound* pBound = pFragInst->GetArchetype()->GetBound();
const int componentCount = (pBound->GetType() == phBound::COMPOSITE) ? static_cast<const phBoundComposite*>(pBound)->GetNumBounds() : 1;
for (int componentId = 0; componentId < componentCount; componentId++)
{
const fragTypeChild* pFragChild = pFragInst->GetTypePhysics()->GetChild(componentId);
const int boneIndex = pVehicle->GetFragInst()->GetType()->GetBoneIndexFromID(pFragChild->GetBoneID());
const char* boneName = pVehicle->GetDrawable()->GetSkeletonData()->GetBoneData(boneIndex)->GetName();
if (stricmp(boneName, g_dumpSelectedEntitiesOBJVehicleBoneName) == 0)
{
char dumpPath[RAGE_MAX_PATH] = "";
strcpy(dumpPath, g_dumpSelectedEntitiesOBJPath);
char* ext = strrchr(dumpPath, '.');
if (ext) { strcpy(ext, atVarString("_%s.obj", boneName).c_str()); }
else { strcat(ext, atVarString("_%s.obj", boneName).c_str()); }
CDumpGeometryToOBJ dump(dumpPath, "materials.mtl");
int geomsAttachedToBoneCount = 0;
for (int geomIndex = 0; geomIndex < model.GetGeometryCount(); geomIndex++)
{
const grmGeometry& geom = model.GetGeometry(geomIndex);
const grmShader& shader = pDrawable->GetShaderGroup().GetShader(model.GetShaderIndex(geomIndex));
g_dumpGeometry = &dump;
s_geomPtr = &geom;
g_entityBoundsMin = Vec3V(V_FLT_MAX);
g_entityBoundsMax = -g_entityBoundsMin;
const int numTrianglesAttachedToBone = GeometryUtil::CountTrianglesForDrawable(pDrawable, LOD_HIGH, pEntity->GetModelName(), DumpSelectedEntitiesToOBJ_geometry::func, NULL, boneIndex);
if (numTrianglesAttachedToBone > 0)
{
const char* materialNames[] =
{
"white",
"red",
"green",
"blue",
"cyan",
"magenta",
"yellow",
};
dump.MaterialBegin(materialNames[geomsAttachedToBoneCount%NELEM(materialNames)]);
GeometryUtil::AddTrianglesForDrawable(pDrawable, LOD_HIGH, pEntity->GetModelName(), DumpSelectedEntitiesToOBJ_geometry::func_vehicletest, DumpSelectedEntitiesToOBJ_triangle::func, Mat34V(V_IDENTITY), NULL, boneIndex);
dump.MaterialEnd();
if (g_dumpSelectedEntitiesOBJVerbose)
{
Displayf("%d triangles with shader \"%s\" attached to geometry %d (%s) and bone '%s'", numTrianglesAttachedToBone, g_dumpSelectedEntitiesOBJVehicleShaderNameFilter, geomIndex, shader.GetName(), g_dumpSelectedEntitiesOBJVehicleBoneName);
}
geomsAttachedToBoneCount++;
}
}
if (g_dumpSelectedEntitiesOBJVerbose)
{
Displayf("%d geometries with shader \"%s\" attached to bone '%s'", geomsAttachedToBoneCount, g_dumpSelectedEntitiesOBJVehicleShaderNameFilter, g_dumpSelectedEntitiesOBJVehicleBoneName);
}
dump.Close();
}
}
}
}
}
}
}
}
else
{
CDumpGeometryToOBJ dump(g_dumpSelectedEntitiesOBJPath, "materials.mtl");
for (int i = 0; i < numEntities; i++)
{
const CEntity* pEntity = static_cast<const CEntity*>(g_PickerManager.GetEntity(i));
if (pEntity)
{
if ((g_dumpSelectedEntitiesOBJEntityLODs & BIT(pEntity->GetLodData().GetLodType())) == 0)
{
continue;
}
g_dumpGeometry = &dump;
g_entityBoundsMin = Vec3V(V_FLT_MAX);
g_entityBoundsMax = -g_entityBoundsMin;
if (g_dumpSelectedEntitiesOBJUseColourPerEntity)
{
const char* materialNames[] =
{
"white",
"red",
"green",
"blue",
"cyan",
"magenta",
"yellow",
};
dump.MaterialBegin(materialNames[i%NELEM(materialNames)]);
}
const int numTriangles = GeometryUtil::AddTrianglesForEntity(pEntity, -1, DumpSelectedEntitiesToOBJ_geometry::func, DumpSelectedEntitiesToOBJ_triangle::func);
if (g_dumpSelectedEntitiesOBJVerbose)
{
Displayf("dumped %d triangles for %s, local bounds min = %f,%f,%f, max = %f,%f,%f", numTriangles, pEntity->GetModelName(), VEC3V_ARGS(g_entityBoundsMin), VEC3V_ARGS(g_entityBoundsMax));
Displayf(" matrix col0 = %f,%f,%f", VEC3V_ARGS(pEntity->GetMatrix().GetCol0()));
Displayf(" matrix col1 = %f,%f,%f", VEC3V_ARGS(pEntity->GetMatrix().GetCol1()));
Displayf(" matrix col2 = %f,%f,%f", VEC3V_ARGS(pEntity->GetMatrix().GetCol2()));
Displayf(" matrix col3 = %f,%f,%f", VEC3V_ARGS(pEntity->GetMatrix().GetCol3()));
}
if (g_dumpSelectedEntitiesOBJUseColourPerEntity)
{
dump.MaterialEnd();
}
}
}
dump.Close();
}
}
}};
bank.AddText ("Path" , &g_dumpSelectedEntitiesOBJPath[0], sizeof(g_dumpSelectedEntitiesOBJPath), false);
bank.AddToggle("Use colour per entity", &g_dumpSelectedEntitiesOBJUseColourPerEntity);
bank.AddSeparator();
bank.AddToggle("Entity LOD - HD ", &g_dumpSelectedEntitiesOBJEntityLODs, BIT(LODTYPES_DEPTH_HD ));
bank.AddToggle("Entity LOD - ORPHANHD", &g_dumpSelectedEntitiesOBJEntityLODs, BIT(LODTYPES_DEPTH_ORPHANHD));
bank.AddToggle("Entity LOD - LOD ", &g_dumpSelectedEntitiesOBJEntityLODs, BIT(LODTYPES_DEPTH_LOD ));
bank.AddToggle("Entity LOD - SLOD1 ", &g_dumpSelectedEntitiesOBJEntityLODs, BIT(LODTYPES_DEPTH_SLOD1 ));
bank.AddToggle("Entity LOD - SLOD2 ", &g_dumpSelectedEntitiesOBJEntityLODs, BIT(LODTYPES_DEPTH_SLOD2 ));
bank.AddToggle("Entity LOD - SLOD3 ", &g_dumpSelectedEntitiesOBJEntityLODs, BIT(LODTYPES_DEPTH_SLOD3 ));
bank.AddToggle("Entity LOD - SLOD4 ", &g_dumpSelectedEntitiesOBJEntityLODs, BIT(LODTYPES_DEPTH_SLOD4 ));
bank.AddSeparator();
bank.AddToggle("Drawable LOD - HIGH", &g_dumpSelectedEntitiesOBJDrawableLODs, BIT(LOD_HIGH));
bank.AddToggle("Drawable LOD - MED ", &g_dumpSelectedEntitiesOBJDrawableLODs, BIT(LOD_MED ));
bank.AddToggle("Drawable LOD - LOW ", &g_dumpSelectedEntitiesOBJDrawableLODs, BIT(LOD_LOW ));
bank.AddToggle("Drawable LOD - VLOW", &g_dumpSelectedEntitiesOBJDrawableLODs, BIT(LOD_VLOW));
bank.AddSeparator();
bank.AddToggle("Required Bucket - DEFAULT ", &g_dumpSelectedEntitiesOBJRequiredBuckets, BIT(CRenderer::RB_MODEL_DEFAULT ));
bank.AddToggle("Required Bucket - SHADOW ", &g_dumpSelectedEntitiesOBJRequiredBuckets, BIT(CRenderer::RB_MODEL_SHADOW ));
bank.AddToggle("Required Bucket - REFLECTION", &g_dumpSelectedEntitiesOBJRequiredBuckets, BIT(CRenderer::RB_MODEL_REFLECTION));
bank.AddToggle("Required Bucket - MIRROR ", &g_dumpSelectedEntitiesOBJRequiredBuckets, BIT(CRenderer::RB_MODEL_MIRROR ));
bank.AddToggle("Required Bucket - WATER ", &g_dumpSelectedEntitiesOBJRequiredBuckets, BIT(CRenderer::RB_MODEL_WATER ));
bank.AddSeparator();
bank.AddToggle("Excluded Bucket - DEFAULT ", &g_dumpSelectedEntitiesOBJExcludedBuckets, BIT(CRenderer::RB_MODEL_DEFAULT ));
bank.AddToggle("Excluded Bucket - SHADOW ", &g_dumpSelectedEntitiesOBJExcludedBuckets, BIT(CRenderer::RB_MODEL_SHADOW ));
bank.AddToggle("Excluded Bucket - REFLECTION", &g_dumpSelectedEntitiesOBJExcludedBuckets, BIT(CRenderer::RB_MODEL_REFLECTION));
bank.AddToggle("Excluded Bucket - MIRROR ", &g_dumpSelectedEntitiesOBJExcludedBuckets, BIT(CRenderer::RB_MODEL_MIRROR ));
bank.AddToggle("Excluded Bucket - WATER ", &g_dumpSelectedEntitiesOBJExcludedBuckets, BIT(CRenderer::RB_MODEL_WATER ));
bank.AddSeparator();
bank.AddToggle("Vehicle Test" , &g_dumpSelectedEntitiesOBJVehicleTest);
bank.AddText ("Vehicle Shader Name Filter", &g_dumpSelectedEntitiesOBJVehicleShaderNameFilter[0], sizeof(g_dumpSelectedEntitiesOBJVehicleShaderNameFilter), false);
bank.AddText ("Vehicle Bone Name" , &g_dumpSelectedEntitiesOBJVehicleBoneName[0], sizeof(g_dumpSelectedEntitiesOBJVehicleBoneName), false);
bank.AddToggle("Flip" , &g_dumpSelectedEntitiesOBJFlip);
bank.AddToggle("Verbose" , &g_dumpSelectedEntitiesOBJVerbose);
bank.AddButton("Dump to OBJ" , DumpSelectedEntitiesToOBJ_button::func);
}
bank.PopGroup();
}
static CUiGadgetInspector* g_histogramInspectorWindow = NULL;
static bool g_histogramInspectorWindowAttached = false;
const int NUM_DMA_INSPECTOR_ROWS = 12;
static void CDMA_PopulateInspectorWindowCB(CUiGadgetText* pResult, u32 row, u32 col)
{
if (pResult && row < NUM_DMA_INSPECTOR_ROWS)
{
if (DMA::g_histogram.m_modelName[0] == '\0')
{
pResult->SetString("");
return;
}
if (col == 0) { switch (row)
{
case 0: pResult->SetString("LOD distance"); return;
case 1: pResult->SetString("value range"); return;
case 2: pResult->SetString("bucket range"); return;
case 3: pResult->SetString("# values"); return;
case 4: pResult->SetString(atVarString("# values <= %f", DMA::g_histogram.m_valueRef).c_str()); return;
case 5: pResult->SetString(atVarString("value @ ref %f", DMA::g_histogram.m_ratioRef).c_str()); return;
}}
else if (col == 1) { switch (row)
{
case 0: pResult->SetString(atVarString("%f", DMA::g_modelLODDistance).c_str()); return;
case 1: pResult->SetString(atVarString("%f..%f", DMA::g_histogram.GetValueMin(), DMA::g_histogram.GetValueMax()).c_str()); return;
case 2: pResult->SetString(atVarString("%d..%d", DMA::g_histogram.m_bucketMin, DMA::g_histogram.m_bucketMax).c_str()); return;
case 3: pResult->SetString(atVarString("%d", DMA::g_histogram.m_bucketSum).c_str()); return;
case 4: pResult->SetString(atVarString("%d (%.2f%%)", DMA::g_histogram.m_valueRefCount, 100.0f*(float)DMA::g_histogram.m_valueRefCount/(float)DMA::g_histogram.m_bucketSum).c_str()); return;
case 5: pResult->SetString(atVarString("%f", DMA::g_histogram.GetRatioRefValue()).c_str()); return;
}}
if (DMA::g_settings.m_debugBucketIndex >= 0 &&
DMA::g_settings.m_debugBucketIndex < DMA::g_histogram.m_bucketCount)
{
if (col == 0) { switch (row)
{
case 6: pResult->SetString("debug bucket idx"); return;
case 7: pResult->SetString("... value range"); return;
case 8: pResult->SetString("... # values"); return;
}}
else if (col == 1) { switch (row)
{
case 6: pResult->SetString(atVarString("%d", DMA::g_settings.m_debugBucketIndex).c_str()); return;
case 7: pResult->SetString(atVarString("%f..%f", DMA::g_histogram.GetBucketMinValue(DMA::g_settings.m_debugBucketIndex), DMA::g_histogram.GetBucketMaxValue(DMA::g_settings.m_debugBucketIndex)).c_str()); return;
case 8: pResult->SetString(atVarString("%d (%.2f%%)", DMA::g_histogram.m_buckets[DMA::g_settings.m_debugBucketIndex], 100.0f*(float)DMA::g_histogram.m_buckets[DMA::g_settings.m_debugBucketIndex]/(float)DMA::g_histogram.m_bucketSum).c_str()); return;
}}
}
else
{
pResult->SetString("");
}
}
}
__COMMENT(static) void CDebugModelAnalysisInterface::Update(const CEntity* pEntity)
{
CGtaPickerInterface* pPickerInterface = static_cast<CGtaPickerInterface*>(g_PickerManager.GetInterface());
if (pPickerInterface)
{
if (DMA::g_settings.m_enabled)
{
if (!g_histogramInspectorWindow)
{
CUiColorScheme colorScheme;
const float afColumnOffsets[] = { 0.0f, 128.0f };
g_histogramInspectorWindow = rage_new CUiGadgetInspector(0.0f, 0.0f, 256.0f, NUM_DMA_INSPECTOR_ROWS, NELEM(afColumnOffsets), afColumnOffsets, colorScheme);
g_histogramInspectorWindow->SetUpdateCellCB(CDMA_PopulateInspectorWindowCB);
g_histogramInspectorWindow->SetNumEntries(NUM_DMA_INSPECTOR_ROWS);
}
if (!g_histogramInspectorWindowAttached)
{
pPickerInterface->AttachInspectorChild(g_histogramInspectorWindow);
g_histogramInspectorWindowAttached = true;
}
}
else
{
if (g_histogramInspectorWindowAttached)
{
pPickerInterface->DetachInspectorChild(g_histogramInspectorWindow);
g_histogramInspectorWindowAttached = false;
}
}
}
// ============================
if (pPickerInterface->GetCurrentInspector() &&
pPickerInterface->GetCurrentInspector() == g_histogramInspectorWindow)
{
if (pEntity == NULL ||
pEntity->GetDrawable() == NULL ||
pEntity->GetDrawable()->GetLodGroup().ContainsLod(LOD_HIGH) == false)
{
DMA::g_histogram.m_modelName[0] = '\0';
return;
}
if (!pEntity->GetIsTypeBuilding() &&
!pEntity->GetIsTypeObject() &&
!pEntity->GetIsTypeDummyObject())
{
DMA::g_histogram.m_modelName[0] = '\0';
return; // don't support stuff which might be skinned, for now ..
}
const CBaseModelInfo* pModelInfo = pEntity->GetBaseModelInfo();
if (pModelInfo)
{
DMA::g_modelLODDistance = pModelInfo->GetLodDistanceUnscaled();
DMA::g_modelWorldCentre = pEntity->GetTransform().GetPosition();
}
else
{
DMA::g_histogram.m_modelName[0] = '\0';
DMA::g_modelLODDistance = 0.0f;
}
if (DMA::g_settings.m_trackPickerWindow && g_histogramInspectorWindow)
{
const CUiGadgetSimpleListAndWindow* pPickerWindow = pPickerInterface->GetMainWindow();
if (pPickerWindow)
{
const fwBox2D inspectorBounds = g_histogramInspectorWindow->GetBounds();
const fwBox2D pickerWinBounds = pPickerWindow->GetBounds();
DMA::g_settings.m_windowX = (int)pickerWinBounds.x0;
DMA::g_settings.m_windowY = (int)pickerWinBounds.y1 + 2;
DMA::g_settings.m_windowW = (int)inspectorBounds.x1 - (int)pickerWinBounds.x0;
}
}
DMA::g_settings.m_debugDrawMatrix = pEntity->GetMatrix();
DMA::g_histogram.Update(pEntity->GetDrawable(), pEntity->GetModelName(), DMA::g_settings, &DMA::g_settings);
DMA::g_histogram.UpdateWindow(DMA::g_settings, DMA::g_settings.m_debugBucketIndex);
if (DMA::g_settings.m_trackPickerWindow && g_histogramInspectorWindow)
{
const CUiGadgetSimpleListAndWindow* pPickerWindow = pPickerInterface->GetMainWindow();
if (pPickerWindow)
{
const fwBox2D inspectorBounds = g_histogramInspectorWindow->GetBounds();
const Vec2V scale(1.0f/1280.0f, 1.0f/720.0f);
fwBox2D histogramBounds;
histogramBounds.x0 = (float)DMA::g_settings.m_windowX;
histogramBounds.y0 = (float)DMA::g_settings.m_windowY;
histogramBounds.x1 = (float)DMA::g_settings.m_windowX + (float)DMA::g_settings.m_windowW;
histogramBounds.y1 = (float)DMA::g_settings.m_windowY + (float)DMA::g_settings.m_windowH;
// draw outline around inspector
grcDebugDraw::Quad(
scale*Vec2V(inspectorBounds.x0 - 1.0f, inspectorBounds.y0 - 1.0f),
scale*Vec2V(inspectorBounds.x1 + 1.0f, inspectorBounds.y0 - 1.0f),
scale*Vec2V(inspectorBounds.x1 + 1.0f, inspectorBounds.y1 + 1.0f),
scale*Vec2V(inspectorBounds.x0 - 1.0f, inspectorBounds.y1 + 1.0f),
Color32(255,255,255,255),
false
);
// draw outline around histogram
grcDebugDraw::Quad(
scale*Vec2V(histogramBounds.x0 - 1.0f, histogramBounds.y0 - 1.0f),
scale*Vec2V(histogramBounds.x1 + 1.0f, histogramBounds.y0 - 1.0f),
scale*Vec2V(histogramBounds.x1 + 1.0f, histogramBounds.y1 + 1.0f),
scale*Vec2V(histogramBounds.x0 - 1.0f, histogramBounds.y1 + 1.0f),
Color32(255,255,255,255),
false
);
}
}
}
}
#endif // __BANK