git mv Code\Sandbox\Editor Code/Editor

Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>
This commit is contained in:
Esteban Papp
2021-06-29 12:41:59 -07:00
parent 9f0bbf3b74
commit e34e36cb35
1415 changed files with 0 additions and 0 deletions
+176
View File
@@ -0,0 +1,176 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "3DConnexionDriver.h"
#if defined(AZ_PLATFORM_WINDOWS)
//////////////////////////////////////////////////////////////////////////
C3DConnexionDriver::C3DConnexionDriver()
{
m_pRawInputDeviceList = 0;
m_pRawInputDevices = 0;
m_nUsagePage1Usage8Devices = 0;
m_fMultiplier = 1.0f;
InitDevice();
}
//////////////////////////////////////////////////////////////////////////
C3DConnexionDriver::~C3DConnexionDriver()
{
}
bool C3DConnexionDriver::InitDevice()
{
// Find the Raw Devices
UINT nDevices;
// Get Number of devices attached
if (GetRawInputDeviceList(NULL, &nDevices, sizeof(RAWINPUTDEVICELIST)) != 0)
{
return false;
}
// Create list large enough to hold all RAWINPUTDEVICE structs
if ((m_pRawInputDeviceList = (PRAWINPUTDEVICELIST)malloc(sizeof(RAWINPUTDEVICELIST) * nDevices)) == NULL)
{
return false;
}
// Now get the data on the attached devices
if (GetRawInputDeviceList(m_pRawInputDeviceList, &nDevices, sizeof(RAWINPUTDEVICELIST)) == -1)
{
return false;
}
m_pRawInputDevices = (PRAWINPUTDEVICE)malloc(nDevices * sizeof(RAWINPUTDEVICE));
m_nUsagePage1Usage8Devices = 0;
// Look through device list for RIM_TYPEHID devices with UsagePage == 1, Usage == 8
for (UINT i = 0; i < nDevices; i++)
{
//Doc says RIM_TYPEHID: Data comes from an HID that is not a keyboard or a mouse.
if (m_pRawInputDeviceList[i].dwType == RIM_TYPEHID)
{
UINT nchars = 300;
TCHAR deviceName[300];
if (GetRawInputDeviceInfo(m_pRawInputDeviceList[i].hDevice,
RIDI_DEVICENAME, deviceName, &nchars) >= 0)
{
//_RPT3(_CRT_WARN, "Device[%d]: handle=0x%x name = %S\n", i, g_pRawInputDeviceList[i].hDevice, deviceName);
}
RID_DEVICE_INFO dinfo;
UINT sizeofdinfo = sizeof(dinfo);
dinfo.cbSize = sizeofdinfo;
if (GetRawInputDeviceInfo(m_pRawInputDeviceList[i].hDevice,
RIDI_DEVICEINFO, &dinfo, &sizeofdinfo) >= 0)
{
if (dinfo.dwType == RIM_TYPEHID)
{
RID_DEVICE_INFO_HID* phidInfo = &dinfo.hid;
// Add this one to the list of interesting devices?
// Actually only have to do this once to get input from all usage 1, usagePage 8 devices
// This just keeps out the other usages.
// You might want to put up a list for users to select amongst the different devices.
// In particular, to assign separate functionality to the different devices.
if (phidInfo->usUsagePage == 1 && phidInfo->usUsage == 8)
{
m_pRawInputDevices[m_nUsagePage1Usage8Devices].usUsagePage = phidInfo->usUsagePage;
m_pRawInputDevices[m_nUsagePage1Usage8Devices].usUsage = phidInfo->usUsage;
m_pRawInputDevices[m_nUsagePage1Usage8Devices].dwFlags = 0;
m_pRawInputDevices[m_nUsagePage1Usage8Devices].hwndTarget = NULL;
m_nUsagePage1Usage8Devices++;
}
}
}
}
}
// Register for input from the devices in the list
if (RegisterRawInputDevices(m_pRawInputDevices, m_nUsagePage1Usage8Devices, sizeof(RAWINPUTDEVICE)) == false)
{
return false;
}
return true;
}
//////////////////////////////////////////////////////////////////////////
bool C3DConnexionDriver::GetInputMessageData(LPARAM lParam, S3DConnexionMessage& msg)
{
ZeroStruct(msg);
RAWINPUTHEADER header;
UINT size = sizeof(header);
if (GetRawInputData((HRAWINPUT)lParam, RID_HEADER, &header, &size, sizeof(RAWINPUTHEADER)) == -1)
{
return false;
}
// Set aside enough memory for the full event
char rawbuffer[128];
LPRAWINPUT event = (LPRAWINPUT)rawbuffer;
size = sizeof(rawbuffer);
if (GetRawInputData((HRAWINPUT)lParam, RID_INPUT, event, &size, sizeof(RAWINPUTHEADER)) == -1)
{
return false;
}
else
{
if (event->header.dwType == RIM_TYPEHID)
{
static BOOL bGotTranslation = FALSE,
bGotRotation = FALSE;
static int all6DOFs[6] = {0};
LPRAWHID pRawHid = &event->data.hid;
// Translation or Rotation packet? They come in two different packets.
if (pRawHid->bRawData[0] == 1) // Translation vector
{
msg.raw_translation[0] = (pRawHid->bRawData[1] & 0x000000ff) | ((signed short)(pRawHid->bRawData[2] << 8) & 0xffffff00);
msg.raw_translation[1] = (pRawHid->bRawData[3] & 0x000000ff) | ((signed short)(pRawHid->bRawData[4] << 8) & 0xffffff00);
msg.raw_translation[2] = (pRawHid->bRawData[5] & 0x000000ff) | ((signed short)(pRawHid->bRawData[6] << 8) & 0xffffff00);
msg.vTranslate.x = msg.raw_translation[0] / 255.f * m_fMultiplier;
msg.vTranslate.y = msg.raw_translation[1] / 255.f * m_fMultiplier;
msg.vTranslate.z = msg.raw_translation[2] / 255.f * m_fMultiplier;
msg.bGotTranslation = true;
}
else if (pRawHid->bRawData[0] == 2) // Rotation vector
{
msg.raw_rotation[0] = (pRawHid->bRawData[1] & 0x000000ff) | ((signed short)(pRawHid->bRawData[2] << 8) & 0xffffff00);
msg.raw_rotation[1] = (pRawHid->bRawData[3] & 0x000000ff) | ((signed short)(pRawHid->bRawData[4] << 8) & 0xffffff00);
msg.raw_rotation[2] = (pRawHid->bRawData[5] & 0x000000ff) | ((signed short)(pRawHid->bRawData[6] << 8) & 0xffffff00);
msg.vRotate.x = msg.raw_rotation[0] / 255.f * m_fMultiplier;
msg.vRotate.y = msg.raw_rotation[1] / 255.f * m_fMultiplier;
msg.vRotate.z = msg.raw_rotation[2] / 255.f * m_fMultiplier;
msg.bGotRotation = true;
}
else if (pRawHid->bRawData[0] == 3) // Buttons (display most significant byte to least)
{
msg.buttons[0] = (unsigned char)pRawHid->bRawData[1];
msg.buttons[1] = (unsigned char)pRawHid->bRawData[2];
msg.buttons[2] = (unsigned char)pRawHid->bRawData[3];
CryLog("Button mask: %.2x %.2x %.2x\n", (unsigned char)pRawHid->bRawData[3], (unsigned char)pRawHid->bRawData[2], (unsigned char)pRawHid->bRawData[1]);
if (msg.buttons[0] == 1)
{
m_fMultiplier /= 2.0f;
}
else if (msg.buttons[0] == 2)
{
m_fMultiplier *= 2.0f;
}
}
}
}
return true;
}
#endif
+68
View File
@@ -0,0 +1,68 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_3DCONNEXIONDRIVER_H
#define CRYINCLUDE_EDITOR_UTIL_3DCONNEXIONDRIVER_H
#pragma once
#include "Include/IPlugin.h"
struct S3DConnexionMessage
{
bool bGotTranslation;
bool bGotRotation;
int raw_translation[3];
int raw_rotation[3];
Vec3 vTranslate;
Vec3 vRotate;
unsigned char buttons[3];
S3DConnexionMessage()
: bGotRotation(false)
, bGotTranslation(false)
{
raw_translation[0] = raw_translation[1] = raw_translation[2] = 0;
raw_rotation[0] = raw_rotation[1] = raw_rotation[2] = 0;
vTranslate.Set(0, 0, 0);
vRotate.Set(0, 0, 0);
buttons[0] = buttons[1] = buttons[2] = 0;
};
};
#if defined(AZ_PLATFORM_WINDOWS)
AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING
class SANDBOX_API C3DConnexionDriver
: public IPlugin
{
AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING
public:
C3DConnexionDriver();
~C3DConnexionDriver();
bool InitDevice();
bool GetInputMessageData(LPARAM lParam, S3DConnexionMessage& msg);
void Release() { delete this; };
void ShowAbout() {};
const char* GetPluginGUID() { return "{AD109901-9128-4ffd-8E67-137CB2B1C41B}"; };
DWORD GetPluginVersion() { return 1; };
const char* GetPluginName() { return "3DConnexionDriver"; };
bool CanExitNow() { return true; };
void OnEditorNotify([[maybe_unused]] EEditorNotifyEvent aEventId){}
private:
class C3DConnexionDriverImpl* m_pImpl;
PRAWINPUTDEVICELIST m_pRawInputDeviceList;
PRAWINPUTDEVICE m_pRawInputDevices;
int m_nUsagePage1Usage8Devices;
float m_fMultiplier;
};
#endif
#endif // CRYINCLUDE_EDITOR_UTIL_3DCONNEXIONDRIVER_H
@@ -0,0 +1,396 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "AbstractGroupProxyModel.h"
AbstractGroupProxyModel::AbstractGroupProxyModel(QObject* parent)
: QAbstractProxyModel(parent)
{
}
AbstractGroupProxyModel::~AbstractGroupProxyModel()
{
}
QVariant AbstractGroupProxyModel::data(const QModelIndex& index, int role) const
{
if (!index.isValid())
{
return QVariant();
}
const GroupItem* group = reinterpret_cast<GroupItem*>(index.internalPointer());
if (index.row() >= group->subGroups.count())
{
return sourceModel()->data(mapToSource(index), role);
}
else if (role == Qt::DisplayRole && index.column() == 0 && !group->subGroups.at(index.row())->groupTitle.isEmpty())
{
return group->subGroups[index.row()]->groupTitle;
}
else if (group->subGroups.at(index.row())->groupSourceIndex.isValid())
{
return group->subGroups.at(index.row())->groupSourceIndex.data(role);
}
return QVariant();
}
int AbstractGroupProxyModel::rowCount(const QModelIndex& parent) const
{
if (!sourceModel())
{
return 0;
}
// invalid parent - root item is used
if (!parent.isValid())
{
return m_rootItem.subGroups.count() + m_rootItem.sourceIndexes.count();
}
// this is the group the parent is in.
const GroupItem* group = reinterpret_cast<GroupItem*>(parent.internalPointer());
if (parent.row() < group->subGroups.count())
{
return group->subGroups[parent.row()]->subGroups.count() + group->subGroups[parent.row()]->sourceIndexes.count();
}
return 0;
}
int AbstractGroupProxyModel::columnCount(const QModelIndex& parent) const
{
Q_UNUSED(parent)
if (!sourceModel())
{
return 0;
}
return sourceModel()->columnCount(QModelIndex());
}
QModelIndex AbstractGroupProxyModel::index(int row, int column, const QModelIndex& parent) const
{
if (row >= rowCount(parent) || column >= columnCount(parent))
{
return QModelIndex();
}
if (!parent.isValid())
{
return createIndex(row, column, const_cast<GroupItem*>(&m_rootItem));
}
const GroupItem* group = reinterpret_cast<GroupItem*>(parent.internalPointer());
GroupItem* newParent = group->subGroups[parent.row()];
return createIndex(row, column, newParent);
}
QVariant AbstractGroupProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
return sourceModel()->headerData(section, orientation, role);
}
QModelIndex AbstractGroupProxyModel::parent(const QModelIndex& index) const
{
GroupItem* group = reinterpret_cast<GroupItem*>(index.internalPointer());
if (!group)
{
return QModelIndex();
}
const GroupItem* parentGroup = FindGroup(group);
if (!parentGroup)
{
return QModelIndex();
}
const int row = parentGroup->subGroups.indexOf(group);
return createIndex(row, 0, const_cast<GroupItem*>(parentGroup));
}
bool AbstractGroupProxyModel::hasChildren(const QModelIndex& parent) const
{
if (!parent.isValid())
{
return true;
}
const GroupItem* group = reinterpret_cast<GroupItem*>(parent.internalPointer());
return parent.row() < group->subGroups.count();
}
Qt::ItemFlags AbstractGroupProxyModel::flags(const QModelIndex& index) const
{
const QModelIndex sourceIndex = mapToSource(index);
if (!sourceIndex.isValid())
{
return Qt::ItemIsEnabled | Qt::ItemIsSelectable;
}
return sourceModel()->flags(sourceIndex);
}
QModelIndex AbstractGroupProxyModel::mapToSource(const QModelIndex& proxyIndex) const
{
GroupItem* group = reinterpret_cast<GroupItem*>(proxyIndex.internalPointer());
if (!group)
{
return QModelIndex();
}
const int i = proxyIndex.row() - group->subGroups.count();
if (i < 0)
{
return QModelIndex();
}
return group->sourceIndexes[i].sibling(group->sourceIndexes[i].row(), proxyIndex.column());
}
QModelIndex AbstractGroupProxyModel::mapFromSource(const QModelIndex& sourceIndex) const
{
if (!sourceIndex.isValid())
{
return QModelIndex();
}
GroupItem* group = FindIndex(sourceIndex.sibling(sourceIndex.row(), 0));
if (!group)
{
return QModelIndex();
}
if (group->groupSourceIndex == sourceIndex)
{
GroupItem* parentGroup = FindGroup(group);
return createIndex(parentGroup->subGroups.indexOf(group), sourceIndex.column(), parentGroup);
}
return createIndex(group->subGroups.count() + group->sourceIndexes.indexOf(sourceIndex.sibling(sourceIndex.row(), 0)),
sourceIndex.column(), const_cast<GroupItem*>(group));
}
AbstractGroupProxyModel::GroupItem* AbstractGroupProxyModel::FindIndex(const QModelIndex& index, GroupItem* group) const
{
if (group == nullptr)
{
group = const_cast<GroupItem*>(&m_rootItem);
}
if (group->sourceIndexes.contains(index) || group->groupSourceIndex == index)
{
return group;
}
for (GroupItem* subGroup : group->subGroups)
{
GroupItem* g = FindIndex(index, subGroup);
if (g)
{
return g;
}
}
return nullptr;
}
AbstractGroupProxyModel::GroupItem* AbstractGroupProxyModel::FindGroup(GroupItem* group, GroupItem* parent) const
{
if (parent == nullptr)
{
parent = const_cast<GroupItem*>(&m_rootItem);
}
if (parent->subGroups.contains(group))
{
return parent;
}
for (GroupItem* subGroup : parent->subGroups)
{
GroupItem* g = FindGroup(group, subGroup);
if (g)
{
return g;
}
}
return nullptr;
}
void AbstractGroupProxyModel::setSourceModel(QAbstractItemModel* sourceModel)
{
QAbstractProxyModel::setSourceModel(sourceModel);
connect(sourceModel, &QAbstractItemModel::rowsInserted, this, &AbstractGroupProxyModel::SourceRowsInserted);
connect(sourceModel, &QAbstractItemModel::rowsAboutToBeRemoved, this, &AbstractGroupProxyModel::SourceRowsAboutToBeRemoved);
connect(sourceModel, &QAbstractItemModel::dataChanged, this, &AbstractGroupProxyModel::SourceDataChanged);
connect(sourceModel, &QAbstractItemModel::modelAboutToBeReset, this, &AbstractGroupProxyModel::slotSourceAboutToBeReset);
connect(sourceModel, &QAbstractItemModel::modelReset, this, &AbstractGroupProxyModel::slotSourceReset);
connect(sourceModel, &QAbstractItemModel::layoutAboutToBeChanged, this, &AbstractGroupProxyModel::slotSourceAboutToBeReset);
connect(sourceModel, &QAbstractItemModel::layoutChanged, this, &AbstractGroupProxyModel::slotSourceReset);
RebuildTree();
}
void AbstractGroupProxyModel::slotSourceAboutToBeReset()
{
beginResetModel();
qDeleteAll(m_rootItem.subGroups);
m_rootItem.subGroups.clear();
m_rootItem.sourceIndexes.clear();
}
void AbstractGroupProxyModel::slotSourceReset()
{
const int rowCount = sourceModel() ? sourceModel()->rowCount() : 0;
for (int row = 0; row < rowCount; ++row)
{
const QModelIndex sourceIndex = sourceModel()->index(row, 0);
GroupItem* group = CreateGroupIfNotExists(GroupForSourceIndex(sourceIndex));
if (IsGroupIndex(sourceIndex))
{
group->groupSourceIndex = sourceIndex;
}
else
{
group->sourceIndexes.push_back(sourceIndex);
}
}
endResetModel();
}
void AbstractGroupProxyModel::RebuildTree()
{
beginResetModel();
{
QSignalBlocker blocker(this);
slotSourceAboutToBeReset();
slotSourceReset();
}
endResetModel();
Q_EMIT GroupUpdated();
}
int AbstractGroupProxyModel::subGroupCount() const
{
return m_rootItem.subGroups.count();
}
void AbstractGroupProxyModel::SourceRowsInserted(const QModelIndex& p, int from, int to)
{
if (p.isValid())
{
return;
}
for (int row = from; row <= to; ++row)
{
const QModelIndex sourceIndex = sourceModel()->index(row, 0);
GroupItem* group = CreateGroupIfNotExists(GroupForSourceIndex(sourceIndex));
if (IsGroupIndex(sourceIndex))
{
group->groupSourceIndex = sourceIndex;
}
else
{
const int modelRow = group->subGroups.count() + group->sourceIndexes.count();
beginInsertRows(parent(createIndex(0, 0, group)), modelRow, modelRow);
group->sourceIndexes.push_back(sourceIndex);
endInsertRows();
}
}
Q_EMIT GroupUpdated();
}
void AbstractGroupProxyModel::SourceRowsAboutToBeRemoved(const QModelIndex& p, int from, int to)
{
if (p.isValid())
{
return;
}
for (int row = from; row <= to; ++row)
{
const QModelIndex sourceIndex = sourceModel()->index(row, 0);
GroupItem* group = const_cast<GroupItem*>(FindIndex(sourceIndex));
if (!group)
{
continue;
}
if (group->groupSourceIndex != sourceIndex)
{
const int modelRow = group->subGroups.count() + group->sourceIndexes.indexOf(sourceIndex);
beginRemoveRows(parent(createIndex(0, 0, group)), modelRow, modelRow);
group->sourceIndexes.remove(group->sourceIndexes.indexOf(sourceIndex));
endRemoveRows();
}
RemoveEmptyGroup(group);
}
Q_EMIT GroupUpdated();
}
void AbstractGroupProxyModel::SourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
if (topLeft.parent().isValid())
{
return;
}
for (int row = topLeft.row(); row <= bottomRight.row(); ++row)
{
const QModelIndex sourceIndex = sourceModel()->index(row, 0);
const GroupItem* currentGroup = FindIndex(sourceIndex);
GroupItem* newGroup = CreateGroupIfNotExists(GroupForSourceIndex(sourceIndex));
if (currentGroup != newGroup)
{
SourceRowsAboutToBeRemoved(QModelIndex(), row, row);
SourceRowsInserted(QModelIndex(), row, row);
}
else
{
emit dataChanged(mapFromSource(sourceIndex), mapFromSource(sourceIndex.sibling(row, columnCount() - 1)));
}
}
Q_EMIT GroupUpdated();
}
AbstractGroupProxyModel::GroupItem* AbstractGroupProxyModel::CreateGroupIfNotExists(QStringList group)
{
GroupItem* currentGroup = &m_rootItem;
while (true)
{
if (group.isEmpty())
{
return currentGroup;
}
auto matchingSubGroup = std::find_if(currentGroup->subGroups.begin(), currentGroup->subGroups.end(),
[=](const GroupItem* g)
{
return QString::compare(g->groupTitle, group.first(), Qt::CaseInsensitive) == 0;
}
);
if (matchingSubGroup == currentGroup->subGroups.end())
{
GroupItem* newGroup = new GroupItem;
newGroup->groupTitle = group.first();
beginInsertRows(parent(createIndex(0, 0, currentGroup)),
currentGroup->subGroups.size(), currentGroup->subGroups.size());
currentGroup->subGroups.push_back(newGroup);
endInsertRows();
currentGroup = currentGroup->subGroups[currentGroup->subGroups.size() - 1];
}
else
{
currentGroup = *matchingSubGroup;
}
group.pop_front();
}
}
void AbstractGroupProxyModel::RemoveEmptyGroup(GroupItem* group)
{
if (!group->subGroups.isEmpty() || !group->sourceIndexes.isEmpty() || group == &m_rootItem
|| (group->groupSourceIndex.isValid() && !IsGroupIndex(group->groupSourceIndex)))
{
return;
}
GroupItem* parentGroup = const_cast<GroupItem*>(FindGroup(group));
const int row = parentGroup->subGroups.indexOf(group);
beginRemoveRows(parent(createIndex(0, 0, parentGroup)), row, row);
delete parentGroup->subGroups.takeAt(row);
endRemoveRows();
RemoveEmptyGroup(parentGroup);
}
#include <Util/moc_AbstractGroupProxyModel.cpp>
@@ -0,0 +1,83 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef ABSTRACTGROUPPROXYMODEL_H
#define ABSTRACTGROUPPROXYMODEL_H
#if !defined(Q_MOC_RUN)
#include <QAbstractProxyModel>
#endif
#if !defined(Q_MOC_RUN)
#include <QPixmap>
#endif
class AbstractGroupProxyModel
: public QAbstractProxyModel
{
Q_OBJECT
public:
AbstractGroupProxyModel(QObject* parent = 0);
~AbstractGroupProxyModel();
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex& parent = QModelIndex()) const override;
int columnCount(const QModelIndex& parent = QModelIndex()) const override;
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex& index) const override;
bool hasChildren(const QModelIndex& parent = QModelIndex()) const override;
Qt::ItemFlags flags(const QModelIndex& index) const override;
QModelIndex mapToSource(const QModelIndex& proxyIndex) const override;
QModelIndex mapFromSource(const QModelIndex& sourceIndex) const override;
void setSourceModel(QAbstractItemModel* sourceModel) override;
signals:
void GroupUpdated();
protected:
virtual QStringList GroupForSourceIndex(const QModelIndex& sourceIndex) const = 0;
virtual bool IsGroupIndex([[maybe_unused]] const QModelIndex& sourceIndex) const { return false; }
void slotSourceAboutToBeReset();
void slotSourceReset();
void RebuildTree();
int subGroupCount() const;
private:
struct GroupItem
{
QPersistentModelIndex groupSourceIndex;
QString groupTitle;
QVector<GroupItem*> subGroups;
QVector<QPersistentModelIndex> sourceIndexes;
~GroupItem()
{
qDeleteAll(subGroups);
}
};
GroupItem* FindIndex(const QModelIndex& index, GroupItem* group = 0) const;
GroupItem* FindGroup(GroupItem* group, GroupItem* parent = 0) const;
void SourceRowsInserted(const QModelIndex& parent, int from, int to);
void SourceRowsAboutToBeRemoved(const QModelIndex& parent, int from, int to);
void SourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
GroupItem* CreateGroupIfNotExists(QStringList group);
void RemoveEmptyGroup(GroupItem* group);
GroupItem m_rootItem;
};
#endif // ABSTRACTGROUPPROXYMODEL_H
+21
View File
@@ -0,0 +1,21 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "AbstractSortModel.h"
AbstractSortModel::AbstractSortModel(QObject* parent)
: QAbstractTableModel(parent)
{
}
bool AbstractSortModel::LessThan(const QModelIndex& lhs, const QModelIndex& rhs) const
{
return lhs.data().toString() < rhs.data().toString();
}
#include <Util/moc_AbstractSortModel.cpp>
+25
View File
@@ -0,0 +1,25 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef ABSTRACTSORTMODEL_H
#define ABSTRACTSORTMODEL_H
#if !defined(Q_MOC_RUN)
#include <QAbstractItemModel>
#endif
class AbstractSortModel
: public QAbstractTableModel
{
Q_OBJECT
public:
AbstractSortModel(QObject* parent = nullptr);
virtual bool LessThan(const QModelIndex& lhs, const QModelIndex& rhs) const;
};
#endif // ABSTRACTSORTMODEL_H
+856
View File
@@ -0,0 +1,856 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#pragma warning ( disable : 4244 ) // conversion from 'double' to 'float', possible loss of data.
/**** Decompose.h - Basic declarations ****/
typedef struct
{
float x, y, z, w;
} Quatern; /* Quaternernion */
enum QuaternPart
{
X, Y, Z, W
};
typedef Quatern HVect; /* Homogeneous 3D vector */
typedef float HMatrix[4][4]; /* Right-handed, for column vectors */
typedef struct
{
HVect t; /* Translation components */
Quatern q; /* Essential rotation */
Quatern u; /* Stretch rotation */
HVect k; /* Stretch factors */
float f; /* Sign of determinant */
} SAffineParts;
float polar_decomp(HMatrix M, HMatrix Q, HMatrix S);
HVect spect_decomp(HMatrix S, HMatrix U);
Quatern snuggle(Quatern q, HVect* k);
/******* Matrix Preliminaries *******/
/** Fill out 3x3 matrix to 4x4 **/
#define mat_pad(A) (A[W][X] = A[X][W] = A[W][Y] = A[Y][W] = A[W][Z] = A[Z][W] = 0, A[W][W] = 1)
/** Copy nxn matrix A to C using "gets" for assignment **/
#define mat_copy(C, gets, A, n) {int i, j; for (i = 0; i < n; i++) {for (j = 0; j < n; j++) { \
C[i][j] gets (A[i][j]); } \
} \
}
/** Copy transpose of nxn matrix A to C using "gets" for assignment **/
#define mat_tpose(AT, gets, A, n) {int i, j; for (i = 0; i < n; i++) {for (j = 0; j < n; j++) { \
AT[i][j] gets (A[j][i]); } \
} \
}
/** Assign nxn matrix C the element-wise combination of A and B using "op" **/
#define mat_binop(C, gets, A, op, B, n) {int i, j; for (i = 0; i < n; i++) {for (j = 0; j < n; j++) { \
C[i][j] gets (A[i][j]) op (B[i][j]); } \
} \
}
/** Multiply the upper left 3x3 parts of A and B to get AB **/
static void mat_mult(HMatrix A, HMatrix B, HMatrix AB)
{
int i, j;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
AB[i][j] = A[i][0] * B[0][j] + A[i][1] * B[1][j] + A[i][2] * B[2][j];
}
}
}
/** Return dot product of length 3 vectors va and vb **/
static float vdot(float* va, float* vb)
{
return (va[0] * vb[0] + va[1] * vb[1] + va[2] * vb[2]);
}
/** Set v to cross product of length 3 vectors va and vb **/
static void vcross(float* va, float* vb, float* v)
{
v[0] = va[1] * vb[2] - va[2] * vb[1];
v[1] = va[2] * vb[0] - va[0] * vb[2];
v[2] = va[0] * vb[1] - va[1] * vb[0];
}
/** Set MadjT to transpose of inverse of M times determinant of M **/
static void adjoint_transpose(HMatrix M, HMatrix MadjT)
{
vcross(M[1], M[2], MadjT[0]);
vcross(M[2], M[0], MadjT[1]);
vcross(M[0], M[1], MadjT[2]);
}
/******* Quaternernion Preliminaries *******/
/* Construct a (possibly non-unit) Quaternernion from real components. */
static Quatern Qt_(float x, float y, float z, float w)
{
Quatern qq;
qq.x = x;
qq.y = y;
qq.z = z;
qq.w = w;
return (qq);
}
/* Return conjugate of Quaternernion. */
static Quatern Qt_Conj(Quatern q)
{
Quatern qq;
qq.x = -q.x;
qq.y = -q.y;
qq.z = -q.z;
qq.w = q.w;
return (qq);
}
/* Return Quaternernion product qL * qR. Note: order is important!
* To combine rotations, use the product Mul(qSecond, qFirst),
* which gives the effect of rotating by qFirst then qSecond. */
static Quatern Qt_Mul(Quatern qL, Quatern qR)
{
Quatern qq;
qq.w = qL.w * qR.w - qL.x * qR.x - qL.y * qR.y - qL.z * qR.z;
qq.x = qL.w * qR.x + qL.x * qR.w + qL.y * qR.z - qL.z * qR.y;
qq.y = qL.w * qR.y + qL.y * qR.w + qL.z * qR.x - qL.x * qR.z;
qq.z = qL.w * qR.z + qL.z * qR.w + qL.x * qR.y - qL.y * qR.x;
return (qq);
}
/* Return product of Quaternernion q by scalar w. */
static Quatern Qt_Scale(Quatern q, float w)
{
Quatern qq;
qq.w = q.w * w;
qq.x = q.x * w;
qq.y = q.y * w;
qq.z = q.z * w;
return (qq);
}
/* Construct a unit Quaternernion from rotation matrix. Assumes matrix is
* used to multiply column vector on the left: vnew = mat vold. Works
* correctly for right-handed coordinate system and right-handed rotations.
* Translation and perspective components ignored. */
static Quatern Qt_FromMatrix(HMatrix mat)
{
/* This algorithm avoids near-zero divides by looking for a large component
* - first w, then x, y, or z. When the trace is greater than zero,
* |w| is greater than 1/2, which is as small as a largest component can be.
* Otherwise, the largest diagonal entry corresponds to the largest of |x|,
* |y|, or |z|, one of which must be larger than |w|, and at least 1/2. */
Quatern qu = { 0.0f, 0.0f, 0.0f, 1.0f };
double tr, s;
tr = mat[X][X] + mat[Y][Y] + mat[Z][Z];
if (tr >= 0.0)
{
s = sqrt(tr + mat[W][W]);
qu.w = s * 0.5;
s = 0.5 / s;
qu.x = (mat[Z][Y] - mat[Y][Z]) * s;
qu.y = (mat[X][Z] - mat[Z][X]) * s;
qu.z = (mat[Y][X] - mat[X][Y]) * s;
}
else
{
int h = X;
if (mat[Y][Y] > mat[X][X])
{
h = Y;
}
if (mat[Z][Z] > mat[h][h])
{
h = Z;
}
switch (h)
{
#define caseMacro(i, j, k, I, J, K) \
case I: \
s = sqrt((mat[I][I] - (mat[J][J] + mat[K][K])) + mat[W][W]); \
qu.i = s * 0.5; \
s = 0.5 / s; \
qu.j = (mat[I][J] + mat[J][I]) * s; \
qu.k = (mat[K][I] + mat[I][K]) * s; \
qu.w = (mat[K][J] - mat[J][K]) * s; \
break
caseMacro(x, y, z, X, Y, Z);
caseMacro(y, z, x, Y, Z, X);
caseMacro(z, x, y, Z, X, Y);
}
}
if (mat[W][W] != 1.0)
{
qu = Qt_Scale(qu, 1.0f / sqrt(mat[W][W]));
}
return (qu);
}
/******* Decomp Auxiliaries *******/
static HMatrix mat_id = {
{1, 0, 0, 0}, {0, 1, 0, 0}, {0, 0, 1, 0}, {0, 0, 0, 1}
};
/** Compute either the 1 or infinity norm of M, depending on tpose **/
static float mat_norm(HMatrix M, int tpose)
{
int i;
float sum, max;
max = 0.0;
for (i = 0; i < 3; i++)
{
if (tpose)
{
sum = fabs(M[0][i]) + fabs(M[1][i]) + fabs(M[2][i]);
}
else
{
sum = fabs(M[i][0]) + fabs(M[i][1]) + fabs(M[i][2]);
}
if (max < sum)
{
max = sum;
}
}
return max;
}
static float norm_inf(HMatrix M) {return mat_norm(M, 0); }
static float norm_one(HMatrix M) {return mat_norm(M, 1); }
/** Return index of column of M containing maximum abs entry, or -1 if M=0 **/
static int find_max_col(HMatrix M)
{
float abs, max;
int i, j, col;
max = 0.0;
col = -1;
for (i = 0; i < 3; i++)
{
for (j = 0; j < 3; j++)
{
abs = M[i][j];
if (abs < 0.0)
{
abs = -abs;
}
if (abs > max)
{
max = abs;
col = j;
}
}
}
return col;
}
/** Setup u for Household reflection to zero all v components but first **/
static void make_reflector(float* v, float* u)
{
float s = sqrt(vdot(v, v));
u[0] = v[0];
u[1] = v[1];
u[2] = v[2] + ((v[2] < 0.0) ? -s : s);
s = sqrt(2.0 / vdot(u, u));
u[0] = u[0] * s;
u[1] = u[1] * s;
u[2] = u[2] * s;
}
/** Apply Householder reflection represented by u to column vectors of M **/
static void reflect_cols(HMatrix M, float* u)
{
int i, j;
for (i = 0; i < 3; i++)
{
float s = u[0] * M[0][i] + u[1] * M[1][i] + u[2] * M[2][i];
for (j = 0; j < 3; j++)
{
M[j][i] -= u[j] * s;
}
}
}
/** Apply Householder reflection represented by u to row vectors of M **/
static void reflect_rows(HMatrix M, float* u)
{
int i, j;
for (i = 0; i < 3; i++)
{
float s = vdot(u, M[i]);
for (j = 0; j < 3; j++)
{
M[i][j] -= u[j] * s;
}
}
}
/** Find orthogonal factor Q of rank 1 (or less) M **/
static void do_rank1(HMatrix M, HMatrix Q)
{
float v1[3], v2[3], s;
int col;
mat_copy(Q, =, mat_id, 4);
/* If rank(M) is 1, we should find a non-zero column in M */
col = find_max_col(M);
if (col < 0)
{
return; /* Rank is 0 */
}
v1[0] = M[0][col];
v1[1] = M[1][col];
v1[2] = M[2][col];
make_reflector(v1, v1);
reflect_cols(M, v1);
v2[0] = M[2][0];
v2[1] = M[2][1];
v2[2] = M[2][2];
make_reflector(v2, v2);
reflect_rows(M, v2);
s = M[2][2];
if (s < 0.0)
{
Q[2][2] = -1.0;
}
reflect_cols(Q, v1);
reflect_rows(Q, v2);
}
/** Find orthogonal factor Q of rank 2 (or less) M using adjoint transpose **/
static void do_rank2(HMatrix M, HMatrix MadjT, HMatrix Q)
{
float v1[3], v2[3];
float w, x, y, z, c, s, d;
int col;
/* If rank(M) is 2, we should find a non-zero column in MadjT */
col = find_max_col(MadjT);
if (col < 0)
{
do_rank1(M, Q);
return;
} /* Rank<2 */
v1[0] = MadjT[0][col];
v1[1] = MadjT[1][col];
v1[2] = MadjT[2][col];
make_reflector(v1, v1);
reflect_cols(M, v1);
vcross(M[0], M[1], v2);
make_reflector(v2, v2);
reflect_rows(M, v2);
w = M[0][0];
x = M[0][1];
y = M[1][0];
z = M[1][1];
if (w * z > x * y)
{
c = z + w;
s = y - x;
d = sqrt(c * c + s * s);
c = c / d;
s = s / d;
Q[0][0] = Q[1][1] = c;
Q[0][1] = -(Q[1][0] = s);
}
else
{
c = z - w;
s = y + x;
d = sqrt(c * c + s * s);
c = c / d;
s = s / d;
Q[0][0] = -(Q[1][1] = c);
Q[0][1] = Q[1][0] = s;
}
Q[0][2] = Q[2][0] = Q[1][2] = Q[2][1] = 0.0;
Q[2][2] = 1.0;
reflect_cols(Q, v1);
reflect_rows(Q, v2);
}
/******* Polar Decomposition *******/
/* Polar Decomposition of 3x3 matrix in 4x4,
* M = QS. See Nicholas Higham and Robert S. Schreiber,
* Fast Polar Decomposition of An Arbitrary Matrix,
* Technical Report 88-942, October 1988,
* Department of Computer Science, Cornell University.
*/
float polar_decomp(HMatrix M, HMatrix Q, HMatrix S)
{
#define TOL 1.0e-6
HMatrix Mk, MadjTk, Ek;
float det, M_one, M_inf, MadjT_one, MadjT_inf, E_one, gamma, g1, g2;
mat_tpose(Mk, =, M, 3);
M_one = norm_one(Mk);
M_inf = norm_inf(Mk);
do
{
adjoint_transpose(Mk, MadjTk);
det = vdot(Mk[0], MadjTk[0]);
if (det == 0.0)
{
do_rank2(Mk, MadjTk, Mk);
break;
}
MadjT_one = norm_one(MadjTk);
MadjT_inf = norm_inf(MadjTk);
gamma = sqrt(sqrt((MadjT_one * MadjT_inf) / (M_one * M_inf)) / fabs(det));
g1 = gamma * 0.5;
g2 = 0.5 / (gamma * det);
mat_copy(Ek, =, Mk, 3);
mat_binop(Mk, =, g1 * Mk, +, g2 * MadjTk, 3);
mat_copy(Ek, -=, Mk, 3);
E_one = norm_one(Ek);
M_one = norm_one(Mk);
M_inf = norm_inf(Mk);
} while (E_one > (M_one * TOL));
mat_tpose(Q, =, Mk, 3);
mat_pad(Q);
mat_mult(Mk, M, S);
mat_pad(S);
for (int i = 0; i < 3; i++)
{
for (int j = i; j < 3; j++)
{
S[i][j] = S[j][i] = 0.5 * (S[i][j] + S[j][i]);
}
}
return (det);
}
/******* Spectral Decomposition *******/
/* Compute the spectral decomposition of symmetric positive semi-definite S.
* Returns rotation in U and scale factors in result, so that if K is a diagonal
* matrix of the scale factors, then S = U K (U transpose). Uses Jacobi method.
* See Gene H. Golub and Charles F. Van Loan. Matrix Computations. Hopkins 1983.
*/
HVect spect_decomp(HMatrix S, HMatrix U)
{
HVect kv;
double Diag[3], OffD[3]; /* OffD is off-diag (by omitted index) */
double g, h, fabsh, fabsOffDi, t, theta, c, s, tau, ta, OffDq, a, b;
static char nxt[] = {Y, Z, X};
int sweep;
mat_copy(U, =, mat_id, 4);
Diag[X] = S[X][X];
Diag[Y] = S[Y][Y];
Diag[Z] = S[Z][Z];
OffD[X] = S[Y][Z];
OffD[Y] = S[Z][X];
OffD[Z] = S[X][Y];
for (sweep = 20; sweep > 0; sweep--)
{
float sm = fabs(OffD[X]) + fabs(OffD[Y]) + fabs(OffD[Z]);
if (sm == 0.0)
{
break;
}
for (int i = Z; i >= X; i--)
{
int p = nxt[i];
int q = nxt[p];
fabsOffDi = fabs(OffD[i]);
g = 100.0 * fabsOffDi;
if (fabsOffDi > AZ::Constants::FloatEpsilon)
{
h = Diag[q] - Diag[p];
fabsh = fabs(h);
if (fabsh + g == fabsh)
{
t = OffD[i] / h;
}
else
{
theta = 0.5 * h / OffD[i];
t = 1.0 / (fabs(theta) + sqrt(theta * theta + 1.0));
if (theta < 0.0)
{
t = -t;
}
}
c = 1.0 / sqrt(t * t + 1.0);
s = t * c;
tau = s / (c + 1.0);
ta = t * OffD[i];
OffD[i] = 0.0;
Diag[p] -= ta;
Diag[q] += ta;
OffDq = OffD[q];
OffD[q] -= s * (OffD[p] + tau * OffD[q]);
OffD[p] += s * (OffDq - tau * OffD[p]);
for (int j = Z; j >= X; j--)
{
a = U[j][p];
b = U[j][q];
U[j][p] -= s * (b + tau * a);
U[j][q] += s * (a - tau * b);
}
}
}
}
kv.x = Diag[X];
kv.y = Diag[Y];
kv.z = Diag[Z];
kv.w = 1.0;
return (kv);
}
/******* Spectral Axis Adjustment *******/
/* Given a unit Quaternernion, q, and a scale vector, k, find a unit Quaternernion, p,
* which permutes the axes and turns freely in the plane of duplicate scale
* factors, such that q p has the largest possible w component, i.e. the
* smallest possible angle. Permutes k's components to go with q p instead of q.
* See Ken Shoemake and Tom Duff. Matrix Animation and Polar Decomposition.
* Proceedings of Graphics Interface 1992. Details on p. 262-263.
*/
Quatern snuggle(Quatern q, HVect* k)
{
#define SQRTHALF (0.7071067811865475244f)
#define sgn(n, v) ((n) ? -(v) : (v))
#define swap(a, i, j) {a[3] = a[i]; a[i] = a[j]; a[j] = a[3]; }
#define cycle(a, p) if (p) {a[3] = a[0]; a[0] = a[1]; a[1] = a[2]; a[2] = a[3]; } \
else {a[3] = a[2]; a[2] = a[1]; a[1] = a[0]; a[0] = a[3]; }
Quatern p = { 0.0f, 0.0f, 0.0f, 1.0f };
float ka[4];
int i, turn = -1;
ka[X] = k->x;
ka[Y] = k->y;
ka[Z] = k->z;
if (ka[X] == ka[Y])
{
if (ka[X] == ka[Z])
{
turn = W;
}
else
{
turn = Z;
}
}
else
{
if (ka[X] == ka[Z])
{
turn = Y;
}
else if (ka[Y] == ka[Z])
{
turn = X;
}
}
if (turn >= 0)
{
Quatern qtoz, qp;
unsigned neg[3], win;
double mag[3], t;
static Quatern qxtoz = {0, SQRTHALF, 0, SQRTHALF};
static Quatern qytoz = {SQRTHALF, 0, 0, SQRTHALF};
static Quatern qppmm = { 0.5, 0.5, -0.5, -0.5};
static Quatern qpppp = { 0.5, 0.5, 0.5, 0.5};
static Quatern qmpmm = {-0.5, 0.5, -0.5, -0.5};
static Quatern qpppm = { 0.5, 0.5, 0.5, -0.5};
static Quatern q0001 = { 0.0, 0.0, 0.0, 1.0};
static Quatern q1000 = { 1.0, 0.0, 0.0, 0.0};
switch (turn)
{
default:
return (Qt_Conj(q));
case X:
q = Qt_Mul(q, qtoz = qxtoz);
swap(ka, X, Z);
break;
case Y:
q = Qt_Mul(q, qtoz = qytoz);
swap(ka, Y, Z);
break;
case Z:
qtoz = q0001;
break;
}
q = Qt_Conj(q);
mag[0] = (double)q.z * q.z + (double)q.w * q.w - 0.5;
mag[1] = (double)q.x * q.z - (double)q.y * q.w;
mag[2] = (double)q.y * q.z + (double)q.x * q.w;
for (i = 0; i < 3; i++)
{
neg[i] = (mag[i] < 0.0);
if (neg[i])
{
mag[i] = -mag[i];
}
}
if (mag[0] > mag[1])
{
if (mag[0] > mag[2])
{
win = 0;
}
else
{
win = 2;
}
}
else
{
if (mag[1] > mag[2])
{
win = 1;
}
else
{
win = 2;
}
}
switch (win)
{
case 0:
if (neg[0])
{
p = q1000;
}
else
{
p = q0001;
} break;
case 1:
if (neg[1])
{
p = qppmm;
}
else
{
p = qpppp;
} cycle(ka, 0);
break;
case 2:
if (neg[2])
{
p = qmpmm;
}
else
{
p = qpppm;
} cycle(ka, 1);
break;
}
qp = Qt_Mul(q, p);
t = sqrt(mag[win] + 0.5);
p = Qt_Mul(p, Qt_(0.0, 0.0, -qp.z / t, qp.w / t));
p = Qt_Mul(qtoz, Qt_Conj(p));
}
else
{
float qa[4], pa[4];
unsigned lo, hi, neg[4], par = 0;
double all, big, two;
qa[0] = q.x;
qa[1] = q.y;
qa[2] = q.z;
qa[3] = q.w;
for (i = 0; i < 4; i++)
{
pa[i] = 0.0;
neg[i] = (qa[i] < 0.0);
if (neg[i])
{
qa[i] = -qa[i];
}
par ^= neg[i];
}
/* Find two largest components, indices in hi and lo */
if (qa[0] > qa[1])
{
lo = 0;
}
else
{
lo = 1;
}
if (qa[2] > qa[3])
{
hi = 2;
}
else
{
hi = 3;
}
if (qa[lo] > qa[hi])
{
if (qa[lo ^ 1] > qa[hi])
{
hi = lo;
lo ^= 1;
}
else
{
hi ^= lo;
lo ^= hi;
hi ^= lo;
}
}
else
{
if (qa[hi ^ 1] > qa[lo])
{
lo = hi ^ 1;
}
}
all = (qa[0] + qa[1] + qa[2] + qa[3]) * 0.5;
two = (qa[hi] + qa[lo]) * SQRTHALF;
big = qa[hi];
if (all > two)
{
if (all > big)/*all*/
{
{
int ii;
for (ii = 0; ii < 4; ii++)
{
pa[ii] = sgn(neg[ii], 0.5);
}
}
cycle(ka, par)
}
else
{ /*big*/
pa[hi] = sgn(neg[hi], 1.0);
}
}
else
{
if (two > big)/*two*/
{
pa[hi] = sgn(neg[hi], SQRTHALF);
pa[lo] = sgn(neg[lo], SQRTHALF);
if (lo > hi)
{
hi ^= lo;
lo ^= hi;
hi ^= lo;
}
if (hi == W)
{
hi = "\001\002\000"[lo];
lo = 3 - hi - lo;
}
swap(ka, hi, lo)
}
else
{ /*big*/
pa[hi] = sgn(neg[hi], 1.0);
}
}
p.x = -pa[0];
p.y = -pa[1];
p.z = -pa[2];
p.w = pa[3];
}
k->x = ka[X];
k->y = ka[Y];
k->z = ka[Z];
return (p);
}
/******* Decompose Affine Matrix *******/
/* Decompose 4x4 affine matrix A as TFRUK(U transpose), where t contains the
* translation components, q contains the rotation R, u contains U, k contains
* scale factors, and f contains the sign of the determinant.
* Assumes A transforms column vectors in right-handed coordinates.
* See Ken Shoemake and Tom Duff. Matrix Animation and Polar Decomposition.
* Proceedings of Graphics Interface 1992.
*/
static void decomp_affine(HMatrix A, SAffineParts* parts)
{
HMatrix Q, S, U;
Quatern p;
float det;
parts->t = Qt_(A[X][W], A[Y][W], A[Z][W], 0);
det = polar_decomp(A, Q, S);
if (det < 0.0)
{
mat_copy(Q, =, -Q, 3);
parts->f = -1;
}
else
{
parts->f = 1;
}
parts->q = Qt_FromMatrix(Q);
parts->k = spect_decomp(S, U);
parts->u = Qt_FromMatrix(U);
p = snuggle(parts->u, &parts->k);
parts->u = Qt_Mul(parts->u, p);
}
static void spectral_decomp_affine(HMatrix A, SAffineParts* parts)
{
HMatrix Q, S, U;
float det;
parts->t = Qt_(A[X][W], A[Y][W], A[Z][W], 0);
det = polar_decomp(A, Q, S);
if (det < 0.0)
{
mat_copy(Q, =, -Q, 3);
parts->f = -1;
}
else
{
parts->f = 1;
}
parts->q = Qt_FromMatrix(Q);
parts->k = spect_decomp(S, U);
parts->u = Qt_FromMatrix(U);
}
// Decompose matrix to affine parts.
void AffineParts::Decompose(const Matrix34& tm)
{
SAffineParts parts;
Matrix44 tm44(tm);
HMatrix& H = *((HMatrix*)&tm44); // Treat HMatrix as a Matrix44.
decomp_affine(H, &parts);
rot = Quat(parts.q.w, parts.q.x, parts.q.y, parts.q.z);
rotScale = Quat(parts.u.w, parts.u.x, parts.u.y, parts.u.z);
pos = Vec3(parts.t.x, parts.t.y, parts.t.z);
scale = Vec3(parts.k.x, parts.k.y, parts.k.z);
fDet = parts.f;
}
// Spectral matrix decompostion to affine parts.
void AffineParts::SpectralDecompose(const Matrix34& tm)
{
SAffineParts parts;
Matrix44 tm44(tm);
HMatrix& H = *((HMatrix*)&tm44); // Treat HMatrix as a Matrix44.
spectral_decomp_affine(H, &parts);
rot = Quat(parts.q.w, parts.q.x, parts.q.y, parts.q.z);
rotScale = Quat(parts.u.w, parts.u.x, parts.u.y, parts.u.z);
pos = Vec3(parts.t.x, parts.t.y, parts.t.z);
scale = Vec3(parts.k.x, parts.k.y, parts.k.z);
fDet = parts.f;
}
+32
View File
@@ -0,0 +1,32 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_AFFINEPARTS_H
#define CRYINCLUDE_EDITOR_UTIL_AFFINEPARTS_H
#pragma once
struct AffineParts
{
Vec3 pos; //!< Translation components
Quat rot; //!< Essential rotation.
Quat rotScale; //!< Stretch rotation.
Vec3 scale; //!< Stretch factors.
float fDet; //!< Sign of determinant.
/** Decompose matrix to its affnie parts.
*/
void Decompose(const Matrix34& mat);
/** Decompose matrix to its affnie parts.
Assume there`s no stretch rotation.
*/
void SpectralDecompose(const Matrix34& mat);
};
#endif // CRYINCLUDE_EDITOR_UTIL_AFFINEPARTS_H
@@ -0,0 +1,64 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "AutoDirectoryRestoreFileDialog.h"
// Qt
#include <QMessageBox>
CAutoDirectoryRestoreFileDialog::CAutoDirectoryRestoreFileDialog(
QFileDialog::AcceptMode acceptMode,
QFileDialog::FileMode fileMode,
const QString& defaultSuffix,
const QString& directory /* = {} */,
const QString& filter /* = {} */,
QFileDialog::Options options /* = {} */,
const QString& caption /* = {} */,
QWidget* parent /* = nullptr */)
: QFileDialog(parent, caption, QString(""), filter)
{
char resolvedPath[AZ_MAX_PATH_LEN] = { 0 };
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(directory.toUtf8().data(), resolvedPath, AZ_MAX_PATH_LEN);
setDirectory(QString::fromUtf8(resolvedPath));
setAcceptMode(acceptMode);
setDefaultSuffix(defaultSuffix);
setFileMode(fileMode);
setOptions(options);
}
int CAutoDirectoryRestoreFileDialog::exec()
{
int result = -1;
while ((result = QFileDialog::exec()) == QDialog::Accepted)
{
bool problem = false;
foreach(const QString&fileName, selectedFiles())
{
QFileInfo info(fileName);
if (!CryStringUtils::IsValidFileName(info.fileName().toStdString().c_str()))
{
QMessageBox::warning(this, tr("Error"), tr("Please select a valid file name (standard English alphanumeric characters only)"));
problem = true;
break;
}
}
if (!problem)
{
return result;
}
}
return result;
}
#include <Util/moc_AutoDirectoryRestoreFileDialog.cpp>
@@ -0,0 +1,37 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_AUTODIRECTORYRESTOREFILEDIALOG_H
#define CRYINCLUDE_EDITOR_UTIL_AUTODIRECTORYRESTOREFILEDIALOG_H
#pragma once
#if !defined(Q_MOC_RUN)
#include <QFileDialog>
#endif
class CAutoDirectoryRestoreFileDialog
: public QFileDialog
{
Q_OBJECT
public:
explicit CAutoDirectoryRestoreFileDialog(
QFileDialog::AcceptMode acceptMode,
QFileDialog::FileMode fileMode = QFileDialog::AnyFile,
const QString& defaultSuffix = {},
const QString& directory = {},
const QString& filter = {},
QFileDialog::Options options = QFileDialog::Options(),
const QString& caption = {},
QWidget* parent = nullptr);
virtual ~CAutoDirectoryRestoreFileDialog() {}
int exec() override;
};
#endif // CRYINCLUDE_EDITOR_UTIL_AUTODIRECTORYRESTOREFILEDIALOG_H
+24
View File
@@ -0,0 +1,24 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "AutoLogTime.h"
CAutoLogTime::CAutoLogTime(const char* what)
{
m_what = what;
CLogFile::FormatLine("---- Start: %s", m_what);
m_t0 = GetTickCount();
}
CAutoLogTime::~CAutoLogTime()
{
m_t1 = GetTickCount();
CLogFile::FormatLine("---- End: %s (%d seconds)", m_what, (m_t1 - m_t0) / 1000);
}
+24
View File
@@ -0,0 +1,24 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_AUTOLOGTIME_H
#define CRYINCLUDE_EDITOR_UTIL_AUTOLOGTIME_H
#pragma once
class CAutoLogTime
{
public:
CAutoLogTime(const char* what);
~CAutoLogTime();
private:
const char* m_what;
int m_t0, m_t1;
};
#endif // CRYINCLUDE_EDITOR_UTIL_AUTOLOGTIME_H
+47
View File
@@ -0,0 +1,47 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "ColorUtils.h"
// Qt
#include <QColor>
//////////////////////////////////////////////////////////////////////////
QColor ColorLinearToGamma(ColorF col)
{
float r = clamp_tpl(col.r, 0.0f, 1.0f);
float g = clamp_tpl(col.g, 0.0f, 1.0f);
float b = clamp_tpl(col.b, 0.0f, 1.0f);
float a = clamp_tpl(col.a, 0.0f, 1.0f);
r = (float)(r <= 0.0031308 ? (12.92 * r) : (1.055 * pow((double)r, 1.0 / 2.4) - 0.055));
g = (float)(g <= 0.0031308 ? (12.92 * g) : (1.055 * pow((double)g, 1.0 / 2.4) - 0.055));
b = (float)(b <= 0.0031308 ? (12.92 * b) : (1.055 * pow((double)b, 1.0 / 2.4) - 0.055));
return QColor(FtoI(r * 255.0f), FtoI(g * 255.0f), FtoI(b * 255.0f), FtoI(a * 255.0f));
}
//////////////////////////////////////////////////////////////////////////
ColorF ColorGammaToLinear(const QColor& col)
{
float r = (float)col.red() / 255.0f;
float g = (float)col.green() / 255.0f;
float b = (float)col.blue() / 255.0f;
float a = (float)col.alpha() / 255.0f;
return ColorF((float)(r <= 0.04045 ? (r / 12.92) : pow(((double)r + 0.055) / 1.055, 2.4)),
(float)(g <= 0.04045 ? (g / 12.92) : pow(((double)g + 0.055) / 1.055, 2.4)),
(float)(b <= 0.04045 ? (b / 12.92) : pow(((double)b + 0.055) / 1.055, 2.4)), a);
}
QColor ColorToQColor(uint32 color)
{
return QColor::fromRgbF((float)GetRValue(color) / 255.0f,
(float)GetGValue(color) / 255.0f,
(float)GetBValue(color) / 255.0f);
}
+21
View File
@@ -0,0 +1,21 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Utility classes used by Editor.
#pragma once
#include <Cry_Color.h>
class QColor;
QColor ColorLinearToGamma(ColorF col);
ColorF ColorGammaToLinear(const QColor& col);
QColor ColorToQColor(uint32 color);
+139
View File
@@ -0,0 +1,139 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ColumnGroupHeaderView.h"
// Qt
#include <QPainter>
// Editor
#include "Util/ColumnGroupProxyModel.h"
ColumnGroupHeaderView::ColumnGroupHeaderView(QWidget* parent)
: QHeaderView(Qt::Horizontal, parent)
, m_groupModel(nullptr)
, m_showGroups(false)
{
setSectionsMovable(true);
setStretchLastSection(true);
setSortIndicatorShown(false);
}
void ColumnGroupHeaderView::setModel(QAbstractItemModel* model)
{
QHeaderView::setModel(model);
m_groupModel = qobject_cast<ColumnGroupProxyModel*>(model);
if (m_groupModel)
{
connect(m_groupModel, SIGNAL(SortChanged()), this, SLOT(update()));
}
}
QSize ColumnGroupHeaderView::sizeHint() const
{
QSize s = QHeaderView::sizeHint();
if (m_showGroups)
{
s.setHeight(s.height() + GroupViewHeight());
}
return s;
}
bool ColumnGroupHeaderView::IsGroupsShown() const
{
return m_showGroups;
}
bool ColumnGroupHeaderView::event(QEvent* event)
{
switch (event->type())
{
case QEvent::Paint:
{
if (!m_showGroups || !m_groupModel)
{
return true;
}
QPainter painter(this);
painter.fillRect(rect(), QColor(145, 145, 145));
int xOffset = 10;
int yOffset = 10;
auto groups = m_groupModel->Groups();
m_groups.clear();
foreach(int column, groups)
{
const int width = sectionSize(column);
const QRect r(xOffset, yOffset, width == 0 ? defaultSectionSize() : width, QHeaderView::sizeHint().height());
xOffset += r.width() + 10;
yOffset += 10;
if (column != groups.last())
{
painter.setPen(Qt::black);
painter.drawLine(r.bottomRight() + QPoint(-3, 0), r.bottomRight() + QPoint(-3, 3));
painter.drawLine(r.bottomRight() + QPoint(-3, 3), r.bottomRight() + QPoint(10, 3));
}
m_groups.push_back({ r, column });
paintSection(&painter, r, column);
}
break;
}
case QEvent::MouseButtonRelease:
{
auto mouseEvent = static_cast<QMouseEvent*>(event);
if (m_showGroups && m_groupModel)
{
foreach(const Group &group, m_groups)
{
if (group.rect.contains(mouseEvent->pos()))
{
auto sortOrder = m_groupModel->SortOrder(group.col);
m_groupModel->sort(group.col, (sortOrder == Qt::AscendingOrder) ?
Qt::DescendingOrder : Qt::AscendingOrder);
}
}
}
break;
}
default:
return QHeaderView::event(event);
break;
}
return true;
}
void ColumnGroupHeaderView::ShowGroups(bool showGroups)
{
m_showGroups = showGroups;
Q_EMIT geometriesChanged();
}
void ColumnGroupHeaderView::updateGeometries()
{
setViewportMargins(0, m_showGroups ? GroupViewHeight() : 0, 0, 0);
QHeaderView::updateGeometries();
}
int ColumnGroupHeaderView::GroupViewHeight() const
{
if (!m_groupModel)
{
return 0;
}
int groupCount = m_groupModel->Groups().size();
return QHeaderView::sizeHint().height() + qMax(0, groupCount - 1) * 10 + 20;
}
#include <Util/moc_ColumnGroupHeaderView.cpp>
+55
View File
@@ -0,0 +1,55 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef COLUMNGROUPHEADERVIEW_H
#define COLUMNGROUPHEADERVIEW_H
#if !defined(Q_MOC_RUN)
#include <QHeaderView>
#include <QVector>
#endif
class ColumnGroupProxyModel;
class ColumnGroupHeaderView
: public QHeaderView
{
Q_OBJECT
public:
ColumnGroupHeaderView(QWidget* parent = 0);
void setModel(QAbstractItemModel* model) override;
QSize sizeHint() const override;
bool IsGroupsShown() const;
bool event(QEvent* event) override;
public slots:
void ShowGroups(bool showGroups);
protected slots:
void updateGeometries() override;
private:
int GroupViewHeight() const;
private:
struct Group
{
QRect rect;
int col;
};
ColumnGroupProxyModel* m_groupModel;
bool m_showGroups;
QVector<Group> m_groups;
};
#endif // COLUMNGROUPHEADERVIEW_H
@@ -0,0 +1,78 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ColumnGroupItemDelegate.h"
// Qt
#include <QHeaderView>
#include <QPainter>
#include <QTreeView>
ColumnGroupItemDelegate::ColumnGroupItemDelegate(QObject* parent)
: QStyledItemDelegate(parent)
{
}
QSize ColumnGroupItemDelegate::sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const
{
// group title indexes have no own width, their text is drawn over all columns
if (index.model()->hasChildren(index) && index.column() == 0)
{
return QSize(32, QStyledItemDelegate::sizeHint(option, index).height());
}
return QStyledItemDelegate::sizeHint(option, index);
}
void ColumnGroupItemDelegate::paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const
{
if (index.model()->hasChildren(index))
{
painter->setClipping(false);
painter->setPen(option.palette.text().color());
if (option.state & QStyle::State_Selected)
{
painter->setPen(option.palette.highlightedText().color());
}
QRect textRect = option.rect;
textRect.setRight(qobject_cast<QWidget*>(parent())->width());
if (option.state & QStyle::State_Selected && index.column() == 0)
{
painter->fillRect(textRect, option.palette.highlight());
}
// there's just one text - somewhere in the model row, show it!
QString content;
const int columnCount = index.model()->columnCount(index.parent());
for (int column = 0; column < columnCount; ++column)
{
content += index.sibling(index.row(), column).data().toString();
}
int alignment = index.data(Qt::TextAlignmentRole).toInt();
if (index.column() == 0)
{
painter->drawText(textRect, (alignment == 0 ? Qt::AlignLeft | Qt::AlignVCenter : alignment) | Qt::ElideRight, content);
}
if (!index.parent().isValid() && index.row() > 0)
{
QTreeView* tv = qobject_cast<QTreeView*>(option.styleObject);
if (tv)
{
// draw a line in the same color as the table header for separation between groups
QStyleOptionHeader header;
header.rect = QRect(QPoint(1, textRect.top()), textRect.topRight() - QPoint(1,0));
tv->style()->drawControl(QStyle::CE_HeaderSection, &header, painter, tv->header());
}
}
}
else
{
QStyledItemDelegate::paint(painter, option, index);
}
}
@@ -0,0 +1,23 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef COLUMNGROUPITEMDELEGATE_H
#define COLUMNGROUPITEMDELEGATE_H
#include <QStyledItemDelegate>
class ColumnGroupItemDelegate
: public QStyledItemDelegate
{
public:
ColumnGroupItemDelegate(QObject* parent = 0);
QSize sizeHint(const QStyleOptionViewItem& option, const QModelIndex& index) const override;
void paint(QPainter* painter, const QStyleOptionViewItem& option, const QModelIndex& index) const override;
};
#endif // COLUMNGROUPITEMDELEGATE_H
+108
View File
@@ -0,0 +1,108 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ColumnGroupProxyModel.h"
// Editor
#include "Util/ColumnSortProxyModel.h"
#include "Util/AbstractSortModel.h"
ColumnGroupProxyModel::ColumnGroupProxyModel(QObject* parent)
: AbstractGroupProxyModel(parent)
, m_sortModel(new ColumnSortProxyModel(this))
, m_freeSortColumn(-1)
{
AbstractGroupProxyModel::setSourceModel(m_sortModel);
connect(m_sortModel, &ColumnSortProxyModel::SortChanged, this, &ColumnGroupProxyModel::SortChanged);
}
void ColumnGroupProxyModel::sort(int column, Qt::SortOrder order)
{
if (m_freeSortColumn != -1)
{
m_sortModel->RemoveColumnWithoutSorting(m_freeSortColumn);
m_freeSortColumn = -1;
}
if (!m_groups.contains(column))
{
m_freeSortColumn = column;
}
m_sortModel->sort(column, order);
}
void ColumnGroupProxyModel::setSourceModel(QAbstractItemModel* sourceModel)
{
Q_ASSERT(qobject_cast<AbstractSortModel*>(sourceModel));
m_sortModel->setSourceModel(sourceModel);
RebuildTree();
}
void ColumnGroupProxyModel::AddGroup(int column)
{
if (!m_groups.contains(column))
{
m_groups.push_back(column);
sort(column);
Q_EMIT GroupsChanged();
}
}
void ColumnGroupProxyModel::RemoveGroup(int column)
{
if (m_groups.contains(column))
{
m_groups.remove(m_groups.indexOf(column));
m_sortModel->RemoveColumn(column);
Q_EMIT GroupsChanged();
}
}
void ColumnGroupProxyModel::SetGroups(const QVector<int>& columns)
{
m_groups.clear();
foreach(int col, columns)
{
m_groups.push_back(col);
m_sortModel->AddColumnWithoutSorting(col);
}
m_sortModel->SortModel();
Q_EMIT GroupsChanged();
}
void ColumnGroupProxyModel::ClearGroups()
{
m_groups.clear();
m_sortModel->ClearColumns();
Q_EMIT GroupsChanged();
}
QVector<int> ColumnGroupProxyModel::Groups() const
{
return m_groups;
}
bool ColumnGroupProxyModel::IsColumnSorted(int col) const
{
return m_sortModel->IsColumnSorted(col);
}
Qt::SortOrder ColumnGroupProxyModel::SortOrder(int col) const
{
return m_sortModel->SortOrder(col);
}
QStringList ColumnGroupProxyModel::GroupForSourceIndex(const QModelIndex& sourceIndex) const
{
QStringList group;
foreach(int column, m_groups)
group.push_back(QString::fromLatin1("%1: %2").arg(headerData(column, Qt::Horizontal).toString(), sourceIndex.sibling(sourceIndex.row(), column).data().toString()));
return group;
}
#include <Util/moc_ColumnGroupProxyModel.cpp>
+52
View File
@@ -0,0 +1,52 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef COLUMNGROUPPROXYMODEL_H
#define COLUMNGROUPPROXYMODEL_H
#if !defined(Q_MOC_RUN)
#include "AbstractGroupProxyModel.h"
#include <QVector>
#endif
class ColumnSortProxyModel;
class ColumnGroupProxyModel
: public AbstractGroupProxyModel
{
Q_OBJECT
public:
ColumnGroupProxyModel(QObject* parent = nullptr);
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
void setSourceModel(QAbstractItemModel* sourceModel) override;
void AddGroup(int column);
void RemoveGroup(int column);
void SetGroups(const QVector<int>& columns);
void ClearGroups();
QVector<int> Groups() const;
bool IsColumnSorted(int col) const;
Qt::SortOrder SortOrder(int col) const;
protected:
QStringList GroupForSourceIndex(const QModelIndex& sourceIndex) const override;
signals:
void GroupsChanged();
void SortChanged();
private:
ColumnSortProxyModel* m_sortModel;
QVector<int> m_groups;
int m_freeSortColumn;
};
#endif // COLUMNGROUPPROXYMODEL_H
+163
View File
@@ -0,0 +1,163 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ColumnGroupTreeView.h"
// Editor
#include "Util/ColumnGroupHeaderView.h"
#include "Util/ColumnGroupProxyModel.h"
#include "Util/ColumnGroupItemDelegate.h"
ColumnGroupTreeView::ColumnGroupTreeView(QWidget* parent)
: QTreeView(parent)
, m_header(new ColumnGroupHeaderView)
, m_groupModel(new ColumnGroupProxyModel(this))
{
setSortingEnabled(true);
setHeader(m_header);
setItemDelegate(new ColumnGroupItemDelegate(this));
setSelectionMode(QAbstractItemView::ExtendedSelection);
setSelectionBehavior(QAbstractItemView::SelectRows);
QTreeView::setModel(m_groupModel);
connect(m_groupModel, &QAbstractItemModel::modelAboutToBeReset, this, &ColumnGroupTreeView::SaveOpenState);
connect(m_groupModel, &QAbstractItemModel::modelReset, this, &ColumnGroupTreeView::RestoreOpenState);
connect(m_groupModel, SIGNAL(GroupUpdated()), this, SLOT(SpanGroups()));
connect(m_groupModel, &ColumnGroupProxyModel::GroupsChanged, this, &ColumnGroupTreeView::expandAll);
}
void ColumnGroupTreeView::setModel(QAbstractItemModel* model)
{
m_groupModel->setSourceModel(model);
if (model)
{
connect(model, &QAbstractItemModel::modelReset, this, &QTreeView::expandAll, Qt::QueuedConnection);
}
}
bool ColumnGroupTreeView::IsGroupsShown() const
{
return m_header->IsGroupsShown();
}
void ColumnGroupTreeView::ShowGroups(bool showGroups)
{
m_header->ShowGroups(showGroups);
}
QSet<QString> GetOpenNodes(QTreeView* tree, const QModelIndex& parent)
{
int rows = tree->model()->rowCount(parent);
QSet<QString> results;
for (int row = 0; row < rows; ++row)
{
auto index = tree->model()->index(row, 0, parent);
if (tree->isExpanded(index))
{
results.insert(index.data().toString());
}
results.unite(GetOpenNodes(tree, index));
}
return results;
}
void ColumnGroupTreeView::SaveOpenState()
{
m_openNodes = GetOpenNodes(this, QModelIndex());
}
void RestoreOpenNodes(QTreeView* tree, const QSet<QString> openNodes, const QModelIndex& parent)
{
int rows = tree->model()->rowCount(parent);
QSet<QString> results;
for (int row = 0; row < rows; ++row)
{
auto index = tree->model()->index(row, 0, parent);
auto text = index.data().toString();
if (openNodes.contains(text))
{
tree->expand(index);
}
RestoreOpenNodes(tree, openNodes, index);
}
}
void ColumnGroupTreeView::RestoreOpenState()
{
RestoreOpenNodes(this, m_openNodes, QModelIndex());
}
void ColumnGroupTreeView::Sort(int column, Qt::SortOrder order)
{
m_groupModel->sort(column, order);
m_header->setSortIndicator(column, order);
}
void ColumnGroupTreeView::ToggleSortOrder(int column)
{
auto sortOrder = m_groupModel->SortOrder(column) == Qt::AscendingOrder ? Qt::DescendingOrder : Qt::AscendingOrder;
m_groupModel->sort(column, sortOrder);
}
void ColumnGroupTreeView::AddGroup(int column)
{
m_groupModel->AddGroup(column);
}
void ColumnGroupTreeView::RemoveGroup(int column)
{
m_groupModel->RemoveGroup(column);
}
void ColumnGroupTreeView::SetGroups(const QVector<int>& columns)
{
m_groupModel->SetGroups(columns);
}
void ColumnGroupTreeView::ClearGroups()
{
m_groupModel->ClearGroups();
}
QVector<int> ColumnGroupTreeView::Groups() const
{
return m_groupModel->Groups();
}
void ColumnGroupTreeView::SpanGroups(const QModelIndex& index)
{
int childrenCount = m_groupModel->rowCount(index);
for (int row = 0; row < childrenCount; ++row)
{
auto childIndex = m_groupModel->index(row, 0, index);
bool hasChildren = m_groupModel->hasChildren(childIndex);
if (hasChildren)
{
setFirstColumnSpanned(row, index, true);
SpanGroups(childIndex);
}
}
}
QModelIndex ColumnGroupTreeView::mapToSource(const QModelIndex& proxyIndex) const
{
auto sortProxy = qobject_cast<QAbstractProxyModel*>(m_groupModel->sourceModel());
return sortProxy->mapToSource(m_groupModel->mapToSource(proxyIndex));
}
QModelIndex ColumnGroupTreeView::mapFromSource(const QModelIndex& sourceModel) const
{
auto sortProxy = qobject_cast<QAbstractProxyModel*>(m_groupModel->sourceModel());
return m_groupModel->mapFromSource(sortProxy->mapFromSource(sourceModel));
}
#include <Util/moc_ColumnGroupTreeView.cpp>
+76
View File
@@ -0,0 +1,76 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef COLUMNGROUPTREEVIEW_H
#define COLUMNGROUPTREEVIEW_H
#if !defined(Q_MOC_RUN)
#include <QTreeView>
#include <QPainter>
#endif
class ColumnGroupProxyModel;
class ColumnGroupHeaderView;
class ColumnGroupTreeView
: public QTreeView
{
Q_OBJECT
public:
ColumnGroupTreeView(QWidget* parent = 0);
void setModel(QAbstractItemModel* model) override;
bool IsGroupsShown() const;
QModelIndex mapToSource(const QModelIndex& proxyIndex) const;
QModelIndex mapFromSource(const QModelIndex& sourceModel) const;
public slots:
void ShowGroups(bool showGroups);
void Sort(int column, Qt::SortOrder order = Qt::AscendingOrder);
void ToggleSortOrder(int column);
void AddGroup(int column);
void RemoveGroup(int column);
void SetGroups(const QVector<int>& columns);
void ClearGroups();
QVector<int> Groups() const;
protected:
void paintEvent(QPaintEvent* event)
{
if (model() && model()->rowCount() > 0)
{
QTreeView::paintEvent(event);
}
else
{
const QMargins margins(2, 2, 2, 2);
QPainter painter(viewport());
QString text(tr("There are no items to show."));
QRect textRect = painter.fontMetrics().boundingRect(text).marginsAdded(margins);
textRect.moveCenter(viewport()->rect().center());
textRect.moveTop(viewport()->rect().top());
painter.drawText(textRect, Qt::AlignCenter, text);
}
}
private slots:
void SaveOpenState();
void RestoreOpenState();
void SpanGroups(const QModelIndex& index = QModelIndex());
private:
ColumnGroupHeaderView* m_header;
ColumnGroupProxyModel* m_groupModel;
QSet<QString> m_openNodes;
bool m_showGroups;
};
#endif // COLUMNGROUPTREEVIEW_H
+283
View File
@@ -0,0 +1,283 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ColumnSortProxyModel.h"
// Editor
#include "Util/AbstractSortModel.h"
class ColumnSortProxyModelLessThan
{
public:
inline ColumnSortProxyModelLessThan(int column, const AbstractSortModel* source)
: sort_column(column)
, source_model(source) {}
inline bool operator()(int r1, int r2) const
{
QModelIndex i1 = source_model->index(r1, sort_column);
QModelIndex i2 = source_model->index(r2, sort_column);
return source_model->LessThan(i1, i2);
}
private:
int sort_column;
const AbstractSortModel* source_model;
};
class ColumnSortProxyModelGreaterThan
{
public:
inline ColumnSortProxyModelGreaterThan(int column, const AbstractSortModel* source)
: sort_column(column)
, source_model(source) {}
inline bool operator()(int r1, int r2) const
{
QModelIndex i1 = source_model->index(r1, sort_column);
QModelIndex i2 = source_model->index(r2, sort_column);
return source_model->LessThan(i2, i1);
}
private:
int sort_column;
const AbstractSortModel* source_model;
};
ColumnSortProxyModel::ColumnSortProxyModel(QObject* parent)
: QAbstractProxyModel(parent)
{
}
ColumnSortProxyModel::~ColumnSortProxyModel()
{
}
QVariant ColumnSortProxyModel::data(const QModelIndex& index, int role) const
{
Q_ASSERT(index.isValid() && index.model() == this);
return sourceModel()->data(mapToSource(index), role);
}
QVariant ColumnSortProxyModel::headerData(int section, Qt::Orientation orientation, int role) const
{
return sourceModel()->headerData(section, orientation, role);
}
int ColumnSortProxyModel::rowCount(const QModelIndex& index) const
{
if (!sourceModel() || index.isValid())
{
return 0;
}
return sourceModel()->rowCount(index);
}
int ColumnSortProxyModel::columnCount(const QModelIndex& index) const
{
if (!sourceModel() || index.isValid())
{
return 0;
}
return sourceModel()->columnCount(index);
}
QModelIndex ColumnSortProxyModel::index(int row, int column, const QModelIndex& parent) const
{
Q_UNUSED(parent);
Q_ASSERT(!parent.isValid());
return createIndex(row, column);
}
QModelIndex ColumnSortProxyModel::parent(const QModelIndex& index) const
{
Q_UNUSED(index);
return QModelIndex();
}
QModelIndex ColumnSortProxyModel::mapFromSource(const QModelIndex& sourceIndex) const
{
Q_ASSERT(!sourceIndex.isValid() || sourceIndex.model() == sourceModel());
if (!sourceIndex.isValid())
{
return QModelIndex();
}
int row = m_mappingToSource.indexOf(sourceIndex.row());
return createIndex(row, sourceIndex.column());
}
QModelIndex ColumnSortProxyModel::mapToSource(const QModelIndex& proxyIndex) const
{
Q_ASSERT(!proxyIndex.isValid() || proxyIndex.model() == this);
if (!proxyIndex.isValid())
{
return QModelIndex();
}
int row = m_mappingToSource.at(proxyIndex.row());
return sourceModel()->index(row, proxyIndex.column());
}
void ColumnSortProxyModel::setSourceModel(QAbstractItemModel* sourceModel)
{
Q_ASSERT(qobject_cast<AbstractSortModel*>(sourceModel));
QAbstractProxyModel::setSourceModel(sourceModel);
connect(sourceModel, &QAbstractItemModel::rowsInserted, this, &ColumnSortProxyModel::SortModel);
connect(sourceModel, &QAbstractItemModel::rowsRemoved, this, &ColumnSortProxyModel::SortModel);
connect(sourceModel, &QAbstractItemModel::modelAboutToBeReset, this, &ColumnSortProxyModel::beginResetModel);
connect(sourceModel, &QAbstractItemModel::modelReset, this, [=]()
{
{ QSignalBlocker sb(this);
SortModel();
} endResetModel();
});
connect(sourceModel, &QAbstractItemModel::layoutChanged, this, &ColumnSortProxyModel::SortModel);
connect(sourceModel, &QAbstractItemModel::dataChanged, this, &ColumnSortProxyModel::SourceDataChanged);
SortModel();
}
void ColumnSortProxyModel::sort(int column, Qt::SortOrder order)
{
int id = ColumnContains(column);
if (id == -1)
{
AddColumn(column, order);
}
else
{
if (m_columns.at(id).sortOrder != order)
{
m_columns[id].sortOrder = order;
SortModel();
}
}
}
void ColumnSortProxyModel::AddColumn(int column, Qt::SortOrder order)
{
if (ColumnContains(column) == -1)
{
m_columns.push_front({ column, order });
SortModel();
}
}
void ColumnSortProxyModel::AddColumnWithoutSorting(int column, Qt::SortOrder order)
{
if (ColumnContains(column) == -1)
{
m_columns.push_front({ column, order });
}
}
void ColumnSortProxyModel::RemoveColumn(int column)
{
int id = ColumnContains(column);
if (id != -1)
{
m_columns.remove(id);
SortModel();
}
}
void ColumnSortProxyModel::RemoveColumnWithoutSorting(int column)
{
int id = ColumnContains(column);
if (id != -1)
{
m_columns.remove(id);
}
}
void ColumnSortProxyModel::SetColumns(const QVector<int>& columns)
{
m_columns.clear();
foreach(int col, columns)
{
m_columns.push_back({ col, Qt::AscendingOrder });
}
SortModel();
}
void ColumnSortProxyModel::ClearColumns()
{
SortModel();
}
bool ColumnSortProxyModel::IsColumnSorted(int col) const
{
int id = ColumnContains(col);
return (id != -1);
}
Qt::SortOrder ColumnSortProxyModel::SortOrder(int col) const
{
int id = ColumnContains(col);
if (id != -1)
{
return m_columns.at(id).sortOrder;
}
return Qt::AscendingOrder;
}
void ColumnSortProxyModel::SortModel()
{
emit layoutAboutToBeChanged();
int size = sourceModel() ? sourceModel()->rowCount() : 0;
m_mappingToSource.resize(size);
for (int i = 0; i < size; ++i)
{
m_mappingToSource[i] = i;
}
foreach(Column col, m_columns)
{
if (col.sortOrder == Qt::AscendingOrder)
{
ColumnSortProxyModelLessThan lt(col.column, qobject_cast<AbstractSortModel*>(sourceModel()));
std::stable_sort(m_mappingToSource.begin(), m_mappingToSource.end(), lt);
}
else
{
ColumnSortProxyModelGreaterThan gt(col.column, qobject_cast<AbstractSortModel*>(sourceModel()));
std::stable_sort(m_mappingToSource.begin(), m_mappingToSource.end(), gt);
}
}
emit layoutChanged();
emit SortChanged();
}
void ColumnSortProxyModel::SourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight)
{
for (int col = topLeft.column(); col <= bottomRight.column(); ++col)
{
if (ColumnContains(col) != -1)
{
SortModel();
return;
}
}
}
int ColumnSortProxyModel::ColumnContains(int col) const
{
for (int i = 0; i < m_columns.count(); ++i)
{
if (m_columns.at(i).column == col)
{
return i;
}
}
return -1;
}
#include <Util/moc_ColumnSortProxyModel.cpp>
+83
View File
@@ -0,0 +1,83 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef COLUMNSORTPROXYMODEL_H
#define COLUMNSORTPROXYMODEL_H
#if !defined(Q_MOC_RUN)
#include <QAbstractProxyModel>
#include <QVector>
#endif
class ColumnGroupProxyModel;
/*!
\brief Proxy model used to sort on multiple columns
It's using a stable sort to sort on multiple columns.
Every time a value is changed (on one of the sorted columns), it will resort everything.
THis model does not work with tree models!
*/
class ColumnSortProxyModel
: public QAbstractProxyModel
{
Q_OBJECT
public:
ColumnSortProxyModel(QObject* parent = nullptr);
~ColumnSortProxyModel();
QVariant data(const QModelIndex& index, int role = Qt::DisplayRole) const override;
QVariant headerData(int section, Qt::Orientation orientation, int role = Qt::DisplayRole) const override;
int rowCount(const QModelIndex& index = QModelIndex()) const override;
int columnCount(const QModelIndex& index = QModelIndex()) const override;
QModelIndex index(int row, int column, const QModelIndex& parent = QModelIndex()) const override;
QModelIndex parent(const QModelIndex& index) const override;
QModelIndex mapFromSource(const QModelIndex& sourceIndex) const override;
QModelIndex mapToSource(const QModelIndex& proxyIndex) const override;
void setSourceModel(QAbstractItemModel* sourceModel) override;
void sort(int column, Qt::SortOrder order = Qt::AscendingOrder) override;
void AddColumn(int column, Qt::SortOrder order = Qt::AscendingOrder);
void RemoveColumn(int column);
void SetColumns(const QVector<int>& columns);
void ClearColumns();
bool IsColumnSorted(int col) const;
Qt::SortOrder SortOrder(int col) const;
signals:
void SortChanged();
private:
friend class ColumnGroupProxyModel;
void AddColumnWithoutSorting(int column, Qt::SortOrder order = Qt::AscendingOrder);
void RemoveColumnWithoutSorting(int column);
private slots:
void SortModel();
void SourceDataChanged(const QModelIndex& topLeft, const QModelIndex& bottomRight);
private:
struct Column
{
int column;
Qt::SortOrder sortOrder;
};
int ColumnContains(int col) const;
QVector<Column> m_columns;
QVector<int> m_mappingToSource;
};
#endif //COLUMNSORTPROXYMODEL_H
+455
View File
@@ -0,0 +1,455 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
/* BEGIN CONTENTS OF README.TXT -------------------------------------
The ConvexDecomposition library was written by John W. Ratcliff mailto:jratcliffscarab@gmail.com
What is Convex Decomposition?
Convex Decomposition is when you take an arbitrarily complex triangle mesh and sub-divide it into
a collection of discrete compound pieces (each represented as a convex hull) to approximate
the original shape of the objet.
This is required since few physics engines can treat aribtrary triangle mesh objects as dynamic
objects. Even those engines which can handle this use case incurr a huge performance and memory
penalty to do so.
By breaking a complex triangle mesh up into a discrete number of convex components you can greatly
improve performance for dynamic simulations.
--------------------------------------------------------------------------------
This code is released under the MIT license.
The code is functional but could use the following improvements:
(1) The convex hull generator, originally written by Stan Melax, could use some major code cleanup.
(2) The code to remove T-junctions appears to have a bug in it. This code was working fine before,
but I haven't had time to debug why it stopped working.
(3) Island generation once the mesh has been split is currently disabled due to the fact that the
Remove Tjunctions functionality has a bug in it.
(4) The code to perform a raycast against a triangle mesh does not currently use any acceleration
data structures.
(5) When a split is performed, the surface that got split is not 'capped'. This causes a problem
if you use a high recursion depth on your convex decomposition. It will cause the object to
be modelled as if it had a hollow interior. A lot of work was done to solve this problem, but
it hasn't been integrated into this code drop yet.
*/// ---------- END CONTENTS OF README.TXT ----------------------------
// a set of routines that let you do common 3d math
// operations without any vector, matrix, or quaternion
// classes or templates.
//
// a vector (or point) is a 'NxF32 *' to 3 floating point numbers.
// a matrix is a 'NxF32 *' to an array of 16 floating point numbers representing a 4x4 transformation matrix compatible with D3D or OGL
// a quaternion is a 'NxF32 *' to 4 floats representing a quaternion x,y,z,w
//
//
/*!
**
** Copyright (c) 2009 by John W. Ratcliff mailto:jratcliffscarab@gmail.com
**
** Portions of this source has been released with the PhysXViewer application, as well as
** Rocket, CreateDynamics, ODF, and as a number of sample code snippets.
**
** If you find this code useful or you are feeling particularily generous I would
** ask that you please go to http://www.amillionpixels.us and make a donation
** to Troy DeMolay.
**
** DeMolay is a youth group for young men between the ages of 12 and 21.
** It teaches strong moral principles, as well as leadership skills and
** public speaking. The donations page uses the 'pay for pixels' paradigm
** where, in this case, a pixel is only a single penny. Donations can be
** made for as small as $4 or as high as a $100 block. Each person who donates
** will get a link to their own site as well as acknowledgement on the
** donations blog located here http://www.amillionpixels.blogspot.com/
**
** If you wish to contact me you can use the following methods:
**
** Skype ID: jratcliff63367
** Yahoo: jratcliff63367
** AOL: jratcliff1961
** email: jratcliffscarab@gmail.com
**
**
** The MIT license:
**
** Permission is hereby granted, free of charge, to any person obtaining a copy
** of this software and associated documentation files (the "Software"), to deal
** in the Software without restriction, including without limitation the rights
** to use, copy, modify, merge, publish, distribute, sublicense, and/or sell
** copies of the Software, and to permit persons to whom the Software is furnished
** to do so, subject to the following conditions:
**
** The above copyright notice and this permission notice shall be included in all
** copies or substantial portions of the Software.
** THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
** IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
** FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
** AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
** WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN
** CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
*/
#pragma warning(disable:4996)
class TVec
{
public:
TVec(NxF64 _x, NxF64 _y, NxF64 _z) { x = _x; y = _y; z = _z; };
TVec(void) { };
NxF64 x;
NxF64 y;
NxF64 z;
};
class CTriangulator
{
public:
/// Default constructor
CTriangulator();
/// Default destructor
virtual ~CTriangulator();
/// Returns the given point in the triangulator array
inline TVec get(const TU32 id) { return mPoints[id]; }
virtual void reset(void)
{
mInputPoints.clear();
mPoints.clear();
mIndices.clear();
}
virtual void addPoint(NxF64 x, NxF64 y, NxF64 z)
{
TVec v(x, y, z);
// update bounding box...
if (mInputPoints.empty())
{
mMin = v;
mMax = v;
}
else
{
if (x < mMin.x)
{
mMin.x = x;
}
if (y < mMin.y)
{
mMin.y = y;
}
if (z < mMin.z)
{
mMin.z = z;
}
if (x > mMax.x)
{
mMax.x = x;
}
if (y > mMax.y)
{
mMax.y = y;
}
if (z > mMax.z)
{
mMax.z = z;
}
}
mInputPoints.push_back(v);
}
// Triangulation happens in 2d. We could inverse transform the polygon around the normal direction, or we just use the two most signficant axes
// Here we find the two longest axes and use them to triangulate. Inverse transforming them would introduce more doubleing point error and isn't worth it.
virtual NxU32* triangulate(NxU32& tcount, NxF64 epsilon)
{
NxU32* ret = 0;
tcount = 0;
mEpsilon = epsilon;
if (!mInputPoints.empty())
{
mPoints.clear();
NxF64 dx = mMax.x - mMin.x; // locate the first, second and third longest edges and store them in i1, i2, i3
NxF64 dy = mMax.y - mMin.y;
NxF64 dz = mMax.z - mMin.z;
NxU32 i1, i2, i3;
if (dx > dy && dx > dz)
{
i1 = 0;
if (dy > dz)
{
i2 = 1;
i3 = 2;
}
else
{
i2 = 2;
i3 = 1;
}
}
else if (dy > dx && dy > dz)
{
i1 = 1;
if (dx > dz)
{
i2 = 0;
i3 = 2;
}
else
{
i2 = 2;
i3 = 0;
}
}
else
{
i1 = 2;
if (dx > dy)
{
i2 = 0;
i3 = 1;
}
else
{
i2 = 1;
i3 = 0;
}
}
NxU32 pcount = (NxU32)mInputPoints.size();
const NxF64* points = &mInputPoints[0].x;
for (NxU32 i = 0; i < pcount; i++)
{
TVec v(points[i1], points[i2], points[i3]);
mPoints.push_back(v);
points += 3;
}
mIndices.clear();
triangulate(mIndices);
tcount = (NxU32)mIndices.size() / 3;
if (tcount)
{
ret = &mIndices[0];
}
}
return ret;
}
virtual const NxF64* getPoint(NxU32 index)
{
return &mInputPoints[index].x;
}
private:
NxF64 mEpsilon;
TVec mMin;
TVec mMax;
TVecVector mInputPoints;
TVecVector mPoints;
TU32Vector mIndices;
/// Tests if a point is inside the given triangle
bool _insideTriangle(const TVec& A, const TVec& B, const TVec& C, const TVec& P);
/// Returns the area of the contour
NxF64 _area();
bool _snip(NxI32 u, NxI32 v, NxI32 w, NxI32 n, NxI32* V);
/// Processes the triangulation
void _process(TU32Vector& indices);
/// Triangulates the contour
void triangulate(TU32Vector& indices);
};
/// Default constructor
CTriangulator::CTriangulator(void)
{
}
/// Default destructor
CTriangulator::~CTriangulator()
{
}
/// Triangulates the contour
void CTriangulator::triangulate(TU32Vector& indices)
{
_process(indices);
}
/// Processes the triangulation
void CTriangulator::_process(TU32Vector& indices)
{
const NxI32 n = (const NxI32)mPoints.size();
if (n < 3)
{
return;
}
NxI32* V = (NxI32*)MEMALLOC_MALLOC(sizeof(NxI32) * n);
bool flipped = false;
if (0.0f < _area())
{
for (NxI32 v = 0; v < n; v++)
{
V[v] = v;
}
}
else
{
flipped = true;
for (NxI32 v = 0; v < n; v++)
{
V[v] = (n - 1) - v;
}
}
NxI32 nv = n;
NxI32 count = 2 * nv;
for (NxI32 m = 0, v = nv - 1; nv > 2; )
{
if (0 >= (count--))
{
return;
}
NxI32 u = v;
if (nv <= u)
{
u = 0;
}
v = u + 1;
if (nv <= v)
{
v = 0;
}
NxI32 w = v + 1;
if (nv <= w)
{
w = 0;
}
if (_snip(u, v, w, nv, V))
{
NxI32 a, b, c, s, t;
a = V[u];
b = V[v];
c = V[w];
if (flipped)
{
indices.push_back(a);
indices.push_back(b);
indices.push_back(c);
}
else
{
indices.push_back(c);
indices.push_back(b);
indices.push_back(a);
}
m++;
for (s = v, t = v + 1; t < nv; s++, t++)
{
V[s] = V[t];
}
nv--;
count = 2 * nv;
}
}
MEMALLOC_FREE(V);
}
/// Returns the area of the contour
NxF64 CTriangulator::_area()
{
NxI32 n = (NxU32)mPoints.size();
NxF64 A = 0.0f;
for (NxI32 p = n - 1, q = 0; q < n; p = q++)
{
const TVec& pval = mPoints[p];
const TVec& qval = mPoints[q];
A += pval.x * qval.y - qval.x * pval.y;
}
A *= 0.5f;
return A;
}
bool CTriangulator::_snip(NxI32 u, NxI32 v, NxI32 w, NxI32 n, NxI32* V)
{
NxI32 p;
const TVec& A = mPoints[V[u]];
const TVec& B = mPoints[V[v]];
const TVec& C = mPoints[V[w]];
if (mEpsilon > (((B.x - A.x) * (C.y - A.y)) - ((B.y - A.y) * (C.x - A.x))))
{
return false;
}
for (p = 0; p < n; p++)
{
if ((p == u) || (p == v) || (p == w))
{
continue;
}
const TVec& P = mPoints[V[p]];
if (_insideTriangle(A, B, C, P))
{
return false;
}
}
return true;
}
/// Tests if a point is inside the given triangle
bool CTriangulator::_insideTriangle(const TVec& A, const TVec& B, const TVec& C, const TVec& P)
{
NxF64 ax, ay, bx, by, cx, cy, apx, apy, bpx, bpy, cpx, cpy;
NxF64 cCROSSap, bCROSScp, aCROSSbp;
ax = C.x - B.x;
ay = C.y - B.y;
bx = A.x - C.x;
by = A.y - C.y;
cx = B.x - A.x;
cy = B.y - A.y;
apx = P.x - A.x;
apy = P.y - A.y;
bpx = P.x - B.x;
bpy = P.y - B.y;
cpx = P.x - C.x;
cpy = P.y - C.y;
aCROSSbp = ax * bpy - ay * bpx;
cCROSSap = cx * apy - cy * apx;
bCROSScp = bx * cpy - by * cpx;
return ((aCROSSbp >= 0.0f) && (bCROSScp >= 0.0f) && (cCROSSap >= 0.0f));
}
+73
View File
@@ -0,0 +1,73 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_CRYMEMFILE_H
#define CRYINCLUDE_EDITOR_UTIL_CRYMEMFILE_H
#pragma once
#include <QBuffer>
// derived class to get correct memory allocation/deallocation with custom memory manager - and to avoid memory leaks from calling Detach()
class CCryMemFile
: public QBuffer
{
public: // ---------------------------------------------------------------
CCryMemFile()
: QBuffer(&m_lpBuffer)
{
open(QIODevice::WriteOnly);
}
CCryMemFile(char* lpBuffer, int nBufferSize)
: QBuffer(&m_lpBuffer), m_lpBuffer(lpBuffer, nBufferSize)
{
open(QIODevice::WriteOnly);
}
virtual ~CCryMemFile()
{
close(); // call Close() to make sure the Free() is using my v-table
}
qulonglong GetPosition() const
{
return QBuffer::pos();
}
qulonglong GetLength() const
{
return QBuffer::size();
}
void Write(const void* lpBuf, unsigned int nCount)
{
QBuffer::write(reinterpret_cast<const char*>(lpBuf), nCount);
}
// only for temporary use
void* GetMemPtr() const
{
return const_cast<char*>(m_lpBuffer.data());
}
void Close()
{
QBuffer::close();
}
char* Detach()
{
assert(0); // dangerous - most likely we cause memory leak - better use GetMemPtr
return 0;
}
private:
QByteArray m_lpBuffer;
};
#endif // CRYINCLUDE_EDITOR_UTIL_CRYMEMFILE_H
+154
View File
@@ -0,0 +1,154 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "DynamicArray2D.h"
// Editor
#include "Util/fastlib.h"
//////////////////////////////////////////////////////////////////////
// Construction / destruction
//////////////////////////////////////////////////////////////////////
CDynamicArray2D::CDynamicArray2D(unsigned int iDimension1, unsigned int iDimension2)
{
////////////////////////////////////////////////////////////////////////
// Declare a 2D array on the free store
////////////////////////////////////////////////////////////////////////
unsigned int i;
// Save the position of the array dimensions
m_Dimension1 = iDimension1;
m_Dimension2 = iDimension2;
// First dimension
m_Array = new float* [m_Dimension1];
assert(m_Array);
// Second dimension
for (i = 0; i < m_Dimension1; ++i)
{
m_Array[i] = new float[m_Dimension2];
// Init all fields with 0
memset(&m_Array[i][0], 0, m_Dimension2 * sizeof(float));
}
}
CDynamicArray2D::~CDynamicArray2D()
{
////////////////////////////////////////////////////////////////////////
// Remove the 2D array and all its sub arrays from the free store
////////////////////////////////////////////////////////////////////////
unsigned int i;
for (i = 0; i < m_Dimension1; ++i)
{
delete [] m_Array[i];
}
delete [] m_Array;
m_Array = 0;
}
void CDynamicArray2D::GetMemoryUsage(ICrySizer* pSizer)
{
pSizer->Add((char*)this, m_Dimension1 * m_Dimension2 * sizeof(float) + sizeof(*this));
}
void CDynamicArray2D::ScaleImage(CDynamicArray2D* pDestination)
{
////////////////////////////////////////////////////////////////////////
// Scale an image stored (in an array class) to a new size
////////////////////////////////////////////////////////////////////////
unsigned int i, j, iOldWidth;
int iXSrcFl, iXSrcCe, iYSrcFl, iYSrcCe;
float fXSrc, fYSrc;
float fHeight[4];
float fHeightWeight[4];
float fHeightBottom;
float fHeightTop;
assert(pDestination);
assert(pDestination->m_Dimension1 > 1);
// Width has to be zero based, not a count
iOldWidth = m_Dimension1 - 1;
// Loop trough each field of the new image and interpolate the value
// from the source heightmap
for (i = 0; i < pDestination->m_Dimension1; i++)
{
// Calculate the average source array position
fXSrc = i / (float) pDestination->m_Dimension1 * iOldWidth;
assert(fXSrc >= 0.0f && fXSrc <= iOldWidth);
// Precalculate floor and ceiling values. Use fast asm integer floor and
// fast asm float / integer conversion
iXSrcFl = ifloor(fXSrc);
iXSrcCe = FloatToIntRet((float) ceil(fXSrc));
// Distribution between left and right height values
fHeightWeight[0] = (float) iXSrcCe - fXSrc;
fHeightWeight[1] = fXSrc - (float) iXSrcFl;
// Avoid error when floor() and ceil() return the same value
if (fHeightWeight[0] == 0.0f && fHeightWeight[1] == 0.0f)
{
fHeightWeight[0] = 0.5f;
fHeightWeight[1] = 0.5f;
}
for (j = 0; j < pDestination->m_Dimension1; j++)
{
// Calculate the average source array position
fYSrc = j / (float) pDestination->m_Dimension1 * iOldWidth;
assert(fYSrc >= 0.0f && fYSrc <= iOldWidth);
// Precalculate floor and ceiling values. Use fast asm integer floor and
// fast asm float / integer conversion
iYSrcFl = ifloor(fYSrc);
iYSrcCe = FloatToIntRet((float) ceil(fYSrc));
// Get the four nearest height values
fHeight[0] = m_Array[iXSrcFl][iYSrcFl];
fHeight[1] = m_Array[iXSrcCe][iYSrcFl];
fHeight[2] = m_Array[iXSrcFl][iYSrcCe];
fHeight[3] = m_Array[iXSrcCe][iYSrcCe];
// Calculate how much weight each height value has
// Distribution between top and bottom height values
fHeightWeight[2] = (float) iYSrcCe - fYSrc;
fHeightWeight[3] = fYSrc - (float) iYSrcFl;
// Avoid error when floor() and ceil() return the same value
if (fHeightWeight[2] == 0.0f && fHeightWeight[3] == 0.0f)
{
fHeightWeight[2] = 0.5f;
fHeightWeight[3] = 0.5f;
}
// Interpolate between the four nearest height values
// Get the height for the given X position trough interpolation between
// the left and the right height
fHeightBottom = (fHeight[0] * fHeightWeight[0] + fHeight[1] * fHeightWeight[1]);
fHeightTop = (fHeight[2] * fHeightWeight[0] + fHeight[3] * fHeightWeight[1]);
// Set the new value in the destination heightmap
pDestination->m_Array[i][j] = fHeightBottom * fHeightWeight[2] + fHeightTop * fHeightWeight[3];
}
}
}
+38
View File
@@ -0,0 +1,38 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Interface of the class CDynamicArray.
#ifndef CRYINCLUDE_EDITOR_UTIL_DYNAMICARRAY2D_H
#define CRYINCLUDE_EDITOR_UTIL_DYNAMICARRAY2D_H
#pragma once
class CDynamicArray2D
{
public:
// constructor
CDynamicArray2D(unsigned int iDimension1, unsigned int iDimension2);
// destructor
virtual ~CDynamicArray2D();
//
void ScaleImage(CDynamicArray2D* pDestination);
//
void GetMemoryUsage(ICrySizer* pSizer);
float** m_Array; //
private:
unsigned int m_Dimension1; //
unsigned int m_Dimension2; //
};
#endif // CRYINCLUDE_EDITOR_UTIL_DYNAMICARRAY2D_H
@@ -0,0 +1,38 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "EditorAutoLevelLoadTest.h"
CEditorAutoLevelLoadTest& CEditorAutoLevelLoadTest::Instance()
{
static CEditorAutoLevelLoadTest levelLoadTest;
return levelLoadTest;
}
CEditorAutoLevelLoadTest::CEditorAutoLevelLoadTest()
{
GetIEditor()->RegisterNotifyListener(this);
}
CEditorAutoLevelLoadTest::~CEditorAutoLevelLoadTest()
{
GetIEditor()->UnregisterNotifyListener(this);
}
void CEditorAutoLevelLoadTest::OnEditorNotifyEvent(EEditorNotifyEvent event)
{
switch (event)
{
case eNotify_OnEndSceneOpen:
CLogFile::WriteLine("[LevelLoadFinished]");
exit(0);
break;
}
}
@@ -0,0 +1,26 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_EDITORAUTOLEVELLOADTEST_H
#define CRYINCLUDE_EDITOR_UTIL_EDITORAUTOLEVELLOADTEST_H
#pragma once
class CEditorAutoLevelLoadTest
: public IEditorNotifyListener
{
public:
static CEditorAutoLevelLoadTest& Instance();
private:
CEditorAutoLevelLoadTest();
virtual ~CEditorAutoLevelLoadTest();
virtual void OnEditorNotifyEvent(EEditorNotifyEvent event);
};
#endif // CRYINCLUDE_EDITOR_UTIL_EDITORAUTOLEVELLOADTEST_H
+300
View File
@@ -0,0 +1,300 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "EditorUtils.h"
#include "EditorToolsApplicationAPI.h"
// Qt
#include <QColor>
#include <QMessageBox>
#define GetAValue(rgba) (LOBYTE((rgba)>>24)) // Microsoft does not provide this one so let's make our own.
#define RGBA(r,g,b,a) ((COLORREF)(((BYTE)(r)|((WORD)((BYTE)(g))<<8))|(((DWORD)(BYTE)(b))<<16)|(((DWORD)(BYTE)(a))<<24)))
//////////////////////////////////////////////////////////////////////////
void HeapCheck::Check([[maybe_unused]] const char* file, [[maybe_unused]] int line)
{
#ifdef _DEBUG
#ifdef Q_OS_WIN
_ASSERTE(_CrtCheckMemory());
#endif
/*
int heapstatus = _heapchk();
switch( heapstatus )
{
case _HEAPOK:
break;
case _HEAPEMPTY:
break;
case _HEAPBADBEGIN:
{
CString str;
str.Format( "Bad Start of Heap, at file %s line:%d",file,line );
MessageBox( NULL,str,"Heap Check",MB_OK );
}
break;
case _HEAPBADNODE:
{
CString str;
str.Format( "Bad Node in Heap, at file %s line:%d",file,line );
MessageBox( NULL,str,"Heap Check",MB_OK );
}
break;
}
*/
#endif
}
#ifdef LoadCursor
#undef LoadCursor
#endif
QCursor CMFCUtils::LoadCursor(unsigned int nIDResource, int hotX, int hotY)
{
QString path;
switch (nIDResource)
{
case IDC_HAND_INTERNAL:
path = QStringLiteral("cursor1.cur");
break;
case IDC_ZOOM_INTERNAL:
path = QStringLiteral("cur00001.cur");
break;
case IDC_BRUSH_INTERNAL:
path = QStringLiteral("cur00002.cur");
break;
case IDC_ARRBLCK:
path = QStringLiteral("cur00003.cur");
break;
case IDC_ARRBLCKCROSS:
path = QStringLiteral("cur00004.cur");
break;
case IDC_ARRWHITE:
path = QStringLiteral("cur00005.cur");
break;
case IDC_COLOR_PICKER:
path = QStringLiteral("pick_cursor.cur");
break;
case IDC_HIT_CURSOR:
path = QStringLiteral("hit.cur");
break;
case IDC_ARROW_ADDKEY:
path = QStringLiteral("arr_addkey.cur");
break;
case IDC_LEFTRIGHT:
path = QStringLiteral("leftright.cur");
break;
case IDC_POINTER_OBJHIT:
path = QStringLiteral("pointerHit.cur");
break;
case IDC_POINTER_LINK:
path = QStringLiteral("pointer_link.cur");
break;
case IDC_POINTER_LINKNOW:
path = QStringLiteral("pointer_linknow.cur");
break;
case IDC_POINTER_OBJECT_ROTATE:
path = QStringLiteral("object_rotate.cur");
break;
case IDC_POINTER_OBJECT_SCALE:
path = QStringLiteral("object_scale.cur");
break;
case IDC_POINTER_OBJECT_MOVE:
path = QStringLiteral("object_move.cur");
break;
case IDC_POINTER_PLUS:
path = QStringLiteral("pointer_plus.cur");
break;
case IDC_POINTER_MINUS:
path = QStringLiteral("pointer_minus.cur");
break;
case IDC_POINTER_FLATTEN:
path = QStringLiteral("pointer_flatten.cur");
break;
case IDC_POINTER_SMOOTH:
path = QStringLiteral("pointer_smooth.cur");
break;
case IDC_POINTER_SO_SELECT:
path = QStringLiteral("pointer_so_select.cur");
break;
case IDC_POINTER_SO_SELECT_PLUS:
path = QStringLiteral("pointer_so_sel_plus.cur");
break;
case IDC_POINTER_SO_SELECT_MINUS:
path = QStringLiteral("pointer_.cur");
break;
case IDC_POINTER_DRAG_ITEM:
path = QStringLiteral("pointerDragItem.cur");
break;
case IDC_CURSOR_HAND_DRAG:
path = QStringLiteral("handDrag.cur");
break;
case IDC_CURSOR_HAND_FINGER:
path = QStringLiteral("cursor2.cur");
break;
case IDC_ARROW_UP:
path = QStringLiteral("arrow_up.cur");
break;
case IDC_ARROW_DOWN:
path = QStringLiteral("arrow_down.cur");
break;
case IDC_ARROW_DOWNRIGHT:
path = QStringLiteral("arrow_downright.cur");
break;
case IDC_ARROW_UPRIGHT:
path = QStringLiteral("arrow_upright.cur");
break;
case IDC_POINTER_GET_HEIGHT:
path = QStringLiteral("pointer_getheight.cur");
break;
default:
return QCursor();
}
path = QStringLiteral(":/cursors/res/") + path;
QPixmap pm(path);
if (!pm.isNull() && (hotX < 0 || hotY < 0))
{
QFile f(path);
f.open(QFile::ReadOnly);
QDataStream stream(&f);
stream.setByteOrder(QDataStream::LittleEndian);
f.read(10);
quint16 x;
stream >> x;
hotX = x;
stream >> x;
hotY = x;
}
return QCursor(pm, hotX, hotY);
}
//////////////////////////////////////////////////////////////////////////-
QString TrimTrailingZeros(QString str)
{
if (str.contains('.'))
{
for (int p = str.size() - 1; p >= 0; --p)
{
if (str.at(p) == '.')
{
return str.left(p);
}
else if (str.at(p) != '0')
{
return str.left(p + 1);
}
}
return QString("0");
}
return str;
}
//////////////////////////////////////////////////////////////////////////
// This function is supposed to format float in user-friendly way,
// omitting the exponent notation.
//
// Why not using printf? Its formatting rules has following drawbacks:
// %g - will use exponent for small numbers;
// %.Nf - doesn't allow to control total amount of significant numbers,
// which exposes limited precision during binary-to-decimal fraction
// conversion.
//////////////////////////////////////////////////////////////////////////
void FormatFloatForUI(QString& str, int significantDigits, double value)
{
str = TrimTrailingZeros(QString::number(value, 'f', significantDigits));
return;
}
//////////////////////////////////////////////////////////////////////////-
//////////////////////////////////////////////////////////////////////////
QColor ColorLinearToGamma(ColorF col)
{
float r = clamp_tpl(col.r, 0.0f, 1.0f);
float g = clamp_tpl(col.g, 0.0f, 1.0f);
float b = clamp_tpl(col.b, 0.0f, 1.0f);
float a = clamp_tpl(col.a, 0.0f, 1.0f);
r = (float)(r <= 0.0031308 ? (12.92 * r) : (1.055 * pow((double)r, 1.0 / 2.4) - 0.055));
g = (float)(g <= 0.0031308 ? (12.92 * g) : (1.055 * pow((double)g, 1.0 / 2.4) - 0.055));
b = (float)(b <= 0.0031308 ? (12.92 * b) : (1.055 * pow((double)b, 1.0 / 2.4) - 0.055));
return QColor(FtoI(r * 255.0f), FtoI(g * 255.0f), FtoI(b * 255.0f), FtoI(a * 255.0f));
}
//////////////////////////////////////////////////////////////////////////
ColorF ColorGammaToLinear(const QColor& col)
{
float r = (float)col.red() / 255.0f;
float g = (float)col.green() / 255.0f;
float b = (float)col.blue() / 255.0f;
float a = (float)col.alpha() / 255.0f;
return ColorF((float)(r <= 0.04045 ? (r / 12.92) : pow(((double)r + 0.055) / 1.055, 2.4)),
(float)(g <= 0.04045 ? (g / 12.92) : pow(((double)g + 0.055) / 1.055, 2.4)),
(float)(b <= 0.04045 ? (b / 12.92) : pow(((double)b + 0.055) / 1.055, 2.4)), a);
}
QColor ColorToQColor(uint32 color)
{
return QColor::fromRgbF((float)GetRValue(color) / 255.0f,
(float)GetGValue(color) / 255.0f,
(float)GetBValue(color) / 255.0f);
}
namespace EditorUtils
{
AZ_PUSH_DISABLE_WARNING(4273, "-Wunknown-warning-option")
AzWarningAbsorber::AzWarningAbsorber(const char* window)
: m_window(window)
AZ_POP_DISABLE_WARNING
{
BusConnect();
}
AzWarningAbsorber::~AzWarningAbsorber()
{
AZ::Debug::TraceMessageBus::Handler::BusDisconnect();
}
bool AzWarningAbsorber::OnPreWarning(const char* window, const char*, int, const char*, const char*)
{
if ((window)&&(m_window == window))
{
return true;
}
return false;
}
const char* LevelFile::GetOldCryFileExtension()
{
const char* oldCryExtension = nullptr;
EditorInternal::EditorToolsApplicationRequestBus::BroadcastResult(
oldCryExtension, &EditorInternal::EditorToolsApplicationRequests::GetOldCryLevelExtension);
AZ_Assert(oldCryExtension, "Cannot retrieve file extension");
return oldCryExtension;
}
const char* LevelFile::GetDefaultFileExtension()
{
const char* levelExtension = nullptr;
EditorInternal::EditorToolsApplicationRequestBus::BroadcastResult(
levelExtension, &EditorInternal::EditorToolsApplicationRequests::GetLevelExtension);
AZ_Assert(levelExtension, "Cannot retrieve file extension");
return levelExtension;
}
} // namespace EditorUtils
+704
View File
@@ -0,0 +1,704 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Utility classes used by Editor.
#ifndef CRYINCLUDE_EDITOR_UTIL_EDITORUTILS_H
#define CRYINCLUDE_EDITOR_UTIL_EDITORUTILS_H
#pragma once
#include <IXml.h>
#include "Util/FileUtil.h"
#include <Cry_Color.h>
//! Typedef for quaternion.
//typedef CryQuat Quat;
#include <QColor>
#include <QDataStream>
#include <QGuiApplication>
#include <QSet>
#include <Include/SandboxAPI.h>
#include <AzCore/Debug/TraceMessageBus.h>
#ifndef MIN
#define MIN(a, b) ((a) < (b) ? (a) : (b))
#endif
#ifndef MAX
#define MAX(a, b) ((a) > (b) ? (a) : (b))
#endif
#ifdef LoadCursor
#undef LoadCursor
#endif
#define LINE_EPS (0.00001f)
template <typename T, size_t N>
char (&ArraySizeHelper(T (&array)[N]))[N];
#define arraysize(array) (sizeof(ArraySizeHelper(array)))
/// Some preprocessor utils
/// http://altdevblogaday.com/2011/07/12/abusing-the-c-preprocessor/
#define JOIN(x, y) JOIN2(x, y)
#define JOIN2(x, y) x##y
#define LIST_0(x)
#define LIST_1(x) x##1
#define LIST_2(x) LIST_1(x), x##2
#define LIST_3(x) LIST_2(x), x##3
#define LIST_4(x) LIST_3(x), x##4
#define LIST_5(x) LIST_4(x), x##5
#define LIST_6(x) LIST_5(x), x##6
#define LIST_7(x) LIST_6(x), x##7
#define LIST_8(x) LIST_7(x), x##8
#define LIST(cnt, x) JOIN(LIST_, cnt)(x)
//! Checks heap for errors.
struct HeapCheck
{
//! Runs consistency checks on the heap.
static void Check(const char* file, int line);
};
#ifdef _DEBUG
#define HEAP_CHECK HeapCheck::Check(__FILE__, __LINE__);
#else
#define HEAP_CHECK
#endif
#define MAKE_SURE(x, action) { if (!(x)) { assert(0 && #x); action; } \
}
namespace EditorUtils
{
// Class to create scoped variable value.
template<typename TType>
class TScopedVariableValue
{
public:
//Relevant for containers, should not be used manually.
TScopedVariableValue()
: m_pVariable(nullptr)
{
}
// Main constructor.
TScopedVariableValue(TType& tVariable, const TType& tConstructValue, const TType& tDestructValue)
: m_pVariable(&tVariable)
, m_tConstructValue(tConstructValue)
, m_tDestructValue(tDestructValue)
{
*m_pVariable = m_tConstructValue;
}
// Transfers ownership.
TScopedVariableValue(TScopedVariableValue& tInput)
: m_pVariable(tInput.m_pVariable)
, m_tConstructValue(tInput.m_tConstructValue)
, m_tDestructValue(tInput.m_tDestructValue)
{
// I'm not sure if anyone should use this but for now I'm adding one.
tInput.m_pVariable = nullptr;
}
// Move constructor: needed to use CreateScopedVariable, and transfers ownership.
TScopedVariableValue(TScopedVariableValue&& tInput)
{
std::move(m_pVariable, tInput.m_tVariable);
std::move(m_tConstructValue, tInput.m_tConstructValue);
std::move(m_tDestructValue, tInput.m_tDestructtValue);
}
// Applies the scoping exit, if the variable is valid.
virtual ~TScopedVariableValue()
{
if (m_pVariable)
{
*m_pVariable = m_tDestructValue;
}
}
// Transfers ownership, if not self assignment.
TScopedVariableValue& operator=(TScopedVariableValue& tInput)
{
// I'm not sure if this makes sense to exist... but for now I'm adding one.
if (this != &tInput)
{
m_pVariable = tInput.m_pVariable;
m_tConstructValue = tInput.m_tConstructValue;
m_tDestructValue = tInput.m_tDestructValue;
tInput.m_pVariable = nullptr;
}
return *this;
}
protected:
TType* m_pVariable;
TType m_tConstructValue;
TType m_tDestructValue;
};
// Helper function to create scoped variable.
// Ideal usage: auto tMyVariable=CreateScoped(tContainedVariable,tConstructValue,tDestructValue);
template<typename TType>
TScopedVariableValue<TType> CreateScopedVariableValue(TType& tVariable, const TType& tConstructValue, const TType& tDestructValue)
{
return TScopedVariableValue<TType>(tVariable, tConstructValue, tDestructValue);
}
class AzWarningAbsorber
: public AZ::Debug::TraceMessageBus::Handler
{
public:
SANDBOX_API AzWarningAbsorber(const char* window);
SANDBOX_API ~AzWarningAbsorber();
bool OnPreWarning(const char* window, const char* fileName, int line, const char* func, const char* message) override;
AZStd::string m_window;
};
namespace LevelFile
{
//! Retrieve old cry level file extension (With prepending '.')
const char* GetOldCryFileExtension();
//! Retrieve default level file extension (With prepending '.')
const char* GetDefaultFileExtension();
}
};
//////////////////////////////////////////////////////////////////////////
// XML Helper functions.
//////////////////////////////////////////////////////////////////////////
namespace XmlHelpers
{
SANDBOX_API inline XmlNodeRef CreateXmlNode(const char* sTag)
{
return GetISystem()->CreateXmlNode(sTag);
}
inline bool SaveXmlNode(IFileUtil* pFileUtil, XmlNodeRef node, const char* filename)
{
if (!pFileUtil->OverwriteFile(filename))
{
return false;
}
return node->saveToFile(filename);
}
SANDBOX_API inline XmlNodeRef LoadXmlFromFile(const char* fileName)
{
return GetISystem()->LoadXmlFromFile(fileName);
}
SANDBOX_API inline XmlNodeRef LoadXmlFromBuffer(const char* buffer, size_t size, bool suppressWarnings = false)
{
return GetISystem()->LoadXmlFromBuffer(buffer, size, false, suppressWarnings);
}
}
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
// Drag Drop helper functions
//////////////////////////////////////////////////////////////////////////
namespace EditorDragDropHelpers
{
inline QString GetAnimationNameClipboardFormat()
{
return QStringLiteral("application/x-animation-browser-copy");
}
inline QString GetFragmentClipboardFormat()
{
return QStringLiteral("application/x-preview-fragment-properties");
}
}
//////////////////////////////////////////////////////////////////////////
/*!
* StdMap Wraps std::map to provide easier to use interface.
*/
template <class Key, class Value>
struct StdMap
{
private:
typedef std::map<Key, Value> Map;
Map m;
public:
typedef typename Map::iterator Iterator;
typedef typename Map::const_iterator ConstIterator;
void Insert(const Key& key, const Value& value) { m[key] = value; }
int GetCount() const { return m.size(); };
bool IsEmpty() const { return m.empty(); };
void Clear() { m.clear(); }
int Erase(const Key& key) { return m.erase(key); };
Value& operator[](const Key& key) { return m[key]; };
bool Find(const Key& key, Value& value) const
{
ConstIterator it = m.find(key);
if (it == m.end())
{
return false;
}
value = it->second;
return true;
}
Iterator Find(const Key& key) { return m.find(key); }
ConstIterator Find(const Key& key) const { return m.find(key); }
bool FindKeyByValue(const Value& value, Key& key) const
{
for (ConstIterator it = m.begin(); it != m.end(); ++it)
{
if (it->second == value)
{
key = it->first;
return true;
}
}
return false;
}
Iterator Begin() { return m.begin(); };
Iterator End() { return m.end(); };
ConstIterator Begin() const { return m.begin(); };
ConstIterator End() const { return m.end(); };
void GetAsVector(std::vector<Value>& array) const
{
array.resize(m.size());
int i = 0;
for (ConstIterator it = m.begin(); it != m.end(); ++it)
{
array[i++] = it->second;
}
}
};
//////////////////////////////////////////////////////////////////////////
//
// Convert String representation of color to RGB integer value.
//
//////////////////////////////////////////////////////////////////////////
inline QColor String2Color(const QString& val)
{
unsigned int r = 0, g = 0, b = 0;
int res = 0;
res = azsscanf(val.toUtf8().data(), "R:%d,G:%d,B:%d", &r, &g, &b);
if (res != 3)
{
res = azsscanf(val.toUtf8().data(), "R:%d G:%d B:%d", &r, &g, &b);
}
if (res != 3)
{
res = azsscanf(val.toUtf8().data(), "%d,%d,%d", &r, &g, &b);
}
if (res != 3)
{
res = azsscanf(val.toUtf8().data(), "%d %d %d", &r, &g, &b);
}
if (res != 3)
{
azsscanf(val.toUtf8().data(), "%x", &r);
return r;
}
return QColor(r, g, b);
}
// Converts QColor to Vector.
inline Vec3 Rgb2Vec(const QColor& color)
{
return Vec3(aznumeric_cast<float>(color.redF()), aznumeric_cast<float>(color.greenF()), aznumeric_cast<float>(color.blueF()));
}
// Converts QColor to ColorF.
inline ColorF Rgb2ColorF(const QColor& color)
{
return ColorF(aznumeric_cast<float>(color.redF()), aznumeric_cast<float>(color.greenF()), aznumeric_cast<float>(color.blueF()), 1.0f);
}
// Converts QColor to Vector.
inline QColor Vec2Rgb(const Vec3& color)
{
return QColor(aznumeric_cast<int>(color.x * 255), aznumeric_cast<int>(color.y * 255), aznumeric_cast<int>(color.z * 255));
}
// Converts ColorF to QColor.
inline QColor ColorF2Rgb(const ColorF& color)
{
return QColor(aznumeric_cast<int>(color.r * 255), aznumeric_cast<int>(color.g * 255), aznumeric_cast<int>(color.b * 255));
}
//////////////////////////////////////////////////////////////////////////
// Tokenize string.
//////////////////////////////////////////////////////////////////////////
inline QString TokenizeString(const QString& s, LPCSTR pszTokens, int& iStart)
{
assert(iStart >= 0);
QByteArray str = s.toUtf8();
if (pszTokens == NULL)
{
return str;
}
auto pszPlace = str.begin() + iStart;
auto pszEnd = str.end();
if (pszPlace < pszEnd)
{
int nIncluding = (int)strspn(pszPlace, pszTokens);
;
if ((pszPlace + nIncluding) < pszEnd)
{
pszPlace += nIncluding;
int nExcluding = (int)strcspn(pszPlace, pszTokens);
int iFrom = iStart + nIncluding;
int nUntil = nExcluding;
iStart = iFrom + nUntil + 1;
return (str.mid(iFrom, nUntil));
}
}
// return empty string, done tokenizing
iStart = -1;
return "";
}
// This template function will join strings from a vector into a single string, using a separator char
template<class T>
inline void JoinStrings(const QList<T>& rStrings, QString& rDestStr, char aSeparator = ',')
{
for (size_t i = 0, iCount = rStrings.size(); i < iCount; ++i)
{
rDestStr += rStrings[i];
if (i < iCount - 1)
{
rDestStr += aSeparator;
}
}
}
// This function will split a string containing separated strings, into a vector of strings
// better version of TokenizeString
inline void SplitString(const QString& rSrcStr, QStringList& rDestStrings, char aSeparator = ',')
{
int crtPos = 0, lastPos = 0;
while (true)
{
crtPos = rSrcStr.indexOf(aSeparator, lastPos);
if (-1 == crtPos)
{
crtPos = rSrcStr.length();
if (crtPos != lastPos)
{
rDestStrings.push_back(rSrcStr.mid(lastPos, crtPos - lastPos));
}
break;
}
else
{
if (crtPos != lastPos)
{
rDestStrings.push_back(rSrcStr.mid(lastPos, crtPos - lastPos));
}
}
lastPos = crtPos + 1;
}
}
// Format unsigned number to string with 1000s separator
inline QString FormatWithThousandsSeperator(const unsigned int number)
{
QString string;
string = QString::number(number);
for (int p = string.length() - 3; p > 0; p -= 3)
{
string.insert(p, ',');
}
return string;
}
void FormatFloatForUI(QString& str, int significantDigits, double value);
//////////////////////////////////////////////////////////////////////////
// Simply sub string searching case insensitive.
//////////////////////////////////////////////////////////////////////////
inline const char* strstri(const char* pString, const char* pSubstring)
{
int i, j, k;
for (i = 0; pString[i]; i++)
{
for (j = i, k = 0; tolower(pString[j]) == tolower(pSubstring[k]); j++, k++)
{
if (!pSubstring[k + 1])
{
return (pString + i);
}
}
}
return NULL;
}
inline bool CheckVirtualKey(Qt::MouseButton button)
{
return (qApp->property("pressedMouseButtons").toInt() & button) != 0;
}
inline bool CheckVirtualKey(Qt::Key virtualKey)
{
return qApp->property("pressedKeys").value<QSet<int>>().contains(virtualKey);
}
class QColor;
QColor ColorLinearToGamma(ColorF col);
ColorF ColorGammaToLinear(const QColor& col);
QColor ColorToQColor(uint32 color);
class QCursor;
class QPixmap;
template<typename T>
class QVector;
/*! Collection of Utility MFC functions.
*/
struct CMFCUtils
{
static QCursor LoadCursor(unsigned int nIDResource, int hotX = -1, int hotY = -1);
};
#ifndef _AFX
class CArchive : public QDataStream
{
public:
enum Mode
{
load,
store
};
CArchive(QIODevice* device, Mode mode)
: QDataStream(device)
, m_mode(mode)
{
setByteOrder(LittleEndian);
}
bool IsLoading() const
{
return m_mode == load;
}
bool IsStoring() const
{
return m_mode == store;
}
uint Read(void* buffer, uint size)
{
QDataStream::readRawData(reinterpret_cast<char*>(buffer), size);
return size;
}
uint Write(void* buffer, uint size)
{
// There is a bug in QT with writing files larger than 32MB. It separates
// the write into 32MB blocks, but doesn't write the last block correctly.
// To deal with this, we'll separate into blocks here so QT doesn't have to.
// QT bug in qfileengine_win.cpp line 434. Block size is calculated once and always
// used as the amount of data to write, but for the last block, unless there is exactly
// block size left to write, the actual remaining amount needs to be written, not the
// whole block size. This will cause WriteFile() to either write garbage to the file or
// attempt to get into memory it doesn't have access to.
const uint blockSize = 1024 * 1024 * 32; // This is the size QT uses for blocks.
uint totalBytesLeftToWrite = size;
uint totalBytesWritten = 0;
while (totalBytesLeftToWrite > 0)
{
uint bytesToWrite = AZ::GetMin(blockSize, totalBytesLeftToWrite);
uint bytesWritten = QDataStream::writeRawData(reinterpret_cast<char*>(buffer) + totalBytesWritten, bytesToWrite);
totalBytesLeftToWrite -= bytesWritten;
totalBytesWritten += bytesWritten;
// If something goes wrong, stop.
if (bytesWritten != bytesToWrite)
{
break;
}
}
return totalBytesWritten;
}
private:
Mode m_mode;
};
inline quint64 readStringLength(CArchive& ar, int& charSize)
{
// This is legacy MFC converted code. It used to use AfxReadStringLength() which has a complicated
// decoding pattern.
// The basic algorithm is that it reads in an 8 bit int, and if the length is less than 2^8,
// then that's the length. Next it reads in a 16 bit int, and if the length is less than 2^16,
// then that's the length. It does the same thing for 32 bit values and finally for 64 bit values.
// The 16 bit length also indicates whether or not it's a UCS2 / wide-char Windows string, if it's
// 0xfffe, but that comes after the first byte marker indicating there's a 16 bit length value.
// So, if the first 3 bytes are: 0xFF, 0xFF, 0xFE, it's a 2 byte string being read in, and the real
// length follows those 3 bytes (which may still be an 8, 16, or 32 bit length).
// default to one byte strings
charSize = 1;
quint8 len8;
ar >> len8;
if (len8 < 0xff)
{
return len8;
}
quint16 len16;
ar >> len16;
if (len16 == 0xfffe)
{
charSize = 2;
ar >> len8;
if (len8 < 0xff)
{
return len8;
}
ar >> len16;
}
if (len16 < 0xffff)
{
return len16;
}
quint32 len32;
ar >> len32;
if (len32 < 0xffffffff)
{
return len32;
}
quint64 len64;
ar >> len64;
return len64;
}
inline CArchive& operator>>(CArchive& ar, QString& str)
{
int charSize = 1;
auto length = readStringLength(ar, charSize);
QByteArray data = ar.device()->read(length * charSize);
if (charSize == 1)
{
str = QString::fromUtf8(data);
}
else
{
char* raw = data.data();
// check if it's short aligned; if it isn't, we need to copy to a temp buffer
if ((reinterpret_cast<uintptr_t>(raw) & 1) != 0)
{
ushort* shortAlignedData = new ushort[length];
memcpy(shortAlignedData, raw, length * 2);
str = QString::fromUtf16(shortAlignedData, aznumeric_cast<int>(length));
delete[] shortAlignedData;
}
else
{
str = QString::fromUtf16(reinterpret_cast<ushort*>(raw), aznumeric_cast<int>(length));
}
}
return ar;
}
inline CArchive& operator<<(CArchive& ar, const QString& str)
{
// This is written to mimic how MFC archiving worked, which was to
// write markers to indicate the size of the length -
// so a length that will fit into 8 bits takes 8 bits.
// A length that requires more than 8 bits, puts an 8 bit marker (0xff)
// to indicate that the length is greater, then 16 bits for the length.
// If the length requires 32 bits, there's an 8 bit marker (0xff), a
// 16 bit marker (0xffff) and then the 32 bit length.
// Note that the legacy code could also encode to 16 bit Windows wide character
// streams; that isn't necessary though, given that Qt supports Utf-8 out of the
// box and is much less ambiguous on other platforms.
QByteArray data = str.toUtf8();
int length = data.length();
if (length < 255)
{
ar << static_cast<quint8>(length);
}
else if (length < 0xfffe) // 0xfffe instead of 0xffff because 0xfffe indicated Windows wide character strings, which we aren't bothering with anymore
{
ar << static_cast<quint8>(0xff);
ar << static_cast<quint16>(length);
}
else
{
ar << static_cast<quint8>(0xff);
ar << static_cast<quint16>(0xffff);
ar << static_cast<quint32>(length);
}
ar.device()->write(data);
return ar;
}
#endif
#endif // CRYINCLUDE_EDITOR_UTIL_EDITORUTILS_H
+214
View File
@@ -0,0 +1,214 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "FileChangeMonitor.h"
// Qt
#include <QDateTime>
#include <QTimer>
CFileChangeMonitor* CFileChangeMonitor::s_pFileMonitorInstance = NULL;
//////////////////////////////////////////////////////////////////////////
CFileChangeMonitor::CFileChangeMonitor(QObject* parent)
: QObject(parent)
{
ed_logFileChanges = 0;
}
//////////////////////////////////////////////////////////////////////////
CFileChangeMonitor::~CFileChangeMonitor()
{
for (TListeners::iterator it = m_listeners.begin(); it != m_listeners.end(); ++it)
{
CFileChangeMonitorListener* pListener = *it;
if (pListener)
{
pListener->SetMonitor(NULL);
}
}
// Send to thread a kill event.
StopMonitor();
}
CFileChangeMonitor* CFileChangeMonitor::Instance()
{
if (!s_pFileMonitorInstance)
{
s_pFileMonitorInstance = new CFileChangeMonitor();
s_pFileMonitorInstance->Initialize();
}
return s_pFileMonitorInstance;
}
void CFileChangeMonitor::DeleteInstance()
{
SAFE_DELETE(s_pFileMonitorInstance);
}
void CFileChangeMonitor::Initialize()
{
REGISTER_CVAR(ed_logFileChanges, 0, VF_NULL, "If its 1, then enable the logging of file monitor file changes");
m_watcher.reset(new QFileSystemWatcher);
connect(m_watcher.data(), &QFileSystemWatcher::fileChanged,
this, &CFileChangeMonitor::OnFileChange);
connect(m_watcher.data(), &QFileSystemWatcher::directoryChanged,
this, &CFileChangeMonitor::OnDirectoryChange);
AddIgnoreFileMask("*$tmp*");
}
bool CFileChangeMonitor::IsDirectory(const char* sFileName)
{
QFileInfo finfo(sFileName);
return finfo.isDir();
}
//////////////////////////////////////////////////////////////////////////
bool CFileChangeMonitor::IsFile(const char* sFileName)
{
QFileInfo finfo(sFileName);
return finfo.isFile();
}
void CFileChangeMonitor::AddIgnoreFileMask(const char* pMask)
{
Log("Adding '%s' to ignore masks for changed files.", pMask);
m_ignoreMasks.append(QString::fromLatin1(pMask));
}
void CFileChangeMonitor::RemoveIgnoreFileMask(const char* pMask, int aAfterDelayMsec)
{
QTimer::singleShot(aAfterDelayMsec, [=]() {
m_ignoreMasks.removeAll(QString::fromLatin1(pMask));
});
}
//////////////////////////////////////////////////////////////////////////
bool CFileChangeMonitor::MonitorItem(const QString& sItem)
{
QFileInfo finfo(sItem);
if (finfo.isDir())
{
QDir dir(sItem);
m_entries.insert(sItem, std::move(dir.entryInfoList(QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot)));
}
return m_watcher->addPath(sItem);
}
void CFileChangeMonitor::StopMonitor()
{
if (m_watcher)
{
disconnect(m_watcher.data(), &QFileSystemWatcher::fileChanged,
this, &CFileChangeMonitor::OnFileChange);
disconnect(m_watcher.data(), &QFileSystemWatcher::directoryChanged,
this, &CFileChangeMonitor::OnDirectoryChange);
}
}
void CFileChangeMonitor::SetEnabled(bool bEnable)
{
m_watcher->blockSignals(!bEnable);
}
bool CFileChangeMonitor::IsEnabled()
{
return !m_watcher->signalsBlocked();
}
void CFileChangeMonitor::Subscribe(CFileChangeMonitorListener* pListener)
{
assert(pListener);
pListener->SetMonitor(this);
m_listeners.insert(pListener);
}
void CFileChangeMonitor::Unsubscribe(CFileChangeMonitorListener* pListener)
{
assert(pListener);
m_listeners.erase(pListener);
pListener->SetMonitor(NULL);
}
void CFileChangeMonitor::OnDirectoryChange(const QString &path)
{
QDir dir(path);
auto entries = dir.entryInfoList(QDir::Files|QDir::Dirs|QDir::NoDotAndDotDot);
const auto prev = m_entries.value(path);
for (const auto &fi : prev)
{
int eindex = entries.indexOf(fi);
if (eindex >= 0)
{
if (fi.lastModified() != entries.at(eindex).lastModified())
{
NotifyListeners(fi.canonicalFilePath(), SFileChangeInfo::eChangeType_Modified);
}
}
else
{
NotifyListeners(fi.canonicalFilePath(), SFileChangeInfo::eChangeType_Deleted);
}
}
for (const auto &fi : entries)
{
if (!prev.contains(fi))
{
NotifyListeners(fi.canonicalFilePath(), SFileChangeInfo::eChangeType_Created);
}
}
m_entries.insert(path, std::move(entries));
NotifyListeners(path, SFileChangeInfo::eChangeType_Modified);
}
void CFileChangeMonitor::OnFileChange(const QString &path)
{
QFileInfo finfo(path);
NotifyListeners(path, finfo.exists() ? SFileChangeInfo::eChangeType_Modified : SFileChangeInfo::eChangeType_Deleted);
}
void CFileChangeMonitor::NotifyListeners(const QString &path, SFileChangeInfo::EChangeType changeType)
{
for (const auto &glob : m_ignoreMasks)
{
QRegExp exp(glob, Qt::CaseInsensitive, QRegExp::Wildcard);
if (path.contains(exp))
{
return; // mask matches, ignore event
}
}
SFileChangeInfo change;
change.filename = path;
change.changeType = changeType;
for (auto it = m_listeners.begin(); it != m_listeners.end(); ++it)
{
CFileChangeMonitorListener* pListener = *it;
if (pListener)
{
pListener->OnFileMonitorChange(change);
}
}
}
+136
View File
@@ -0,0 +1,136 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_FILECHANGEMONITOR_H
#define CRYINCLUDE_EDITOR_UTIL_FILECHANGEMONITOR_H
#pragma once
#if !defined(Q_MOC_RUN)
#include <AzCore/std/containers/set.h>
#include <QFileInfoList>
#include <QFileSystemWatcher>
#include <QObject>
#include <QQueue>
#include <QScopedPointer>
#endif
class CFileChangeMonitorListener;
struct SFileChangeInfo
{
enum EChangeType
{
//! error or unknown change type
eChangeType_Unknown,
//! the file was created
eChangeType_Created,
//! the file was deleted
eChangeType_Deleted,
//! the file was modified (size changed,write)
eChangeType_Modified,
//! this is the old name of a renamed file
eChangeType_RenamedOldName,
//! this is the new name of a renamed file
eChangeType_RenamedNewName
};
SFileChangeInfo()
: changeType(eChangeType_Unknown)
{
}
bool operator==(const SFileChangeInfo& rhs) const
{
return changeType == rhs.changeType && filename == rhs.filename;
}
QString filename;
EChangeType changeType;
};
// Monitors directory for any changed files
class CFileChangeMonitor : public QObject
{
public:
friend class CEditorFileMonitor;
typedef AZStd::set<CFileChangeMonitorListener*> TListeners;
protected:
explicit CFileChangeMonitor(QObject* parent = nullptr);
~CFileChangeMonitor();
void Initialize();
static void DeleteInstance();
static CFileChangeMonitor* s_pFileMonitorInstance;
public:
static CFileChangeMonitor* Instance();
bool MonitorItem(const QString& sItem);
void StopMonitor();
void SetEnabled(bool bEnable);
bool IsEnabled();
//! get next modified file, this file will be delete from list after calling this function,
//! call it until HaveModifiedFiles return true or this function returns false
void Subscribe(CFileChangeMonitorListener* pListener);
void Unsubscribe(CFileChangeMonitorListener* pListener);
bool IsDirectory(const char* pFilename);
bool IsFile(const char* pFilename);
bool IsLoggingChanges() const
{
return ed_logFileChanges != 0;
}
void AddIgnoreFileMask(const char* pMask);
void RemoveIgnoreFileMask(const char* pMask, int aAfterDelayMsec = 1000);
private:
void OnDirectoryChange(const QString &path);
void OnFileChange(const QString &path);
void NotifyListeners(const QString &path, SFileChangeInfo::EChangeType changeType);
int ed_logFileChanges;
QScopedPointer<QFileSystemWatcher> m_watcher;
TListeners m_listeners;
QQueue<SFileChangeInfo> m_changes;
QStringList m_ignoreMasks;
QHash<QString, QFileInfoList> m_entries;
};
// Used as base class (aka interface) to subscribe for file change events
class CFileChangeMonitorListener
{
public:
CFileChangeMonitorListener()
: m_pMonitor(NULL)
{
}
virtual ~CFileChangeMonitorListener()
{
if (m_pMonitor)
{
m_pMonitor->Unsubscribe(this);
}
}
virtual void OnFileMonitorChange(const SFileChangeInfo& rChange) = 0;
void SetMonitor(CFileChangeMonitor* pMonitor)
{
m_pMonitor = pMonitor;
}
private:
CFileChangeMonitor* m_pMonitor;
};
#endif // CRYINCLUDE_EDITOR_UTIL_FILECHANGEMONITOR_H
+166
View File
@@ -0,0 +1,166 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "FileEnum.h"
CFileEnum::CFileEnum()
: m_hEnumFile(0)
{
}
CFileEnum::~CFileEnum()
{
if (m_hEnumFile)
{
delete m_hEnumFile;
m_hEnumFile = 0;
}
}
bool CFileEnum::StartEnumeration(
const QString& szEnumPath,
const QString& szEnumPattern,
QFileInfo* pFile)
{
//////////////////////////////////////////////////////////////////////
// Take path and search pattern as separate arguments
//////////////////////////////////////////////////////////////////////
// Build enumeration path
QString szPath = szEnumPath;
if (!szPath.endsWith('\\') && !szPath.endsWith('/'))
{
szPath += QDir::separator();
}
szPath += szEnumPattern;
return StartEnumeration(szPath, pFile);
}
bool CFileEnum::StartEnumeration(const QString& szEnumPathAndPattern, QFileInfo* pFile)
{
// End any previous enumeration
if (m_hEnumFile)
{
delete m_hEnumFile;
m_hEnumFile = 0;
}
QStringList parts = szEnumPathAndPattern.split(QRegularExpression(R"([\\/])"));
QString pattern = parts.takeLast();
QString path = parts.join(QDir::separator());
// Start the enumeration
m_hEnumFile = new QDirIterator(path, QStringList(pattern));
if (!m_hEnumFile->hasNext())
{
// No files found
delete m_hEnumFile;
m_hEnumFile = 0;
return false;
}
m_hEnumFile->next();
*pFile = m_hEnumFile->fileInfo();
return true;
}
bool CFileEnum::GetNextFile(QFileInfo* pFile)
{
// Fill file strcuture
if (!m_hEnumFile->hasNext())
{
// No more files left
delete m_hEnumFile;
m_hEnumFile = 0;
return false;
}
m_hEnumFile->next();
*pFile = m_hEnumFile->fileInfo();
// At least one file left
return true;
}
inline bool ScanDirectoryRecursive(
const QString& root,
const QString& path,
const QString& file,
QStringList& files,
bool bRecursive)
{
bool bFoundAny = false;
QDirIterator hFile(root + path, QStringList(file));
if (hFile.hasNext())
{
// Find the rest of the files.
do
{
hFile.next();
QFileInfo foundFile = hFile.fileInfo();
bFoundAny = true;
files.push_back(path + foundFile.fileName());
} while (hFile.hasNext());
}
if (bRecursive)
{
QDirIterator hFile2(root + path, QStringList("*.*"));
if (hFile2.hasNext())
{
// Find directories.
do
{
hFile2.next();
QFileInfo foundFile = hFile2.fileInfo();
if (foundFile.isDir())
{
// If recursive.
if (!foundFile.fileName().startsWith('.'))
{
if (ScanDirectoryRecursive(
root,
path + foundFile.fileName() + QDir::separator(),
file,
files,
bRecursive))
{
bFoundAny = true;
}
}
}
} while (hFile2.hasNext());
}
}
return bFoundAny;
}
bool CFileEnum::ScanDirectory(
const QString& path,
const QString& file,
QStringList& files,
bool bRecursive,
bool /* bSkipPaks - unused, left for backwards API compatibility */)
{
return ScanDirectoryRecursive(path, "", file, files, bRecursive);
}
+37
View File
@@ -0,0 +1,37 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_FILEENUM_H
#define CRYINCLUDE_EDITOR_UTIL_FILEENUM_H
#pragma once
#include <QDirIterator>
class QFileInfo;
class QString;
class QStringList;
class CFileEnum
{
public:
CFileEnum();
virtual ~CFileEnum();
bool GetNextFile(QFileInfo* pFile);
bool StartEnumeration(const QString& szEnumPathAndPattern, QFileInfo* pFile);
bool StartEnumeration(const QString& szEnumPath, const QString& szEnumPattern, QFileInfo* pFile);
static bool ScanDirectory(
const QString& path,
const QString& file,
QStringList& files,
bool bRecursive = true,
bool bSkipPaks = false);
protected:
QDirIterator* m_hEnumFile;
};
#endif // CRYINCLUDE_EDITOR_UTIL_FILEENUM_H
File diff suppressed because it is too large Load Diff
+208
View File
@@ -0,0 +1,208 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include "CryThread.h"
#include "StringUtils.h"
#include "../Include/SandboxAPI.h"
#include <QString>
#include <QFileInfo>
#include "../Include/IFileUtil.h"
class QStringList;
class QMenu;
class SANDBOX_API CFileUtil
{
public:
static bool ScanDirectory(const QString& path, const QString& fileSpec, IFileUtil::FileArray& files,
bool recursive = true, bool addDirAlso = false, IFileUtil::ScanDirectoryUpdateCallBack updateCB = nullptr, bool bSkipPaks = false);
static void ShowInExplorer(const QString& path);
// Try to compile the given lua file: returns true if compilation succeeded, false on failure.
static bool CompileLuaFile(const char* luaFilename);
static bool ExtractFile(QString& file, bool bMsgBoxAskForExtraction = true, const char* pDestinationFilename = nullptr);
static void EditTextFile(const char* txtFile, int line = 0, IFileUtil::ETextFileType fileType = IFileUtil::FILE_TYPE_SCRIPT);
static void EditTextureFile(const char* txtureFile, bool bUseGameFolder);
static bool EditMayaFile(const char* mayaFile, const bool bExtractFromPak, const bool bUseGameFolder);
static bool EditFile(const char* filePath, const bool bExtrackFromPak, const bool bUseGameFolder);
//! dcc filename calculation and extraction sub-routines
static bool CalculateDccFilename(const QString& assetFilename, QString& dccFilename);
//! Reformat filter string for (MFC) CFileDialog style file filtering
static void FormatFilterString(QString& filter);
//! Open file selection dialog.
static bool SelectFile(const QString& fileSpec, const QString& searchFolder, QString& fullFileName);
//! Open file selection dialog.
static bool SelectFiles(const QString& fileSpec, const QString& searchFolder, QStringList& files);
//! Display OpenFile dialog and allow to select multiple files.
//! @return true if selected, false if canceled.
//! @outputFile Inputs and Outputs filename.
/*
static bool SelectSingleFile(IFileUtil::ECustomFileType fileType, QString& outputFile, const QString& filter = "", const QString& initialDir = "");
static bool SelectSingleFile(IFileUtil::ECustomFileType fileType, char* outputFile, int outputSize, const char* filter = "", const char* initialDir = "");
static bool SelectSingleFile(IFileUtil::ECustomFileType fileType, QString& outputFile, const QString& filter = {}, const QString& initialDir = {});
*/
static bool SelectSaveFile(const QString& fileFilter, const QString& defaulExtension, const QString& startFolder, QString& fileName);
//! Attempt to make a file writable
static bool OverwriteFile(const QString& filename);
//! Checks out the file from source control API. Blocks until completed
static bool CheckoutFile(const char* filename, QWidget* parentWindow = nullptr);
//! Discard changes to a file from source control API. Blocks until completed
static bool RevertFile(const char* filename, QWidget* parentWindow = nullptr);
//! Renames (moves) a file through the source control API. Blocks until completed
static bool RenameFile(const char* sourceFile, const char* targetFile, QWidget* parentWindow = nullptr);
//! Deletes a file using source control API. Blocks until completed.
static bool DeleteFromSourceControl(const char* filename, QWidget* parentWindow = nullptr);
//! Attempts to get the latest version of a file from source control. Blocks until completed
static bool GetLatestFromSourceControl(const char* filename, QWidget* parentWindow = nullptr);
//! Gather information about a file using the source control API. Blocks until completed
static bool GetFileInfoFromSourceControl(const char* filename, AzToolsFramework::SourceControlFileInfo& fileInfo, QWidget* parentWindow = nullptr);
//! Creates this directory if it doesn't exist. Returns false if the director doesn't exist and couldn't be created.
static bool CreateDirectory(const char* dir);
//! Makes a backup file.
static void BackupFile(const char* filename);
//! Makes a backup file, marked with a datestamp, e.g. myfile.20071014.093320.xml
//! If bUseBackupSubDirectory is true, moves backup file into a relative subdirectory "backups"
static void BackupFileDated(const char* filename, bool bUseBackupSubDirectory = false);
// ! Added deltree as a copy from the function found in Crypak.
static bool Deltree(const char* szFolder, bool bRecurse);
// Checks if a file or directory exist.
// We are using 3 functions here in order to make the names more instructive for the programmers.
// Those functions only work for OS files and directories.
static bool Exists(const QString& strPath, bool boDirectory, IFileUtil::FileDesc* pDesc = nullptr);
static bool FileExists(const QString& strFilePath, IFileUtil::FileDesc* pDesc = nullptr);
static bool PathExists(const QString& strPath);
static bool GetDiskFileSize(const char* pFilePath, uint64& rOutSize);
// This function should be used only with physical files.
static bool IsFileExclusivelyAccessable(const QString& strFilePath);
// Creates the entire path, if needed.
static bool CreatePath(const QString& strPath);
// Attempts to delete a file (if read only it will set its attributes to normal first).
static bool DeleteFile(const QString& strPath);
// Attempts to remove a directory (if read only it will set its attributes to normal first).
static bool RemoveDirectory(const QString& strPath);
// Calls predicate() with each entry in the directory
static void ForEach(const QString& path, std::function<void(const QString&)> predicate, bool recurse = true);
//! Copies all the elements from the source directory to the target directory.
//! It doesn't copy the source folder to the target folder, only it's contents.
//! THIS FUNCTION IS NOT DESIGNED FOR MULTI-THREADED USAGE
//! The ignore list can either take a single string of a file name or folder name or it can take PIPE separated names ("something|somethingelse")
//! It is not a pattern match, merely a file or folder name match.
static IFileUtil::ECopyTreeResult CopyTree(const QString& strSourceDirectory, const QString& strTargetDirectory, bool boRecurse = true, bool boConfirmOverwrite = false, const char* const ignoreFilesAndFolders = nullptr);
//////////////////////////////////////////////////////////////////////////
// @param LPPROGRESS_ROUTINE pfnProgress - called by the system to notify of file copy progress
// @param LPBOOL pbCancel - when the contents of this BOOL are set to TRUE, the system cancels the copy operation
static IFileUtil::ECopyTreeResult CopyFile(const QString& strSourceFile, const QString& strTargetFile, bool boConfirmOverwrite = false, ProgressRoutine pfnProgress = nullptr, bool* pbCancel = nullptr);
// As we don't have a FileUtil interface here, we have to duplicate some code :-( in order to keep
// function calls clean.
// Moves all the elements from the source directory to the target directory.
// It doesn't move the source folder to the target folder, only it's contents.
// THIS FUNCTION IS NOT DESIGNED FOR MULTI-THREADED USAGE
static IFileUtil::ECopyTreeResult MoveTree(const QString& strSourceDirectory, const QString& strTargetDirectory, bool boRecurse = true, bool boConfirmOverwrite = false);
// Show Popup Menu with file commands include Source Control commands
// filename: a name of file without path
// fullGamePath: a game path to folder like "/Game/Objects" without filename
// wnd: pointer to window class, can be nullptr
// isSelected: output value indicated if Select menu item was chosen, if pointer is 0 - no Select menu item.
// pItems: you can specify additional menu items and get the result of selection using this parameter.
// return false if source control operation failed
static QString PopupQMenu(const QString& filename, const QString& fullGamePath, QWidget* parent);
static QString PopupQMenu(const QString& filename, const QString& fullGamePath, QWidget* parent, bool* pIsSelected, const QStringList& extraItemsFront);
static QString PopupQMenu(const QString& filename, const QString& fullGamePath, QWidget* parent, bool* pIsSelected, const QStringList& extraItemsFront, const QStringList& extraItemsBack);
static void PopulateQMenu(QWidget* caller, QMenu* menu, const QString& filename, const QString& fullGamePath);
static void GatherAssetFilenamesFromLevel(std::set<QString>& rOutFilenames, bool bMakeLowerCase = false, bool bMakeUnixPath = false);
// Get file attributes include source control attributes if available
static uint32 GetAttributes(const char* filename, bool bUseSourceControl = true);
// Returns true if the files have the same content, false otherwise
static bool CompareFiles(const QString& strFilePath1, const QString& strFilePath2);
// Sort Columns( Ascending/Descending )
static bool SortAscendingFileNames(const IFileUtil::FileDesc& desc1, const IFileUtil::FileDesc& desc2);
static bool SortDescendingFileNames(const IFileUtil::FileDesc& desc1, const IFileUtil::FileDesc& desc2);
static bool SortAscendingDates(const IFileUtil::FileDesc& desc1, const IFileUtil::FileDesc& desc2);
static bool SortDescendingDates(const IFileUtil::FileDesc& desc1, const IFileUtil::FileDesc& desc2);
static bool SortAscendingSizes(const IFileUtil::FileDesc& desc1, const IFileUtil::FileDesc& desc2);
static bool SortDescendingSizes(const IFileUtil::FileDesc& desc1, const IFileUtil::FileDesc& desc2);
// Return true is the filepath is a absolute path
static bool IsAbsPath(const QString& filepath);
private:
// True means to use the custom file dialog, false means to use the smart file open dialog.
static bool s_singleFileDlgPref[IFileUtil::EFILE_TYPE_LAST];
static bool s_multiFileDlgPref[IFileUtil::EFILE_TYPE_LAST];
// Keep this variant of this method private! pIsSelected is captured in a lambda, and so requires menu use exec() and never use show()
static void PopulateQMenu(QWidget* caller, QMenu* menu, const QString& filename, const QString& fullGamePath, bool* pIsSelected);
static bool ExtractDccFilenameFromAssetDatabase(const QString& assetFilename, QString& dccFilename);
static bool ExtractDccFilenameUsingNamingConventions(const QString& assetFilename, QString& dccFilename);
};
class CAutoRestorePrimaryCDRoot
{
public:
~CAutoRestorePrimaryCDRoot();
};
//
// A helper for creating a temp file to write to, then copying that over the destination
// file only if it changes (to avoid requiring the user to check out source controlled
// file unnecessarily)
//
class SANDBOX_API CTempFileHelper
{
public:
CTempFileHelper(const char* pFileName);
~CTempFileHelper();
// Gets the path to the temp file that should be written to
const QString& GetTempFilePath() { return m_tempFileName; }
// After the temp file has been written and closed, this should be called to update
// the destination file.
// If bBackup is true CFileUtil::BackupFile will be called if the file has changed.
bool UpdateFile(bool bBackup);
private:
QString m_fileName;
QString m_tempFileName;
};
+187
View File
@@ -0,0 +1,187 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "FileUtil_impl.h"
bool CFileUtil_impl::ScanDirectory(const QString& path, const QString& fileSpec, FileArray& files, bool recursive, bool addDirAlso, ScanDirectoryUpdateCallBack updateCB, bool bSkipPaks)
{
return CFileUtil::ScanDirectory(path, fileSpec, files, recursive, addDirAlso, updateCB, bSkipPaks);
}
void CFileUtil_impl::ShowInExplorer(const QString& path)
{
CFileUtil::ShowInExplorer(path);
}
bool CFileUtil_impl::CompileLuaFile(const char* luaFilename)
{
return CFileUtil::CompileLuaFile(luaFilename);
}
bool CFileUtil_impl::ExtractFile(QString& file, bool bMsgBoxAskForExtraction, const char* pDestinationFilename)
{
return CFileUtil::ExtractFile(file, bMsgBoxAskForExtraction, pDestinationFilename);
}
void CFileUtil_impl::EditTextFile(const char* txtFile, int line, ETextFileType fileType)
{
CFileUtil::EditTextFile(txtFile, line, fileType);
}
void CFileUtil_impl::EditTextureFile(const char* txtureFile, bool bUseGameFolder)
{
CFileUtil::EditTextureFile(txtureFile, bUseGameFolder);
}
bool CFileUtil_impl::CalculateDccFilename(const QString& assetFilename, QString& dccFilename)
{
return CFileUtil::CalculateDccFilename(assetFilename, dccFilename);
}
void CFileUtil_impl::FormatFilterString(QString& filter)
{
CFileUtil::FormatFilterString(filter);
}
bool CFileUtil_impl::SelectSaveFile(const QString& fileFilter, const QString& defaulExtension, const QString& startFolder, QString& fileName)
{
return CFileUtil::SelectSaveFile(fileFilter, defaulExtension, startFolder, fileName);
}
bool CFileUtil_impl::OverwriteFile(const QString& filename)
{
return CFileUtil::OverwriteFile(filename);
}
bool CFileUtil_impl::CheckoutFile(const char* filename, QWidget* parentWindow)
{
return CFileUtil::CheckoutFile(filename, parentWindow);
}
bool CFileUtil_impl::RevertFile(const char* filename, QWidget* parentWindow)
{
return CFileUtil::RevertFile(filename, parentWindow);
}
bool CFileUtil_impl::RenameFile(const char* sourceFile, const char* targetFile, QWidget* parentWindow)
{
return CFileUtil::RenameFile(sourceFile, targetFile, parentWindow);
}
bool CFileUtil_impl::DeleteFromSourceControl(const char* filename, QWidget* parentWindow)
{
return CFileUtil::DeleteFromSourceControl(filename, parentWindow);
}
bool CFileUtil_impl::GetLatestFromSourceControl(const char* filename, QWidget* parentWindow)
{
return CFileUtil::GetLatestFromSourceControl(filename, parentWindow);
}
bool CFileUtil_impl::GetFileInfoFromSourceControl(const char* filename, AzToolsFramework::SourceControlFileInfo& fileInfo, QWidget* parentWindow)
{
return CFileUtil::GetFileInfoFromSourceControl(filename, fileInfo, parentWindow);
}
void CFileUtil_impl::CreateDirectory(const char* dir)
{
CFileUtil::CreateDirectory(dir);
}
void CFileUtil_impl::BackupFile(const char* filename)
{
CFileUtil::BackupFile(filename);
}
void CFileUtil_impl::BackupFileDated(const char* filename, bool bUseBackupSubDirectory)
{
CFileUtil::BackupFileDated(filename, bUseBackupSubDirectory);
}
bool CFileUtil_impl::Deltree(const char* szFolder, bool bRecurse)
{
return CFileUtil::Deltree(szFolder, bRecurse);
}
bool CFileUtil_impl::Exists(const QString& strPath, bool boDirectory, FileDesc* pDesc)
{
return CFileUtil::Exists(strPath, boDirectory, pDesc);
}
bool CFileUtil_impl::FileExists(const QString& strFilePath, FileDesc* pDesc)
{
return CFileUtil::FileExists(strFilePath, pDesc);
}
bool CFileUtil_impl::PathExists(const QString& strPath)
{
return CFileUtil::PathExists(strPath);
}
bool CFileUtil_impl::GetDiskFileSize(const char* pFilePath, uint64& rOutSize)
{
return CFileUtil::GetDiskFileSize(pFilePath, rOutSize);
}
bool CFileUtil_impl::IsFileExclusivelyAccessable(const QString& strFilePath)
{
return CFileUtil::IsFileExclusivelyAccessable(strFilePath);
}
bool CFileUtil_impl::CreatePath(const QString& strPath)
{
return CFileUtil::CreatePath(strPath);
}
bool CFileUtil_impl::DeleteFile(const QString& strPath)
{
return CFileUtil::DeleteFile(strPath);
}
bool CFileUtil_impl::RemoveDirectory(const QString& strPath)
{
return CFileUtil::RemoveDirectory(strPath);
}
IFileUtil::ECopyTreeResult CFileUtil_impl::CopyTree(const QString& strSourceDirectory, const QString& strTargetDirectory, bool boRecurse, bool boConfirmOverwrite)
{
return CFileUtil::CopyTree(strSourceDirectory, strTargetDirectory, boRecurse, boConfirmOverwrite);
}
IFileUtil::ECopyTreeResult CFileUtil_impl::CopyFile(const QString& strSourceFile, const QString& strTargetFile, bool boConfirmOverwrite, ProgressRoutine pfnProgress, bool* pbCancel)
{
return CFileUtil::CopyFile(strSourceFile, strTargetFile, boConfirmOverwrite, pfnProgress, pbCancel);
}
IFileUtil::ECopyTreeResult CFileUtil_impl::MoveTree(const QString& strSourceDirectory, const QString& strTargetDirectory, bool boRecurse, bool boConfirmOverwrite)
{
return CFileUtil::MoveTree(strSourceDirectory, strTargetDirectory, boRecurse, boConfirmOverwrite);
}
void CFileUtil_impl::GatherAssetFilenamesFromLevel(std::set<QString>& rOutFilenames, bool bMakeLowerCase, bool bMakeUnixPath)
{
CFileUtil::GatherAssetFilenamesFromLevel(rOutFilenames, bMakeLowerCase, bMakeUnixPath);
}
uint32 CFileUtil_impl::GetAttributes(const char* filename, bool bUseSourceControl)
{
return CFileUtil::GetAttributes(filename, bUseSourceControl);
}
bool CFileUtil_impl::CompareFiles(const QString& strFilePath1, const QString& strFilePath2)
{
return CFileUtil::CompareFiles(strFilePath1, strFilePath2);
}
QString CFileUtil_impl::GetPath(const QString& path)
{
return Path::GetPath(path);
}
+133
View File
@@ -0,0 +1,133 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#pragma once
#include "Include/IFileUtil.h"
#ifdef DeleteFile
#undef DeleteFile
#endif
#ifdef CreateDirectory
#undef CreateDirectory
#endif
#ifdef RemoveDirectory
#undef RemoveDirectory
#endif
#ifdef CopyFile
#undef CopyFile
#endif
AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING
class SANDBOX_API CFileUtil_impl
: public IFileUtil
{
AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING
public:
bool ScanDirectory(const QString& path, const QString& fileSpec, FileArray& files, bool recursive = true, bool addDirAlso = false, ScanDirectoryUpdateCallBack updateCB = nullptr, bool bSkipPaks = false) override;
void ShowInExplorer(const QString& path) override;
bool CompileLuaFile(const char* luaFilename) override;
bool ExtractFile(QString& file, bool bMsgBoxAskForExtraction = true, const char* pDestinationFilename = nullptr) override;
void EditTextFile(const char* txtFile, int line = 0, ETextFileType fileType = FILE_TYPE_SCRIPT) override;
void EditTextureFile(const char* txtureFile, bool bUseGameFolder) override;
//! dcc filename calculation and extraction sub-routines
bool CalculateDccFilename(const QString& assetFilename, QString& dccFilename) override;
//! Reformat filter string for (MFC) CFileDialog style file filtering
void FormatFilterString(QString& filter) override;
bool SelectSaveFile(const QString& fileFilter, const QString& defaulExtension, const QString& startFolder, QString& fileName) override;
//! Attempt to make a file writable
bool OverwriteFile(const QString& filename) override;
//! Checks out the file from source control API. Blocks until completed
bool CheckoutFile(const char* filename, QWidget* parentWindow = nullptr) override;
//! Discard changes to a file from source control API. Blocks until completed
bool RevertFile(const char* filename, QWidget* parentWindow = nullptr) override;
//! Renames (moves) a file through the source control API. Blocks until completed
bool RenameFile(const char* sourceFile, const char* targetFile, QWidget* parentWindow = nullptr) override;
//! Deletes a file using source control API. Blocks until completed.
bool DeleteFromSourceControl(const char* filename, QWidget* parentWindow = nullptr) override;
//! Attempts to get the latest version of a file from source control. Blocks until completed
bool GetLatestFromSourceControl(const char* filename, QWidget* parentWindow = nullptr) override;
//! Gather information about a file using the source control API. Blocks until completed
bool GetFileInfoFromSourceControl(const char* filename, AzToolsFramework::SourceControlFileInfo& fileInfo, QWidget* parentWindow = nullptr) override;
//! Creates this directory.
void CreateDirectory(const char* dir) override;
//! Makes a backup file.
void BackupFile(const char* filename) override;
//! Makes a backup file, marked with a datestamp, e.g. myfile.20071014.093320.xml
//! If bUseBackupSubDirectory is true, moves backup file into a relative subdirectory "backups"
void BackupFileDated(const char* filename, bool bUseBackupSubDirectory = false) override;
// ! Added deltree as a copy from the function found in Crypak.
bool Deltree(const char* szFolder, bool bRecurse) override;
// Checks if a file or directory exist.
// We are using 3 functions here in order to make the names more instructive for the programmers.
// Those functions only work for OS files and directories.
bool Exists(const QString& strPath, bool boDirectory, FileDesc* pDesc = nullptr) override;
bool FileExists(const QString& strFilePath, FileDesc* pDesc = nullptr) override;
bool PathExists(const QString& strPath) override;
bool GetDiskFileSize(const char* pFilePath, uint64& rOutSize) override;
// This function should be used only with physical files.
bool IsFileExclusivelyAccessable(const QString& strFilePath) override;
// Creates the entire path, if needed.
bool CreatePath(const QString& strPath) override;
// Attempts to delete a file (if read only it will set its attributes to normal first).
bool DeleteFile(const QString& strPath) override;
// Attempts to remove a directory (if read only it will set its attributes to normal first).
bool RemoveDirectory(const QString& strPath) override;
// Copies all the elements from the source directory to the target directory.
// It doesn't copy the source folder to the target folder, only it's contents.
// THIS FUNCTION IS NOT DESIGNED FOR MULTI-THREADED USAGE
ECopyTreeResult CopyTree(const QString& strSourceDirectory, const QString& strTargetDirectory, bool boRecurse = true, bool boConfirmOverwrite = false) override;
//////////////////////////////////////////////////////////////////////////
// @param LPPROGRESS_ROUTINE pfnProgress - called by the system to notify of file copy progress
// @param LPBOOL pbCancel - when the contents of this BOOL are set to TRUE, the system cancels the copy operation
ECopyTreeResult CopyFile(const QString& strSourceFile, const QString& strTargetFile, bool boConfirmOverwrite = false, ProgressRoutine pfnProgress = NULL, bool* pbCancel = NULL) override;
// As we don't have a FileUtil interface here, we have to duplicate some code :-( in order to keep
// function calls clean.
// Moves all the elements from the source directory to the target directory.
// It doesn't move the source folder to the target folder, only it's contents.
// THIS FUNCTION IS NOT DESIGNED FOR MULTI-THREADED USAGE
ECopyTreeResult MoveTree(const QString& strSourceDirectory, const QString& strTargetDirectory, bool boRecurse = true, bool boConfirmOverwrite = false) override;
void GatherAssetFilenamesFromLevel(std::set<QString>& rOutFilenames, bool bMakeLowerCase = false, bool bMakeUnixPath = false) override;
// Get file attributes include source control attributes if available
uint32 GetAttributes(const char* filename, bool bUseSourceControl = true) override;
// Returns true if the files have the same content, false otherwise
bool CompareFiles(const QString& strFilePath1, const QString& strFilePath2) override;
// Extract path from full specified file path.
QString GetPath(const QString& path) override;
};
+219
View File
@@ -0,0 +1,219 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "GdiUtil.h"
// Qt
#include <QPainter>
#include <QMessageBox>
bool ComputeThumbsLayoutInfo(float aContainerWidth, float aThumbWidth, float aMargin, UINT aThumbCount, UINT& rThumbsPerRow, float& rNewMargin)
{
rThumbsPerRow = 0;
rNewMargin = 0;
if (aThumbWidth <= 0 || aMargin <= 0 || (aThumbWidth + aMargin * 2) <= 0)
{
return false;
}
if (aContainerWidth <= 0)
{
return true;
}
rThumbsPerRow = (int) aContainerWidth / (aThumbWidth + aMargin * 2);
if ((aThumbWidth + aMargin * 2) * aThumbCount < aContainerWidth)
{
rNewMargin = aMargin;
}
else
{
if (rThumbsPerRow > 0)
{
rNewMargin = (aContainerWidth - rThumbsPerRow * aThumbWidth);
if (rNewMargin > 0)
{
rNewMargin = (float)rNewMargin / rThumbsPerRow / 2.0f;
}
}
}
return true;
}
QColor ScaleColor(const QColor& c, float aScale)
{
QColor aColor = c;
if (!aColor.isValid())
{
// help out scaling, by starting at very low black
aColor = QColor(1, 1, 1);
}
int r = aColor.red();
int g = aColor.green();
int b = aColor.blue();
r *= aScale;
g *= aScale;
b *= aScale;
return QColor(CLAMP(r, 0, 255), CLAMP(g, 0, 255), CLAMP(b, 0, 255));
}
CAlphaBitmap::CAlphaBitmap()
{
m_width = m_height = 0;
}
CAlphaBitmap::~CAlphaBitmap()
{
Free();
}
bool CAlphaBitmap::Create(void* pData, UINT aWidth, UINT aHeight, bool bVerticalFlip, bool bPremultiplyAlpha)
{
if (!aWidth || !aHeight)
{
return false;
}
m_bmp = QImage(aWidth, aHeight, QImage::Format_RGBA8888);
if (m_bmp.isNull())
{
return false;
}
std::vector<UINT> vBuffer;
if (pData)
{
// copy over the raw 32bpp data
bVerticalFlip = !bVerticalFlip; // in Qt, the flip is not required. Still, keep the API behaving the same
if (bVerticalFlip)
{
UINT nBufLen = aWidth * aHeight;
vBuffer.resize(nBufLen);
if (IsBadReadPtr(pData, nBufLen * 4))
{
//TODO: remove after testing alot the browser, it doesnt happen anymore
QMessageBox::critical(QApplication::activeWindow(), QString(), QObject::tr("Bad image data ptr!"));
Free();
return false;
}
assert(!vBuffer.empty());
if (vBuffer.empty())
{
Free();
return false;
}
UINT scanlineSize = aWidth * 4;
for (UINT i = 0, iCount = aHeight; i < iCount; ++i)
{
// top scanline position
UINT* pTopScanPos = (UINT*)&vBuffer[0] + i * aWidth;
// bottom scanline position
UINT* pBottomScanPos = (UINT*)pData + (aHeight - i - 1) * aWidth;
// save a scanline from top
memcpy(pTopScanPos, pBottomScanPos, scanlineSize);
}
pData = &vBuffer[0];
}
// premultiply alpha, AlphaBlend GDI expects it
if (bPremultiplyAlpha)
{
for (UINT y = 0; y < aHeight; ++y)
{
BYTE* pPixel = (BYTE*) pData + aWidth * 4 * y;
for (UINT x = 0; x < aWidth; ++x)
{
pPixel[0] = ((int)pPixel[0] * pPixel[3] + 127) >> 8;
pPixel[1] = ((int)pPixel[1] * pPixel[3] + 127) >> 8;
pPixel[2] = ((int)pPixel[2] * pPixel[3] + 127) >> 8;
pPixel += 4;
}
}
}
memcpy(m_bmp.bits(), pData, aWidth * aHeight * 4);
if (m_bmp.isNull())
{
return false;
}
}
else
{
m_bmp.fill(Qt::transparent);
}
// we dont need this screen DC anymore
m_width = aWidth;
m_height = aHeight;
return true;
}
QImage& CAlphaBitmap::GetBitmap()
{
return m_bmp;
}
void CAlphaBitmap::Free()
{
}
UINT CAlphaBitmap::GetWidth()
{
return m_width;
}
UINT CAlphaBitmap::GetHeight()
{
return m_height;
}
void CheckerboardFillRect(QPainter* pGraphics, const QRect& rRect, int checkDiameter, const QColor& aColor1, const QColor& aColor2)
{
pGraphics->save();
pGraphics->setClipRect(rRect);
// Create a checkerboard background for easier readability
pGraphics->fillRect(rRect, aColor1);
QBrush lightBrush(aColor2);
// QRect bottom/right methods are short one unit for legacy reasons. Compute bottomr/right of the rectange ourselves to get the full size.
const int rectRight = rRect.x() + rRect.width();
const int rectBottom = rRect.y() + rRect.height();
for (int i = rRect.left(); i < rectRight; i += checkDiameter)
{
for (int j = rRect.top(); j < rectBottom; j += checkDiameter)
{
if ((i / checkDiameter) % 2 ^ (j / checkDiameter) % 2)
{
pGraphics->fillRect(QRect(i, j, checkDiameter, checkDiameter), lightBrush);
}
}
}
pGraphics->restore();
}
+63
View File
@@ -0,0 +1,63 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Utilitarian classes for double buffer GDI rendering and 32bit bitmaps
#ifndef CRYINCLUDE_EDITOR_UTIL_GDIUTIL_H
#define CRYINCLUDE_EDITOR_UTIL_GDIUTIL_H
#pragma once
//! function used to compute thumbs per row and spacing, used in asset browser and other tools where thumb layout is needed and maybe GDI canvas used
//! \param aContainerWidth the thumbs' container width
//! \param aThumbWidth the thumb image width
//! \param aMargin the thumb default minimum horizontal margin
//! \param aThumbCount the thumb count
//! \param rThumbsPerRow returned thumb count per single row
//! \param rNewMargin returned new computed margin between thumbs
//! \note The margin between thumbs will grow/shrink dynamically to keep up with the thumb count per row
bool ComputeThumbsLayoutInfo(float aContainerWidth, float aThumbWidth, float aMargin, UINT aThumbCount, UINT& rThumbsPerRow, float& rNewMargin);
QColor ScaleColor(const QColor& coor, float aScale);
//! This class loads alpha-channel bitmaps and holds a DC for use with AlphaBlend function
class CRYEDIT_API CAlphaBitmap
{
public:
CAlphaBitmap();
~CAlphaBitmap();
//! creates the bitmap from raw 32bpp data
//! \param pData the 32bpp raw image data, RGBA, can be NULL and it would create just an empty bitmap
//! \param aWidth the bitmap width
//! \param aHeight the bitmap height
bool Create(void* pData, UINT aWidth, UINT aHeight, bool bVerticalFlip = false, bool bPremultiplyAlpha = false);
//! \return the actual bitmap
QImage& GetBitmap();
//! free the bitmap and DC
void Free();
//! \return bitmap width
UINT GetWidth();
//! \return bitmap height
UINT GetHeight();
protected:
QImage m_bmp;
UINT m_width, m_height;
};
//! Fill a rectangle with a checkerboard pattern.
//! \param pGraphics The Graphics object used for drawing
//! \param rRect The rectangle to be filled
//! \param checkDiameter the diameter of the check squares
//! \param aColor1 the color that starts in the top left corner check square
//! \param aColor2 the second color used for check squares
void CheckerboardFillRect(QPainter* pGraphics, const QRect& rRect, int checkDiameter, const QColor& aColor1, const QColor& aColor2);
#endif // CRYINCLUDE_EDITOR_UTIL_GDIUTIL_H
+315
View File
@@ -0,0 +1,315 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "GeometryUtil.h"
#if 0
struct SPointSorter
{
SPointSorter(const Vec3& pt)
: pt(pt) {}
bool operator()(const Vec3& lhs, const Vec3& rhs)
{
float isLeft = IsLeft(pt, lhs, rhs);
if (isLeft > 0.0f)
{
return true;
}
else if (isLeft < 0.0f)
{
return false;
}
else
{
return (lhs - pt).GetLengthSquared2D() < (rhs - pt).GetLengthSquared2D();
}
}
const Vec3 pt;
};
//===================================================================
// ConvexHull2D
// Implements Graham's scan
//===================================================================
void ConvexHull2DGraham(std::vector<Vec3>& ptsOut, const std::vector<Vec3>& ptsIn)
{
FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI);
const unsigned nPtsIn = ptsIn.size();
if (nPtsIn < 3)
{
ptsOut = ptsIn;
return;
}
unsigned iBotRight = 0;
for (unsigned iPt = 1; iPt < nPtsIn; ++iPt)
{
if (ptsIn[iPt].y < ptsIn[iBotRight].y)
{
iBotRight = iPt;
}
else if (ptsIn[iPt].y == ptsIn[iBotRight].y && ptsIn[iPt].x < ptsIn[iBotRight].x)
{
iBotRight = iPt;
}
}
static std::vector<Vec3> ptsSorted; // avoid memory allocation
ptsSorted.assign(ptsIn.begin(), ptsIn.end());
std::swap(ptsSorted[0], ptsSorted[iBotRight]);
{
FRAME_PROFILER("SORT Graham", gEnv->pSystem, PROFILE_AI)
std::sort(ptsSorted.begin() + 1, ptsSorted.end(), SPointSorter(ptsSorted[0]));
}
ptsSorted.erase(std::unique(ptsSorted.begin(), ptsSorted.end(), ptEqual), ptsSorted.end());
const unsigned nPtsSorted = ptsSorted.size();
if (nPtsSorted < 3)
{
ptsOut = ptsSorted;
return;
}
ptsOut.resize(0);
ptsOut.push_back(ptsSorted[0]);
ptsOut.push_back(ptsSorted[1]);
unsigned int i = 2;
while (i < nPtsSorted)
{
if (ptsOut.size() <= 1)
{
AIWarning("Badness in ConvexHull2D");
AILogComment("i = %d ptsIn = ", i);
for (unsigned j = 0; j < ptsIn.size(); ++j)
{
AILogComment("%6.3f, %6.3f, %6.3f", ptsIn[j].x, ptsIn[j].y, ptsIn[j].z);
}
AILogComment("ptsSorted = ");
for (unsigned j = 0; j < ptsSorted.size(); ++j)
{
AILogComment("%6.3f, %6.3f, %6.3f", ptsSorted[j].x, ptsSorted[j].y, ptsSorted[j].z);
}
ptsOut.resize(0);
return;
}
const Vec3& pt1 = ptsOut[ptsOut.size() - 1];
const Vec3& pt2 = ptsOut[ptsOut.size() - 2];
const Vec3& p = ptsSorted[i];
float isLeft = IsLeft(pt2, pt1, p);
if (isLeft > 0.0f)
{
ptsOut.push_back(p);
++i;
}
else if (isLeft < 0.0f)
{
ptsOut.pop_back();
}
else
{
ptsOut.pop_back();
ptsOut.push_back(p);
++i;
}
}
}
#endif
//===================================================================
// IsLeft: tests if a point is Left|On|Right of an infinite line.
// Input: three points P0, P1, and P2
// Return: >0 for P2 left of the line through P0 and P1
// =0 for P2 on the line
// <0 for P2 right of the line
//===================================================================
inline float IsLeft(Vec3 P0, Vec3 P1, const Vec3& P2)
{
bool swap = false;
if (P0.x < P1.x)
{
swap = true;
}
else if (P0.x == P1.x && P0.y < P1.y)
{
swap = true;
}
if (swap)
{
std::swap(P0, P1);
}
float res = (P1.x - P0.x) * (P2.y - P0.y) - (P2.x - P0.x) * (P1.y - P0.y);
const float tol = 0.0000f;
if (res > tol || res < -tol)
{
return swap ? -res : res;
}
else
{
return 0.0f;
}
}
inline bool ptEqual(const Vec3& lhs, const Vec3& rhs)
{
const float tol = 0.01f;
return (fabs(lhs.x - rhs.x) < tol) && (fabs(lhs.y - rhs.y) < tol);
}
inline float IsLeftAndrew(const Vec3& p0, const Vec3& p1, const Vec3& p2)
{
return (p1.x - p0.x) * (p2.y - p0.y) - (p2.x - p0.x) * (p1.y - p0.y);
}
inline bool PointSorterAndrew(const Vec3& lhs, const Vec3& rhs)
{
if (lhs.x < rhs.x)
{
return true;
}
if (lhs.x > rhs.x)
{
return false;
}
return lhs.y < rhs.y;
}
//===================================================================
// ConvexHull2D
// Implements Andrew's algorithm
//
// Copyright 2001, softSurfer (www.softsurfer.com)
// This code may be freely used and modified for any purpose
// providing that this copyright notice is included with it.
// SoftSurfer makes no warranty for this code, and cannot be held
// liable for any real or imagined damage resulting from its use.
// Users of this code must verify correctness for their application.
//===================================================================
SANDBOX_API void ConvexHull2DAndrew(std::vector<Vec3>& ptsOut, const std::vector<Vec3>& ptsIn)
{
FUNCTION_PROFILER(gEnv->pSystem, PROFILE_AI);
const int n = (int)ptsIn.size();
if (n < 3)
{
ptsOut = ptsIn;
return;
}
std::vector<Vec3> P = ptsIn;
{
FRAME_PROFILER("SORT Andrew", gEnv->pSystem, PROFILE_AI)
std::sort(P.begin(), P.end(), PointSorterAndrew);
}
// the output array ptsOut[] will be used as the stack
int i;
ptsOut.clear();
ptsOut.reserve(P.size());
// Get the indices of points with min x-coord and min|max y-coord
int minmin = 0, minmax;
float xmin = P[0].x;
for (i = 1; i < n; i++)
{
if (P[i].x != xmin)
{
break;
}
}
minmax = i - 1;
if (minmax == n - 1)
{
// degenerate case: all x-coords == xmin
ptsOut.push_back(P[minmin]);
if (P[minmax].y != P[minmin].y) // a nontrivial segment
{
ptsOut.push_back(P[minmax]);
}
ptsOut.push_back(P[minmin]); // add polygon endpoint
return;
}
// Get the indices of points with max x-coord and min|max y-coord
int maxmin, maxmax = n - 1;
float xmax = P[n - 1].x;
for (i = n - 2; i >= 0; i--)
{
if (P[i].x != xmax)
{
break;
}
}
maxmin = i + 1;
// Compute the lower hull on the stack H
ptsOut.push_back(P[minmin]); // push minmin point onto stack
i = minmax;
while (++i <= maxmin)
{
// the lower line joins P[minmin] with P[maxmin]
if (IsLeftAndrew(P[minmin], P[maxmin], P[i]) >= 0 && i < maxmin)
{
continue; // ignore P[i] above or on the lower line
}
while ((int)ptsOut.size() > 1) // there are at least 2 points on the stack
{
// test if P[i] is left of the line at the stack top
if (IsLeftAndrew(ptsOut[ptsOut.size() - 2], ptsOut.back(), P[i]) > 0)
{
break; // P[i] is a new hull vertex
}
else
{
ptsOut.pop_back(); // pop top point off stack
}
}
ptsOut.push_back(P[i]); // push P[i] onto stack
}
// Next, compute the upper hull on the stack H above the bottom hull
if (maxmax != maxmin) // if distinct xmax points
{
ptsOut.push_back(P[maxmax]); // push maxmax point onto stack
}
int bot = (int)ptsOut.size() - 1; // the bottom point of the upper hull stack
i = maxmin;
while (--i >= minmax)
{
// the upper line joins P[maxmax] with P[minmax]
if (IsLeftAndrew(P[maxmax], P[minmax], P[i]) >= 0 && i > minmax)
{
continue; // ignore P[i] below or on the upper line
}
while ((int)ptsOut.size() > bot + 1) // at least 2 points on the upper stack
{
// test if P[i] is left of the line at the stack top
if (IsLeftAndrew(ptsOut[ptsOut.size() - 2], ptsOut.back(), P[i]) > 0)
{
break; // P[i] is a new hull vertex
}
else
{
ptsOut.pop_back(); // pop top po2int off stack
}
}
ptsOut.push_back(P[i]); // push P[i] onto stack
}
if (minmax != minmin)
{
ptsOut.push_back(P[minmin]); // push joining endpoint onto stack
}
if (!ptsOut.empty() && ptEqual(ptsOut.front(), ptsOut.back()))
{
ptsOut.pop_back();
}
}
+26
View File
@@ -0,0 +1,26 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Geometry utilities
#ifndef CRYINCLUDE_EDITOR_UTIL_GEOMETRYUTIL_H
#define CRYINCLUDE_EDITOR_UTIL_GEOMETRYUTIL_H
#pragma once
void ConvexHull2DGraham(std::vector<Vec3>& ptsOut, const std::vector<Vec3>& ptsIn);
//! Generates 2D convex hull from ptsIn using Andrew's algorithm.
SANDBOX_API void ConvexHull2DAndrew(std::vector<Vec3>& ptsOut, const std::vector<Vec3>& ptsIn);
//! Generates 2D convex hull from ptsIn
inline void ConvexHull2D(std::vector<Vec3>& ptsOut, const std::vector<Vec3>& ptsIn)
{
// [Mikko] Note: The convex hull calculation is bound by the sorting.
// The sort in Andrew's seems to be about 3-4x faster than Graham's--using Andrew's for now.
ConvexHull2DAndrew(ptsOut, ptsIn);
}
#endif // CRYINCLUDE_EDITOR_UTIL_GEOMETRYUTIL_H
+16
View File
@@ -0,0 +1,16 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "GuidUtil.h"
//////////////////////////////////////////////////////////////////////////
const GUID GuidUtil::NullGuid = {
0, 0, 0, { 0, 0, 0, 0, 0, 0, 0, 0 }
};
+78
View File
@@ -0,0 +1,78 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Utility functions to work with GUIDs.
#ifndef CRYINCLUDE_EDITOR_UTIL_GUIDUTIL_H
#define CRYINCLUDE_EDITOR_UTIL_GUIDUTIL_H
#pragma once
#include "AzCore/Math/Uuid.h"
struct GuidUtil
{
//! Convert GUID to string in the valid format.
//! The valid format for a GUID is {XXXXXXXX-XXXX-XXXX-XXXX-XXXXXXXXXXXX} where X is a hex digit.
static const char* ToString(REFGUID guid);
//! Convert from guid string in valid format to GUID class.
static GUID FromString(const char* guidString);
static bool IsEmpty(REFGUID guid);
static const GUID NullGuid;
};
/** Used to compare GUID keys.
*/
struct guid_less_predicate
{
bool operator()(REFGUID guid1, REFGUID guid2) const
{
return memcmp(&guid1, &guid2, sizeof(GUID)) < 0;
}
};
//////////////////////////////////////////////////////////////////////////
inline bool GuidUtil::IsEmpty(REFGUID guid)
{
return guid == NullGuid;
}
//////////////////////////////////////////////////////////////////////////
inline const char* GuidUtil::ToString(REFGUID guid)
{
static char guidString[64];
sprintf_s(guidString, "{%.8X-%.4X-%.4X-%.2X%.2X-%.2X%.2X%.2X%.2X%.2X%.2X}", guid.Data1, guid.Data2, guid.Data3, guid.Data4[0], guid.Data4[1],
guid.Data4[2], guid.Data4[3], guid.Data4[4], guid.Data4[5], guid.Data4[6], guid.Data4[7]);
return guidString;
}
//////////////////////////////////////////////////////////////////////////
inline GUID GuidUtil::FromString(const char* guidString)
{
GUID guid;
unsigned int d[8];
memset(&d, 0, sizeof(guid));
guid.Data1 = 0;
guid.Data2 = 0;
guid.Data3 = 0;
azsscanf(guidString, "{%8" SCNx32 "-%4hX-%4hX-%2X%2X-%2X%2X%2X%2X%2X%2X}",
&guid.Data1, &guid.Data2, &guid.Data3, &d[0], &d[1], &d[2], &d[3], &d[4], &d[5], &d[6], &d[7]);
guid.Data4[0] = d[0];
guid.Data4[1] = d[1];
guid.Data4[2] = d[2];
guid.Data4[3] = d[3];
guid.Data4[4] = d[4];
guid.Data4[5] = d[5];
guid.Data4[6] = d[6];
guid.Data4[7] = d[7];
return guid;
}
#endif // CRYINCLUDE_EDITOR_UTIL_GUIDUTIL_H
+19
View File
@@ -0,0 +1,19 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Handy macro when working with observers in interfaces
#ifndef CRYINCLUDE_EDITOR_UTIL_IOBSERVABLE_H
#define CRYINCLUDE_EDITOR_UTIL_IOBSERVABLE_H
#pragma once
#define DEFINE_OBSERVABLE_PURE_METHODS(observerClassName) \
virtual bool RegisterObserver(observerClassName * pObserver) = 0; \
virtual bool UnregisterObserver(observerClassName * pObserver) = 0; \
virtual void UnregisterAllObservers() = 0;
#endif // CRYINCLUDE_EDITOR_UTIL_IOBSERVABLE_H
+72
View File
@@ -0,0 +1,72 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_IXMLHISTORYMANAGER_H
#define CRYINCLUDE_EDITOR_UTIL_IXMLHISTORYMANAGER_H
#pragma once
// Helper class to handle Redo/Undo on set of Xml nodes
struct IXmlUndoEventHandler
{
virtual bool SaveToXml(XmlNodeRef& xmlNode) = 0;
virtual bool LoadFromXml(const XmlNodeRef& xmlNode) = 0;
virtual bool ReloadFromXml(const XmlNodeRef& xmlNode) = 0;
};
struct IXmlHistoryEventListener
{
enum EHistoryEventType
{
eHET_HistoryDeleted,
eHET_HistoryCleared,
eHET_HistorySaved,
eHET_VersionChanged,
eHET_VersionAdded,
eHET_HistoryInvalidate,
eHET_HistoryGroupChanged,
eHET_HistoryGroupAdded,
eHET_HistoryGroupRemoved,
};
virtual void OnEvent(EHistoryEventType event, void* pData = NULL) = 0;
};
struct IXmlHistoryView
{
virtual bool LoadXml(int typeId, const XmlNodeRef& xmlNode, IXmlUndoEventHandler*& pUndoEventHandler, uint32 userindex) = 0;
virtual void UnloadXml(int typeId) = 0;
};
struct IXmlHistoryManager
{
// Undo/Redo
virtual bool Undo() = 0;
virtual bool Redo() = 0;
virtual bool Goto(int historyNum) = 0;
virtual void RecordUndo(IXmlUndoEventHandler* pEventHandler, const char* desc) = 0;
virtual void UndoEventHandlerDestroyed(IXmlUndoEventHandler* pEventHandler, uint32 typeId = 0, bool destoryForever = false) = 0;
virtual void RestoreUndoEventHandler(IXmlUndoEventHandler* pEventHandler, uint32 typeId) = 0;
virtual void RegisterEventListener(IXmlHistoryEventListener* pEventListener) = 0;
virtual void UnregisterEventListener(IXmlHistoryEventListener* pEventListener) = 0;
// History
virtual void ClearHistory(bool flagAsSaved = false) = 0;
virtual int GetVersionCount() const = 0;
virtual const string& GetVersionDesc(int number) const = 0;
virtual int GetCurrentVersionNumber() const = 0;
// Views
virtual void RegisterView(IXmlHistoryView* pView) = 0;
virtual void UnregisterView(IXmlHistoryView* pView) = 0;
};
#endif // CRYINCLUDE_EDITOR_UTIL_IXMLHISTORYMANAGER_H
+104
View File
@@ -0,0 +1,104 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Image implementation,
#include "EditorDefs.h"
#include "Image.h"
bool CImageEx::ConvertToFloatImage(CFloatImage& dstImage)
{
uint32 pixelMask;
float intToFloat;
switch (GetFormat())
{
case eTF_Unknown:
case eTF_R8G8B8A8:
pixelMask = std::numeric_limits<uint8>::max();
intToFloat = static_cast<float>(pixelMask);
break;
case eTF_R16G16:
pixelMask = std::numeric_limits<uint16>::max();
intToFloat = static_cast<float>(pixelMask);
break;
default:
return false;
break;
}
dstImage.Allocate(GetWidth(), GetHeight());
uint32* srcPixel = GetData();
float* dstPixel = dstImage.GetData();
for (int pixel = 0; pixel < (GetHeight() * GetWidth()); pixel++)
{
dstPixel[pixel] = clamp_tpl(static_cast<float>(srcPixel[pixel] & pixelMask) / intToFloat, 0.0f, 1.0f);
}
return true;
}
void CImageEx::SwapRedAndBlue()
{
if (!IsValid())
{
return;
}
// Set the loop pointers
uint32* pPixData = GetData();
uint32* pPixDataEnd = pPixData + GetWidth() * GetHeight();
// Switch R and B
while (pPixData != pPixDataEnd)
{
// Extract the bits, shift them, put them back and advance to the next pixel
*pPixData = (*pPixData & 0xFF000000) | ((*pPixData & 0x00FF0000) >> 16) | (*pPixData & 0x0000FF00) | ((*pPixData & 0x000000FF) << 16);
++pPixData;
}
}
void CImageEx::ReverseUpDown()
{
if (!IsValid())
{
return;
}
uint32* pPixData = GetData();
uint32* pReversePix = new uint32[GetWidth() * GetHeight()];
for (int i = GetHeight() - 1, i2 = 0; i >= 0; i--, i2++)
{
for (int k = 0; k < GetWidth(); k++)
{
pReversePix[i2 * GetWidth() + k] = pPixData[i * GetWidth() + k];
}
}
Attach(pReversePix, GetWidth(), GetHeight());
}
void CImageEx::FillAlpha(unsigned char value)
{
if (!IsValid())
{
return;
}
// Set the loop pointers
uint32* pPixData = GetData();
uint32* pPixDataEnd = pPixData + GetWidth() * GetHeight();
while (pPixData != pPixDataEnd)
{
*pPixData = (*pPixData & 0x00FFFFFF) | (value << 24);
++pPixData;
}
}
+500
View File
@@ -0,0 +1,500 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Generic image class
#include <ITexture.h>
#ifndef CRYINCLUDE_EDITOR_UTIL_IMAGE_H
#define CRYINCLUDE_EDITOR_UTIL_IMAGE_H
#pragma once
#include "MemoryBlock.h"
#include "Util/XmlArchive.h"
enum class ImageRotationDegrees
{
Rotate0,
Rotate90,
Rotate180,
Rotate270
};
/*!
* Templated image class.
*/
template <class T>
class TImage
{
public:
TImage()
: m_data(0)
, m_width(0)
, m_height(0)
, m_bHasAlphaChannel(false)
, m_bIsLimitedHDR(false)
, m_bIsCubemap(false)
, m_bIsSRGB(true)
, m_nNumberOfMipmaps(1)
, m_format(eTF_Unknown)
, m_strDccFilename("")
{
}
virtual ~TImage() {}
T& ValueAt(int x, int y) { return m_data[x + y * m_width]; }
const T& ValueAt(int x, int y) const { return m_data[x + y * m_width]; }
const T& ValueAtSafe(int x, int y) const
{
static T zero = 0;
if (0 <= x && x < m_width && 0 <= y && y < m_height)
{
return m_data[x + y * m_width];
}
return zero;
}
T* GetData() const { return m_data; }
int GetWidth() const { return m_width; }
int GetHeight() const { return m_height; }
bool HasAlphaChannel() const { return m_bHasAlphaChannel; }
bool IsLimitedHDR() const { return m_bIsLimitedHDR; }
bool IsCubemap() const { return m_bIsCubemap; }
unsigned int GetNumberOfMipMaps() const { return m_nNumberOfMipmaps; }
// Returns:
// size in bytes
int GetSize() const { return m_width * m_height * sizeof(T); }
bool IsValid() const { return m_data != 0; }
void Attach(T* data, int width, int height)
{
assert(data);
m_memory = new CMemoryBlock();
m_memory->Attach(data, width * height * sizeof(T));
m_data = data;
m_width = width;
m_height = height;
m_strDccFilename = "";
}
void Attach(const TImage<T>& img)
{
assert(img.IsValid());
m_memory = img.m_memory;
m_data = (T*)m_memory->GetBuffer();
m_width = img.m_width;
m_height = img.m_height;
m_strDccFilename = img.m_strDccFilename;
}
void Detach()
{
m_memory = 0;
m_data = 0;
m_width = 0;
m_height = 0;
m_strDccFilename = "";
}
bool Allocate(int width, int height)
{
if (width < 1)
{
width = 1;
}
if (height < 1)
{
height = 1;
}
if (m_data && (m_width == width && m_height == height))
{
return true;
}
// New memory block.
m_memory = new CMemoryBlock();
m_memory->Allocate(width * height * sizeof(T)); // +width for crash safety.
m_data = (T*)m_memory->GetBuffer();
m_width = width;
m_height = height;
if (!m_data)
{
return false;
}
return true;
}
void Release()
{
m_memory = 0;
m_data = 0;
m_width = 0;
m_height = 0;
m_strDccFilename = "";
}
// Copy operator.
void Copy(const TImage<T>& img)
{
if (!img.IsValid())
{
return;
}
if (m_width != img.GetWidth() || m_height != img.GetHeight())
{
Allocate(img.GetWidth(), img.GetHeight());
}
*m_memory = *img.m_memory;
m_data = (T*)m_memory->GetBuffer();
m_strDccFilename = img.m_strDccFilename;
}
//////////////////////////////////////////////////////////////////////////
void Clear()
{
Fill(0);
}
//////////////////////////////////////////////////////////////////////////
void Fill(unsigned char c)
{
if (IsValid())
{
memset(GetData(), c, GetSize());
}
}
//////////////////////////////////////////////////////////////////////////
void GetSubImage(int x1, int y1, int width, int height, TImage<T>& img) const
{
int size = width * height;
img.Allocate(width, height);
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
img.ValueAt(x, y) = ValueAtSafe(x1 + x, y1 + y);
}
}
}
void SetSubImage(int x1, int y1, const TImage<T>& subImage, float heightOffset, float fClamp)
{
int width = subImage.GetWidth();
int height = subImage.GetHeight();
FitSubRect(x1, y1, width, height);
if (width <= 0 || height <= 0)
{
return;
}
if (fClamp < 0.0f)
{
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
ValueAt(x1 + x, y1 + y) = subImage.ValueAt(x, y) + heightOffset;
}
}
}
else
{
T TClamp = fClamp;
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
ValueAt(x1 + x, y1 + y) = clamp_tpl(f32(subImage.ValueAt(x, y) + heightOffset), 0.0f, f32(TClamp));
}
}
}
}
void SetSubImage(int x1, int y1, const TImage<T>& subImage)
{
int width = subImage.GetWidth();
int height = subImage.GetHeight();
FitSubRect(x1, y1, width, height);
if (width <= 0 || height <= 0)
{
return;
}
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
ValueAt(x1 + x, y1 + y) = subImage.ValueAt(x, y);
}
}
}
void FitSubRect(int& x1, int& y1, int& width, int& height)
{
if (x1 < 0)
{
width = width + x1;
x1 = 0;
}
if (y1 < 0)
{
height = height + y1;
y1 = 0;
}
if (x1 + width > m_width)
{
width = m_width - x1;
}
if (y1 + height > m_height)
{
height = m_height - y1;
}
}
//! Compress image to memory block.
void Compress(CMemoryBlock& mem) const
{
assert(IsValid());
m_memory->Compress(mem);
}
//! Uncompress image from memory block.
bool Uncompress(const CMemoryBlock& mem)
{
assert(IsValid());
// New memory block.
_smart_ptr<CMemoryBlock> temp = new CMemoryBlock();
mem.Uncompress(*temp);
bool bValid = (GetSize() == m_memory->GetSize())
|| ((GetSize() + m_width * sizeof(T)) == m_memory->GetSize());
if (bValid)
{
m_memory = temp;
m_data = (T*)m_memory->GetBuffer();
}
return bValid;
//assert( GetSize() == m_memory.GetSize() );
}
void SetHasAlphaChannel(bool bHasAlphaChannel) { m_bHasAlphaChannel = bHasAlphaChannel; }
void SetIsLimitedHDR(bool bIsLimitedHDR) { m_bIsLimitedHDR = bIsLimitedHDR; }
void SetIsCubemap(bool bIsCubemap) { m_bIsCubemap = bIsCubemap; }
void SetNumberOfMipMaps(unsigned int nNumberOfMips) { m_nNumberOfMipmaps = nNumberOfMips; }
void Serialize(CXmlArchive& ar);
void SetFormatDescription(const QString& str) { m_formatDescription = str; };
const QString& GetFormatDescription() const { return m_formatDescription; };
void SetFormat(ETEX_Format format) { m_format = format; }
ETEX_Format GetFormat() const { return m_format; }
void SetSRGB(bool bEnable) { m_bIsSRGB = bEnable; }
bool GetSRGB() const { return m_bIsSRGB; }
void SetDccFilename(const QString& str) { m_strDccFilename = str; };
const QString& GetDccFilename() const { return m_strDccFilename; }
// RotateOrt() - orthonormal image rotation
void RotateOrt(const TImage<T>& img, ImageRotationDegrees degrees)
{
if (!img.IsValid())
{
return;
}
int width;
int height;
if (degrees == ImageRotationDegrees::Rotate90 || degrees == ImageRotationDegrees::Rotate270)
{
width = img.GetHeight();
height = img.GetWidth();
}
else
{
width = img.GetWidth();
height = img.GetHeight();
}
if (m_width != width || m_height != height)
{
Allocate(width, height);
}
for (int y = 0; y < m_height; y++)
{
for (int x = 0; x < m_width; x++)
{
if (degrees == ImageRotationDegrees::Rotate90)
{
ValueAt(x, y) = img.ValueAt(m_height - y - 1, x);
}
else if (degrees == ImageRotationDegrees::Rotate180)
{
ValueAt(x, y) = img.ValueAt(m_width - x - 1, m_height - y - 1);
}
else if (degrees == ImageRotationDegrees::Rotate270)
{
ValueAt(x, y) = img.ValueAt(y, m_width - x - 1);
}
else
{
ValueAt(x, y) = img.ValueAt(x, y);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void ScaleToFit(const TImage<T>& img)
{
uint32 x, y, u, v;
T* destRow, *dest, *src, *sourceRow;
if (!img.IsValid())
{
return;
}
const uint32 srcW = img.GetWidth();
const uint32 srcH = img.GetHeight();
const uint32 trgW = GetWidth();
const uint32 trgH = GetHeight();
const uint32 xratio = trgW > 0 ? (srcW << 16) / trgW : 1;
const uint32 yratio = trgH > 0 ? (srcH << 16) / trgH : 1;
src = img.GetData();
destRow = GetData();
v = 0;
for (y = 0; y < trgH; y++)
{
u = 0;
sourceRow = src + (v >> 16) * srcW;
dest = destRow;
for (x = 0; x < trgW; x++)
{
*dest++ = sourceRow[u >> 16];
u += xratio;
}
v += yratio;
destRow += trgW;
}
}
private:
// Restrict use of copy constructor.
TImage(const TImage<T>& img);
TImage<T>& operator=(const TImage<T>& img);
//! Memory holding image data.
_smart_ptr<CMemoryBlock> m_memory;
T* m_data;
int m_width;
int m_height;
bool m_bHasAlphaChannel;
bool m_bIsLimitedHDR;
bool m_bIsCubemap;
bool m_bIsSRGB;
unsigned int m_nNumberOfMipmaps;
QString m_formatDescription;
QString m_strDccFilename;
ETEX_Format m_format;
};
template <class T>
void TImage<T>::Serialize(CXmlArchive& ar)
{
if (ar.bLoading)
{
// Loading
ar.root->getAttr("ImageWidth", m_width);
ar.root->getAttr("ImageHeight", m_height);
ar.root->getAttr("Mipmaps", m_nNumberOfMipmaps);
ar.root->getAttr("IsCubemap", m_bIsCubemap);
bool bIsSRGB;
if (ar.root->getAttr("IsSRGB", bIsSRGB))
{
m_bIsSRGB = bIsSRGB;
}
ar.root->getAttr("dccFilename", m_strDccFilename);
int format;
if (ar.root->getAttr("format", format))
{
m_format = (ETEX_Format)format;
}
else
{
m_format = eTF_Unknown;
}
Allocate(m_width, m_height);
void* pData = 0;
int nDataSize = 0;
bool bHaveBlock = ar.pNamedData->GetDataBlock(ar.root->getTag(), pData, nDataSize);
if (bHaveBlock && nDataSize == GetSize())
{
m_data = (T*)pData;
}
}
else
{
// Saving.
ar.root->setAttr("ImageWidth", m_width);
ar.root->setAttr("ImageHeight", m_height);
ar.root->setAttr("Mipmaps", m_nNumberOfMipmaps);
ar.root->setAttr("IsCubemap", m_bIsCubemap);
ar.root->setAttr("IsSRGB", m_bIsSRGB);
ar.root->setAttr("format", (int)m_format);
ar.root->setAttr("dccFilename", m_strDccFilename);
ar.pNamedData->AddDataBlock(ar.root->getTag(), (void*)m_data, GetSize());
}
};
//////////////////////////////////////////////////////////////////////////
// Define types of most commonly used images.
//////////////////////////////////////////////////////////////////////////
typedef TImage<float> CFloatImage;
typedef TImage<unsigned char> CByteImage;
typedef TImage<unsigned short> CWordImage;
//////////////////////////////////////////////////////////////////////////
class CImageEx
: public TImage < unsigned int >
{
public:
CImageEx()
: TImage() { m_bGetHistogramEqualization = false; };
EDITOR_CORE_API bool ConvertToFloatImage(CFloatImage& dstImage);
EDITOR_CORE_API void SwapRedAndBlue();
EDITOR_CORE_API void ReverseUpDown();
EDITOR_CORE_API void FillAlpha(unsigned char value = 0xff);
// request histogram equalization for HDRs
void SetHistogramEqualization(bool bHistogramEqualization) { m_bGetHistogramEqualization = bHistogramEqualization; }
bool GetHistogramEqualization() { return m_bGetHistogramEqualization; }
private:
bool m_bGetHistogramEqualization;
};
#endif // CRYINCLUDE_EDITOR_UTIL_IMAGE_H
+190
View File
@@ -0,0 +1,190 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ImageASC.h"
// Editor
#include "Util/Image.h"
//---------------------------------------------------------------------------
bool CImageASC::Save(const QString& fileName, const CFloatImage& image)
{
// There are two types of ARCGrid file formats - binary (ADF) and ASCII (ASC).
// See here: https://en.wikipedia.org/wiki/Esri_grid
uint32 width = image.GetWidth();
uint32 height = image.GetHeight();
float* pixels = image.GetData();
string fileHeader;
fileHeader.Format(
// Number of columns and rows in the data
"ncols %d\n"
"nrows %d\n"
// The coordinates of the bottom-left corner.
// These numbers represent coordinates on a globe, so this choice of values is arbitrary.
"xllcorner 0.0\n"
"yllcorner 0.0\n"
// The size of each grid square.
// The problem is that cellsize represents the size of a square on a grid being projected onto a globe.
// This number can be used to convert size to degrees, based on where on the globe it appears.
// We don't have a real-world location associated with our data, so this size choice is arbitrary.
"cellsize 0.0003\n"
// The value used for missing data. Since we shouldn't have any missing data, we'll choose a value that can't appear below.
"nodata_value -1\n"
, width, height);
FILE* file = nullptr;
azfopen(&file, fileName.toUtf8().data(), "wt");
if (!file)
{
return false;
}
// First print the file header
fprintf(file, fileHeader.c_str());
// Then print all the pixels.
for (int y = 0; y < height; y++)
{
for (int x = 0; x < width; x++)
{
fprintf(file, "%.7f ", pixels[x + y * width]);
}
fprintf(file, "\n");
}
fclose(file);
return true;
}
//---------------------------------------------------------------------------
bool CImageASC::Load(const QString& fileName, CFloatImage& image)
{
FILE* file = nullptr;
azfopen(&file, fileName.toUtf8().data(), "rt");
if (!file)
{
return false;
}
const char seps[] = " \r\n\t";
char* token;
int32 width = 0;
int32 height = 0;
float nodataValue = 0.0f;
bool validData = true;
// Read the file into memory
fseek(file, 0, SEEK_END);
int fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
char* str = new char[fileSize];
fread(str, fileSize, 1, file);
// Break all of the values in the file apart into tokens.
char* nextToken = nullptr;
token = azstrtok(str, 0, seps, &nextToken);
// ncols = grid width
validData = validData && (azstricmp(token, "ncols") == 0);
token = azstrtok(NULL, 0, seps, &nextToken);
width = atoi(token);
// nrows = grid height
token = azstrtok(NULL, 0, seps, &nextToken);
validData = validData && (azstricmp(token, "nrows") == 0);
token = azstrtok(NULL, 0, seps, &nextToken);
height = atoi(token);
// xllcorner = leftmost coordinate. (Skip, we don't care about it)
token = azstrtok(NULL, 0, seps, &nextToken);
validData = validData && (azstricmp(token, "xllcorner") == 0);
token = azstrtok(NULL, 0, seps, &nextToken);
// yllcorner = bottommost coordinate. (Skip, we don't care about it)
token = azstrtok(NULL, 0, seps, &nextToken);
validData = validData && (azstricmp(token, "yllcorner") == 0);
token = azstrtok(NULL, 0, seps, &nextToken);
// cellsize = size of each grid cell. (Skip, we don't care about it)
token = azstrtok(NULL, 0, seps, &nextToken);
validData = validData && (azstricmp(token, "cellsize") == 0);
token = azstrtok(NULL, 0, seps, &nextToken);
// nodata_value = the value used for missing data. We'll replace these with 0 height.
token = azstrtok(NULL, 0, seps, &nextToken);
validData = validData && (azstricmp(token, "nodata_value") == 0);
token = azstrtok(NULL, 0, seps, &nextToken);
nodataValue = atof(token);
if (!validData)
{
// Bad file. not supported asc.
delete[]str;
fclose(file);
return false;
}
image.Allocate(width, height);
// Read in the pixel data
float* p = image.GetData();
int size = width * height;
int i = 0;
float pixelValue;
float maxPixel = 0.0f;
while (token != NULL && i < size)
{
token = azstrtok(NULL, 0, seps, &nextToken);
if (token != NULL)
{
// Negative heights aren't supported, clamp to 0.
pixelValue = max(0.0, atof(token));
// If this is a location we specifically don't have data for, set it to 0.
if (pixelValue == nodataValue)
{
pixelValue = 0.0f;
}
*p++ = pixelValue;
maxPixel = max(maxPixel, pixelValue);
i++;
}
}
if (maxPixel > 0.0f)
{
// Scale our range down to 0 - 1
float* pp = image.GetData();
for (i = 0; i < size; i++)
{
pp[i] = clamp_tpl(pp[i] / maxPixel, 0.0f, 1.0f);
}
}
delete[]str;
fclose(file);
return true;
}
+22
View File
@@ -0,0 +1,22 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_IMAGEASC_H
#define CRYINCLUDE_EDITOR_UTIL_IMAGEASC_H
#pragma once
#include "Util/Image.h"
class SANDBOX_API CImageASC
{
public:
bool Load(const QString& fileName, CFloatImage& outImage);
bool Save(const QString& fileName, const CFloatImage& image);
};
#endif // CRYINCLUDE_EDITOR_UTIL_IMAGEASC_H
+206
View File
@@ -0,0 +1,206 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ImageBT.h"
//---------------------------------------------------------------------------
// Load and save the VTP Binary Terrain (BT) format, documented here:
// http://vterrain.org/Implementation/Formats/BT.html
// This structure represents a binary layout in the file. To direct load & save it, we need to remove all structure memory padding
#pragma pack(push,1)
struct BtHeader
{
char headerTag[7]; // Should be "binterr"
char headerTagVersion[3]; // Should be "1.3"
int32 columns; // # of columns in the heightfield
int32 rows; // # of rows in the heightfield
int16 bytesPerPoint; // bytes per height value, either 2 for signed ints or 4 for floats
int16 isFloatingPointData; // 1 if height values are floats, 0 for 16-bit signed ints
int16 horizUnits; // 0 if degrees, 1 if meters, 2 if international feet, 3 if US survey feet
int16 utmZone; // UTM projection zone 1 to 60 or -1 to -60 (see https://en.wikipedia.org/wiki/Universal_Transverse_Mercator_coordinate_system )
int16 datum; // Datum value (6001 to 6094), see http://www.epsg.org/
double leftExtent; // left coordinate projection of the file
double rightExtent; // right coordinate projection of the file
double bottomExtent; // bottom coordinate projection of the file
double topExtent; // top coordinate projection of the file
int16 externalProjection; // 1 if projection is in an external .prj file, 0 if it's contained in the header
float scale; // vertical units in meters. 0.0 should be treated as 1.0
char unused[190];
};
#pragma pack(pop)
bool CImageBT::Save(const QString& fileName, const CFloatImage& image)
{
int width = image.GetWidth();
int height = image.GetHeight();
float *pixels = image.GetData();
// Create a header with reasonable default values.
BtHeader header =
{
{'b', 'i', 'n', 't', 'e', 'r', 'r'},
{'1', '.', '3' },
width,
height,
sizeof(float), // we'll use floats to make sure we can capture the full potential range of heightfield values
1, // use floats
1, // units are meters
0, // no UTM projection zone
6326, // WGS84 Datum value. Recommended by VTP as the default if you don't care about Datum values.
0.0, // set the left extent to 0?
double(width), // set the right extent to the width? (assumes 1 m per pixel)
double(height), // set the bottom extent to the height? (assumes 1 m per pixel)
0.0, // set the top extent to 0?
0, // no external prj file
1.0f
};
memset(header.unused, 0, sizeof(header.unused));
FILE* file = nullptr;
azfopen(&file, fileName.toUtf8().data(), "wb");
if (!file)
{
return false;
}
fwrite(&header, sizeof(header), 1, file);
for (int32 y = 0; y < height; y++)
{
for (int32 x = 0; x < width; x++)
{
float heightmapValue = pixels[(y * width) + x];
fwrite(&heightmapValue, sizeof(float), 1, file);
}
}
fclose(file);
return true;
}
//---------------------------------------------------------------------------
bool CImageBT::Load(const QString& fileName, CFloatImage& image)
{
FILE* file = nullptr;
azfopen(&file, fileName.toUtf8().data(), "rb");
if (!file)
{
return false;
}
// Get the file size
fseek(file, 0, SEEK_END);
int fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
// Our file needs to be at least as big as the BT file header.
if (fileSize < sizeof(BtHeader))
{
return false;
}
// Get the BT header data
BtHeader header;
memset(&header, 0, sizeof(BtHeader)); // C4701 potentially uninitialized local variable 'header' used
bool validData = true;
validData = validData && (fread(&header, sizeof(BtHeader), 1, file) != 0);
// Do some quick error-checking on the header to make sure it meets our expectations
// Does the header have the right header tag? (binterr1.0 - binterr1.3)
validData = validData && (memcmp(header.headerTag, "binterr", sizeof(header.headerTag)) == 0);
validData = validData && (header.headerTagVersion[0] == '1') && (header.headerTagVersion[1] == '.') && (header.headerTagVersion[2] >= '0') && (header.headerTagVersion[2] <= '3');
// Will the grid fit into a reasonable image size?
validData = validData && (header.columns >= 0) && (header.columns < 65536);
validData = validData && (header.rows >= 0) && (header.rows < 65536);
// Do we either have 32-bit floats or 16-bit ints?
validData = validData && (((header.isFloatingPointData == 1) && (header.bytesPerPoint == 4)) || ((header.isFloatingPointData == 0) && (header.bytesPerPoint == 2)));
// Is the remaining data exactly the size needed to fill our image?
validData = validData && ((fileSize - sizeof(BtHeader)) == (header.columns * header.rows * header.bytesPerPoint));
if (!validData)
{
fclose(file);
return false;
}
if (header.scale == 0.0f)
{
header.scale = 1.0f;
}
// The BT format defines the data as stored in column-first order, from bottom to top.
// However, some BT files store the data in row-first order, from top to bottom.
// There isn't anything that clearly specifies which type of file it is. If you load it the wrong way,
// the data will look like a bunch of wavy stripes.
// The only difference I've found in test files is datum values above 8000, which appears to be an invalid value for datum
// (it should be 6001-6904 according to the BT definition)
const int invalidDatumValueDenotingColumnFirstData = 8000;
bool isColumnFirstData = (header.datum >= invalidDatumValueDenotingColumnFirstData) ? true : false;
float height = 0.0f;
int imageWidth, imageHeight;
if (isColumnFirstData)
{
imageWidth = header.rows;
imageHeight = header.columns;
}
else
{
imageWidth = header.columns;
imageHeight = header.rows;
}
image.Allocate(imageWidth, imageHeight);
float* p = image.GetData();
float maxPixel = 0.0f;
// Read in the pixel data
for (int32 y = 0; y < imageHeight; y++)
{
for (int32 x = 0; x < imageWidth; x++)
{
if (header.isFloatingPointData)
{
fread(&height, sizeof(float), 1, file);
}
else
{
int16 intHeight = 0;
fread(&intHeight, sizeof(int16), 1, file);
height = static_cast<float>(intHeight);
}
// Scale based on what our header defines, and clamp the values to positive range, negatives not supported
p[(y * imageWidth) + x] = max((height * header.scale), 0.0f);
maxPixel = max(maxPixel, p[(y * imageWidth) + x]);
}
}
// Scale our range down to 0 - 1
if (maxPixel > 0.0f)
{
p = image.GetData();
for (int32 i = 0; i < (imageWidth * imageHeight); i++)
{
p[i] = clamp_tpl(p[i] / maxPixel, 0.0f, 1.0f);
}
}
fclose(file);
return true;
}
+22
View File
@@ -0,0 +1,22 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_IMAGEBT_H
#define CRYINCLUDE_EDITOR_UTIL_IMAGEBT_H
#pragma once
#include "Util/Image.h"
class SANDBOX_API CImageBT
{
public:
bool Load(const QString& fileName, CFloatImage& outImage);
bool Save(const QString& fileName, const CFloatImage& image);
};
#endif // CRYINCLUDE_EDITOR_UTIL_IMAGEBT_H
+506
View File
@@ -0,0 +1,506 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ImageGif.h"
// Editor
#include "Util/Image.h"
//---------------------------------------------------------------------------
#define NEXTBYTE (*ptr++)
#define IMAGESEP 0x2c
#define GRAPHIC_EXT 0xf9
#define PLAINTEXT_EXT 0x01
#define APPLICATION_EXT 0xff
#define COMMENT_EXT 0xfe
#define START_EXTENSION 0x21
#define INTERLACEMASK 0x40
#define COLORMAPMASK 0x80
#define CHK(x) x
#pragma pack(push,1)
struct SGIFRGBcolor
{
uint8 red, green, blue;
};
struct SGIFRGBPixel
{
uint8 red, green, blue, alpha;
};
#pragma pack(pop)
static int BitOffset = 0, /* Bit Offset of next code */
XC = 0, YC = 0, /* Output X and Y coords of current pixel */
Pass = 0, /* Used by output routine if interlaced pic */
OutCount = 0, /* Decompressor output 'stack count' */
RWidth, RHeight, /* screen dimensions */
Width, Height, /* image dimensions */
LeftOfs, TopOfs, /* image offset */
BitsPerPixel, /* Bits per pixel, read from GIF header */
BytesPerScanline, /* bytes per scanline in output raster */
ColorMapSize, /* number of colors */
Background, /* background color */
CodeSize, /* Code size, read from GIF header */
InitCodeSize, /* Starting code size, used during Clear */
Code, /* Value returned by ReadCode */
MaxCode, /* limiting value for current code size */
ClearCode, /* GIF clear code */
EOFCode, /* GIF end-of-information code */
CurCode, OldCode, InCode, /* Decompressor variables */
FirstFree, /* First free code, generated per GIF spec */
FreeCode, /* Decompressor, next free slot in hash table*/
FinChar, /* Decompressor variable */
BitMask, /* AND mask for data size */
ReadMask; /* Code AND mask for current code size */
static bool Interlace, HasColormap;
static SGIFRGBPixel* Image; /* The result array */
static SGIFRGBcolor* Palette; /* The palette that is used */
static uint8* IndexImage;
static uint8* Raster; /* The raster data stream, unblocked */
static uint8 used[256];
static int numused;
const char* id87 = "GIF87a";
const char* id89 = "GIF89a";
static int log2 (int);
/* Fetch the next code from the raster data stream. The codes can be
* any length from 3 to 12 bits, packed into 8-bit bytes, so we have to
* maintain our location in the Raster array as a BIT Offset. We compute
* the uint8 Offset into the raster array by dividing this by 8, pick up
* three bytes, compute the bit Offset into our 24-bit chunk, shift to
* bring the desired code to the bottom, then mask it off and return it.
*/
inline int ReadCode (void)
{
int RawCode, ByteOffset;
ByteOffset = BitOffset / 8;
RawCode = Raster[ByteOffset] + (0x100 * Raster[ByteOffset + 1]);
if (CodeSize >= 8)
{
RawCode += (0x10000 * Raster[ByteOffset + 2]);
}
RawCode >>= (BitOffset % 8);
BitOffset += CodeSize;
return RawCode & ReadMask;
}
inline void AddToPixel (uint8 Index)
{
if (YC < Height)
{
SGIFRGBPixel* p = Image + YC * BytesPerScanline + XC;
p->red = Palette[Index].red;
p->green = Palette[Index].green;
p->blue = Palette[Index].blue;
p->alpha = 0;
IndexImage[YC * BytesPerScanline + XC] = Index;
}
if (!used[Index])
{
used[Index] = 1;
numused++;
}
/* Update the X-coordinate, and if it overflows, update the Y-coordinate */
if (++XC == Width)
{
/* If a non-interlaced picture, just increment YC to the next scan line.
* If it's interlaced, deal with the interlace as described in the GIF
* spec. Put the decoded scan line out to the screen if we haven't gone
* past the bottom of it
*/
XC = 0;
if (!Interlace)
{
YC++;
}
else
{
switch (Pass)
{
case 0:
YC += 8;
if (YC >= Height)
{
Pass++;
YC = 4;
}
break;
case 1:
YC += 8;
if (YC >= Height)
{
Pass++;
YC = 2;
}
break;
case 2:
YC += 4;
if (YC >= Height)
{
Pass++;
YC = 1;
}
break;
case 3:
YC += 2;
break;
default:
break;
}
}
}
}
bool CImageGif::Load(const QString& fileName, CImageEx& outImage)
{
bool ret = false;
std::vector<uint8> data;
CCryFile file;
if (!file.Open(fileName.toUtf8().data(), "rb"))
{
CLogFile::FormatLine("File not found %s", fileName.toUtf8().data());
return false;
}
long filesize = file.GetLength();
data.resize(filesize);
uint8* ptr = &data[0];
file.ReadRaw(ptr, filesize);
/* Detect if this is a GIF file */
if (strncmp ((char*)ptr, "GIF87a", 6) && strncmp ((char*)ptr, "GIF89a", 6))
{
CLogFile::FormatLine("Bad GIF file format %s", fileName.toUtf8().data());
return false;
}
int numcols;
unsigned char ch, ch1;
uint8* ptr1;
int i;
short transparency = -1;
TImage<uint8> outImageIndex;
/* The hash table used by the decompressor */
int* Prefix;
int* Suffix;
/* An output array used by the decompressor */
int* OutCode;
CHK (Prefix = new int [4096]);
CHK (Suffix = new int [4096]);
CHK (OutCode = new int [1025]);
BitOffset = 0;
XC = YC = 0;
Pass = 0;
OutCount = 0;
Palette = NULL;
CHK (Raster = new uint8 [filesize]);
if (strncmp((char*) ptr, id87, 6))
{
if (strncmp((char*) ptr, id89, 6))
{
CLogFile::FormatLine("Bad GIF file format %s",fileName.toUtf8().data());
goto cleanup;
}
}
ptr += 6;
/* Get variables from the GIF screen descriptor */
ch = NEXTBYTE;
RWidth = ch + 0x100 * NEXTBYTE; /* screen dimensions... not used. */
ch = NEXTBYTE;
RHeight = ch + 0x100 * NEXTBYTE;
ch = NEXTBYTE;
HasColormap = ((ch & COLORMAPMASK) ? true : false);
BitsPerPixel = (ch & 7) + 1;
numcols = ColorMapSize = 1 << BitsPerPixel;
BitMask = ColorMapSize - 1;
Background = NEXTBYTE; /* background color... not used. */
if (NEXTBYTE) /* supposed to be NULL */
{
CLogFile::FormatLine("Bad GIF file format %s", fileName.toUtf8().data());
goto cleanup;
}
/* Read in global colormap. */
SGIFRGBcolor mspPal[1024];
if (HasColormap)
{
for (i = 0; i < ColorMapSize; i++)
{
mspPal[i].red = NEXTBYTE;
mspPal[i].green = NEXTBYTE;
mspPal[i].blue = NEXTBYTE;
used[i] = 0;
}
Palette = mspPal;
numused = 0;
} /* else no colormap in GIF file */
/* look for image separator */
for (ch = NEXTBYTE; ch != IMAGESEP; ch = NEXTBYTE)
{
i = ch;
if (ch != START_EXTENSION)
{
CLogFile::FormatLine("Bad GIF file format %s", fileName.toUtf8().data());
goto cleanup;
}
/* handle image extensions */
switch (ch = NEXTBYTE)
{
case GRAPHIC_EXT:
ch = NEXTBYTE;
if (ptr[0] & 0x1)
{
transparency = ptr[3]; /* transparent color index */
}
ptr += ch;
break;
case PLAINTEXT_EXT:
break;
case APPLICATION_EXT:
break;
case COMMENT_EXT:
break;
default:
{
CLogFile::FormatLine("Invalid GIF89 extension %s", fileName.toUtf8().data());
goto cleanup;
}
}
ch = NEXTBYTE;
while (ch)
{
ptr += ch;
ch = NEXTBYTE;
}
}
//if (transparency >= 0)
//mfSet_transparency(transparency);
/* Now read in values from the image descriptor */
ch = NEXTBYTE;
LeftOfs = ch + 0x100 * NEXTBYTE;
ch = NEXTBYTE;
TopOfs = ch + 0x100 * NEXTBYTE;
ch = NEXTBYTE;
Width = ch + 0x100 * NEXTBYTE;
ch = NEXTBYTE;
Height = ch + 0x100 * NEXTBYTE;
Interlace = ((NEXTBYTE & INTERLACEMASK) ? true : false);
// Set the dimensions which will also allocate the image data
// buffer.
outImage.Allocate(Width, Height);
//mfSet_dimensions (Width, Height);
Image = (SGIFRGBPixel*)outImage.GetData();
outImageIndex.Allocate(Width, Height);
IndexImage = outImageIndex.GetData();
/* Note that I ignore the possible existence of a local color map.
* I'm told there aren't many files around that use them, and the spec
* says it's defined for future use. This could lead to an error
* reading some files.
*/
/* Start reading the raster data. First we get the intial code size
* and compute decompressor constant values, based on this code size.
*/
CodeSize = NEXTBYTE;
ClearCode = (1 << CodeSize);
EOFCode = ClearCode + 1;
FreeCode = FirstFree = ClearCode + 2;
/* The GIF spec has it that the code size is the code size used to
* compute the above values is the code size given in the file, but the
* code size used in compression/decompression is the code size given in
* the file plus one. (thus the ++).
*/
CodeSize++;
InitCodeSize = CodeSize;
MaxCode = (1 << CodeSize);
ReadMask = MaxCode - 1;
/* Read the raster data. Here we just transpose it from the GIF array
* to the Raster array, turning it from a series of blocks into one long
* data stream, which makes life much easier for ReadCode().
*/
ptr1 = Raster;
do
{
ch = ch1 = NEXTBYTE;
while (ch--)
{
*ptr1++ = NEXTBYTE;
}
if ((ptr1 - Raster) > filesize)
{
CLogFile::FormatLine("Corrupted GIF file (unblock) %s", fileName.toUtf8().data());
goto cleanup;
}
}
while (ch1);
BytesPerScanline = Width;
/* Decompress the file, continuing until you see the GIF EOF code.
* One obvious enhancement is to add checking for corrupt files here.
*/
Code = ReadCode ();
while (Code != EOFCode)
{
/* Clear code sets everything back to its initial value, then reads the
* immediately subsequent code as uncompressed data.
*/
if (Code == ClearCode)
{
CodeSize = InitCodeSize;
MaxCode = (1 << CodeSize);
ReadMask = MaxCode - 1;
FreeCode = FirstFree;
CurCode = OldCode = Code = ReadCode();
FinChar = CurCode & BitMask;
AddToPixel (FinChar);
}
else
{
/* If not a clear code, then must be data: save same as CurCode and InCode */
CurCode = InCode = Code;
/* If greater or equal to FreeCode, not in the hash table yet;
* repeat the last character decoded
*/
if (CurCode >= FreeCode)
{
CurCode = OldCode;
OutCode[OutCount++] = FinChar;
}
/* Unless this code is raw data, pursue the chain pointed to by CurCode
* through the hash table to its end; each code in the chain puts its
* associated output code on the output queue.
*/
while (CurCode > BitMask)
{
if (OutCount > 1024)
{
CLogFile::FormatLine("Corrupted GIF file (OutCount) %s", fileName.toUtf8().data());
goto cleanup;
}
OutCode[OutCount++] = Suffix[CurCode];
CurCode = Prefix[CurCode];
}
/* The last code in the chain is treated as raw data. */
FinChar = CurCode & BitMask;
OutCode[OutCount++] = FinChar;
/* Now we put the data out to the Output routine.
* It's been stacked LIFO, so deal with it that way...
*/
for (i = OutCount - 1; i >= 0; i--)
{
AddToPixel (OutCode[i]);
}
OutCount = 0;
/* Build the hash table on-the-fly. No table is stored in the file. */
Prefix[FreeCode] = OldCode;
Suffix[FreeCode] = FinChar;
OldCode = InCode;
/* Point to the next slot in the table. If we exceed the current
* MaxCode value, increment the code size unless it's already 12. If it
* is, do nothing: the next code decompressed better be CLEAR
*/
FreeCode++;
if (FreeCode >= MaxCode)
{
if (CodeSize < 12)
{
CodeSize++;
MaxCode *= 2;
ReadMask = (1 << CodeSize) - 1;
}
}
}
Code = ReadCode ();
}
ret = true;
cleanup:
if (Raster)
{
CHK (delete [] Raster);
}
if (Prefix)
{
CHK (delete [] Prefix);
}
if (Suffix)
{
CHK (delete [] Suffix);
}
if (OutCode)
{
CHK (delete [] OutCode);
}
return ret;
}
+22
View File
@@ -0,0 +1,22 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_IMAGEGIF_H
#define CRYINCLUDE_EDITOR_UTIL_IMAGEGIF_H
#pragma once
class CImageEx;
class CImageGif
{
public:
bool Load(const QString& fileName, CImageEx& outImage);
};
#endif // CRYINCLUDE_EDITOR_UTIL_IMAGEGIF_H
+223
View File
@@ -0,0 +1,223 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ImageHistogram.h"
CImageHistogram::CImageHistogram()
: m_imageFormat(eImageFormat_32BPP_RGBA)
{
ClearHistogram();
}
CImageHistogram::~CImageHistogram()
{
}
void CImageHistogram::ComputeHistogram(BYTE* pImageData, UINT aWidth, UINT aHeight, EImageFormat aFormat)
{
ClearHistogram();
UINT r, g, b, a;
int lumIndex = 0;
UINT pixelCount = aWidth * aHeight;
m_imageFormat = aFormat;
while (pixelCount--)
{
r = g = b = a = 0;
switch (aFormat)
{
case eImageFormat_32BPP_RGBA:
{
r = *pImageData;
g = *(pImageData + 1);
b = *(pImageData + 2);
a = *(pImageData + 3);
pImageData += 4;
break;
}
case eImageFormat_32BPP_BGRA:
{
b = *pImageData;
g = *(pImageData + 1);
r = *(pImageData + 2);
a = *(pImageData + 3);
pImageData += 4;
break;
}
case eImageFormat_32BPP_ARGB:
{
a = *(pImageData);
r = *(pImageData + 1);
g = *(pImageData + 2);
b = *(pImageData + 3);
pImageData += 4;
break;
}
case eImageFormat_32BPP_ABGR:
{
a = *(pImageData);
b = *(pImageData + 1);
g = *(pImageData + 2);
r = *(pImageData + 3);
pImageData += 4;
break;
}
case eImageFormat_24BPP_RGB:
{
r = *pImageData;
g = *(pImageData + 1);
b = *(pImageData + 2);
pImageData += 3;
break;
}
case eImageFormat_24BPP_BGR:
{
r = *pImageData;
g = *(pImageData + 1);
b = *(pImageData + 2);
pImageData += 3;
break;
}
case eImageFormat_8BPP:
{
r = *pImageData;
++pImageData;
break;
}
}
++m_count[0][r];
++m_count[1][g];
++m_count[2][b];
++m_count[3][a];
lumIndex = (r + b + g) / 3;
lumIndex = CLAMP(lumIndex, 0, kNumColorLevels - 1);
++m_lumCount[lumIndex];
if (m_maxCount[0] < m_count[0][r])
{
m_maxCount[0] = m_count[0][r];
}
if (m_maxCount[1] < m_count[1][g])
{
m_maxCount[1] = m_count[1][g];
}
if (m_maxCount[2] < m_count[2][b])
{
m_maxCount[2] = m_count[2][b];
}
if (m_maxCount[3] < m_count[3][a])
{
m_maxCount[3] = m_count[3][a];
}
if (m_maxLumCount < m_lumCount[lumIndex])
{
m_maxLumCount = m_lumCount[lumIndex];
}
}
ComputeStatisticsForChannel(0);
ComputeStatisticsForChannel(1);
ComputeStatisticsForChannel(2);
ComputeStatisticsForChannel(3);
m_meanAvg = (m_mean[0] + m_mean[1] + m_mean[2]) / 3;
m_stdDevAvg = (m_stdDev[0] + m_stdDev[1] + m_stdDev[2]) / 3;
m_medianAvg = (m_median[0] + m_median[1] + m_median[2]) / 3;
}
void CImageHistogram::ClearHistogram()
{
const int kSize = kNumColorLevels * sizeof(UINT);
memset(m_count[0], 0, kSize);
memset(m_count[1], 0, kSize);
memset(m_count[2], 0, kSize);
memset(m_count[3], 0, kSize);
memset(m_lumCount, 0, kSize);
m_maxCount[0] = 0;
m_maxCount[1] = 0;
m_maxCount[2] = 0;
m_maxCount[3] = 0;
m_maxLumCount = 0;
memset(m_mean, 0, kNumChannels * sizeof(float));
memset(m_stdDev, 0, kNumChannels * sizeof(float));
memset(m_median, 0, kNumChannels * sizeof(float));
m_meanAvg = m_stdDevAvg = m_medianAvg = 0;
}
void CImageHistogram::CopyComputedDataFrom(CImageHistogram* histogram)
{
const int kSize = kNumColorLevels * sizeof(UINT);
memcpy(m_count[0], histogram->m_count[0], kSize);
memcpy(m_count[1], histogram->m_count[1], kSize);
memcpy(m_count[2], histogram->m_count[2], kSize);
memcpy(m_count[3], histogram->m_count[3], kSize);
memcpy(m_lumCount, histogram->m_lumCount, kSize);
memcpy(m_maxCount, histogram->m_maxCount, kNumChannels * sizeof(UINT));
memcpy(m_mean, histogram->m_mean, kNumChannels * sizeof(float));
memcpy(m_stdDev, histogram->m_stdDev, kNumChannels * sizeof(float));
memcpy(m_median, histogram->m_median, kNumChannels * sizeof(float));
m_maxLumCount = histogram->m_maxLumCount;
m_meanAvg = histogram->m_meanAvg;
m_stdDevAvg = histogram->m_stdDevAvg;
m_medianAvg = histogram->m_medianAvg;
m_imageFormat = histogram->m_imageFormat;
}
void CImageHistogram::ComputeStatisticsForChannel(int aIndex)
{
int hits = 0;
int total = 0;
//
// mean
// std deviation
//
for (size_t i = 0; i < kNumColorLevels; ++i)
{
hits = m_count[aIndex][i];
m_mean[aIndex] += i * hits;
m_stdDev[aIndex] += i * i * hits;
total += hits;
}
total = (total ? total : 1);
m_mean[aIndex] = (float)m_mean[aIndex] / total;
m_stdDev[aIndex] = m_stdDev[aIndex] / total - m_mean[aIndex] * m_mean[aIndex];
m_stdDev[aIndex] = m_stdDev[aIndex] <= 0 ? 0 : sqrtf(m_stdDev[aIndex]);
int halfTotal = total / 2;
int median = 0, v = 0;
// find median value
for (; median < kNumColorLevels; ++median)
{
v += m_count[aIndex][median];
if (v >= halfTotal)
{
break;
}
}
m_median[aIndex] = median;
}
+57
View File
@@ -0,0 +1,57 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_IMAGEHISTOGRAM_H
#define CRYINCLUDE_EDITOR_UTIL_IMAGEHISTOGRAM_H
#pragma once
#include "Include/EditorCoreAPI.h"
class EDITOR_CORE_API CImageHistogram
{
public:
static const int kNumChannels = 4;
static const int kNumColorLevels = 256;
enum EImageFormat
{
eImageFormat_8BPP,
eImageFormat_24BPP_RGB,
eImageFormat_24BPP_BGR,
eImageFormat_32BPP_RGBA,
eImageFormat_32BPP_BGRA,
eImageFormat_32BPP_ARGB,
eImageFormat_32BPP_ABGR
};
CImageHistogram();
virtual ~CImageHistogram();
// Description:
// Compute the histogram of an image
// Arguments:
// pImageData - the image data
// aWidth - the width of the image in pixels
// aHeight - the height of the image in pixels
// aBitsPerPixel - the number of bits per pixel, currently supported: 8 (monochrome), 24 (RGB) and 32 (RGBA)
void ComputeHistogram(BYTE* pImageData, unsigned int aWidth, unsigned int aHeight, EImageFormat aFormat = eImageFormat_32BPP_RGBA);
void ClearHistogram();
void CopyComputedDataFrom(CImageHistogram* histogram);
protected:
void ComputeStatisticsForChannel(int aIndex);
public:
unsigned int m_count[kNumChannels][kNumColorLevels];
unsigned int m_lumCount[kNumColorLevels];
unsigned int m_maxCount[kNumChannels];
unsigned int m_maxLumCount;
float m_mean[kNumChannels], m_stdDev[kNumChannels], m_median[kNumChannels];
float m_meanAvg, m_stdDevAvg, m_medianAvg;
EImageFormat m_imageFormat;
};
#endif // CRYINCLUDE_EDITOR_UTIL_IMAGEHISTOGRAM_H
+349
View File
@@ -0,0 +1,349 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ImagePainter.h"
// Editor
#include "Terrain/Heightmap.h"
#include "Terrain/Layer.h"
SEditorPaintBrush::SEditorPaintBrush(CHeightmap& rHeightmap, CLayer& rLayer,
const bool bMaskByLayerSettings, const uint32 dwLayerIdMask, const bool bFlood)
: bBlended(true)
, m_rHeightmap(rHeightmap)
, m_rLayer(rLayer)
, m_cFilterColor(1, 1, 1)
, m_dwLayerIdMask(dwLayerIdMask)
, m_bFlood(bFlood)
{
if (bMaskByLayerSettings)
{
m_fMinAltitude = m_rLayer.GetLayerStart();
m_fMaxAltitude = m_rLayer.GetLayerEnd();
m_fMinSlope = tan(m_rLayer.GetLayerMinSlopeAngle() / 90.1f * g_PI / 2.0f); // 0..90 -> 0..~1/0
m_fMaxSlope = tan(m_rLayer.GetLayerMaxSlopeAngle() / 90.1f * g_PI / 2.0f); // 0..90 -> 0..~1/0
}
else
{
m_fMinAltitude = -FLT_MAX;
m_fMaxAltitude = FLT_MAX;
m_fMinSlope = 0;
m_fMaxSlope = FLT_MAX;
}
}
float SEditorPaintBrush::GetMask(const float fX, const float fY) const
{
// Our expectation is that fX and fY are values of [0, 1) (i.e. includes 0, excludes 1).
// We're mapping this back to an int range where the width & height are generally powers of 2. So for example, we're mapping to 0 - 1023.
// To preserve maximum precision in our floats, and for ease of understanding, we're going to expect that our floats actually represent
// the 0 - 1024 range (i.e. 1 is 1024, not 1023), so that way each increment of a float is 1/1024 instead of 1/1023. This means that a value
// of 1 that's passed in is off the right edge of our range and technically invalid, so we'll just clamp if that happens.
int iX = AZStd::clamp(static_cast<uint64>(fX * m_rHeightmap.GetWidth()), static_cast<uint64>(0), static_cast<uint64>(m_rHeightmap.GetWidth() - 1));
int iY = AZStd::clamp(static_cast<uint64>(fY * m_rHeightmap.GetHeight()), static_cast<uint64>(0), static_cast<uint64>(m_rHeightmap.GetHeight() - 1));
float fAltitude = m_rHeightmap.GetZInterpolated(fX * m_rHeightmap.GetWidth(), fY * m_rHeightmap.GetHeight());
// Check if altitude is within brush min/max altitude
if (fAltitude < m_fMinAltitude || fAltitude > m_fMaxAltitude)
{
return 0;
}
float fSlope = m_rHeightmap.GetAccurateSlope(fX * m_rHeightmap.GetWidth(), fY * m_rHeightmap.GetHeight());
// Check if slope is within brush min/max slope
if (fSlope < m_fMinSlope || fSlope > m_fMaxSlope)
{
return 0;
}
// Soft slope test
// float fSlopeAplha = 1.f;
// fSlopeAplha *= CLAMP((m_fMaxSlope-fSlope)*4 + 0.25f,0,1);
// fSlopeAplha *= CLAMP((fSlope-m_fMinSlope)*4 + 0.25f,0,1);
if (m_dwLayerIdMask != 0xffffffff)
{
LayerWeight weight = m_rHeightmap.GetLayerWeightAt(iX, iY);
if ((weight.PrimaryId() & CLayer::e_undefined) != m_dwLayerIdMask)
{
return 0;
}
}
return 1;
}
//////////////////////////////////////////////////////////////////////////
void CImagePainter::PaintBrush(const float fpx, const float fpy, TImage<LayerWeight>& image, const SEditorPaintBrush& brush)
{
float fX = fpx * image.GetWidth(), fY = fpy * image.GetHeight();
// By using 1/width and 1/height as our scale, this means we're expecting to generate values of [0, 1).
// i.e. we're expecting to generate 0/width to (width-1)/width, and 0/height to (height-1)/height.
// This aligns with the expectations of how GetMask() will use these values.
const float fScaleX = 1.0f / image.GetWidth();
const float fScaleY = 1.0f / image.GetHeight();
////////////////////////////////////////////////////////////////////////
// Draw an attenuated spot on the map
////////////////////////////////////////////////////////////////////////
float fMaxDist, fAttenuation, fYSquared;
float fHardness = brush.hardness;
unsigned int pos;
LayerWeight* sourceData = image.GetData();
// Calculate the maximum distance
fMaxDist = brush.fRadius * image.GetWidth();
assert(image.GetWidth() == image.GetHeight());
int width = image.GetWidth();
int height = image.GetHeight();
int iMinX = (int)floor(fX - fMaxDist), iMinY = (int)floor(fY - fMaxDist);
int iMaxX = (int)ceil(fX + fMaxDist), iMaxY = (int)ceil(fY + fMaxDist);
for (int iPosY = iMinY; iPosY <= iMaxY; iPosY++)
{
// Skip invalid locations
if (iPosY < 0 || iPosY > height - 1)
{
continue;
}
float fy = (float)iPosY - fY;
// Precalculate
fYSquared = (float)(fy * fy);
for (int iPosX = iMinX; iPosX <= iMaxX; iPosX++)
{
float fx = (float)iPosX - fX;
// Skip invalid locations
if (iPosX < 0 || iPosX > width - 1)
{
continue;
}
// Only circle.
float dist = sqrtf(fYSquared + fx * fx);
if (!brush.m_bFlood && dist > fMaxDist)
{
continue;
}
float fMask = brush.GetMask(iPosX * fScaleX, iPosY * fScaleY);
if (fMask < 0.5f)
{
continue;
}
// Calculate the array index
pos = iPosX + iPosY * width;
// Calculate attenuation factor
fAttenuation = brush.m_bFlood ? 1.0f : 1.0f - __min(1.0f, dist / fMaxDist);
float h = static_cast<float>(sourceData[pos].GetWeight(brush.color) / 255.0f);
float dh = 1.0f - h;
float fh = clamp_tpl((fAttenuation) * dh * fHardness + h, 0.0f, 1.0f);
// A non-zero distance between our weight sample and the center point of the brush
// can cause fAttenuation to be ~0.999, so if h (the current weight) is 254, any
// value less than 1 * dh will give us a value between 254 and 255.
// As we convert from 0-1 back to 0-255 number ranges, it's important to round
// instead of truncating so that we don't have to have an exact distance of 0
// to reach a value of 255.
uint8 weight = static_cast<uint8>(clamp_tpl(round(fh * 255.0f), 0.0f, 255.0f));
sourceData[pos].SetWeight(brush.color, weight);
}
}
}
void CImagePainter::PaintBrushWithPattern(const float fpx, const float fpy, CImageEx& outImageBGR,
const uint32 dwOffsetX, const uint32 dwOffsetY, const float fScaleX, const float fScaleY,
const SEditorPaintBrush& brush, const CImageEx& imgPattern)
{
float fX = fpx * fScaleX, fY = fpy * fScaleY;
////////////////////////////////////////////////////////////////////////
// Draw an attenuated spot on the map
////////////////////////////////////////////////////////////////////////
float fMaxDist, fAttenuation, fYSquared;
float fHardness = brush.hardness;
unsigned int pos;
uint32* srcBGR = outImageBGR.GetData();
uint32* pat = imgPattern.GetData();
int value = brush.color;
// Calculate the maximum distance
fMaxDist = brush.fRadius;
int width = outImageBGR.GetWidth();
int height = outImageBGR.GetHeight();
int patwidth = imgPattern.GetWidth();
int patheight = imgPattern.GetHeight();
int iMinX = (int)floor(fX - fMaxDist), iMinY = (int)floor(fY - fMaxDist);
int iMaxX = (int)ceil(fX + fMaxDist), iMaxY = (int)ceil(fY + fMaxDist);
bool bSRGB = imgPattern.GetSRGB();
for (int iPosY = iMinY; iPosY < iMaxY; iPosY++)
{
// Skip invalid locations
if (iPosY - dwOffsetY < 0 || iPosY - dwOffsetY > height - 1)
{
continue;
}
float fy = (float)iPosY - fY;
// Precalculate
fYSquared = (float)(fy * fy);
int32 iPatY = ((uint32)iPosY) % patheight;
assert(iPatY >= 0 && iPatY < patheight);
for (int iPosX = iMinX; iPosX < iMaxX; iPosX++)
{
float fx = (float)iPosX - fX;
// Skip invalid locations
if (iPosX - dwOffsetX < 0 || iPosX - dwOffsetX > width - 1)
{
continue;
}
// Only circle.
float dist = sqrtf(fYSquared + fx * fx);
if (!brush.m_bFlood && dist > fMaxDist)
{
continue;
}
// Calculate the array index
pos = (iPosX - dwOffsetX) + (iPosY - dwOffsetY) * width;
// Calculate attenuation factor
fAttenuation = brush.m_bFlood ? 1.0f : 1.0f - __min(1.0f, dist / fMaxDist);
assert(fAttenuation >= 0.0f && fAttenuation <= 1.0f);
// Note that GetMask expects a range of [0, 1), so it's correct to divide by
// fScaleX and fScaleY instead of (fScaleX-1) and (fScaleY-1).
float fMask = brush.GetMask(iPosX / fScaleX, iPosY / fScaleY);
uint32 cDstPixBGR = srcBGR[pos];
int32 iPatX = ((uint32)iPosX) % patwidth;
assert(iPatX >= 0 && iPatX < patwidth);
uint32 cSrcPix = pat[iPatX + iPatY * patwidth];
float s = fAttenuation * fHardness * fMask;
assert(s >= 0.0f && s <= 1.0f);
if (fcmp(s, 0))
{
// If the blend would be entirely biased to the pixel in outImage then don't modify anything
// (The logic below is susceptible to floating point inaccuracy and would change the pixel
// even though it is not supposed to)
continue;
}
const float fRecip255 = 1.0f / 255.0f;
// Convert Src to Linear Space (Src is pattern texture, can be in linear or gamma space)
ColorF cSrc = ColorF(GetRValue(cSrcPix), GetGValue(cSrcPix), GetBValue(cSrcPix)) * fRecip255;
if (bSRGB)
{
cSrc.srgb2rgb();
}
ColorF cMtl = brush.m_cFilterColor;
cMtl.srgb2rgb();
cSrc *= cMtl;
cSrc.clamp(0.0f, 1.0f);
// Convert Dst to Linear Space ( Dst is always in gamma space ), and load from BGR -> RGB
ColorF cDst = ColorF(GetBValue(cDstPixBGR), GetGValue(cDstPixBGR), GetRValue(cDstPixBGR)) * fRecip255;
cDst.srgb2rgb();
// Linear space blend
ColorF cOut = cSrc * s + cDst * (1.0f - s);
// Convert final result to gamma space and put back in [0..255] range
cOut.rgb2srgb();
cOut *= 255.0f;
// Save the blended result as BGR
// It's important to round as we go from float back to int. If we just truncate,
// we'll end up with consistently darker colors.
srcBGR[pos] = RGB(round(cOut.b), round(cOut.g), round(cOut.r));
}
}
}
void CImagePainter::FillWithPattern(CImageEx& outImage, const uint32 dwOffsetX, const uint32 dwOffsetY,
const CImageEx& imgPattern)
{
unsigned int pos;
uint32* src = outImage.GetData();
uint32* pat = imgPattern.GetData();
int width = outImage.GetWidth();
int height = outImage.GetHeight();
int patwidth = imgPattern.GetWidth();
int patheight = imgPattern.GetHeight();
if (patheight == 0 || patwidth == 0)
{
return;
}
for (int iPosY = 0; iPosY < height; iPosY++)
{
int32 iPatY = ((uint32)iPosY + dwOffsetY) % patheight;
assert(iPatY >= 0 && iPatY < patheight);
for (int iPosX = 0; iPosX < width; iPosX++)
{
// Calculate the array index
pos = iPosX + iPosY * width;
int32 iPatX = ((uint32)iPosX + dwOffsetX) % patwidth;
assert(iPatX >= 0 && iPatX < patwidth);
uint32 cSrc = pat[iPatX + iPatY * patwidth];
src[pos] = cSrc;
}
}
}
+75
View File
@@ -0,0 +1,75 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_IMAGEPAINTER_H
#define CRYINCLUDE_EDITOR_UTIL_IMAGEPAINTER_H
#pragma once
#include "Util/Image.h"
struct LayerWeight;
// Brush structure used for painting.
struct SANDBOX_API SEditorPaintBrush
{
// constructor
SEditorPaintBrush(class CHeightmap& rHeightmap, class CLayer& rLayer,
const bool bMaskByLayerSettings, const uint32 dwLayerIdMask, const bool bFlood);
CHeightmap& m_rHeightmap; // for mask support
unsigned char color; // Painting color
float fRadius; // outer radius (0..1 for the whole terrain size)
float hardness; // 0-1 hardness of brush
bool bBlended; // true=shades of the value are stores, false=the value is either stored or not
bool m_bFlood; // true=fills square area without attenuation, false=fills circle area with attenuation
uint32 m_dwLayerIdMask;// reference Value for the mask, 0xffffffff if not used
CLayer& m_rLayer; // layer we paint with
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
ColorF m_cFilterColor; // (1,1,1) if not used, multiplied with brightness
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
// Arguments:
// fX - 0..1 in the whole terrain
// fY - 0..1 in the whole terrain
// Return:
// 0=paint there 0% .. 1=paint there 100%
float GetMask(const float fX, const float fY) const;
protected: // --------------------------------------------------------------------------
float m_fMinSlope; // in m per m
float m_fMaxSlope; // in m per me
float m_fMinAltitude; // in m
float m_fMaxAltitude; // in m
};
// Contains image painting functions.
class CImagePainter
{
public:
// Paint spot on image at position px,py with specified paint brush parameters (to a layer)
// Arguments:
// fpx - 0..1 in the whole terrain (used for the mask)
// fpy - 0..1 in the whole terrain (used for the mask)
SANDBOX_API void PaintBrush(const float fpx, const float fpy, TImage<LayerWeight>& image, const SEditorPaintBrush& brush);
// Paint spot with pattern (to an RGB image)
// real spot is drawn to (fpx-dwOffsetX,fpy-dwOffsetY) - to get the pattern working we need this info split up like this
// Arguments:
// fpx - 0..1 in the whole terrain (used for the mask)
// fpy - 0..1 in the whole terrain (used for the mask)
void PaintBrushWithPattern(const float fpx, const float fpy, CImageEx& outImage, const uint32 dwOffsetX, const uint32 dwOffsetY,
const float fScaleX, const float fScaleY, const SEditorPaintBrush& brush, const CImageEx& imgPattern);
//
void FillWithPattern(CImageEx& outImage, const uint32 dwOffsetX, const uint32 dwOffsetY, const CImageEx& imgPattern);
};
#endif // CRYINCLUDE_EDITOR_UTIL_IMAGEPAINTER_H
+497
View File
@@ -0,0 +1,497 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ImageTIF.h"
/// libTiff
#include <tiffio.h> // TIFF library
// Function prototypes
static tsize_t libtiffDummyReadProc (thandle_t fd, tdata_t buf, tsize_t size);
static tsize_t libtiffDummyWriteProc (thandle_t fd, tdata_t buf, tsize_t size);
static toff_t libtiffDummySizeProc(thandle_t fd);
static toff_t libtiffDummySeekProc (thandle_t fd, toff_t off, int i);
//static int libtiffDummyCloseProc (thandle_t fd);
// Structure used to pass state to our in-memory TIFF file callbacks
struct MemImage
{
uint8 *buffer;
uint32 offset;
uint32 size;
};
/////////////////// Callbacks to libtiff
static int libtiffDummyMapFileProc(thandle_t, tdata_t*, toff_t*)
{
return 0;
}
static void libtiffDummyUnmapFileProc(thandle_t, tdata_t, toff_t)
{
}
static toff_t libtiffDummySizeProc(thandle_t fd)
{
MemImage *memImage = static_cast<MemImage *>(fd);
return memImage->size;
}
static tsize_t
libtiffDummyReadProc (thandle_t fd, tdata_t buf, tsize_t size)
{
MemImage *memImage = static_cast<MemImage *>(fd);
tsize_t nBytesLeft = memImage->size - memImage->offset;
if (size > nBytesLeft)
{
size = nBytesLeft;
}
memcpy(buf, &memImage->buffer[memImage->offset], size);
memImage->offset += size;
// Return the amount of data read
return size;
}
static tsize_t
libtiffDummyWriteProc ([[maybe_unused]] thandle_t fd, [[maybe_unused]] tdata_t buf, tsize_t size)
{
return (size);
}
static toff_t
libtiffDummySeekProc (thandle_t fd, toff_t off, int i)
{
MemImage *memImage = static_cast<MemImage *>(fd);
switch (i)
{
case SEEK_SET:
memImage->offset = off;
break;
case SEEK_CUR:
memImage->offset += off;
break;
case SEEK_END:
memImage->offset = memImage->size - off;
break;
default:
memImage->offset = off;
break;
}
// This appears to return the location that it went to
return memImage->offset;
}
static int
libtiffDummyCloseProc ([[maybe_unused]] thandle_t fd)
{
// Return a zero meaning all is well
return 0;
}
bool CImageTIF::Load(const QString& fileName, CImageEx& outImage)
{
CCryFile file;
if (!file.Open(fileName.toUtf8().data(), "rb"))
{
CLogFile::FormatLine("File not found %s", fileName.toUtf8().data());
return false;
}
MemImage memImage;
std::vector<uint8> data;
memImage.size = file.GetLength();
data.resize(memImage.size);
memImage.buffer = &data[0];
memImage.offset = 0;
file.ReadRaw(memImage.buffer, memImage.size);
// Open the dummy document (which actually only exists in memory)
TIFF* tif = TIFFClientOpen (fileName.toUtf8().data(), "rm", (thandle_t)&memImage, libtiffDummyReadProc,
libtiffDummyWriteProc, libtiffDummySeekProc,
libtiffDummyCloseProc, libtiffDummySizeProc, libtiffDummyMapFileProc, libtiffDummyUnmapFileProc);
// TIFF* tif = TIFFOpen(fileName,"r");
bool bRet = false;
if (tif)
{
uint32 dwWidth, dwHeight;
size_t npixels;
uint32* raster;
char* dccfilename = NULL;
TIFFGetField(tif, TIFFTAG_IMAGEWIDTH, &dwWidth);
TIFFGetField(tif, TIFFTAG_IMAGELENGTH, &dwHeight);
TIFFGetField(tif, TIFFTAG_IMAGEDESCRIPTION, &dccfilename);
npixels = dwWidth * dwHeight;
raster = (uint32*)_TIFFmalloc((tsize_t)(npixels * sizeof(uint32)));
if (raster)
{
if (TIFFReadRGBAImage(tif, dwWidth, dwHeight, raster, 0))
{
if (outImage.Allocate(dwWidth, dwHeight))
{
char* dest = (char*)outImage.GetData();
uint32 dwPitch = dwWidth * 4;
for (uint32 dwY = 0; dwY < dwHeight; ++dwY)
{
char* src2 = (char*)&raster[(dwHeight - 1 - dwY) * dwWidth];
char* dest2 = &dest[dwPitch * dwY];
memcpy(dest2, src2, dwWidth * 4);
}
if (dccfilename)
{
outImage.SetDccFilename(dccfilename);
}
bRet = true;
}
}
_TIFFfree(raster);
}
TIFFClose(tif);
}
if (!bRet)
{
outImage.Detach();
}
return bRet;
}
bool CImageTIF::Load(const QString& fileName, CFloatImage& outImage)
{
// Defined in GeoTIFF format - http://web.archive.org/web/20160403164508/http://www.remotesensing.org/geotiff/spec/geotiffhome.html
// Used to get the X, Y, Z scales from a GeoTIFF file
static const int GEOTIFF_MODELPIXELSCALE_TAG = 33550;
CCryFile file;
if (!file.Open(fileName.toUtf8().data(), "rb"))
{
CLogFile::FormatLine("File not found %s", fileName.toUtf8().data());
return false;
}
MemImage memImage;
std::vector<uint8> data;
memImage.size = file.GetLength();
data.resize(memImage.size);
memImage.buffer = &data[0];
memImage.offset = 0;
file.ReadRaw(memImage.buffer, memImage.size);
// Open the dummy document (which actually only exists in memory)
TIFF* tif = TIFFClientOpen(fileName.toUtf8().data(), "rm", (thandle_t)&memImage, libtiffDummyReadProc,
libtiffDummyWriteProc, libtiffDummySeekProc,
libtiffDummyCloseProc, libtiffDummySizeProc, libtiffDummyMapFileProc, libtiffDummyUnmapFileProc);
// TIFF* tif = TIFFOpen(fileName,"r");
bool bRet = false;
if (tif)
{
uint32 width = 0, height = 0;
uint16 spp = 0, bpp = 0, format = 0;
char* dccfilename = NULL;
TIFFGetField(tif, TIFFTAG_IMAGEDESCRIPTION, &dccfilename);
TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGEWIDTH, &width);
TIFFGetFieldDefaulted(tif, TIFFTAG_IMAGELENGTH, &height);
TIFFGetFieldDefaulted(tif, TIFFTAG_BITSPERSAMPLE, &bpp); // how many bits each color component is. typically 8-bit, but could be 16-bit.
TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLESPERPIXEL, &spp); // how many color components per pixel? 1=greyscale, 3=RGB, 4=RGBA
TIFFGetFieldDefaulted(tif, TIFFTAG_SAMPLEFORMAT, &format); // format of the pixel data - int, uint, float.
// There are two types of 32-bit floating point TIF semantics. Paint programs tend to use values in the 0.0 - 1.0 range.
// GeoTIFF files use values where 1.0 = 1 meter by default, but also have an optional ZScale parameter to provide additional
// scaling control.
// By default, we'll assume this is a regular TIFF that we want to leave in the 0.0 - 1.0 range.
float pixelValueScale = 1.0f;
// Check to see if it's a GeoTIFF, and if so, whether or not it has the ZScale parameter.
uint32 tagCount = 0;
double *pixelScales = NULL;
if (TIFFGetField(tif, GEOTIFF_MODELPIXELSCALE_TAG, &tagCount, &pixelScales) == 1)
{
// if there's an xyz scale, and the Z scale isn't 0, let's use it.
if ((tagCount == 3) && (pixelScales != NULL) && (pixelScales[2] != 0.0f))
{
pixelValueScale = static_cast<float>(pixelScales[2]);
}
}
uint32 linesize = TIFFScanlineSize(tif);
uint8* linebuf = static_cast<uint8*>(_TIFFmalloc(linesize));
// We assume that a scanline has all of the samples in it. Validate the assumption.
assert(linesize == (width * (bpp / 8) * spp));
// Aliases for linebuf to make it easier to pull different types out of the scanline.
uint16* linebufUint16 = reinterpret_cast<uint16*>(linebuf);
uint32* linebufUint32 = reinterpret_cast<uint32*>(linebuf);
float* linebufFloat = reinterpret_cast<float*>(linebuf);
if (linebuf)
{
if (outImage.Allocate(width, height))
{
float* dest = outImage.GetData();
bRet = true;
float maxPixelValue = 0.0f;
for (uint32 y = 0; y < height; y++)
{
TIFFReadScanline(tif, linebuf, y);
// For each pixel, we either scale or clamp the values to a 16-bit range. It is asymmetric behaviour, but based
// on assumptions about the input data:
// 8-bit values are scaled up because 8-bit textures used as heightmaps are usually scaled-down 16-bit values.
// 32-bit values may or may not need to scale down, depending on the intended authoring range. Our assumption
// is that they were most likely authored with the intent of 1:1 value translations.
for (uint32 x = 0; x < width; x++)
{
switch (bpp)
{
case 8:
// Scale 0-255 to 0.0 - 1.0
dest[(y * width) + x] = static_cast<float>(linebuf[x * spp]) / static_cast<float>(std::numeric_limits<uint8>::max());
break;
case 16:
// Scale 0-65535 to 0.0 - 1.0
dest[(y * width) + x] = static_cast<float>(linebufUint16[x * spp]) / static_cast<float>(std::numeric_limits<uint16>::max());
break;
case 32:
// 32-bit values could be ints or floats.
if (format == SAMPLEFORMAT_INT)
{
// Scale 0-max int32 to 0.0 - 1.0
dest[(y * width) + x] = clamp_tpl(static_cast<float>(linebufUint32[x * spp]) / static_cast<float>(std::numeric_limits<int32>::max()), 0.0f, 1.0f);
}
else if (format == SAMPLEFORMAT_UINT)
{
// Scale 0-max uint32 to 0.0 - 1.0
dest[(y * width) + x] = clamp_tpl(static_cast<float>(linebufUint32[x * spp]) / static_cast<float>(std::numeric_limits<uint32>::max()), 0.0f, 1.0f);
}
else if (format == SAMPLEFORMAT_IEEEFP)
{
dest[(y * width) + x] = linebufFloat[x * spp] * pixelValueScale;
}
else
{
// Unknown / unsupported format.
bRet = false;
}
break;
default:
// Unknown / unsupported format.
bRet = false;
break;
}
maxPixelValue = max(maxPixelValue, dest[(y * width) + x]);
}
}
if (dccfilename)
{
outImage.SetDccFilename(dccfilename);
}
// If this is a GeoTIFF using 32-bit floats, we will end up outside the 0.0 - 1.0 range. Let's scale it back down to 0.0 - 1.0.
if (maxPixelValue > 1.0f)
{
for (uint32 y = 0; y < height; y++)
{
for (uint32 x = 0; x < width; x++)
{
dest[(y * width) + x] = dest[(y * width) + x] / maxPixelValue;
}
}
}
}
_TIFFfree(linebuf);
}
TIFFClose(tif);
}
if (!bRet)
{
outImage.Detach();
}
return bRet;
}
//////////////////////////////////////////////////////////////////////////
bool CImageTIF::SaveRAW(const QString& fileName, const void* pData, int width, int height, int bytesPerChannel, int numChannels, bool bFloat, const char* preset)
{
if (bFloat && (bytesPerChannel != 2 && bytesPerChannel != 4))
{
bFloat = false;
}
bool bRet = false;
CFileUtil::OverwriteFile(fileName);
TIFF* tif = TIFFOpen(fileName.toUtf8().data(), "wb");
if (tif)
{
TIFFSetField(tif, TIFFTAG_IMAGEWIDTH, width);
TIFFSetField(tif, TIFFTAG_IMAGELENGTH, height);
TIFFSetField(tif, TIFFTAG_SAMPLESPERPIXEL, numChannels);
TIFFSetField(tif, TIFFTAG_BITSPERSAMPLE, bytesPerChannel * 8);
TIFFSetField(tif, TIFFTAG_PLANARCONFIG, PLANARCONFIG_CONTIG);
TIFFSetField(tif, TIFFTAG_ROWSPERSTRIP, 1);
TIFFSetField(tif, TIFFTAG_COMPRESSION, COMPRESSION_NONE);
TIFFSetField(tif, TIFFTAG_PHOTOMETRIC, (numChannels == 1) ? PHOTOMETRIC_MINISBLACK : PHOTOMETRIC_RGB);
TIFFSetField(tif, TIFFTAG_ORIENTATION, ORIENTATION_TOPLEFT);
if (bFloat)
{
TIFFSetField(tif, TIFFTAG_SAMPLEFORMAT, SAMPLEFORMAT_IEEEFP);
}
if (preset && preset[0])
{
string tiffphotoshopdata, valueheader;
string presetkeyvalue = string("/preset=") + string(preset);
valueheader.push_back('\x1C');
valueheader.push_back('\x02');
valueheader.push_back('\x28');
valueheader.push_back((presetkeyvalue.size() >> 8) & 0xFF);
valueheader.push_back((presetkeyvalue.size()) & 0xFF);
valueheader.append(presetkeyvalue);
tiffphotoshopdata.push_back('8');
tiffphotoshopdata.push_back('B');
tiffphotoshopdata.push_back('I');
tiffphotoshopdata.push_back('M');
tiffphotoshopdata.push_back('\x04');
tiffphotoshopdata.push_back('\x04');
tiffphotoshopdata.push_back('\x00');
tiffphotoshopdata.push_back('\x00');
tiffphotoshopdata.push_back((valueheader.size() >> 24) & 0xFF);
tiffphotoshopdata.push_back((valueheader.size() >> 16) & 0xFF);
tiffphotoshopdata.push_back((valueheader.size() >> 8) & 0xFF);
tiffphotoshopdata.push_back((valueheader.size()) & 0xFF);
tiffphotoshopdata.append(valueheader);
TIFFSetField(tif, TIFFTAG_PHOTOSHOP, tiffphotoshopdata.size(), tiffphotoshopdata.c_str());
}
size_t pitch = width * bytesPerChannel * numChannels;
char* raster = (char*) _TIFFmalloc((tsize_t)(pitch * height));
memcpy(raster, pData, pitch * height);
bRet = true;
for (int h = 0; h < height; ++h)
{
size_t offset = h * pitch;
int err = TIFFWriteScanline(tif, raster + offset, h, 0);
if (err < 0)
{
bRet = false;
break;
}
}
_TIFFfree(raster);
TIFFClose(tif);
}
return bRet;
}
const char* CImageTIF::GetPreset(const QString& fileName)
{
std::vector<uint8> data;
CCryFile file;
if (!file.Open(fileName.toUtf8().data(), "rb"))
{
CLogFile::FormatLine("File not found %s", fileName.toUtf8().data());
return NULL;
}
MemImage memImage;
memImage.size = file.GetLength();
data.resize(memImage.size);
memImage.buffer = &data[0];
memImage.offset = 0;
file.ReadRaw(memImage.buffer, memImage.size);
TIFF* tif = TIFFClientOpen (fileName.toUtf8().data(), "rm", (thandle_t)&memImage, libtiffDummyReadProc,
libtiffDummyWriteProc, libtiffDummySeekProc,
libtiffDummyCloseProc, libtiffDummySizeProc, libtiffDummyMapFileProc, libtiffDummyUnmapFileProc);
string strReturn;
char* preset = NULL;
int size;
if (tif)
{
TIFFGetField(tif, TIFFTAG_PHOTOSHOP, &size, &preset);
for (int i = 0; i < size; ++i)
{
if (!strncmp((preset + i), "preset", 6))
{
char* presetoffset = preset + i;
strReturn = presetoffset;
if (strReturn.find('/') != -1)
{
strReturn = strReturn.substr(0, strReturn.find('/'));
}
break;
}
}
TIFFClose(tif);
}
return strReturn.c_str();
}
+25
View File
@@ -0,0 +1,25 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_IMAGETIF_H
#define CRYINCLUDE_EDITOR_UTIL_IMAGETIF_H
#pragma once
#include "Util/Image.h"
class SANDBOX_API CImageTIF
{
public:
bool Load(const QString& fileName, CImageEx& outImage);
bool Load(const QString& fileName, CFloatImage& outImage);
bool SaveRAW(const QString& fileName, const void* pData, int width, int height, int bytesPerChannel, int numChannels, bool bFloat, const char* preset);
static const char* GetPreset(const QString& fileName);
};
#endif // CRYINCLUDE_EDITOR_UTIL_IMAGETIF_H
+525
View File
@@ -0,0 +1,525 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Image utilities implementation.
#include "EditorDefs.h"
#include "ImageUtil.h"
// Editor
#include "Util/ImageGif.h"
#include "Util/ImageTIF.h"
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::Save(const QString& strFileName, CImageEx& inImage)
{
QImage imgBitmap;
ImageToQImage(inImage, imgBitmap);
// Explicitly set the pixels per meter in our images to a consistent default.
// The normal default is 96 pixels per inch, or 3780 pixels per meter.
// However, the Windows scaling display setting can cause these numbers to vary
// on different machines, producing output files that have slightly different
// headers from machine to machine, which often isn't desirable.
const int defaultPixelsPerMeter = 3780;
imgBitmap.setDotsPerMeterX(defaultPixelsPerMeter);
imgBitmap.setDotsPerMeterY(defaultPixelsPerMeter);
return imgBitmap.save(strFileName);
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::SaveBitmap(const QString& szFileName, CImageEx& inImage)
{
return Save(szFileName, inImage);
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::SaveJPEG(const QString& strFileName, CImageEx& inImage)
{
return Save(strFileName, inImage);
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::Load(const QString& fileName, CImageEx& image)
{
QImage imgBitmap(fileName);
if (imgBitmap.isNull())
{
CLogFile::FormatLine("Invalid file: %s", fileName.toUtf8().data());
return false;
}
return QImageToImage(imgBitmap, image);
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::LoadJPEG(const QString& strFileName, CImageEx& outImage)
{
return CImageUtil::Load(strFileName, outImage);
}
//===========================================================================
bool CImageUtil::LoadBmp(const QString& fileName, CImageEx& image)
{
return CImageUtil::Load(fileName, image);
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::SavePGM(const QString& fileName, const CImageEx& image)
{
// There are two types of PGM ("Portable Grey Map") files - "raw" (binary) and "plain" (ASCII). This function supports the "plain PGM" format.
// See http://netpbm.sourceforge.net/doc/pgm.html or https://en.wikipedia.org/wiki/Netpbm_format for the definition.
uint32 width = image.GetWidth();
uint32 height = image.GetHeight();
uint32* pixels = image.GetData();
// Create the file header.
string fileHeader;
fileHeader.Format(
// P2 = PGM header for ASCII output. (P5 is PGM header for binary output)
"P2\n"
// width and height of the image
"%d %d\n"
// The maximum grey value in the file. (i.e. the max value for any given pixel below)
"65535\n"
, width, height);
FILE* file = nullptr;
azfopen(&file, fileName.toUtf8().data(), "wt");
if (!file)
{
return false;
}
// First print the file header
fprintf(file, fileHeader.c_str());
// Then print all the pixels.
for (int32 y = 0; y < height; y++)
{
for (int32 x = 0; x < width; x++)
{
fprintf(file, "%d ", pixels[x + (y * width)]);
}
fprintf(file, "\n");
}
fclose(file);
return true;
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::LoadPGM(const QString& fileName, CImageEx& image)
{
FILE* file = nullptr;
azfopen(&file, fileName.toUtf8().data(), "rt");
if (!file)
{
return false;
}
const char seps[] = " \n\t\r";
char* token;
int32 width = 0;
int32 height = 0;
int32 numColors = 1;
fseek(file, 0, SEEK_END);
int fileSize = ftell(file);
fseek(file, 0, SEEK_SET);
char* str = new char[fileSize];
fread(str, fileSize, 1, file);
char* nextToken = nullptr;
token = azstrtok(str, 0, seps, &nextToken);
while (token != NULL && token[0] == '#')
{
if (token != NULL && token[0] == '#')
{
azstrtok(NULL, 0, "\n", &nextToken);
}
token = azstrtok(NULL, 0, seps, &nextToken);
}
if (azstricmp(token, "P2") != 0)
{
// Bad file. not supported pgm.
delete[]str;
fclose(file);
return false;
}
do
{
token = azstrtok(NULL, 0, seps, &nextToken);
if (token != NULL && token[0] == '#')
{
azstrtok(NULL, 0, "\n", &nextToken);
}
} while (token != NULL && token[0] == '#');
width = atoi(token);
do
{
token = azstrtok(NULL, 0, seps, &nextToken);
if (token != NULL && token[0] == '#')
{
azstrtok(NULL, 0, "\n", &nextToken);
}
} while (token != NULL && token[0] == '#');
height = atoi(token);
do
{
token = azstrtok(NULL, 0, seps, &nextToken);
if (token != NULL && token[0] == '#')
{
azstrtok(NULL, 0, "\n", &nextToken);
}
} while (token != NULL && token[0] == '#');
numColors = atoi(token);
image.Allocate(width, height);
uint32* p = image.GetData();
int size = width * height;
int i = 0;
while (token != NULL && i < size)
{
do
{
token = azstrtok(NULL, 0, seps, &nextToken);
} while (token != NULL && token[0] == '#');
*p++ = atoi(token);
i++;
}
delete[]str;
fclose(file);
// If we have 16-bit greyscale values that we're storing into 32-bit pixels, denote it with an appropriate texture type.
if (numColors > 255)
{
image.SetFormat(eTF_R16G16);
}
return true;
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::LoadImage(const QString& fileName, CImageEx& image, bool* pQualityLoss)
{
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];
if (pQualityLoss)
{
*pQualityLoss = false;
}
_splitpath_s(fileName.toUtf8().data(), drive, dir, fname, ext);
// Only DDS has explicit sRGB flag - we'll assume by default all formats are stored in gamma space
image.SetSRGB(true);
if (azstricmp(ext, ".bmp") == 0)
{
return LoadBmp(fileName, image);
}
else if (azstricmp(ext, ".tif") == 0)
{
return CImageTIF().Load(fileName, image);
}
else if (azstricmp(ext, ".jpg") == 0)
{
if (pQualityLoss)
{
*pQualityLoss = true; // we assume JPG has quality loss
}
return LoadJPEG(fileName, image);
}
else if (azstricmp(ext, ".gif") == 0)
{
return CImageGif().Load(fileName, image);
}
else if (azstricmp(ext, ".pgm") == 0)
{
return LoadPGM(fileName, image);
}
else if (azstricmp(ext, ".png") == 0)
{
return CImageUtil::Load(fileName, image);
}
else
{
return CImageUtil::Load(fileName, image);
}
}
//////////////////////////////////////////////////////////////////////////
bool CImageUtil::SaveImage(const QString& fileName, CImageEx& image)
{
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];
// Remove the read-only attribute so the file can be overwritten.
QFile(fileName).setPermissions(QFile::ReadUser | QFile::WriteUser);
_splitpath_s(fileName.toUtf8().data(), drive, dir, fname, ext);
if (azstricmp(ext, ".bmp") == 0)
{
return SaveBitmap(fileName, image);
}
else if (azstricmp(ext, ".jpg") == 0)
{
return SaveJPEG(fileName, image);
}
else if (azstricmp(ext, ".pgm") == 0)
{
return SavePGM(fileName, image);
}
else
{
return Save(fileName, image);
}
}
//////////////////////////////////////////////////////////////////////////
void CImageUtil::ScaleToFit(const CByteImage& srcImage, CByteImage& trgImage)
{
trgImage.ScaleToFit(srcImage);
}
//////////////////////////////////////////////////////////////////////////
void CImageUtil::DownScaleSquareTextureTwice(const CImageEx& srcImage, CImageEx& trgImage, IImageUtil::_EAddrMode eAddressingMode)
{
uint32* pSrcData = srcImage.GetData();
int nSrcWidth = srcImage.GetWidth();
int nSrcHeight = srcImage.GetHeight();
int nTrgWidth = srcImage.GetWidth() >> 1;
int nTrgHeight = srcImage.GetHeight() >> 1;
// reallocate target
trgImage.Release();
trgImage.Allocate(nTrgWidth, nTrgHeight);
uint32* pDstData = trgImage.GetData();
// values in this filter are the log2 of the actual multiplicative values .. see DXCFILTER_BLUR3X3 for the used 3x3 filter
static int filter[3][3] =
{
{0, 1, 0},
{1, 2, 1},
{0, 1, 0}
};
for (int i = 0; i < nTrgHeight; i++)
{
for (int j = 0; j < nTrgWidth; j++)
{
// filter3x3
int x = j << 1;
int y = i << 1;
int r, g, b, a;
r = b = g = a = 0;
uint32 col;
if (eAddressingMode == IImageUtil::WRAP) // TODO: this condition could be compile-time static by making it a template arg
{
for (int ii = 0; ii < 3; ii++)
{
for (int jj = 0; jj < 3; jj++)
{
col = pSrcData[((y + nSrcHeight + ii - 1) % nSrcHeight) * nSrcWidth + ((x + nSrcWidth + jj - 1) % nSrcWidth)];
r += (col & 0xff) << filter[ii][jj];
g += ((col >> 8) & 0xff) << filter[ii][jj];
b += ((col >> 16) & 0xff) << filter[ii][jj];
a += ((col >> 24) & 0xff) << filter[ii][jj];
}
}
}
else
{
assert(eAddressingMode == IImageUtil::CLAMP);
for (int ii = 0; ii < 3; ii++)
{
for (int jj = 0; jj < 3; jj++)
{
int x1 = clamp_tpl<int>((x + jj), 0, nSrcWidth - 1);
int y1 = clamp_tpl<int>((y + ii), 0, nSrcHeight - 1);
col = pSrcData[ y1 * nSrcWidth + x1];
r += (col & 0xff) << filter[ii][jj];
g += ((col >> 8) & 0xff) << filter[ii][jj];
b += ((col >> 16) & 0xff) << filter[ii][jj];
a += ((col >> 24) & 0xff) << filter[ii][jj];
}
}
}
// the sum of the multiplicative values here is 16 so we shift by 4 bits
r >>= 4;
g >>= 4;
b >>= 4;
a >>= 4;
uint32 res = r + (g << 8) + (b << 16) + (a << 24);
*pDstData++ = res;
}
}
}
//////////////////////////////////////////////////////////////////////////
void CImageUtil::ScaleToFit(const CImageEx& srcImage, CImageEx& trgImage)
{
trgImage.ScaleToFit(srcImage);
}
//////////////////////////////////////////////////////////////////////////
void CImageUtil::ScaleToDoubleFit(const CImageEx& srcImage, CImageEx& trgImage)
{
uint32 x, y, u, v;
unsigned int* destRow, * dest, * src, * sourceRow;
uint32 srcW = srcImage.GetWidth();
uint32 srcH = srcImage.GetHeight();
uint32 trgHalfW = trgImage.GetWidth() / 2;
uint32 trgH = trgImage.GetHeight();
uint32 xratio = trgHalfW > 0 ? (srcW << 16) / trgHalfW : 1;
uint32 yratio = trgH > 0 ? (srcH << 16) / trgH : 1;
src = srcImage.GetData();
destRow = trgImage.GetData();
v = 0;
for (y = 0; y < trgH; y++)
{
u = 0;
sourceRow = src + (v >> 16) * srcW;
dest = destRow;
for (x = 0; x < trgHalfW; x++)
{
*(dest + trgHalfW) = sourceRow[u >> 16];
*dest++ = sourceRow[u >> 16];
u += xratio;
}
v += yratio;
destRow += trgHalfW * 2;
}
}
//////////////////////////////////////////////////////////////////////////
void CImageUtil::SmoothImage(CByteImage& image, int numSteps)
{
assert(numSteps > 0);
uint8* buf = image.GetData();
int w = image.GetWidth();
int h = image.GetHeight();
for (int steps = 0; steps < numSteps; steps++)
{
// Smooth the image.
for (int y = 1; y < h - 1; y++)
{
// Precalculate for better speed
uint8* ptr = &buf[y * w + 1];
for (int x = 1; x < w - 1; x++)
{
// Smooth it out
*ptr =
(
(uint32)ptr[1] +
ptr[w] +
ptr[-1] +
ptr[-w] +
ptr[w + 1] +
ptr[w - 1] +
ptr[-w + 1] +
ptr[-w - 1]
) >> 3;
// Next pixel
ptr++;
}
}
}
}
unsigned char CImageUtil::GetBilinearFilteredAt(const int iniX256, const int iniY256, const CByteImage& image)
{
// assert(image.IsValid()); if(!image.IsValid())return(0); // this shouldn't be
DWORD x = (DWORD)(iniX256) >> 8;
DWORD y = (DWORD)(iniY256) >> 8;
if (x >= image.GetWidth() - 1 || y >= image.GetHeight() - 1)
{
return image.ValueAt(x, y); // border is not filtered, 255 to get in range 0..1
}
DWORD rx = (DWORD)(iniX256) & 0xff; // fractional aprt
DWORD ry = (DWORD)(iniY256) & 0xff; // fractional aprt
DWORD top = (DWORD)image.ValueAt((int)x, (int)y) * (256 - rx) // left top
+ (DWORD)image.ValueAt((int)x + 1, (int)y) * rx; // right top
DWORD bottom = (DWORD)image.ValueAt((int)x, (int)y + 1) * (256 - rx) // left bottom
+ (DWORD)image.ValueAt((int)x + 1, (int)y + 1) * rx; // right bottom
return (unsigned char)((top * (256 - ry) + bottom * ry) >> 16);
}
bool CImageUtil::QImageToImage(const QImage& bitmap, CImageEx& image)
{
QImage convertedBitmap;
const QImage *srcBitmap = &bitmap;
if (bitmap.format() != QImage::Format_RGBA8888)
{
convertedBitmap = bitmap.convertToFormat(QImage::Format_RGBA8888);
srcBitmap = &convertedBitmap;
}
if (image.Allocate(srcBitmap->width(), srcBitmap->height()) == false)
{
return false;
}
AZStd::copy(srcBitmap->bits(), srcBitmap->bits() + (srcBitmap->width() * srcBitmap->height() * sizeof(uint32)), reinterpret_cast<uint8*>(image.GetData()));
return true;
}
bool CImageUtil::ImageToQImage(const CImageEx& image, QImage& bitmapObj)
{
bitmapObj = QImage(image.GetWidth(), image.GetHeight(), QImage::Format_RGBA8888);
AZStd::copy(image.GetData(), image.GetData() + image.GetWidth() * image.GetHeight(), reinterpret_cast<uint32*>(bitmapObj.bits()));
return true;
}
+77
View File
@@ -0,0 +1,77 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Image utilities.
#pragma once
#include <Include/IImageUtil.h>
/*!
* Utility Class to manipulate images.
*/
class SANDBOX_API CImageUtil
{
public:
//////////////////////////////////////////////////////////////////////////
// Image loading.
//////////////////////////////////////////////////////////////////////////
//! Load image, detect image type by file extension.
// Arguments:
// pQualityLoss - 0 if info is not needed, pointer to the result otherwise - not need to preinitialize
static bool LoadImage(const QString& fileName, CImageEx& image, bool* pQualityLoss = 0);
//! Save image, detect image type by file extension.
static bool SaveImage(const QString& fileName, CImageEx& image);
// General image fucntions
static bool LoadJPEG(const QString& strFileName, CImageEx& image);
static bool SaveJPEG(const QString& strFileName, CImageEx& image);
static bool SaveBitmap(const QString& szFileName, CImageEx& image);
static bool LoadBmp(const QString& file, CImageEx& image);
//! Save image in PGM format.
static bool SavePGM(const QString& fileName, const CImageEx& image);
//! Load image in PGM format.
static bool LoadPGM(const QString& fileName, CImageEx& image);
//////////////////////////////////////////////////////////////////////////
// Image scaling.
//////////////////////////////////////////////////////////////////////////
//! Scale source image to fit size of target image.
static void ScaleToFit(const CByteImage& srcImage, CByteImage& trgImage);
//! Scale source image to fit size of target image.
static void ScaleToFit(const CImageEx& srcImage, CImageEx& trgImage);
//! Scale source image to fit twice side by side in target image.
static void ScaleToDoubleFit(const CImageEx& srcImage, CImageEx& trgImage);
//! Scale source image twice down image with filering
static void DownScaleSquareTextureTwice(const CImageEx& srcImage, CImageEx& trgImage, IImageUtil::_EAddrMode eAddressingMode = IImageUtil::WRAP);
//! Smooth image.
static void SmoothImage(CByteImage& image, int numSteps);
//////////////////////////////////////////////////////////////////////////
// filtered lookup
//////////////////////////////////////////////////////////////////////////
//! behaviour outside of the texture is not defined
//! \param iniX in fix point 24.8
//! \param iniY in fix point 24.8
//! \return 0..255
static unsigned char GetBilinearFilteredAt(const int iniX256, const int iniY256, const CByteImage& image);
static bool QImageToImage(const QImage& bitmap, CImageEx& image);
static bool ImageToQImage(const CImageEx& image, QImage& bitmapObj);
private:
static bool Load(const QString& fileName, CImageEx& image);
static bool Save(const QString& strFileName, CImageEx& inImage);
};
+82
View File
@@ -0,0 +1,82 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "ImageUtil_impl.h"
// Editor
#include "Util/ImageUtil.h"
bool CImageUtil_impl::LoadImage(const QString& fileName, CImageEx& image, bool* pQualityLoss)
{
return CImageUtil::LoadImage(fileName, image, pQualityLoss);
}
bool CImageUtil_impl::SaveImage(const QString& fileName, CImageEx& image)
{
return CImageUtil::SaveImage(fileName, image);
}
bool CImageUtil_impl::LoadJPEG(const QString& strFileName, CImageEx& image)
{
return CImageUtil::LoadJPEG(strFileName, image);
}
bool CImageUtil_impl::SaveJPEG(const QString& strFileName, CImageEx& image)
{
return CImageUtil::SaveJPEG(strFileName, image);
}
bool CImageUtil_impl::SaveBitmap(const QString& szFileName, CImageEx& image)
{
return CImageUtil::SaveBitmap(szFileName, image);
}
bool CImageUtil_impl::LoadBmp(const QString& file, CImageEx& image)
{
return CImageUtil::LoadBmp(file, image);
}
bool CImageUtil_impl::SavePGM(const QString& fileName, const CImageEx& image)
{
return CImageUtil::SavePGM(fileName, image);
}
bool CImageUtil_impl::LoadPGM(const QString& fileName, CImageEx& image)
{
return CImageUtil::LoadPGM(fileName, image);
}
void CImageUtil_impl::ScaleToFit(const CByteImage& srcImage, CByteImage& trgImage)
{
CImageUtil::ScaleToFit(srcImage, trgImage);
}
void CImageUtil_impl::ScaleToFit(const CImageEx& srcImage, CImageEx& trgImage)
{
CImageUtil::ScaleToFit(srcImage, trgImage);
}
void CImageUtil_impl::ScaleToDoubleFit(const CImageEx& srcImage, CImageEx& trgImage)
{
CImageUtil::ScaleToDoubleFit(srcImage, trgImage);
}
void CImageUtil_impl::DownScaleSquareTextureTwice(const CImageEx& srcImage, CImageEx& trgImage, IImageUtil::_EAddrMode eAddressingMode)
{
CImageUtil::DownScaleSquareTextureTwice(srcImage, trgImage, eAddressingMode);
}
void CImageUtil_impl::SmoothImage(CByteImage& image, int numSteps)
{
CImageUtil::SmoothImage(image, numSteps);
}
unsigned char CImageUtil_impl::GetBilinearFilteredAt(const int iniX256, const int iniY256, const CByteImage& image)
{
return CImageUtil::GetBilinearFilteredAt(iniX256, iniY256, image);
}
+67
View File
@@ -0,0 +1,67 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTILIMAGEUTIL_IMPL_H
#define CRYINCLUDE_EDITOR_UTILIMAGEUTIL_IMPL_H
#pragma once
#include "Include/IImageUtil.h"
class CImageUtil_impl
: public IImageUtil
{
public:
CImageUtil_impl(){}
~CImageUtil_impl(){}
//! Load image, detect image type by file extension.
// Arguments:
// pQualityLoss - 0 if info is not needed, pointer to the result otherwise - not need to preinitialize
virtual bool LoadImage(const QString& fileName, CImageEx& image, bool* pQualityLoss = 0) override;
//! Save image, detect image type by file extension.
virtual bool SaveImage(const QString& fileName, CImageEx& image) override;
// General image fucntions
virtual bool LoadJPEG(const QString& strFileName, CImageEx& image) override;
virtual bool SaveJPEG(const QString& strFileName, CImageEx& image) override;
virtual bool SaveBitmap(const QString& szFileName, CImageEx& image) override;
virtual bool LoadBmp(const QString& file, CImageEx& image) override;
virtual bool SavePGM(const QString& fileName, const CImageEx& image) override;
virtual bool LoadPGM(const QString& fileName, CImageEx& image) override;
//! Scale source image to fit size of target image.
virtual void ScaleToFit(const CByteImage& srcImage, CByteImage& trgImage) override;
//! Scale source image to fit size of target image.
virtual void ScaleToFit(const CImageEx& srcImage, CImageEx& trgImage) override;
//! Scale source image to fit twice side by side in target image.
virtual void ScaleToDoubleFit(const CImageEx& srcImage, CImageEx& trgImage) override;
//! Scale source image twice down image with filtering
enum _EAddrMode
{
WRAP, CLAMP
};
virtual void DownScaleSquareTextureTwice(const CImageEx& srcImage, CImageEx& trgImage,
IImageUtil::_EAddrMode eAddressingMode = IImageUtil::WRAP) override;
//! Smooth image.
virtual void SmoothImage(CByteImage& image, int numSteps) override;
//! behavior outside of the texture is not defined
//! \param iniX in fix point 24.8
//! \param iniY in fix point 24.8
//! \return 0..255
virtual unsigned char GetBilinearFilteredAt(const int iniX256, const int iniY256, const CByteImage& image) override;
};
#endif // CRYINCLUDE_EDITOR_UTILIMAGEUTIL_IMPL_H
+201
View File
@@ -0,0 +1,201 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Tagged files database for 'SmartFileOpen' dialog
#include "EditorDefs.h"
#include "IndexedFiles.h"
volatile TIntAtomic CIndexedFiles::s_bIndexingDone;
CIndexedFiles* CIndexedFiles::s_pIndexedFiles = NULL;
bool CIndexedFiles::m_startedFileIndexing = false;
void CIndexedFiles::Initialize(const QString& path, IFileUtil::ScanDirectoryUpdateCallBack updateCB)
{
m_files.clear();
m_pathToIndex.clear();
m_tags.clear();
m_rootPath = path;
bool anyFiles = CFileUtil::ScanDirectory(path, "*.*", m_files, true, true, updateCB);
if (anyFiles == false)
{
m_files.clear();
return;
}
if (updateCB)
{
updateCB("Parsing & tagging...");
}
for (int i = 0; i < m_files.size(); ++i)
{
m_pathToIndex[m_files[i].filename] = i;
}
PrepareTagTable();
InvokeUpdateCallbacks();
}
void CIndexedFiles::AddFile(const IFileUtil::FileDesc& path)
{
assert(m_pathToIndex.find(path.filename) == m_pathToIndex.end());
m_files.push_back(path);
m_pathToIndex[path.filename] = m_files.size() - 1;
QStringList tags;
GetTags(tags, path.filename);
for (int k = 0; k < tags.size(); ++k)
{
m_tags[tags[k]].insert(m_files.size() - 1);
}
}
void CIndexedFiles::RemoveFile(const QString& path)
{
if (m_pathToIndex.find(path) == m_pathToIndex.end())
{
return;
}
std::map<QString, int>::iterator itr = m_pathToIndex.find(path);
int index = itr->second;
m_pathToIndex.erase(itr);
m_files.erase(m_files.begin() + index);
QStringList tags;
GetTags(tags, path);
for (int k = 0; k < tags.size(); ++k)
{
m_tags[tags[k]].erase(index);
}
}
void CIndexedFiles::Refresh(const QString& path, bool recursive)
{
IFileUtil::FileArray files;
bool anyFiles = CFileUtil::ScanDirectory(m_rootPath, Path::Make(path, "*.*"), files, recursive, recursive ? true : false);
if (anyFiles == false)
{
return;
}
for (int i = 0; i < files.size(); ++i)
{
if (m_pathToIndex.find(files[i].filename) == m_pathToIndex.end())
{
AddFile(files[i]);
}
}
InvokeUpdateCallbacks();
}
void CIndexedFiles::GetFilesWithTags(IFileUtil::FileArray& files, const QStringList& tags) const
{
files.clear();
if (tags.empty())
{
return;
}
int_set candidates;
TagTable::const_iterator i;
// Gets candidate files from the first tag.
for (i = m_tags.begin(); i != m_tags.end(); ++i)
{
if (i->first.startsWith(tags[0]))
{
candidates.insert(i->second.begin(), i->second.end());
}
}
// Reduces the candidates further using additional tags, if any.
for (int k = 1; k < tags.size(); ++k)
{
// Gathers the filter set.
int_set filter;
for (i = m_tags.begin(); i != m_tags.end(); ++i)
{
if (i->first.startsWith(tags[k]))
{
filter.insert(i->second.begin(), i->second.end());
}
}
// Filters the candidates using it.
for (int_set::iterator m = candidates.begin(); m != candidates.end(); )
{
if (filter.find(*m) == filter.end())
{
int_set::iterator target = m;
++m;
candidates.erase(target);
}
else
{
++m;
}
}
}
// Outputs the result.
files.reserve(candidates.size());
for (int_set::const_iterator m = candidates.begin(); m != candidates.end(); ++m)
{
files.push_back(m_files[*m]);
}
}
void CIndexedFiles::GetTags(QStringList& tags, const QString& path) const
{
tags = path.split(QRegularExpression(QStringLiteral(R"([\\/.])")), Qt::SkipEmptyParts);
}
void CIndexedFiles::GetTagsOfPrefix(QStringList& tags, const QString& prefix) const
{
tags.clear();
TagTable::const_iterator i;
for (i = m_tags.begin(); i != m_tags.end(); ++i)
{
if (i->first.startsWith(prefix))
{
tags.push_back(i->first);
}
}
}
void CIndexedFiles::PrepareTagTable()
{
QStringList tags;
for (int i = 0; i < m_files.size(); ++i)
{
GetTags(tags, m_files[i].filename);
for (int k = 0; k < tags.size(); ++k)
{
m_tags[tags[k]].insert(i);
}
}
}
void CIndexedFiles::AddUpdateCallback(std::function<void()> updateCallback)
{
CryAutoLock<CryMutex> lock(m_updateCallbackMutex);
m_updateCallbacks.push_back(updateCallback);
}
void CIndexedFiles::InvokeUpdateCallbacks()
{
CryAutoLock<CryMutex> lock(m_updateCallbackMutex);
for (auto updateCallback : m_updateCallbacks)
{
updateCallback();
}
}
+175
View File
@@ -0,0 +1,175 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Tagged files database for 'SmartFileOpen' dialog
//
// Notice : Refer SmartFileOpenDialog h
#ifndef CRYINCLUDE_EDITOR_UTIL_INDEXEDFILES_H
#define CRYINCLUDE_EDITOR_UTIL_INDEXEDFILES_H
#pragma once
#include "FileUtil.h"
#include <functional>
class CIndexedFiles
{
friend class CFileIndexingThread;
public:
static CIndexedFiles& GetDB()
{
if (!s_pIndexedFiles)
{
assert(!"CIndexedFiles not created! Make sure you use CIndexedFiles::GetDB() after CIndexedFiles::StartFileIndexing() is called.");
}
assert(s_pIndexedFiles);
return *s_pIndexedFiles;
}
static bool HasFileIndexingDone()
{ return s_bIndexingDone > 0; }
static void Create()
{
assert(!s_pIndexedFiles);
s_pIndexedFiles = new CIndexedFiles;
}
static void Destroy()
{
SAFE_DELETE(s_pIndexedFiles);
}
static void StartFileIndexing()
{
assert(s_bIndexingDone == 0);
assert(s_pIndexedFiles);
if (!s_pIndexedFiles)
{
return;
}
GetFileIndexingThread().Start(-1, "FileIndexing");
m_startedFileIndexing = true;
}
static void AbortFileIndexing()
{
if (!m_startedFileIndexing)
{
return;
}
if (HasFileIndexingDone() == false)
{
GetFileIndexingThread().Abort();
}
m_startedFileIndexing = false;
}
static void RegisterCallback(std::function<void()> callback)
{
assert(s_pIndexedFiles);
if (!s_pIndexedFiles)
{
return;
}
s_pIndexedFiles->AddUpdateCallback(callback);
}
public:
void Initialize(const QString& path, IFileUtil::ScanDirectoryUpdateCallBack updateCB = NULL);
// Adds a new file to the database.
void AddFile(const IFileUtil::FileDesc& path);
// Removes a no-longer-existing file from the database.
void RemoveFile(const QString& path);
// Refreshes this database for the subdirectory.
void Refresh(const QString& path, bool recursive = true);
void GetFilesWithTags(IFileUtil::FileArray& files, const QStringList& tags) const;
//! This method returns all the tags which start with a given prefix.
//! It is useful for the tag auto-completion.
void GetTagsOfPrefix(QStringList& tags, const QString& prefix) const;
uint32 GetTotalCount() const
{ return (uint32)m_files.size(); }
private:
static bool m_startedFileIndexing;
std::vector <std::function<void()> > m_updateCallbacks;
IFileUtil::FileArray m_files;
std::map<QString, int> m_pathToIndex;
typedef std::set<int, std::less<int> > int_set;
typedef std::map<QString, int_set, std::less<QString> > TagTable;
TagTable m_tags;
QString m_rootPath;
void GetTags(QStringList& tags, const QString& path) const;
void PrepareTagTable();
CryMutex m_updateCallbackMutex;
void AddUpdateCallback(std::function<void()> updateCallback);
void InvokeUpdateCallbacks();
// A done flag for the background file indexing
static volatile TIntAtomic s_bIndexingDone;
// A thread for the background file indexing
class CFileIndexingThread
: public CryThread<CFileIndexingThread>
{
public:
virtual void Run()
{
CIndexedFiles::GetDB().Initialize("@assets@", CallBack);
CryInterlockedAdd(CIndexedFiles::s_bIndexingDone.Addr(), 1);
}
CFileIndexingThread()
: m_abort(false) {}
void Abort()
{
m_abort = true;
WaitForThread();
}
virtual ~CFileIndexingThread()
{
Abort();
}
private:
bool m_abort;
static bool CallBack([[maybe_unused]] const QString& msg)
{
if (CIndexedFiles::GetFileIndexingThread().m_abort)
{
return false;
}
return true;
}
};
static CFileIndexingThread& GetFileIndexingThread()
{
static CFileIndexingThread s_fileIndexingThread;
return s_fileIndexingThread;
}
// A global database for tagged files
static CIndexedFiles* s_pIndexedFiles;
};
#endif // CRYINCLUDE_EDITOR_UTIL_INDEXEDFILES_H
+571
View File
@@ -0,0 +1,571 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "KDTree.h"
#include <IStatObj.h>
class KDTreeNode
{
public:
KDTreeNode()
{
pChildren[0] = NULL;
pChildren[1] = NULL;
pVertexIndices = NULL;
}
~KDTreeNode()
{
if (!IsLeaf())
{
if (pChildren[0])
{
delete pChildren[0];
}
if (pChildren[1])
{
delete pChildren[1];
}
}
else if (GetVertexBufferSize() > 1)
{
if (pVertexIndices)
{
delete [] pVertexIndices;
}
}
}
uint32 GetVertexBufferSize() const
{
return nVertexIndexBufferSize;
}
float GetSplitPos() const
{
return splitPos;
}
void SetSplitPos(float pos)
{
splitPos = pos;
}
CKDTree::ESplitAxis GetSplitAxis() const
{
if (splitAxis == 0)
{
return CKDTree::eSA_X;
}
if (splitAxis == 1)
{
return CKDTree::eSA_Y;
}
if (splitAxis == 2)
{
return CKDTree::eSA_Z;
}
return CKDTree::eSA_Invalid;
}
void SetSplitAxis(const CKDTree::ESplitAxis& axis)
{
splitAxis = axis;
}
bool IsLeaf() const
{
return pChildren[0] == NULL && pChildren[1] == NULL;
}
KDTreeNode* GetChild(uint32 nIndex) const
{
if (nIndex > 1)
{
return NULL;
}
return pChildren[nIndex];
}
void SetChild(uint32 nIndex, KDTreeNode* pNode)
{
if (nIndex > 1)
{
return;
}
if (pChildren[nIndex])
{
delete pChildren[nIndex];
}
pChildren[nIndex] = pNode;
}
const AABB& GetBoundBox()
{
return boundbox;
}
void SetBoundBox(const AABB& aabb)
{
boundbox = aabb;
}
void SetVertexIndexBuffer(std::vector<uint32>& vertexInfos)
{
nVertexIndexBufferSize = (uint32)vertexInfos.size();
if (nVertexIndexBufferSize == 0)
{
return;
}
if (nVertexIndexBufferSize == 1)
{
oneIndex = vertexInfos[0];
}
else
{
pVertexIndices = new uint32[nVertexIndexBufferSize];
memcpy(pVertexIndices, &vertexInfos[0], sizeof(uint32) * nVertexIndexBufferSize);
}
}
uint32 GetVertexIndex(uint32 nIndex) const
{
if (GetVertexBufferSize() == 1)
{
return oneIndex & 0x00FFFFFF;
}
return pVertexIndices[nIndex] & 0x00FFFFFF;
}
uint32 GetObjIndex(uint32 nIndex) const
{
if (GetVertexBufferSize() == 1)
{
return (oneIndex & 0xFF000000) >> 24;
}
return (pVertexIndices[nIndex] & 0xFF000000) >> 24;
}
private:
union
{
float splitPos; // Interior
uint32 oneIndex; // Leaf
uint32* pVertexIndices; // Leaf : high 8bits - object index, low 24bits - vertex index
};
union
{
uint32 splitAxis; // Interior
uint32 nVertexIndexBufferSize; // Leaf
};
AABB boundbox; // Both
KDTreeNode* pChildren[2]; // Interior
};
CKDTree::ESplitAxis SearchForBestSplitAxis(const AABB& aabb)
{
float xsize = aabb.max.x - aabb.min.x;
float ysize = aabb.max.y - aabb.min.y;
float zsize = aabb.max.z - aabb.min.z;
CKDTree::ESplitAxis axis;
if (xsize > ysize && xsize > zsize)
{
axis = CKDTree::eSA_X;
}
else if (ysize > zsize && ysize > xsize)
{
axis = CKDTree::eSA_Y;
}
else
{
axis = CKDTree::eSA_Z;
}
return axis;
}
bool SearchForBestSplitPos(CKDTree::ESplitAxis axis, const std::vector<CKDTree::SStatObj>& statObjList, std::vector<uint32>& indices, float& outBestSplitPos)
{
if (axis != CKDTree::eSA_X && axis != CKDTree::eSA_Y && axis != CKDTree::eSA_Z)
{
return false;
}
outBestSplitPos = 0;
int nSizeOfIndices(indices.size());
for (int i = 0; i < nSizeOfIndices; ++i)
{
int nObjIndex = (indices[i] & 0xFF000000) >> 24;
int nVertexIndex = (indices[i] & 0xFFFFFF);
const CKDTree::SStatObj* pObj = &statObjList[nObjIndex];
const IIndexedMesh* pMesh = pObj->pStatObj->GetIndexedMesh();
if (pMesh == NULL)
{
continue;
}
IIndexedMesh::SMeshDescription meshDesc;
pMesh->GetMeshDescription(meshDesc);
if (meshDesc.m_pVerts)
{
outBestSplitPos += pObj->tm.TransformPoint(meshDesc.m_pVerts[nVertexIndex])[axis];
}
else if (meshDesc.m_pVertsF16)
{
outBestSplitPos += pObj->tm.TransformPoint(meshDesc.m_pVertsF16[nVertexIndex].ToVec3())[axis];
}
}
outBestSplitPos /= nSizeOfIndices;
return true;
}
struct SSplitInfo
{
AABB aboveBoundbox;
std::vector<uint32> aboveIndices;
AABB belowBoundbox;
std::vector<uint32> belowIndices;
};
bool SplitNode(const std::vector<CKDTree::SStatObj>& statObjList, const AABB& boundbox, const std::vector<uint32>& indices, CKDTree::ESplitAxis splitAxis, float splitPos, SSplitInfo& outInfo)
{
if (splitAxis != CKDTree::eSA_X && splitAxis != CKDTree::eSA_Y && splitAxis != CKDTree::eSA_Z)
{
return false;
}
outInfo.aboveBoundbox = boundbox;
outInfo.belowBoundbox = boundbox;
outInfo.aboveBoundbox.max[splitAxis] = splitPos;
outInfo.belowBoundbox.min[splitAxis] = splitPos;
uint32 iIndexSize = (uint32)indices.size();
outInfo.aboveIndices.reserve(iIndexSize);
outInfo.belowIndices.reserve(iIndexSize);
for (uint32 i = 0; i < iIndexSize; ++i)
{
int nObjIndex = (indices[i] & 0xFF000000) >> 24;
int nVertexIndex = indices[i] & 0xFFFFFF;
const CKDTree::SStatObj* pObj = &statObjList[nObjIndex];
const IIndexedMesh* pMesh = pObj->pStatObj->GetIndexedMesh();
if (pMesh == NULL)
{
return false;
}
IIndexedMesh::SMeshDescription meshDesc;
pMesh->GetMeshDescription(meshDesc);
Vec3 vPos;
if (meshDesc.m_pVerts)
{
vPos = pObj->tm.TransformPoint(meshDesc.m_pVerts[nVertexIndex]);
}
else if (meshDesc.m_pVertsF16)
{
vPos = pObj->tm.TransformPoint(meshDesc.m_pVertsF16[nVertexIndex].ToVec3());
}
else
{
continue;
}
if (vPos[splitAxis] < splitPos)
{
outInfo.aboveIndices.push_back(indices[i]);
assert(outInfo.aboveBoundbox.IsContainPoint(vPos));
}
else
{
outInfo.belowIndices.push_back(indices[i]);
assert(outInfo.belowBoundbox.IsContainPoint(vPos));
}
}
return true;
}
CKDTree::CKDTree()
{
m_pRootNode = NULL;
}
CKDTree::~CKDTree()
{
if (m_pRootNode)
{
delete m_pRootNode;
}
}
bool CKDTree::Build(IStatObj* pStatObj)
{
if (pStatObj == NULL)
{
return false;
}
m_StatObjectList.clear();
if (pStatObj->GetIndexedMesh(true))
{
SStatObj rootObj;
rootObj.tm.SetIdentity();
rootObj.pStatObj = pStatObj;
m_StatObjectList.push_back(rootObj);
}
ConstructStatObjList(pStatObj, Matrix34::CreateIdentity());
AABB entireBoundBox;
entireBoundBox.Reset();
std::vector<uint32> indices;
for (int i = 0, iStatObjSize(m_StatObjectList.size()); i < iStatObjSize; ++i)
{
IIndexedMesh* pMesh = m_StatObjectList[i].pStatObj->GetIndexedMesh(true);
if (pMesh == NULL)
{
continue;
}
IIndexedMesh::SMeshDescription meshDesc;
pMesh->GetMeshDescription(meshDesc);
for (int k = 0; k < meshDesc.m_nVertCount; ++k)
{
entireBoundBox.Add(m_StatObjectList[i].tm.TransformPoint(meshDesc.m_pVerts[k]));
indices.push_back((i << 24) | k);
}
}
if (m_pRootNode)
{
delete m_pRootNode;
}
m_pRootNode = new KDTreeNode;
BuildRecursively(m_pRootNode, entireBoundBox, indices);
return true;
}
void CKDTree::BuildRecursively(KDTreeNode* pNode, const AABB& boundbox, std::vector<uint32>& indices) const
{
pNode->SetBoundBox(boundbox);
if (indices.size() <= s_MinimumVertexSizeInLeafNode)
{
pNode->SetVertexIndexBuffer(indices);
return;
}
ESplitAxis splitAxis = SearchForBestSplitAxis(boundbox);
float splitPos(0);
SearchForBestSplitPos(splitAxis, m_StatObjectList, indices, splitPos);
pNode->SetSplitAxis(splitAxis);
pNode->SetSplitPos(splitPos);
SSplitInfo splitInfo;
if (!SplitNode(m_StatObjectList, boundbox, indices, splitAxis, splitPos, splitInfo))
{
return;
}
if (splitInfo.aboveIndices.empty() || splitInfo.belowIndices.empty())
{
pNode->SetVertexIndexBuffer(indices);
return;
}
KDTreeNode* pChild0 = new KDTreeNode;
KDTreeNode* pChild1 = new KDTreeNode;
pNode->SetChild(0, pChild0);
pNode->SetChild(1, pChild1);
BuildRecursively(pChild0, splitInfo.aboveBoundbox, splitInfo.aboveIndices);
BuildRecursively(pChild1, splitInfo.belowBoundbox, splitInfo.belowIndices);
}
void CKDTree::ConstructStatObjList(IStatObj* pStatObj, const Matrix34& matParent)
{
if (pStatObj == NULL)
{
return;
}
for (int i = 0, nChildObjSize(pStatObj->GetSubObjectCount()); i < nChildObjSize; ++i)
{
IStatObj::SSubObject* pSubObj = pStatObj->GetSubObject(i);
SStatObj s;
s.tm = matParent * pSubObj->localTM;
if (pSubObj->pStatObj && pSubObj->pStatObj->GetIndexedMesh(true))
{
s.pStatObj = pSubObj->pStatObj;
m_StatObjectList.push_back(s);
}
ConstructStatObjList(pSubObj->pStatObj, s.tm);
}
}
bool CKDTree::FindNearestVertex(const Vec3& raySrc, const Vec3& rayDir, float vVertexBoxSize, const Vec3& localCameraPos, Vec3& outPos, Vec3& vOutHitPosOnCube) const
{
return FindNearestVertexRecursively(m_pRootNode, raySrc, rayDir, vVertexBoxSize, localCameraPos, outPos, vOutHitPosOnCube);
}
AABB GetNodeBoundBox(KDTreeNode* pNode, float vVertexBoxSize, const Vec3& localCameraPos)
{
AABB nodeAABB = pNode->GetBoundBox();
float fScreenFactorMin = localCameraPos.GetDistance(nodeAABB.min);
Vec3 vBoundBoxMin(fScreenFactorMin * vVertexBoxSize, fScreenFactorMin * vVertexBoxSize, fScreenFactorMin * vVertexBoxSize);
float fScreenFactorMax = localCameraPos.GetDistance(nodeAABB.max);
Vec3 vBoundBoxMax(fScreenFactorMax * vVertexBoxSize, fScreenFactorMax * vVertexBoxSize, fScreenFactorMax * vVertexBoxSize);
nodeAABB.min -= vBoundBoxMin;
nodeAABB.max += vBoundBoxMax;
return nodeAABB;
}
bool CKDTree::FindNearestVertexRecursively(KDTreeNode* pNode, const Vec3& raySrc, const Vec3& rayDir, float vVertexBoxSize, const Vec3& localCameraPos, Vec3& outPos, Vec3& vOutHitPosOnCube) const
{
if (!pNode)
{
return false;
}
Vec3 vHitPos;
AABB nodeAABB = GetNodeBoundBox(pNode, vVertexBoxSize, localCameraPos);
if (!pNode->GetBoundBox().IsContainPoint(raySrc) && !Intersect::Ray_AABB(raySrc, rayDir, nodeAABB, vHitPos))
{
return false;
}
if (pNode->IsLeaf())
{
if (m_StatObjectList.empty())
{
return false;
}
uint32 nVBuffSize = pNode->GetVertexBufferSize();
if (nVBuffSize == 0)
{
return false;
}
float fNearestDist = 3e10f;
for (uint32 i = 0; i < nVBuffSize; ++i)
{
uint32 nVertexIndex = pNode->GetVertexIndex(i);
uint32 nObjIndex = pNode->GetObjIndex(i);
assert(nObjIndex < m_StatObjectList.size() && nObjIndex >= 0);
const SStatObj* pStatObjInfo = &(m_StatObjectList[nObjIndex]);
IIndexedMesh* pMesh = m_StatObjectList[nObjIndex].pStatObj->GetIndexedMesh();
if (pMesh == NULL)
{
continue;
}
IIndexedMesh::SMeshDescription meshDesc;
pMesh->GetMeshDescription(meshDesc);
Vec3 vCandidatePos(0, 0, 0);
if (meshDesc.m_pVerts)
{
vCandidatePos = pStatObjInfo->tm.TransformPoint(meshDesc.m_pVerts[nVertexIndex]);
}
else if (meshDesc.m_pVertsF16)
{
vCandidatePos = pStatObjInfo->tm.TransformPoint(meshDesc.m_pVertsF16[nVertexIndex].ToVec3());
}
else
{
continue;
}
float fScreenFactor = localCameraPos.GetDistance(vCandidatePos);
Vec3 vBoundBox(fScreenFactor * vVertexBoxSize, fScreenFactor * vVertexBoxSize, fScreenFactor * vVertexBoxSize);
Vec3 vHitPosOnCube;
if (Intersect::Ray_AABB(raySrc, rayDir, AABB(vCandidatePos - vBoundBox, vCandidatePos + vBoundBox), vHitPosOnCube))
{
float fDist = vHitPosOnCube.GetDistance(raySrc);
if (fDist < fNearestDist)
{
fNearestDist = fDist;
outPos = vCandidatePos;
vOutHitPosOnCube = vHitPosOnCube;
}
}
}
if (fNearestDist < 3e10f)
{
return true;
}
return false;
}
Vec3 vNearestPos0, vNearestPos0OnCube;
Vec3 vNearestPos1, vNearestPos1OnCube;
bool bFoundChild0 = FindNearestVertexRecursively(pNode->GetChild(0), raySrc, rayDir, vVertexBoxSize, localCameraPos, vNearestPos0, vNearestPos0OnCube);
bool bFoundChild1 = FindNearestVertexRecursively(pNode->GetChild(1), raySrc, rayDir, vVertexBoxSize, localCameraPos, vNearestPos1, vNearestPos1OnCube);
if (bFoundChild0 && bFoundChild1)
{
float fDist0 = raySrc.GetDistance(vNearestPos0OnCube);
float fDist1 = raySrc.GetDistance(vNearestPos1OnCube);
if (fDist0 < fDist1)
{
outPos = vNearestPos0;
vOutHitPosOnCube = vNearestPos0OnCube;
}
else
{
outPos = vNearestPos1;
vOutHitPosOnCube = vNearestPos1OnCube;
}
}
else if (bFoundChild0 && !bFoundChild1)
{
outPos = vNearestPos0;
vOutHitPosOnCube = vNearestPos0OnCube;
}
else if (!bFoundChild0 && bFoundChild1)
{
outPos = vNearestPos1;
vOutHitPosOnCube = vNearestPos1OnCube;
}
return bFoundChild0 || bFoundChild1;
}
void CKDTree::GetPenetratedBoxes(const Vec3& raySrc, const Vec3& rayDir, std::vector<AABB>& outBoxes)
{
GetPenetratedBoxesRecursively(m_pRootNode, raySrc, rayDir, outBoxes);
}
void CKDTree::GetPenetratedBoxesRecursively(KDTreeNode* pNode, const Vec3& raySrc, const Vec3& rayDir, std::vector<AABB>& outBoxes)
{
Vec3 vHitPos;
if (!pNode || (!pNode->GetBoundBox().IsContainPoint(raySrc) && !Intersect::Ray_AABB(raySrc, rayDir, pNode->GetBoundBox(), vHitPos)))
{
return;
}
outBoxes.push_back(pNode->GetBoundBox());
GetPenetratedBoxesRecursively(pNode->GetChild(0), raySrc, rayDir, outBoxes);
GetPenetratedBoxesRecursively(pNode->GetChild(1), raySrc, rayDir, outBoxes);
}
+56
View File
@@ -0,0 +1,56 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_KDTREE_H
#define CRYINCLUDE_EDITOR_UTIL_KDTREE_H
#pragma once
struct IStatObj;
class KDTreeNode;
class CKDTree
{
public:
CKDTree();
~CKDTree();
bool Build(IStatObj* pStatObj);
bool FindNearestVertex(const Vec3& raySrc, const Vec3& rayDir, float vVertexBoxSize, const Vec3& localCameraPos, Vec3& outPos, Vec3& vOutHitPosOnCube) const;
void GetPenetratedBoxes(const Vec3& raySrc, const Vec3& rayDir, std::vector<AABB>& outBoxes);
enum ESplitAxis
{
eSA_X = 0,
eSA_Y,
eSA_Z,
eSA_Invalid
};
struct SStatObj
{
Matrix34 tm;
_smart_ptr<IStatObj> pStatObj;
};
private:
void BuildRecursively(KDTreeNode* pNode, const AABB& boundbox, std::vector<uint32>& indices) const;
bool FindNearestVertexRecursively(KDTreeNode* pNode, const Vec3& raySrc, const Vec3& rayDir, float vVertexBoxSize, const Vec3& localCameraPos, Vec3& outPos, Vec3& vOutHitPosOnCube) const;
void GetPenetratedBoxesRecursively(KDTreeNode* pNode, const Vec3& raySrc, const Vec3& rayDir, std::vector<AABB>& outBoxes);
void ConstructStatObjList(IStatObj* pStatObj, const Matrix34& matParent);
static const int s_MinimumVertexSizeInLeafNode = 4;
private:
KDTreeNode* m_pRootNode;
std::vector<SStatObj> m_StatObjectList;
};
#endif // CRYINCLUDE_EDITOR_UTIL_KDTREE_H
+25
View File
@@ -0,0 +1,25 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_MAILER_H
#define CRYINCLUDE_EDITOR_UTIL_MAILER_H
#pragma once
//////////////////////////////////////////////////////////////////////////
class CMailer
{
public:
static bool SendMail(const char* _subject, // E-Mail Subject
const char* _messageBody, // Message Text
const std::vector<const char*>& _recipients, // All Recipients' Addresses
const std::vector<const char*>& _attachments, // All File Attachments
bool bShowDialog); // Whether to allow editing by user
};
#endif // CRYINCLUDE_EDITOR_UTIL_MAILER_H
+330
View File
@@ -0,0 +1,330 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Various math and geometry related functions.
#ifndef CRYINCLUDE_EDITOR_UTIL_MATH_H
#define CRYINCLUDE_EDITOR_UTIL_MATH_H
#pragma once
//! Half PI
#define PI_HALF (3.1415926535897932384626433832795f / 2.0f)
//! Epsilon for vector comparasion.
#define FLOAT_EPSILON 0.000001f
//////////////////////////////////////////////////////////////////////////
/** Compare two vectors if they are equal.
*/
inline bool IsVectorsEqual(const Vec3& v1, const Vec3& v2, const float aEpsilon = FLOAT_EPSILON)
{
return (fabs(v2.x - v1.x) < aEpsilon && fabs(v2.y - v1.y) < aEpsilon && fabs(v2.z - v1.z) < aEpsilon);
}
//////////////////////////////////////////////////////////////////////////
// Math utilities.
//////////////////////////////////////////////////////////////////////////
inline float PointToLineDistance2D(const Vec3& p1, const Vec3& p2, const Vec3& p3)
{
float dx = p2.x - p1.x;
float dy = p2.y - p1.y;
if (dx + dy == 0)
{
return (float)sqrt((p3.x - p1.x) * (p3.x - p1.x) + (p3.y - p1.y) * (p3.y - p1.y));
}
float u = ((p3.x - p1.x) * dx + (p3.y - p1.y) * dy) / (dx * dx + dy * dy);
if (u < 0)
{
return (float)sqrt((p3.x - p1.x) * (p3.x - p1.x) + (p3.y - p1.y) * (p3.y - p1.y));
}
else if (u > 1)
{
return (float)sqrt((p3.x - p2.x) * (p3.x - p2.x) + (p3.y - p2.y) * (p3.y - p2.y));
}
else
{
float x = p1.x + u * dx;
float y = p1.y + u * dy;
return (float)sqrt((p3.x - x) * (p3.x - x) + (p3.y - y) * (p3.y - y));
}
}
inline float PointToLineDistance(const Vec3& p1, const Vec3& p2, const Vec3& p3)
{
Vec3 d = p2 - p1;
float u = d.Dot(p3 - p1) / (d).GetLengthSquared();
if (u < 0)
{
return (p3 - p1).GetLength();
}
else if (u > 1)
{
return (p3 - p2).GetLength();
}
else
{
Vec3 p = p1 + u * d;
return (p3 - p).GetLength();
}
}
/** Calculate distance between point and line.
@param p1 Source line point.
@param p2 Target line point.
@param p3 Point to find intersecion with.
@param intersectPoint Intersection point on the line.
@return Distance between point and line.
*/
inline float PointToLineDistance(const Vec3& p1, const Vec3& p2, const Vec3& p3, Vec3& intersectPoint)
{
Vec3 d = p2 - p1;
float fLength2 = d.GetLengthSquared();
if (fLength2 < 0.00001f)
{
// p1-p2 is degenerated to a point
intersectPoint = p1;
return (p3 - p1).GetLength();
}
float u = d.Dot(p3 - p1) / fLength2;
if (u < 0)
{
intersectPoint = p1;
return (p3 - p1).GetLength();
}
else if (u > 1)
{
intersectPoint = p2;
return (p3 - p2).GetLength();
}
else
{
Vec3 p = p1 + u * d;
intersectPoint = p;
return (p3 - p).GetLength();
}
}
/**
Function: LineLineIntersect( const Vec3 &p1, const Vec3 &p2, const Vec3 &p3, const Vec3 &p4, Vec3 &pa, Vec3 &pb, float &mua, float &mub )
paulbourke.net/
Copyright Paul Bourke or a third party contributor where indicated. This source code may be freely used provided credits are given to the author.
Calculate the line segment PaPb that is the shortest route between
two lines P1P2 and P3P4. Calculate also the values of mua and mub where
Pa = P1 + mua (P2 - P1)
Pb = P3 + mub (P4 - P3)
@param p1 Source point of first line.
@param p2 Target point of first line.
@param p3 Source point of second line.
@param p4 Target point of second line.
@return FALSE if no solution exists.
*/
inline bool LineLineIntersect(const Vec3& p1, const Vec3& p2, const Vec3& p3, const Vec3& p4,
Vec3& pa, Vec3& pb, float& mua, float& mub)
{
Vec3 p13, p43, p21;
float d1343, d4321, d1321, d4343, d2121;
float numer, denom;
p13.x = p1.x - p3.x;
p13.y = p1.y - p3.y;
p13.z = p1.z - p3.z;
p43.x = p4.x - p3.x;
p43.y = p4.y - p3.y;
p43.z = p4.z - p3.z;
if (fabs(p43.x) < LINE_EPS && fabs(p43.y) < LINE_EPS && fabs(p43.z) < LINE_EPS)
{
return false;
}
p21.x = p2.x - p1.x;
p21.y = p2.y - p1.y;
p21.z = p2.z - p1.z;
if (fabs(p21.x) < LINE_EPS && fabs(p21.y) < LINE_EPS && fabs(p21.z) < LINE_EPS)
{
return false;
}
d1343 = p13.x * p43.x + p13.y * p43.y + p13.z * p43.z;
d4321 = p43.x * p21.x + p43.y * p21.y + p43.z * p21.z;
d1321 = p13.x * p21.x + p13.y * p21.y + p13.z * p21.z;
d4343 = p43.x * p43.x + p43.y * p43.y + p43.z * p43.z;
d2121 = p21.x * p21.x + p21.y * p21.y + p21.z * p21.z;
denom = d2121 * d4343 - d4321 * d4321;
if (fabs(denom) < LINE_EPS)
{
return false;
}
numer = d1343 * d4321 - d1321 * d4343;
mua = numer / denom;
mub = (d1343 + d4321 * (mua)) / d4343;
pa.x = p1.x + mua * p21.x;
pa.y = p1.y + mua * p21.y;
pa.z = p1.z + mua * p21.z;
pb.x = p3.x + mub * p43.x;
pb.y = p3.y + mub * p43.y;
pb.z = p3.z + mub * p43.z;
return true;
}
/*!
Calculates shortest distance between ray and a arbitary line segment.
@param raySrc Source point of ray.
@param rayTrg Target point of ray.
@param p1 First point of line segment.
@param p2 Second point of line segment.
@param intersectPoint This parameter returns nearest point on line segment to ray.
@return distance fro ray to line segment.
*/
inline float RayToLineDistance(const Vec3& raySrc, const Vec3& rayTrg, const Vec3& p1, const Vec3& p2, Vec3& nearestPoint)
{
Vec3 intPnt;
Vec3 rayLineP1 = raySrc;
Vec3 rayLineP2 = rayTrg;
Vec3 pa, pb;
float ua, ub;
if (!LineLineIntersect(p1, p2, rayLineP1, rayLineP2, pa, pb, ua, ub))
{
return FLT_MAX;
}
float d = 0;
if (ua < 0)
{
d = PointToLineDistance(rayLineP1, rayLineP2, p1, intPnt);
}
else if (ua > 1)
{
d = PointToLineDistance(rayLineP1, rayLineP2, p2, intPnt);
}
else
{
intPnt = rayLineP1 + ub * (rayLineP2 - rayLineP1);
d = (pb - pa).GetLength();
}
nearestPoint = intPnt;
return d;
}
//////////////////////////////////////////////////////////////////////////
inline Matrix34 MatrixFromVector(const Vec3& dir, const Vec3& up = Vec3(0, 0, 1.0f), float rollAngle = 0)
{
// LookAt transform.
Vec3 xAxis, yAxis, zAxis;
Vec3 upVector = up;
if (dir.IsZero())
{
Matrix33 tm;
tm.SetIdentity();
return tm;
}
yAxis = dir.GetNormalized();
if (yAxis.x == 0 && yAxis.y == 0)
{
upVector.Set(-yAxis.z, 0, 0);
}
xAxis = upVector.Cross(yAxis).GetNormalized();
zAxis = xAxis.Cross(yAxis).GetNormalized();
Matrix33 tm;
tm.SetFromVectors(xAxis, yAxis, zAxis);
if (rollAngle != 0)
{
Matrix33 RollMtx;
RollMtx.SetRotationY(rollAngle);
// Matrix multiply.
tm = RollMtx * tm;
}
return tm;
}
//TODO: could we include this function in the core engine math Intersect namespace ?
namespace Intersect
{
//! handy function for Ray AABB intersection, the Ray object is created inside
inline uint8 Ray_AABB(const Vec3& rRayStart, const Vec3& rRayDir, const AABB& rBox, Vec3& rOutPt)
{
return Ray_AABB(Ray(rRayStart, rRayDir), rBox, rOutPt);
}
//! Check if ray intersect edge of bounding box.
//! @param epsilonDist if distance between ray and egde is less then this epsilon then edge was intersected.
//! @param dist Distance between ray and edge.
//! @param intPnt intersection point.
inline bool Ray_AABBEdge(const Vec3& raySrc, const Vec3& rayDir, const AABB& aabb, float epsilonDist, float& dist, Vec3& intPnt)
{
// Check 6 group lines.
Vec3 rayTrg = raySrc + rayDir * 10000.0f;
Vec3 pnt[12];
float d[12];
// Near
d[0] = RayToLineDistance(raySrc, rayTrg, Vec3(aabb.min.x, aabb.min.y, aabb.max.z), Vec3(aabb.max.x, aabb.min.y, aabb.max.z), pnt[0]);
d[1] = RayToLineDistance(raySrc, rayTrg, Vec3(aabb.min.x, aabb.max.y, aabb.max.z), Vec3(aabb.max.x, aabb.max.y, aabb.max.z), pnt[1]);
d[2] = RayToLineDistance(raySrc, rayTrg, Vec3(aabb.min.x, aabb.min.y, aabb.max.z), Vec3(aabb.min.x, aabb.max.y, aabb.max.z), pnt[2]);
d[3] = RayToLineDistance(raySrc, rayTrg, Vec3(aabb.max.x, aabb.min.y, aabb.max.z), Vec3(aabb.max.x, aabb.max.y, aabb.max.z), pnt[3]);
// Far
d[4] = RayToLineDistance(raySrc, rayTrg, Vec3(aabb.min.x, aabb.min.y, aabb.min.z), Vec3(aabb.max.x, aabb.min.y, aabb.min.z), pnt[4]);
d[5] = RayToLineDistance(raySrc, rayTrg, Vec3(aabb.min.x, aabb.max.y, aabb.min.z), Vec3(aabb.max.x, aabb.max.y, aabb.min.z), pnt[5]);
d[6] = RayToLineDistance(raySrc, rayTrg, Vec3(aabb.min.x, aabb.min.y, aabb.min.z), Vec3(aabb.min.x, aabb.max.y, aabb.min.z), pnt[6]);
d[7] = RayToLineDistance(raySrc, rayTrg, Vec3(aabb.max.x, aabb.min.y, aabb.min.z), Vec3(aabb.max.x, aabb.max.y, aabb.min.z), pnt[7]);
// Sides.
d[8] = RayToLineDistance(raySrc, rayTrg, Vec3(aabb.min.x, aabb.min.y, aabb.min.z), Vec3(aabb.min.x, aabb.min.y, aabb.max.z), pnt[8]);
d[9] = RayToLineDistance(raySrc, rayTrg, Vec3(aabb.max.x, aabb.min.y, aabb.min.z), Vec3(aabb.max.x, aabb.min.y, aabb.max.z), pnt[9]);
d[10] = RayToLineDistance(raySrc, rayTrg, Vec3(aabb.min.x, aabb.max.y, aabb.min.z), Vec3(aabb.min.x, aabb.max.y, aabb.max.z), pnt[10]);
d[11] = RayToLineDistance(raySrc, rayTrg, Vec3(aabb.max.x, aabb.max.y, aabb.min.z), Vec3(aabb.max.x, aabb.max.y, aabb.max.z), pnt[11]);
dist = FLT_MAX;
for (int i = 0; i < 12; i++)
{
if (d[i] < dist)
{
dist = d[i];
intPnt = pnt[i];
}
}
if (dist < epsilonDist)
{
return true;
}
return false;
}
};
inline int gcd(int a, int b)
{
int c = a % b;
while (c != 0)
{
a = b;
b = c;
c = a % b;
}
return b;
}
#endif // CRYINCLUDE_EDITOR_UTIL_MATH_H
+203
View File
@@ -0,0 +1,203 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "MemoryBlock.h"
#include "Include/ILogFile.h"
#include <zlib.h>
#include <QMessageBox>
#include <QApplication>
//////////////////////////////////////////////////////////////////////////
CMemoryBlock::CMemoryBlock()
: m_buffer(0)
, m_size(0)
, m_uncompressedSize(0)
, m_owns(false)
{
}
//////////////////////////////////////////////////////////////////////////
CMemoryBlock::CMemoryBlock(const CMemoryBlock& mem)
{
*this = mem;
}
//////////////////////////////////////////////////////////////////////////
CMemoryBlock::~CMemoryBlock()
{
Free();
}
//////////////////////////////////////////////////////////////////////////
CMemoryBlock& CMemoryBlock::operator=(const CMemoryBlock& mem)
{
if (mem.GetSize() > 0)
{
// Do not reallocate.
if (mem.GetSize() > GetSize())
{
if (!Allocate(mem.GetSize()))
{
return *this;
}
}
Copy(mem.GetBuffer(), mem.GetSize());
}
else
{
m_buffer = 0;
m_size = 0;
m_owns = false;
}
m_uncompressedSize = mem.m_uncompressedSize;
return *this;
}
//////////////////////////////////////////////////////////////////////////
bool CMemoryBlock::Allocate(int size, int uncompressedSize)
{
assert(size > 0);
if (m_buffer)
{
m_buffer = realloc(m_buffer, size);
}
else
{
m_buffer = malloc(size);
}
if (!m_buffer)
{
QString str;
str = QStringLiteral("CMemoryBlock::Allocate failed to allocate %1Mb of Memory").arg(size / (1024 * 1024));
CryLogAlways(str.toUtf8().data());
QMessageBox::critical(QApplication::activeWindow(), QString(), str + QString("\r\nSandbox will try to reduce its working memory set to free memory for this allocation."));
GetIEditor()->ReduceMemory();
if (m_buffer)
{
m_buffer = realloc(m_buffer, size);
}
else
{
m_buffer = malloc(size);
}
if (!m_buffer)
{
GetIEditor()->GetLogFile()->Warning("Reducing working memory set failed, Sandbox must quit");
}
else
{
GetIEditor()->GetLogFile()->Warning("Reducing working memory set succeeded\r\nSandbox may become unstable, it is advised to save the level and restart editor.");
}
}
m_owns = true;
m_size = size;
m_uncompressedSize = uncompressedSize;
// Check if allocation failed.
if (m_buffer == 0)
{
return false;
}
return true;
}
//////////////////////////////////////////////////////////////////////////
void CMemoryBlock::Free()
{
if (m_buffer && m_owns)
{
free(m_buffer);
}
m_buffer = 0;
m_owns = false;
m_size = 0;
m_uncompressedSize = 0;
}
//////////////////////////////////////////////////////////////////////////
void CMemoryBlock::Copy(void* src, int size)
{
assert(size <= m_size);
memcpy(m_buffer, src, size);
}
//////////////////////////////////////////////////////////////////////////
void CMemoryBlock::Attach(void* buffer, int size, int uncompressedSize)
{
Free();
m_owns = false;
m_buffer = buffer;
m_size = size;
m_uncompressedSize = uncompressedSize;
}
//////////////////////////////////////////////////////////////////////////
void CMemoryBlock::Detach()
{
Free();
}
//////////////////////////////////////////////////////////////////////////
void CMemoryBlock::Compress(CMemoryBlock& toBlock) const
{
// Cannot compress to itself.
assert(this != &toBlock);
unsigned long destSize = m_size * 2 + 128;
CMemoryBlock temp;
temp.Allocate(destSize);
compress((unsigned char*)temp.GetBuffer(), &destSize, (unsigned char*)GetBuffer(), m_size);
toBlock.Allocate(destSize);
toBlock.Copy(temp.GetBuffer(), destSize);
toBlock.m_uncompressedSize = GetSize();
}
//////////////////////////////////////////////////////////////////////////
void CMemoryBlock::Uncompress(CMemoryBlock& toBlock) const
{
assert(this != &toBlock);
toBlock.Allocate(m_uncompressedSize);
toBlock.m_uncompressedSize = 0;
unsigned long destSize = m_uncompressedSize;
#if !defined(NDEBUG)
int result =
#endif
uncompress((unsigned char*)toBlock.GetBuffer(), &destSize, (unsigned char*)GetBuffer(), GetSize());
assert(result == Z_OK);
assert(destSize == m_uncompressedSize);
}
//////////////////////////////////////////////////////////////////////////
void CMemoryBlock::Serialize(CArchive& ar)
{
if (ar.IsLoading())
{
int size;
// Loading.
ar >> size;
if (size != m_size)
{
Allocate(size);
}
m_size = size;
ar >> m_uncompressedSize;
ar.Read(m_buffer, m_size);
}
else
{
// Saving.
ar << m_size;
ar << m_uncompressedSize;
ar.Write(m_buffer, m_size);
}
}
+81
View File
@@ -0,0 +1,81 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Memory block helper used with ZLib
#ifndef CRYINCLUDE_EDITOR_UTIL_MEMORYBLOCK_H
#define CRYINCLUDE_EDITOR_UTIL_MEMORYBLOCK_H
#pragma once
#include "RefCountBase.h"
#include "Include/EditorCoreAPI.h"
struct IEditor;
class CArchive;
class EDITOR_CORE_API CMemoryBlock
: public CRefCountBase
{
public:
CMemoryBlock();
CMemoryBlock(const CMemoryBlock& mem);
~CMemoryBlock();
CMemoryBlock& operator=(const CMemoryBlock& mem);
//! Allocate or reallocate memory for this block.
//! @param size Amount of memory in bytes to allocate.
//! @return true if the allocation succeeded.
bool Allocate(int size, int uncompressedSize = 0);
//! Frees memory allocated in this block (if owned).
//! Just clears internal references (if unowned).
void Free();
//! Attach memory buffer to this block.
//! Ownership is not transferred; this buffer will not be deleted by CMemoryBlock
void Attach(void* buffer, int size, int uncompressedSize = 0);
//! Detach memory buffer that was previously attached.
//! Note: Implemented as Free()
void Detach();
//! Returns amount of allocated memory in this block.
int GetSize() const { return m_size; }
//! Returns amount of allocated memory in this block.
int GetUncompressedSize() const { return m_uncompressedSize; }
void* GetBuffer() const { return m_buffer; };
//! Copy memory range to memory block.
void Copy(void* src, int size);
//! Compress this memory block to specified memory block.
//! @param toBlock target memory block where compressed result will be stored.
void Compress(CMemoryBlock& toBlock) const;
//! Uncompress this memory block to specified memory block.
//! @param toBlock target memory block where compressed result will be stored.
void Uncompress(CMemoryBlock& toBlock) const;
//! Serialize memory block to archive.
void Serialize(CArchive& ar);
//! Is MemoryBlock is empty.
bool IsEmpty() const { return m_buffer == 0; }
private:
void* m_buffer;
int m_size;
//! If not 0, memory block is compressed.
int m_uncompressedSize;
//! True if memory block owns its memory.
bool m_owns;
};
#endif // CRYINCLUDE_EDITOR_UTIL_MEMORYBLOCK_H
+73
View File
@@ -0,0 +1,73 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Utility for dismissing every modal windows
#include "EditorDefs.h"
#include "ModalWindowDismisser.h"
// Qt
#include <QDialog>
#include <QTimer>
ModalWindowDismisser::ModalWindowDismisser()
{
qApp->installEventFilter(this);
}
ModalWindowDismisser::~ModalWindowDismisser()
{
if (qApp)
{
qApp->removeEventFilter(this);
}
}
void ModalWindowDismisser::DismissWindows()
{
for (QDialog* dialog : m_windows)
{
dialog->close();
}
m_windows.clear();
m_dissmiss = false;
}
bool ModalWindowDismisser::eventFilter(QObject* object, QEvent* event)
{
if (QDialog* dialog = qobject_cast<QDialog*>(object))
{
if (dialog->isModal())
{
if (event->type() == QEvent::Show)
{
auto it = AZStd::find(m_windows.begin(), m_windows.end(), dialog);
if (it == m_windows.end())
{
m_windows.push_back(dialog);
}
if (!m_dissmiss)
{
// Closing the window at the same moment is opened leads to crashes and is unstable,
// so do it after a long 1 ms
QTimer::singleShot(1, this, &ModalWindowDismisser::DismissWindows);
m_dissmiss = true;
}
}
else if (event->type() == QEvent::Close)
{
auto it = AZStd::find(m_windows.begin(), m_windows.end(), dialog);
if (it != m_windows.end())
{
m_windows.erase(it);
}
}
}
}
return false;
}
+28
View File
@@ -0,0 +1,28 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Utility for dismissing every modal windows
#pragma once
#include <QObject>
class QDialog;
class ModalWindowDismisser
: public QObject
{
public:
ModalWindowDismisser();
~ModalWindowDismisser();
private:
void DismissWindows();
bool eventFilter(QObject* object, QEvent* event) override;
std::vector<QDialog*> m_windows;
bool m_dissmiss = false;
};
+367
View File
@@ -0,0 +1,367 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Collection of Named data blocks.
#include "EditorDefs.h"
#include "NamedData.h"
// Editor
#include "Util/CryMemFile.h" // for CryMemFile
#include "Util/PakFile.h" // for CPakFile
//////////////////////////////////////////////////////////////////////////
CNamedData::CNamedData()
{
}
//////////////////////////////////////////////////////////////////////////
CNamedData::~CNamedData()
{
Clear();
}
//////////////////////////////////////////////////////////////////////////
void CNamedData::AddDataBlock(const QString& blockName, void* pData, int nSize, bool bCompress)
{
assert(pData);
assert(nSize > 0);
DataBlock* pBlock = stl::find_in_map(m_blocks, blockName, (DataBlock*)0);
if (pBlock)
{
delete pBlock;
}
pBlock = new DataBlock();
pBlock->bFastCompression = !bCompress;
bCompress = false;
if (bCompress)
{
pBlock->bCompressed = true;
CMemoryBlock temp;
temp.Attach(pData, nSize);
temp.Compress(pBlock->compressedData);
}
else
{
pBlock->bCompressed = false;
pBlock->data.Allocate(nSize);
pBlock->data.Copy(pData, nSize);
}
m_blocks[blockName] = pBlock;
}
void CNamedData::AddDataBlock(const QString& blockName, CMemoryBlock& mem)
{
DataBlock* pBlock = stl::find_in_map(m_blocks, blockName, (DataBlock*)0);
if (pBlock)
{
delete pBlock;
}
pBlock = new DataBlock();
pBlock->bFastCompression = false;
if (mem.GetUncompressedSize() != 0)
{
// This is compressed block.
pBlock->bCompressed = true;
pBlock->compressedData = mem;
}
else
{
pBlock->bCompressed = false;
pBlock->data = mem;
}
m_blocks[blockName] = pBlock;
}
//////////////////////////////////////////////////////////////////////////
void CNamedData::Clear()
{
for (TBlocks::iterator it = m_blocks.begin(); it != m_blocks.end(); ++it)
{
delete it->second;
}
m_blocks.clear();
}
//////////////////////////////////////////////////////////////////////////
bool CNamedData::GetDataBlock(const QString& blockName, void*& pData, int& nSize)
{
pData = 0;
nSize = 0;
bool bUncompressed = false;
CMemoryBlock* mem = GetDataBlock(blockName, bUncompressed);
if (mem)
{
pData = mem->GetBuffer();
nSize = mem->GetSize();
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
CMemoryBlock* CNamedData::GetDataBlock(const QString& blockName, bool& bCompressed)
{
DataBlock* pBlock = stl::find_in_map(m_blocks, blockName, (DataBlock*)0);
if (!pBlock)
{
return 0;
}
if (bCompressed)
{
// Return compressed data.
if (!pBlock->compressedData.IsEmpty())
{
return &pBlock->compressedData;
}
}
else
{
// Return normal data.
if (!pBlock->data.IsEmpty())
{
return &pBlock->data;
}
else
{
// Uncompress compressed block.
if (!pBlock->compressedData.IsEmpty())
{
pBlock->compressedData.Uncompress(pBlock->data);
return &pBlock->data;
}
}
}
return 0;
}
//////////////////////////////////////////////////////////////////////////
bool CNamedData::Serialize(CArchive& ar)
{
if (ar.IsStoring())
{
int iSize = m_blocks.size();
ar << iSize;
for (TBlocks::iterator it = m_blocks.begin(); it != m_blocks.end(); it++)
{
QString key = it->first;
DataBlock* pBlock = it->second;
unsigned int nOriginalSize;
unsigned int nSizeFlags;
unsigned int flags = 0;
if (pBlock->bCompressed)
{
nOriginalSize = pBlock->compressedData.GetUncompressedSize();
// Compressed data.
unsigned long destSize = pBlock->compressedData.GetSize();
void* dest = pBlock->compressedData.GetBuffer();
nSizeFlags = destSize | (1 << 31);
ar << key;
ar << nSizeFlags; // Current size of data + 1 bit for compressed flag.
ar << nOriginalSize; // Size of uncompressed data.
ar << flags; // Some additional flags.
ar.Write(dest, destSize);
}
else
{
nOriginalSize = pBlock->data.GetSize();
void* dest = pBlock->data.GetBuffer();
nSizeFlags = nOriginalSize;
ar << key;
ar << nSizeFlags;
ar << nOriginalSize; // Size of uncompressed data.
ar << flags; // Some additional flags.
ar.Write(dest, nOriginalSize);
}
}
}
else
{
Clear();
int iSize;
ar >> iSize;
for (int i = 0; i < iSize && ar.status() == QDataStream::Ok; i++)
{
QString key;
unsigned int nSizeFlags = 0;
unsigned int nSize = 0;
unsigned int nOriginalSize = 0;
unsigned int flags = 0;
bool bCompressed = false;
DataBlock* pBlock = new DataBlock();
ar >> key;
ar >> nSizeFlags;
ar >> nOriginalSize;
ar >> flags;
nSize = nSizeFlags & (~(1 << 31));
bCompressed = (nSizeFlags & (1 << 31)) != 0;
if (nSize)
{
if (bCompressed)
{
pBlock->compressedData.Allocate(nSize, nOriginalSize);
void* pSrcData = pBlock->compressedData.GetBuffer();
// Read compressed data.
ar.Read(pSrcData, nSize);
}
else
{
pBlock->data.Allocate(nSize);
void* pSrcData = pBlock->data.GetBuffer();
// Read uncompressed data.
ar.Read(pSrcData, nSize);
}
}
m_blocks[key] = pBlock;
}
}
return ar.status() == QDataStream::Ok;
}
//////////////////////////////////////////////////////////////////////////
void CNamedData::Save(CPakFile& pakFile)
{
for (TBlocks::iterator it = m_blocks.begin(); it != m_blocks.end(); it++)
{
QString key = it->first;
DataBlock* pBlock = it->second;
if (!pBlock->bCompressed)
{
QString filename = key + ".editor_data";
pakFile.UpdateFile(filename.toUtf8().data(), pBlock->data, true, pBlock->bFastCompression ? AZ::IO::INestedArchive::LEVEL_FASTEST : AZ::IO::INestedArchive::LEVEL_BETTER);
}
else
{
int nOriginalSize = pBlock->compressedData.GetUncompressedSize();
CCryMemFile memFile;
// Write uncompressed data size.
memFile.Write(&nOriginalSize, sizeof(nOriginalSize));
// Write compressed data.
memFile.Write(pBlock->compressedData.GetBuffer(), pBlock->compressedData.GetSize());
pakFile.UpdateFile((key + ".editor_datac").toUtf8().data(), memFile, false);
}
}
}
//////////////////////////////////////////////////////////////////////////
bool CNamedData::Load(const QString& levelPath, [[maybe_unused]] CPakFile& pakFile)
{
int i;
IFileUtil::FileArray files;
CFileUtil::ScanDirectory(levelPath, "*.editor_data", files, false);
for (i = 0; i < files.size(); i++)
{
QString filename = files[i].filename;
CCryFile cfile;
if (cfile.Open(Path::Make(levelPath, filename).toUtf8().data(), "rb"))
{
int fileSize = cfile.GetLength();
if (fileSize > 0)
{
QString key = Path::GetFileName(filename);
// Read data block.
DataBlock* pBlock = new DataBlock();
pBlock->data.Allocate(fileSize);
cfile.ReadRaw(pBlock->data.GetBuffer(), fileSize);
m_blocks[key] = pBlock;
}
}
}
files.clear();
// Scan compressed data.
CFileUtil::ScanDirectory(levelPath, "*.editor_datac", files, false);
for (i = 0; i < files.size(); i++)
{
QString filename = files[i].filename;
CCryFile cfile;
if (cfile.Open(Path::Make(levelPath, filename).toUtf8().data(), "rb"))
{
int fileSize = cfile.GetLength();
if (fileSize > 0)
{
// Read uncompressed data size.
int nOriginalSize = 0;
cfile.ReadType(&nOriginalSize);
// Read uncompressed data.
int nDataSize = fileSize - sizeof(nOriginalSize);
QString key = Path::GetFileName(filename);
// Read data block.
DataBlock* pBlock = new DataBlock();
pBlock->compressedData.Allocate(nDataSize, nOriginalSize);
cfile.ReadRaw(pBlock->compressedData.GetBuffer(), nDataSize);
m_blocks[key] = pBlock;
}
}
}
return true;
}
void CNamedData::SaveToFiles(const QString& rootPath)
{
for (TBlocks::iterator it = m_blocks.begin(); it != m_blocks.end(); it++)
{
QString key = it->first;
DataBlock* pBlock = it->second;
QString filename = rootPath + key;
if (pBlock->bCompressed)
{
filename += ".editor_datac";
CCryFile file(filename.toUtf8().data(), "wb");
int nOriginalSize = pBlock->compressedData.GetUncompressedSize();
file.Write(&nOriginalSize, sizeof(nOriginalSize));
file.Write(pBlock->compressedData.GetBuffer(), pBlock->compressedData.GetSize());
}
else
{
filename += ".editor_data";
CCryFile file(filename.toUtf8().data(), "wb");
file.Write(pBlock->data.GetBuffer(), pBlock->data.GetSize());
}
}
}
void CNamedData::LoadFromFiles(const QString& rootPath)
{
// TODO: Unify code paths in a nicer way!
CPakFile dummyPak;
Load(rootPath, dummyPak);
}
CNamedData::DataBlock::DataBlock()
{
}
+59
View File
@@ -0,0 +1,59 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Collection of Named data blocks
#ifndef CRYINCLUDE_EDITOR_UTIL_NAMEDDATA_H
#define CRYINCLUDE_EDITOR_UTIL_NAMEDDATA_H
#pragma once
#include "MemoryBlock.h"
#include "QtUtil.h"
class CPakFile;
class CNamedData
{
public:
CNamedData();
virtual ~CNamedData();
void AddDataBlock(const QString& blockName, void* pData, int nSize, bool bCompress = true);
void AddDataBlock(const QString& blockName, CMemoryBlock& block);
//! Returns uncompressed block data.
bool GetDataBlock(const QString& blockName, void*& pData, int& nSize);
//! Returns raw data block in original form (Compressed or Uncompressed).
CMemoryBlock* GetDataBlock(const QString& blockName, bool& bCompressed);
void Clear();
public:
virtual bool Serialize(CArchive& ar);
//! Save named data to pak file.
void Save(CPakFile& pakFile);
//! Load named data from pak file.
bool Load(const QString& levelPath, CPakFile& pakFile);
void SaveToFiles(const QString& rootPath);
void LoadFromFiles(const QString& rootPath);
private:
struct DataBlock
{
DataBlock();
QString blockName;
CMemoryBlock data;
CMemoryBlock compressedData;
//! This block is compressed.
bool bCompressed;
bool bFastCompression;
};
typedef std::map<QString, DataBlock*, stl::less_stricmp<QString> > TBlocks;
TBlocks m_blocks;
};
#endif // CRYINCLUDE_EDITOR_UTIL_NAMEDDATA_H
+102
View File
@@ -0,0 +1,102 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : This file declares templates to be used when a class can
// have a list of observers to feed
#ifndef CRYINCLUDE_EDITOR_UTIL_OBSERVABLE_H
#define CRYINCLUDE_EDITOR_UTIL_OBSERVABLE_H
#pragma once
#include <vector>
//! Observable template class, holds a list of observers which can be called at once using the helper defines
template<class T>
class CObservable
{
public:
// Description:
// Register a new observer for the class, will check if not already added
// Return:
// true - if the observer was successfully added to the list
// false - if the observer is already in the list
bool RegisterObserver(T* pObserver)
{
if (m_observers.end() != std::find(m_observers.begin(), m_observers.end(), pObserver))
{
return false;
}
m_observers.push_back(pObserver);
return true;
}
// Description:
// Unregister an observer from the list
// Return:
// true - if the observer was successfuly removed
// false - if the observer is not in the list
bool UnregisterObserver(T* pObserver)
{
typename std::vector<T*>::iterator iter;
if (m_observers.end() == (iter = std::find(m_observers.begin(), m_observers.end(), pObserver)))
{
return false;
}
m_observers.erase(iter);
return true;
}
// Description:
// Uregister all the observers
void UnregisterAllObservers()
{
m_observers.clear();
}
protected:
std::vector<T*> m_observers;
};
//
// Helper defines to ease the process of calling the methods of all observers in the list
//
// Description:
// Call the method of the observers, this must be used inside the subject class
// Example: CALL_OBSERVERS_METHOD(OnStuffHappened(120, "NO!"))
#define CALL_OBSERVERS_METHOD(methodCall) \
{ for (size_t iObs = 0, iObsCount = m_observers.size(); iObs < iObsCount; ++iObs) { m_observers[iObs]->methodCall; } \
}
// Description:
// Call the method of the observers, this can be used outside the subject class
// Example: CALL_OBSERVERS_METHOD_OF(pSomeObservableSubject, OnStuffHappened(120, "NO!"))
#define CALL_OBSERVERS_METHOD_OF(pObservable, methodCall) \
{ for (size_t iObs = 0, iObsCount = pObservable->m_observers.size(); iObs < iObsCount; ++iObs) { pObservable->m_observers[iObs]->methodCall; } \
}
// Description:
// Call the method of the observers, this can be called using a custom vector of observers
// Example: CALL_SPECIFIED_OBSERVERS_LIST_METHOD(vMyPreciousSpecialObservers, OnStuffHappened(120, "NO!"))
#define CALL_SPECIFIED_OBSERVERS_LIST_METHOD(vObservers, methodCall) \
{ for (size_t iObs = 0, iObsCount = vObservers.size(); iObs < iObsCount; ++iObs) { vObservers[iObs]->methodCall; } \
}
// Description:
// Implement the observable methods when the user class is inheriting from a virtual interface using the observable methods
#define IMPLEMENT_OBSERVABLE_METHODS(observerClassName) \
virtual bool RegisterObserver(observerClassName * pObserver) { return CObservable<observerClassName>::RegisterObserver(pObserver); }; \
virtual bool UnregisterObserver(observerClassName * pObserver) { return CObservable<observerClassName>::UnregisterObserver(pObserver); }; \
virtual void UnregisterAllObservers() { CObservable<observerClassName>::UnregisterAllObservers(); };
#endif // CRYINCLUDE_EDITOR_UTIL_OBSERVABLE_H
+162
View File
@@ -0,0 +1,162 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "PakFile.h"
// AzFramework
#include <AzFramework/Archive/INestedArchive.h>
#include <AzFramework/Archive/IArchive.h>
// Editor
#include "Util/CryMemFile.h"
//////////////////////////////////////////////////////////////////////////
CPakFile::CPakFile()
: m_pArchive(NULL)
, m_pCryPak(NULL)
{
}
//////////////////////////////////////////////////////////////////////////
CPakFile::CPakFile(AZ::IO::IArchive* pCryPak)
: m_pArchive(NULL)
, m_pCryPak(pCryPak)
{
}
//////////////////////////////////////////////////////////////////////////
CPakFile::~CPakFile()
{
Close();
}
//////////////////////////////////////////////////////////////////////////
CPakFile::CPakFile(const char* filename)
{
m_pArchive = NULL;
Open(filename);
}
//////////////////////////////////////////////////////////////////////////
void CPakFile::Close()
{
m_pArchive = NULL;
}
//////////////////////////////////////////////////////////////////////////
bool CPakFile::Open(const char* filename, bool bAbsolutePath)
{
if (m_pArchive)
{
Close();
}
auto pCryPak = m_pCryPak ? m_pCryPak : GetIEditor()->GetSystem()->GetIPak();
if (pCryPak == NULL)
{
return false;
}
if (bAbsolutePath)
{
m_pArchive = pCryPak->OpenArchive(filename, nullptr, AZ::IO::INestedArchive::FLAGS_ABSOLUTE_PATHS);
}
else
{
m_pArchive = pCryPak->OpenArchive(filename);
}
if (m_pArchive)
{
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CPakFile::OpenForRead(const char* filename)
{
if (m_pArchive)
{
Close();
}
auto pCryPak = m_pCryPak ? m_pCryPak : GetIEditor()->GetSystem()->GetIPak();
if (pCryPak == NULL)
{
return false;
}
m_pArchive = pCryPak->OpenArchive(filename, nullptr, AZ::IO::INestedArchive::FLAGS_OPTIMIZED_READ_ONLY | AZ::IO::INestedArchive::FLAGS_ABSOLUTE_PATHS);
if (m_pArchive)
{
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CPakFile::UpdateFile(const char* filename, CCryMemFile& file, bool bCompress)
{
if (m_pArchive)
{
int nSize = file.GetLength();
UpdateFile(filename, file.GetMemPtr(), nSize, bCompress);
file.Close();
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CPakFile::UpdateFile(const char* filename, CMemoryBlock& mem, bool bCompress, int nCompressLevel)
{
if (m_pArchive)
{
return UpdateFile(filename, mem.GetBuffer(), mem.GetSize(), bCompress, nCompressLevel);
}
return false;
}
/////////////////////////////////////////////////////////////////////////
bool CPakFile::UpdateFile(const char* filename, void* pBuffer, int nSize, bool bCompress, int nCompressLevel)
{
if (m_pArchive)
{
if (bCompress)
{
return 0 == m_pArchive->UpdateFile(filename, pBuffer, nSize, AZ::IO::INestedArchive::METHOD_DEFLATE, nCompressLevel);
}
else
{
return 0 == m_pArchive->UpdateFile(filename, pBuffer, nSize);
}
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CPakFile::RemoveFile(const char* filename)
{
if (m_pArchive)
{
return m_pArchive->RemoveFile(filename);
}
return false;
}
//////////////////////////////////////////////////////////////////////////
bool CPakFile::RemoveDir(const char* directory)
{
if (m_pArchive)
{
return m_pArchive->RemoveDir(directory);
}
return false;
}
+60
View File
@@ -0,0 +1,60 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_PAKFILE_H
#define CRYINCLUDE_EDITOR_UTIL_PAKFILE_H
#pragma once
#include <AzFramework/Archive/INestedArchive.h>
class CCryMemFile;
// forward references.
namespace AZ::IO
{
struct IArchive;
}
/*! CPakFile Wraps game implementation of INestedArchive.
Used for storing multiple files into zip archive file.
*/
class SANDBOX_API CPakFile
{
public:
CPakFile();
CPakFile(AZ::IO::IArchive* pCryPak);
~CPakFile();
//! Opens archive for writing.
explicit CPakFile(const char* filename);
//! Opens archive for writing.
bool Open(const char* filename, bool bAbsolutePath = true);
//! Opens archive for reading only.
bool OpenForRead(const char* filename);
void Close();
//! Adds or update file in archive.
bool UpdateFile(const char* filename, CCryMemFile& file, bool bCompress = true);
//! Adds or update file in archive.
bool UpdateFile(const char* filename, CMemoryBlock& mem, bool bCompress = true, int nCompressLevel = AZ::IO::INestedArchive::LEVEL_BETTER);
//! Adds or update file in archive.
bool UpdateFile(const char* filename, void* pBuffer, int nSize, bool bCompress = true, int nCompressLevel = AZ::IO::INestedArchive::LEVEL_BETTER);
//! Remove file from archive.
bool RemoveFile(const char* filename);
//! Remove dir from archive.
bool RemoveDir(const char* directory);
//! Return archive of this pak file wrapper.
AZ::IO::INestedArchive* GetArchive() { return m_pArchive.get(); };
private:
AZ_PUSH_DISABLE_DLL_EXPORT_MEMBER_WARNING
AZStd::intrusive_ptr<AZ::IO::INestedArchive> m_pArchive;
AZ::IO::IArchive* m_pCryPak;
AZ_POP_DISABLE_DLL_EXPORT_MEMBER_WARNING
};
#endif // CRYINCLUDE_EDITOR_UTIL_PAKFILE_H
+560
View File
@@ -0,0 +1,560 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "PathUtil.h"
#include <AzCore/IO/SystemFile.h> // for AZ_MAX_PATH_LEN
#include <AzToolsFramework/API/EditorAssetSystemAPI.h> // for ebus events
#include <AzFramework/StringFunc/StringFunc.h>
#include <AzFramework/API/ApplicationAPI.h>
#include <QRegularExpression>
namespace
{
string g_currentModName; // folder name only!
}
namespace Path
{
//////////////////////////////////////////////////////////////////////////
void SplitPath(const QString& rstrFullPathFilename, QString& rstrDriveLetter, QString& rstrDirectory, QString& rstrFilename, QString& rstrExtension)
{
string strFullPathString(rstrFullPathFilename.toUtf8().data());
string strDriveLetter;
string strDirectory;
string strFilename;
string strExtension;
char* szPath((char*)strFullPathString.c_str());
char* pchLastPosition(szPath);
char* pchCurrentPosition(szPath);
char* pchAuxPosition(szPath);
// Directory named filenames containing ":" are invalid, so we can assume if there is a :
// it will be the drive name.
pchCurrentPosition = strchr(pchLastPosition, ':');
if (pchCurrentPosition == NULL)
{
rstrDriveLetter = "";
}
else
{
strDriveLetter.assign(pchLastPosition, pchCurrentPosition + 1);
pchLastPosition = pchCurrentPosition + 1;
}
pchCurrentPosition = strrchr(pchLastPosition, '\\');
pchAuxPosition = strrchr(pchLastPosition, '/');
if ((pchCurrentPosition == NULL) && (pchAuxPosition == NULL))
{
rstrDirectory = "";
}
else
{
// Since NULL is < valid pointer, so this will work.
if (pchAuxPosition > pchCurrentPosition)
{
pchCurrentPosition = pchAuxPosition;
}
strDirectory.assign(pchLastPosition, pchCurrentPosition + 1);
pchLastPosition = pchCurrentPosition + 1;
}
pchCurrentPosition = strrchr(pchLastPosition, '.');
if (pchCurrentPosition == NULL)
{
rstrExtension = "";
strFilename.assign(pchLastPosition);
}
else
{
strExtension.assign(pchCurrentPosition);
strFilename.assign(pchLastPosition, pchCurrentPosition);
}
rstrDriveLetter = strDriveLetter;
rstrDirectory = strDirectory;
rstrFilename = strFilename;
rstrExtension = strExtension;
}
//////////////////////////////////////////////////////////////////////////
void GetDirectoryQueue(const QString& rstrSourceDirectory, QStringList& rcstrDirectoryTree)
{
string strCurrentDirectoryName;
string strSourceDirectory(rstrSourceDirectory.toUtf8().data());
const char* szSourceDirectory(strSourceDirectory.c_str());
const char* pchCurrentPosition(szSourceDirectory);
const char* pchLastPosition(szSourceDirectory);
rcstrDirectoryTree.clear();
if (strSourceDirectory.empty())
{
return;
}
// It removes as many slashes the path has in its start...
// MAYBE and just maybe we should consider paths starting with
// more than 2 slashes invalid paths...
while ((*pchLastPosition == '\\') || (*pchLastPosition == '/'))
{
++pchLastPosition;
++pchCurrentPosition;
}
do
{
pchCurrentPosition = strpbrk(pchLastPosition, "\\/");
if (pchCurrentPosition == NULL)
{
break;
}
strCurrentDirectoryName.assign(pchLastPosition, pchCurrentPosition);
pchLastPosition = pchCurrentPosition + 1;
// Again, here we are skipping as many consecutive slashes.
while ((*pchLastPosition == '\\') || (*pchLastPosition == '/'))
{
++pchLastPosition;
}
rcstrDirectoryTree.push_back(strCurrentDirectoryName.c_str());
} while (true);
}
//////////////////////////////////////////////////////////////////////////
void ConvertSlashToBackSlash(QString& rstrStringToConvert)
{
rstrStringToConvert.replace('/', '\\');
rstrStringToConvert = CaselessPaths(rstrStringToConvert);
}
//////////////////////////////////////////////////////////////////////////
void ConvertBackSlashToSlash(QString& rstrStringToConvert)
{
rstrStringToConvert.replace('\\', '/');
rstrStringToConvert = CaselessPaths(rstrStringToConvert);
}
//////////////////////////////////////////////////////////////////////////
void SurroundWithQuotes(QString& rstrSurroundString)
{
QString strSurroundString(rstrSurroundString);
if (!strSurroundString.isEmpty())
{
if (strSurroundString[0] != '\"')
{
strSurroundString.insert(0, "\"");
}
if (strSurroundString[strSurroundString.size() - 1] != '\"')
{
strSurroundString.insert(strSurroundString.size(), "\"");
}
}
else
{
strSurroundString.insert(0, "\"");
strSurroundString.insert(strSurroundString.size(), "\"");
}
rstrSurroundString = strSurroundString;
}
//////////////////////////////////////////////////////////////////////////
QString GetExecutableFullPath()
{
return QDir::toNativeSeparators(QCoreApplication::applicationFilePath());
}
//////////////////////////////////////////////////////////////////////////
QString GetWindowsTempDirectory()
{
return QDir::tempPath();
}
//////////////////////////////////////////////////////////////////////////
QString GetEngineRootPath()
{
const char* engineRoot;
EBUS_EVENT_RESULT(engineRoot, AzFramework::ApplicationRequests::Bus, GetEngineRoot);
return QString(engineRoot);
}
//////////////////////////////////////////////////////////////////////////
QString& ReplaceFilename(const QString& strFilepath, const QString& strFilename, QString& strOutputFilename, bool bCallCaselessPath)
{
QString strDriveLetter;
QString strDirectory;
QString strOriginalFilename;
QString strExtension;
SplitPath(strFilepath, strDriveLetter, strDirectory, strOriginalFilename, strExtension);
strOutputFilename = strDriveLetter;
strOutputFilename += strDirectory;
strOutputFilename += strFilename;
strOutputFilename += strExtension;
if (bCallCaselessPath)
{
strOutputFilename = CaselessPaths(strOutputFilename);
}
return strOutputFilename;
}
bool IsFolder(const char* pPath)
{
DWORD attrs = GetFileAttributes(pPath);
if (attrs == FILE_ATTRIBUTE_DIRECTORY)
{
return true;
}
return false;
}
//////////////////////////////////////////////////////////////////////////
QString GetUserSandboxFolder()
{
return QString::fromUtf8("@user@/Sandbox/");
}
//////////////////////////////////////////////////////////////////////////
QString GetResolvedUserSandboxFolder()
{
char resolvedPath[AZ_MAX_PATH_LEN] = { 0 };
gEnv->pFileIO->ResolvePath(GetUserSandboxFolder().toUtf8().data(), resolvedPath, AZ_MAX_PATH_LEN);
return QString::fromLatin1(resolvedPath);
}
// internal function, you should use GetEditingGameDataFolder instead.
AZStd::string GetGameAssetsFolder()
{
const char* resultValue = nullptr;
EBUS_EVENT_RESULT(resultValue, AzToolsFramework::AssetSystemRequestBus, GetAbsoluteDevGameFolderPath);
if (!resultValue)
{
if ((gEnv) && (gEnv->pFileIO))
{
resultValue = gEnv->pFileIO->GetAlias("@devassets@");
}
}
if (!resultValue)
{
resultValue = ".";
}
return resultValue;
}
/// Get the data folder
AZStd::string GetEditingGameDataFolder()
{
// query the editor root. The bus exists in case we want tools to be able to override this.
if (g_currentModName.empty())
{
return GetGameAssetsFolder();
}
AZStd::string str(GetGameAssetsFolder());
str += "Mods\\";
str += g_currentModName;
return str;
}
//! Get the root folder (in source control or other writable assets) where you should save root data.
AZStd::string GetEditingRootFolder()
{
const char* resultValue = nullptr;
EBUS_EVENT_RESULT(resultValue, AzToolsFramework::AssetSystemRequestBus, GetAbsoluteDevRootFolderPath);
if (!resultValue)
{
if ((gEnv) && (gEnv->pFileIO))
{
resultValue = gEnv->pFileIO->GetAlias("@devassets@");
}
}
if (!resultValue)
{
resultValue = ".";
}
return resultValue;
}
AZStd::string MakeModPathFromGamePath(const char* relGamePath)
{
return GetEditingGameDataFolder() + "\\" + relGamePath;
}
QString FullPathToLevelPath(const QString& path)
{
if (path.isEmpty())
{
return "";
}
QString relGamePath;
if (!QFileInfo(path).isRelative())
{
relGamePath = GetRelativePath(path);
}
else
{
relGamePath = path;
}
QString levelpath = GetIEditor()->GetLevelFolder();
QString str = levelpath;
str.replace('/', '\\');
levelpath = CaselessPaths(str);
// Create relative path
QString relLevelPath = QDir(levelpath).relativeFilePath(relGamePath);
if (relLevelPath.isEmpty())
{
assert(0);
return path;
}
relLevelPath.remove(QRegularExpression(QStringLiteral(R"(^[\\/.]*)")));
return relLevelPath;
}
QString Make(const QString& path, const QString& file)
{
if (gEnv->pCryPak->IsAbsPath(file.toUtf8().data()))
{
return file;
}
return CaselessPaths(AddPathSlash(path) + file);
}
QString GetRelativePath(const QString& fullPath, bool bRelativeToGameFolder /*= false*/)
{
if (fullPath.isEmpty())
{
return "";
}
bool relPathfound = false;
AZStd::string relativePath;
AZStd::string fullAssetPath(fullPath.toUtf8().data());
EBUS_EVENT_RESULT(relPathfound, AzToolsFramework::AssetSystemRequestBus, GetRelativeProductPathFromFullSourceOrProductPath, fullAssetPath, relativePath);
if (relPathfound)
{
// do not normalize this path, it will already be an appropriate asset ID.
return CaselessPaths(relativePath.c_str());
}
char rootpath[_MAX_PATH] = { 0 };
azstrcpy(rootpath, _MAX_PATH, Path::GetEditingRootFolder().c_str());
if (bRelativeToGameFolder)
{
azstrcpy(rootpath, _MAX_PATH, Path::GetEditingGameDataFolder().c_str());
}
QString rootPathNormalized(rootpath);
QString srcPathNormalized(fullPath);
#if defined(AZ_PLATFORM_WINDOWS)
// avoid confusing PathRelativePathTo
rootPathNormalized.replace('/', '\\');
srcPathNormalized.replace('/', '\\');
#endif
// Create relative path
char resolvedSrcPath[AZ_MAX_PATH_LEN] = { 0 };
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(srcPathNormalized.toUtf8().data(), resolvedSrcPath, AZ_MAX_PATH_LEN);
QByteArray path = QDir(rootPathNormalized).relativeFilePath(resolvedSrcPath).toUtf8();
if (path.isEmpty())
{
return fullPath;
}
// The following code is required because the windows PathRelativePathTo function will always return "./SomePath" instead of just "SomePath"
// Only remove single dot (.) and slash parts of a path, never the double dot (..)
const char* pBuffer = path.data();
bool bHasDot = false;
while (*pBuffer && pBuffer != path.end())
{
switch (*pBuffer)
{
case '.':
if (bHasDot)
{
// Found a double dot, rewind and stop removing
pBuffer--;
break;
}
// Fall through intended
case '/':
case '\\':
bHasDot = (*pBuffer == '.');
pBuffer++;
continue;
}
break;
}
QString relPath = pBuffer;
return CaselessPaths(relPath);
}
QString GamePathToFullPath(const QString& path)
{
using namespace AzToolsFramework;
AZ_Warning("GamePathToFullPath", path.size() <= AZ_MAX_PATH_LEN, "Path exceeds maximum path length of %d", AZ_MAX_PATH_LEN);
if ((gEnv) && (gEnv->pFileIO) && gEnv->pCryPak && path.size() <= AZ_MAX_PATH_LEN)
{
// first, adjust the file name for mods:
bool fullPathfound = false;
AZStd::string assetFullPath;
AZStd::string adjustedFilePath = path.toUtf8().data();
AssetSystemRequestBus::BroadcastResult(fullPathfound, &AssetSystemRequestBus::Events::GetFullSourcePathFromRelativeProductPath, adjustedFilePath, assetFullPath);
if (fullPathfound)
{
//if the bus message succeeds than normalize and lowercase the path
AzFramework::StringFunc::Path::Normalize(assetFullPath);
return assetFullPath.c_str();
}
// if the bus message didn't succeed, 'guess' the source assets:
else
{
// Not all systems have been converted to use local paths. Some editor files save XML files directly, and a full or correctly aliased path is already passed in.
// If the path passed in exists already, then return the resolved filepath
if (AZ::IO::FileIOBase::GetDirectInstance()->Exists(adjustedFilePath.c_str()))
{
char resolvedPath[AZ_MAX_PATH_LEN + PathUtil::maxAliasLength] = { 0 };
AZ::IO::FileIOBase::GetDirectInstance()->ResolvePath(adjustedFilePath.c_str(), resolvedPath, AZ_MAX_PATH_LEN + PathUtil::maxAliasLength);
return QString::fromUtf8(resolvedPath);
}
// if we get here it means that the Asset Processor does not know about this file. most of the time we should never get here
// the rest of this code just does a bunch of heuristic guesses in case of missing files or if the user has hand-edited
// the asset cache by moving files in via some other means or external process.
if (adjustedFilePath[0] != '@')
{
const char* prefix = (adjustedFilePath[0] == '/' || adjustedFilePath[0] == '\\') ? "@devassets@" : "@devassets@/";
adjustedFilePath = prefix + adjustedFilePath;
}
char szAdjustedFile[AZ_MAX_PATH_LEN + PathUtil::maxAliasLength] = { 0 };
gEnv->pFileIO->ResolvePath(adjustedFilePath.c_str(), szAdjustedFile, AZ_ARRAY_SIZE(szAdjustedFile));
if ((azstrnicmp(szAdjustedFile, "@devassets@", 11) == 0) && ((szAdjustedFile[11] == '/') || (szAdjustedFile[11] == '\\')))
{
if (!gEnv->pCryPak->IsFileExist(szAdjustedFile))
{
AZStd::string newName(szAdjustedFile);
AzFramework::StringFunc::Replace(newName, "@devassets@", "@devroot@/engine", false);
if (gEnv->pCryPak->IsFileExist(newName.c_str()))
{
azstrcpy(szAdjustedFile, AZ_ARRAY_SIZE(szAdjustedFile), newName.c_str());
}
else
{
// getting tricky here, try @devroot@ alone, in case its 'editor'
AzFramework::StringFunc::Replace(newName, "@devassets@", "@devroot@", false);
if (gEnv->pCryPak->IsFileExist(szAdjustedFile))
{
azstrcpy(szAdjustedFile, AZ_ARRAY_SIZE(szAdjustedFile), newName.c_str());
}
// give up, best guess is just @devassets@
}
}
}
// we should very rarely actually get to this point in the code.
// szAdjustedFile may contain an alias at this point. (@assets@/blah.whatever)
// there is a case in which the loose asset exists only within a pak file for some reason
// this is not recommended but it is possible.in that case, we want to return the original szAdjustedFile
// without touching it or resolving it so that crypak can open it successfully.
char adjustedPath[AZ_MAX_PATH_LEN + PathUtil::maxAliasLength] = { 0 };
if (gEnv->pFileIO->ResolvePath(szAdjustedFile, adjustedPath, AZ_MAX_PATH_LEN + PathUtil::maxAliasLength)) // resolve to full path
{
if ((gEnv->pCryPak->IsFileExist(adjustedPath)) || (!gEnv->pCryPak->IsFileExist(szAdjustedFile)))
{
// note that if we get here, then EITHER
// the file exists as a loose asset in the actual adjusted path
// OR the file does not exist in the original passed-in aliased name (like '@assets@/whatever')
// in which case we may as well just resolve the path to a full path and return it.
assetFullPath = adjustedPath;
AzFramework::StringFunc::Path::Normalize(assetFullPath);
azstrcpy(szAdjustedFile, AZ_MAX_PATH_LEN + PathUtil::maxAliasLength, assetFullPath.c_str());
}
// if the above case succeeded then it means that the file does NOT exist loose
// but DOES exist in a pak, in which case we leave szAdjustedFile with the alias on the front of it, meaning
// fopens via crypak will actually succeed.
}
return szAdjustedFile;
}
}
else
{
return "";
}
}
QString ToUnixPath(const QString& strPath, bool bCallCaselessPath)
{
QString str = strPath;
str.replace('\\', '/');
return bCallCaselessPath ? CaselessPaths(str) : str;
}
QString RemoveBackslash(QString path)
{
if (path.isEmpty())
{
return path;
}
int iLenMinus1 = path.length() - 1;
QChar cLastChar = path[iLenMinus1];
if (cLastChar == '\\' || cLastChar == '/')
{
return CaselessPaths(path.mid(0, iLenMinus1));
}
return CaselessPaths(path);
}
QString SubDirectoryCaseInsensitive(const QString& path, const QStringList& parts)
{
if (parts.isEmpty())
{
return path;
}
QStringList modifiedParts = parts;
auto currentPart = modifiedParts.takeFirst();
// case insensitive iterator
QDirIterator it(path);
while (it.hasNext())
{
it.next();
// the current part already exists, use it, case doesn't matter
auto actualName = it.fileName();
if (QString::compare(actualName, currentPart, Qt::CaseInsensitive) == 0)
{
return SubDirectoryCaseInsensitive(QDir(path).absoluteFilePath(actualName), modifiedParts);
}
}
// the current path doesn't exist yet, so just create the complete path in one rush
return QDir(path).absoluteFilePath(parts.join('/'));
}
}
+471
View File
@@ -0,0 +1,471 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Utility functions to simplify working with paths.
#ifndef CRYINCLUDE_EDITOR_UTIL_PATHUTIL_H
#define CRYINCLUDE_EDITOR_UTIL_PATHUTIL_H
#pragma once
#include <CryPath.h>
#if defined(AZ_PLATFORM_WINDOWS)
#include <shlwapi.h>
#endif
#include <Include/EditorCoreAPI.h>
#include <AzCore/IO/SystemFile.h> // for max path
#include <AzCore/std/string/string.h>
#include <AzFramework/StringFunc/StringFunc.h>
#include <QRegularExpression>
#include <QDir>
class QString;
class QStringList;
namespace Path
{
//! creates an absolute path from a relative game path, used for saving game files.
//! Example: Libs/Some/tokens.xml to c:/game/engine/GameName/Mods/ModName/Libs/Some/tokens.xml
//! If you're not working on a mod, it will return it with the game folder prepended.
//! This is the function you should use at all times to convert an asset ID to a full writable editor path.
EDITOR_CORE_API AZStd::string MakeModPathFromGamePath(const char* input);
//! Get the data folder where assets should be saved.
//! if we're working on a mod, will return the mod's root (absolute path, with no slash at the end)
//! if not, will return the default game root (absolute path, with no slash at the end)
//! always returns a full path
EDITOR_CORE_API AZStd::string GetEditingGameDataFolder();
//! Get the root folder (in source control or other writable assets) where you should save root data.
EDITOR_CORE_API AZStd::string GetEditingRootFolder();
//! Set the current mod NAME for editing purposes. After doing this the above functions will take this into account
//! name only, please!
EDITOR_CORE_API void SetModName(const char* input);
//! converts path to lowercase given the cvar for ed_lowercasepaths
inline QString CaselessPaths(const QString& strPath)
{
ICVar* pCvar = gEnv->pConsole->GetCVar("ed_lowercasepaths");
if (pCvar)
{
int uselowercase = pCvar->GetIVal();
if (uselowercase)
{
QString str = strPath;
str = str.toLower();
return str;
}
}
return strPath;
}
//! Split full file name to path and filename
//! @param filepath [IN] Full file name inclusing path.
//! @param path [OUT] Extracted file path.
//! @param file [OUT] Extracted file (with extension).
inline void Split(const QString& filepath, QString& path, QString& file)
{
char path_buffer[_MAX_PATH];
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];
#ifdef AZ_COMPILER_MSVC
_splitpath_s(filepath.toUtf8().data(), drive, AZ_ARRAY_SIZE(drive), dir, AZ_ARRAY_SIZE(dir), fname, AZ_ARRAY_SIZE(fname), ext, AZ_ARRAY_SIZE(ext));
_makepath_s(path_buffer, AZ_ARRAY_SIZE(path_buffer), drive, dir, 0, 0);
path = path_buffer;
_makepath_s(path_buffer, AZ_ARRAY_SIZE(path_buffer), 0, 0, fname, ext);
#else
_splitpath(filepath.toUtf8().data(), drive, dir, fname, ext);
_makepath(path_buffer, drive, dir, 0, 0);
path = path_buffer;
_makepath(path_buffer, 0, 0, fname, ext);
#endif
file = path_buffer;
}
inline void Split(const string& filepath, string& path, string& file)
{
char path_buffer[_MAX_PATH];
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];
#ifdef AZ_COMPILER_MSVC
_splitpath_s(filepath, drive, AZ_ARRAY_SIZE(drive), dir, AZ_ARRAY_SIZE(dir), 0, 0, 0, 0);
_makepath_s(path_buffer, AZ_ARRAY_SIZE(path_buffer), drive, dir, 0, 0);
path = path_buffer;
_makepath_s(path_buffer, AZ_ARRAY_SIZE(path_buffer), 0, 0, fname, ext);
#else
_splitpath(filepath, drive, dir, fname, ext);
_makepath(path_buffer, drive, dir, 0, 0);
path = path_buffer;
_makepath(path_buffer, 0, 0, fname, ext);
#endif
file = path_buffer;
}
//! Split full file name to path and filename
//! @param filepath [IN] Full file name inclusing path.
//! @param path [OUT] Extracted file path.
//! @param filename [OUT] Extracted file (without extension).
//! @param ext [OUT] Extracted files extension.
inline void Split(const QString& filepath, QString& path, QString& filename, QString& fext)
{
char path_buffer[_MAX_PATH];
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];
#ifdef AZ_COMPILER_MSVC
_splitpath_s(filepath.toUtf8().data(), drive, AZ_ARRAY_SIZE(drive), dir, AZ_ARRAY_SIZE(dir), fname, AZ_ARRAY_SIZE(fname), ext, AZ_ARRAY_SIZE(ext));
_makepath_s(path_buffer, AZ_ARRAY_SIZE(path_buffer), drive, dir, 0, 0);
#else
_splitpath(filepath.toUtf8().data(), drive, dir, fname, ext);
_makepath(path_buffer, drive, dir, 0, 0);
#endif
path = path_buffer;
filename = fname;
fext = ext;
}
inline void Split(const string& filepath, string& path, string& filename, string& fext)
{
char path_buffer[_MAX_PATH];
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];
#ifdef AZ_COMPILER_MSVC
_splitpath_s(filepath, drive, AZ_ARRAY_SIZE(drive), dir, AZ_ARRAY_SIZE(dir), fname, AZ_ARRAY_SIZE(fname), ext, AZ_ARRAY_SIZE(ext));
_makepath_s(path_buffer, AZ_ARRAY_SIZE(path_buffer), drive, dir, 0, 0);
#else
_splitpath(filepath, drive, dir, fname, ext);
_makepath(path_buffer, drive, dir, 0, 0);
#endif
path = path_buffer;
filename = fname;
fext = ext;
}
//! Split path into segments
//! @param filepath [IN] path
inline QStringList SplitIntoSegments(const QString& path)
{
return path.split(QRegularExpression(QStringLiteral(R"([\\/])")), Qt::SkipEmptyParts);
}
//! Extract extension from full specified file path.
inline QString GetExt(const QString& filepath)
{
char ext[_MAX_EXT];
#ifdef AZ_COMPILER_MSVC
_splitpath_s(filepath.toUtf8().data(), 0, 0, 0, 0, 0, 0, ext, AZ_ARRAY_SIZE(ext));
#else
_splitpath(filepath.toUtf8().data(), 0, 0, 0, ext);
#endif
if (ext[0] == '.')
{
return ext + 1;
}
return ext;
}
//! Extract path from full specified file path.
inline QString GetPath(const QString& filepath)
{
char path_buffer[_MAX_PATH];
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
#ifdef AZ_COMPILER_MSVC
_splitpath_s(filepath.toUtf8().data(), drive, AZ_ARRAY_SIZE(drive), dir, AZ_ARRAY_SIZE(dir), 0, 0, 0, 0);
_makepath_s(path_buffer, AZ_ARRAY_SIZE(path_buffer), drive, dir, 0, 0);
#else
_splitpath(filepath.toUtf8().data(), drive, dir, 0, 0);
_makepath(path_buffer, drive, dir, 0, 0);
#endif
return CaselessPaths(path_buffer);
}
//! Extract file name with extension from full specified file path.
inline QString GetFile(const QString& filepath)
{
char path_buffer[_MAX_PATH];
char fname[_MAX_FNAME];
char ext[_MAX_EXT];
#ifdef AZ_COMPILER_MSVC
_splitpath_s(filepath.toUtf8().data(), 0, 0, 0, 0, fname, AZ_ARRAY_SIZE(fname), ext, AZ_ARRAY_SIZE(ext));
_makepath_s(path_buffer, AZ_ARRAY_SIZE(path_buffer), 0, 0, fname, ext);
#else
_splitpath(filepath.toUtf8().data(), 0, 0, fname, ext);
_makepath(path_buffer, 0, 0, fname, ext);
#endif
return CaselessPaths(path_buffer);
}
//! Extract file name without extension from full specified file path.
inline QString GetFileName(const QString& filepath)
{
char fname[_MAX_FNAME];
#ifdef AZ_COMPILER_MSVC
_splitpath_s(filepath.toUtf8().data(), 0, 0, 0, 0, fname, AZ_ARRAY_SIZE(fname), 0, 0);
#else
_splitpath(filepath.toUtf8().data(), 0, 0, fname, 0);
#endif
return fname;
}
inline bool EndsWithSlash(QString path)
{
return (path.endsWith(QStringLiteral("\\")) || path.endsWith(QStringLiteral("/")));
}
template<size_t size>
inline bool EndsWithSlash(CryStackStringT<char, size>* path)
{
if ((!path) || (path->empty()))
{
return false;
}
if (
((*path)[path->size() - 1] != '\\') ||
((*path)[path->size() - 1] != '/')
)
{
return true;
}
return false;
}
//! add a backslash if needed
inline QString AddBackslash(QString path)
{
if (path.isEmpty() || EndsWithSlash(path))
{
return path;
}
return CaselessPaths(path + "\\");
}
//! add a slash if needed
inline QString AddSlash(const QString& path)
{
if (path.isEmpty() || EndsWithSlash(path))
{
return path;
}
return CaselessPaths(path + "/");
}
template<size_t size>
inline void AddBackslash(CryStackStringT<char, size>* path)
{
if (path->empty())
{
return;
}
if (!EndsWithSlash(path))
{
(*path) += '\\';
}
}
template<size_t size>
inline void AddSlash(CryStackStringT<char, size>* path)
{
if (path->empty())
{
return;
}
if (!EndsWithSlash(path))
{
(*path) += '/';
}
}
inline QString AddPathSlash(const QString& path)
{
#if defined(AZ_PLATFORM_WINDOWS)
return AddBackslash(path);
#else
return AddSlash(path);
#endif
}
//! Replace extension for given file.
inline QString ReplaceExtension(const QString& filepath, const QString& ext)
{
AZStd::string newPath = filepath.toUtf8().data();
AzFramework::StringFunc::Path::ReplaceExtension(newPath, ext.toUtf8().data());
QString returnString(newPath.c_str());
return CaselessPaths(returnString);
}
//! Replace extension for given file.
inline QString RemoveExtension(const QString& filepath)
{
char path_buffer[_MAX_PATH];
char drive[_MAX_DRIVE];
char dir[_MAX_DIR];
char fname[_MAX_FNAME];
#ifdef AZ_COMPILER_MSVC
_splitpath_s(filepath.toUtf8().data(), drive, AZ_ARRAY_SIZE(drive), dir, AZ_ARRAY_SIZE(dir), fname, AZ_ARRAY_SIZE(fname), 0, 0);
_makepath_s(path_buffer, AZ_ARRAY_SIZE(path_buffer), drive, dir, fname, 0);
#else
_splitpath(filepath.toUtf8().data(), drive, dir, fname, 0);
_makepath(path_buffer, drive, dir, fname, 0);
#endif
return path_buffer;
}
//! Makes a fully specified file path from path and file name.
inline QString Make(const QString& dir, const QString& filename, const QString& ext)
{
char path_buffer[_MAX_PATH];
#ifdef AZ_COMPILER_MSVC
_makepath_s(path_buffer, AZ_ARRAY_SIZE(path_buffer), NULL, dir.toUtf8().data(), filename.toUtf8().data(), ext.toUtf8().data());
#else
_makepath(path_buffer, NULL, dir.toUtf8().data(), filename.toUtf8().data(), ext.toUtf8().data());
#endif
return CaselessPaths(path_buffer);
}
//////////////////////////////////////////////////////////////////////////
EDITOR_CORE_API QString GetRelativePath(const QString& fullPath, bool bRelativeToGameFolder = false);
//////////////////////////////////////////////////////////////////////////
// Description:
// given the assetID of a produced asset, constructs the full path to the SOURCE ASSET that was used to produce it.
// Ex. Objects/box.dds will be converted to C:\Test\Game\Objects\box.tif (or bmp or whatever)
EDITOR_CORE_API QString GamePathToFullPath(const QString& path);
//////////////////////////////////////////////////////////////////////////
inline QString FullPathToGamePath(const QString& path)
{
return CaselessPaths(GetRelativePath(path, true));
}
inline string FullPathToGamePath(const char* path)
{
return CaselessPaths(GetRelativePath(path, true)).toUtf8().data();
}
QString FullPathToLevelPath(const QString& path);
//////////////////////////////////////////////////////////////////////////
// Description:
// Turn any path into an asset ID.
inline QString MakeGamePath(const QString& path)
{
QString fullpath = Path::GamePathToFullPath(path);
// if its in a mod, we still want the 'asset id' of it.
QString dataFolder = Path::AddPathSlash(QString(Path::GetEditingGameDataFolder().c_str()));
if (fullpath.length() > dataFolder.length() && QString::compare(fullpath, dataFolder, Qt::CaseInsensitive) == 0)
{
fullpath = fullpath.right(fullpath.length() - dataFolder.length());
fullpath.replace('\\', '/'); // Slashes use for game files.
return fullpath;
}
fullpath = GetRelativePath(path, true);
if (fullpath.isEmpty())
{
fullpath = path;
}
fullpath.replace('\\', '/'); // Slashes use for game files.
return CaselessPaths(fullpath);
}
inline QString GetAudioLocalizationFolder(bool returnAbsolutePath)
{
// Omit the trailing slash!
QString sLocalizationFolder(QString(PathUtil::GetLocalizationFolder()).left(static_cast<int>(PathUtil::GetLocalizationFolder().size()) - 1));
if (!sLocalizationFolder.isEmpty())
{
sLocalizationFolder = returnAbsolutePath ? (QString(Path::GetEditingGameDataFolder().c_str()) + "/" + sLocalizationFolder + "/dialog/") : sLocalizationFolder + "/dialog/";
}
else
{
gEnv->pSystem->Warning(VALIDATOR_MODULE_EDITOR, VALIDATOR_WARNING, VALIDATOR_FLAG_AUDIO, 0, "The localization folder is not set! Please make sure it is by checking the setting of cvar \"sys_localization_folder\"!");
}
return sLocalizationFolder;
}
//! Returns the aliased path to the user Sandbox folder
EDITOR_CORE_API QString GetUserSandboxFolder();
//! Returns the resolved, non-aliased path to the user Sandbox folder
EDITOR_CORE_API QString GetResolvedUserSandboxFolder();
//! Convert a path to the uniform form.
EDITOR_CORE_API QString ToUnixPath(const QString& strPath, bool bCallCaselessPath = true);
//! Makes a fully specified file path from path and file name.
EDITOR_CORE_API QString Make(const QString& path, const QString& file);
// This had to be created because _splitpath is too dumb about console drives.
EDITOR_CORE_API void SplitPath(const QString& rstrFullPathFilename, QString& rstrDriveLetter, QString& rstrDirectory, QString& rstrFilename, QString& rstrExtension);
// Requires a path from Splithpath: no drive letter and backslash at the end.
EDITOR_CORE_API void GetDirectoryQueue(const QString& rstrSourceDirectory, QStringList& rcstrDirectoryTree);
// Converts all slashes to backslashes so MS things won't complain.
EDITOR_CORE_API void ConvertSlashToBackSlash(QString& rstrStringToConvert);
// Converts backslashes into forward slashes.
EDITOR_CORE_API void ConvertBackSlashToSlash(QString& rstrStringToConvert);
// Surrounds a string with quotes if necessary. This is useful for calling other programs.
EDITOR_CORE_API void SurroundWithQuotes(QString& rstrSurroundString);
// Gets the temporary directory path (which may not exist).
EDITOR_CORE_API QString GetWindowsTempDirectory();
// This function returns the full path used to run the editor.
EDITOR_CORE_API QString GetExecutableFullPath();
// This function returns the engine's root path
EDITOR_CORE_API QString GetEngineRootPath();
// This function replaces the filename from a path, keeping extension and directory/drive path.
// WARNING: do not use the same variable in the last parameter and in any of the others.
EDITOR_CORE_API QString& ReplaceFilename(const QString& strFilepath, const QString& strFilename, QString& strOutputFilename, bool bCallCaselessPath = true);
//! \return true if the given path is a folder and not a file
EDITOR_CORE_API bool IsFolder(const char* pPath);
EDITOR_CORE_API void ConvertSlashToBackSlash(QString& str);
EDITOR_CORE_API void ConvertBackSlashToSlash(QString& str);
EDITOR_CORE_API QString RemoveBackslash(QString path);
/*
* Returns the complete path of the subdirectories in parts inside of path. If
* one of the parts already exists but in different upper and lower case, the resulting
* path will contain that one. Note that the directory is not created!
*/
EDITOR_CORE_API QString SubDirectoryCaseInsensitive(const QString& path, const QStringList& parts);
};
inline QString operator /(const QString& first, const QString& second)
{
return Path::Make(first, second);
}
#endif // CRYINCLUDE_EDITOR_UTIL_PATHUTIL_H
+117
View File
@@ -0,0 +1,117 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "PredefinedAspectRatios.h"
// Editor
#include "Settings.h"
CPredefinedAspectRatios::CPredefinedAspectRatios()
{
m_aspectRatios.reserve(10);
AddAspectRatio(5, 4);
AddAspectRatio(4, 3);
AddAspectRatio(3, 2);
AddAspectRatio(16, 10);
AddAspectRatio(16, 9);
AddAspectRatio(1.85f, 1);
AddAspectRatio(2.39f, 1);
}
CPredefinedAspectRatios::~CPredefinedAspectRatios()
{
}
void CPredefinedAspectRatios::AddAspectRatio(float x, int y)
{
if (y == 0)
{
return;
}
SAspectRatio aspectRatio;
aspectRatio.name = QStringLiteral("%1:%2").arg(x, 0, 'f', 2).arg(y);
aspectRatio.value = x / y;
m_aspectRatios.push_back(aspectRatio);
}
void CPredefinedAspectRatios::AddAspectRatio(int x, int y)
{
if (y == 0)
{
return;
}
SAspectRatio aspectRatio;
aspectRatio.name = QStringLiteral("%1:%2").arg(x).arg(y);
aspectRatio.value = float( x ) / y;
m_aspectRatios.push_back(aspectRatio);
}
float CPredefinedAspectRatios::GetCurrentValue() const
{
return gSettings.viewports.fDefaultAspectRatio;
}
bool CPredefinedAspectRatios::IsEmpty() const
{
return m_aspectRatios.empty();
}
size_t CPredefinedAspectRatios::GetCount() const
{
return m_aspectRatios.size();
}
const QString& CPredefinedAspectRatios::GetName(size_t aspectRatioId) const
{
bool validAspectRatioId = (aspectRatioId < GetCount());
assert(validAspectRatioId);
if (!validAspectRatioId)
{
static QString dummyAspectRatioName("1:1");
return dummyAspectRatioName;
}
const SAspectRatio& aspectRatio = m_aspectRatios[ aspectRatioId ];
return aspectRatio.name;
}
float CPredefinedAspectRatios::GetValue(size_t aspectRatioId) const
{
bool validAspectRatioId = (aspectRatioId < GetCount());
assert(validAspectRatioId);
if (!validAspectRatioId)
{
return 1;
}
const SAspectRatio& aspectRatio = m_aspectRatios[ aspectRatioId ];
return aspectRatio.value;
}
bool CPredefinedAspectRatios::IsCurrent(size_t aspectRatioId) const
{
float selectedValue = GetValue(aspectRatioId);
float currentValue = GetCurrentValue();
const float THRESHOLD = 0.01f;
bool valuesCloseEnough = (fabs(selectedValue - currentValue) <= THRESHOLD);
return valuesCloseEnough;
}
+43
View File
@@ -0,0 +1,43 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_PREDEFINEDASPECTRATIOS_H
#define CRYINCLUDE_EDITOR_UTIL_PREDEFINEDASPECTRATIOS_H
#pragma once
#include <vector>
class CPredefinedAspectRatios
{
public:
CPredefinedAspectRatios();
virtual ~CPredefinedAspectRatios();
void AddAspectRatio(float x, int y);
void AddAspectRatio(int x, int y);
float GetCurrentValue() const;
bool IsEmpty() const;
size_t GetCount() const;
const QString& GetName(size_t aspectRatioId) const;
float GetValue(size_t aspectRatioId) const;
bool IsCurrent(size_t aspectRatioId) const;
private:
struct SAspectRatio
{
QString name;
float value;
};
std::vector< SAspectRatio > m_aspectRatios;
};
#endif // CRYINCLUDE_EDITOR_UTIL_PREDEFINEDASPECTRATIOS_H
+54
View File
@@ -0,0 +1,54 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Reference counted base object.
#ifndef CRYINCLUDE_EDITOR_UTIL_REFCOUNTBASE_H
#define CRYINCLUDE_EDITOR_UTIL_REFCOUNTBASE_H
#pragma once
#include <Include/EditorCoreAPI.h>
//! Derive from this class to get reference counting in your class.
class EDITOR_CORE_API CRefCountBase
{
public:
CRefCountBase() {}
//! Add a new reference to this object.
int AddRef()
{
m_nRefCount++;
return m_nRefCount;
}
//! Release reference to this object.
//! When the reference count reaches zero, the object is deleted.
int Release()
{
int refs = --m_nRefCount;
if (m_nRefCount == 0)
{
delete this;
}
else if (m_nRefCount < 0)
{
CryFatalError("Negative ref count");
}
return refs;
}
protected:
virtual ~CRefCountBase() {}
private:
int m_nRefCount = 0;
};
#endif // CRYINCLUDE_EDITOR_UTIL_REFCOUNTBASE_H
File diff suppressed because it is too large Load Diff
+219
View File
@@ -0,0 +1,219 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_CRYCOMMONTOOLS_STRINGHELPERS_H
#define CRYINCLUDE_CRYCOMMONTOOLS_STRINGHELPERS_H
#pragma once
#include <CryString.h>
namespace StringHelpers
{
// compares two strings to see if they are the same or different, case sensitive
// returns 0 if the strings are the same, a -1 if the first string is bigger, or a 1 if the second string is bigger
int Compare(const string& str0, const string& str1);
int Compare(const wstring& str0, const wstring& str1);
// compares two strings to see if they are the same or different, case is ignored
// returns 0 if the strings are the same, a -1 if the first string is bigger, or a 1 if the second string is bigger
int CompareIgnoreCase(const string& str0, const string& str1);
int CompareIgnoreCase(const wstring& str0, const wstring& str1);
// compares two strings to see if they are the same, case senstive
// returns true if they are the same or false if they are different
bool Equals(const string& str0, const string& str1);
bool Equals(const wstring& str0, const wstring& str1);
// compares two strings to see if they are the same, case is ignored
// returns true if they are the same or false if they are different
bool EqualsIgnoreCase(const string& str0, const string& str1);
bool EqualsIgnoreCase(const wstring& str0, const wstring& str1);
// checks to see if a string starts with a specified string, case sensitive
// returns true if the string does start with a specified string or false if it does not
bool StartsWith(const string& str, const string& pattern);
bool StartsWith(const wstring& str, const wstring& pattern);
// checks to see if a string starts with a specified string, case is ignored
// returns true if the string does start with a specified string or false if it does not
bool StartsWithIgnoreCase(const string& str, const string& pattern);
bool StartsWithIgnoreCase(const wstring& str, const wstring& pattern);
// checks to see if a string ends with a specified string, case sensitive
// returns true if the string does end with a specified string or false if it does not
bool EndsWith(const string& str, const string& pattern);
bool EndsWith(const wstring& str, const wstring& pattern);
// checks to see if a string ends with a specified string, case is ignored
// returns true if the string does end with a specified string or false if it does not
bool EndsWithIgnoreCase(const string& str, const string& pattern);
bool EndsWithIgnoreCase(const wstring& str, const wstring& pattern);
// checks to see if a string contains a specified string, case sensitive
// returns true if the string does contain the specified string or false if it does not
bool Contains(const string& str, const string& pattern);
bool Contains(const wstring& str, const wstring& pattern);
// checks to see if a string contains a specified string, case is ignored
// returns true if the string does contain the specified string or false if it does not
bool ContainsIgnoreCase(const string& str, const string& pattern);
bool ContainsIgnoreCase(const wstring& str, const wstring& pattern);
// checks to see if a string contains a wildcard string pattern, case sensitive
// returns true if the string does match the wildcard string pattern or false if it does not
bool MatchesWildcards(const string& str, const string& wildcards);
bool MatchesWildcards(const wstring& str, const wstring& wildcards);
// checks to see if a string contains a wildcard string pattern, case is ignored
// returns true if the string does match the wildcard string pattern or false if it does not
bool MatchesWildcardsIgnoreCase(const string& str, const string& wildcards);
bool MatchesWildcardsIgnoreCase(const wstring& str, const wstring& wildcards);
bool MatchesWildcardsIgnoreCaseExt(const string& str, const string& wildcards, std::vector<string>& wildcardMatches);
bool MatchesWildcardsIgnoreCaseExt(const wstring& str, const wstring& wildcards, std::vector<wstring>& wildcardMatches);
string TrimLeft(const string& s);
wstring TrimLeft(const wstring& s);
string TrimRight(const string& s);
wstring TrimRight(const wstring& s);
string Trim(const string& s);
wstring Trim(const wstring& s);
string RemoveDuplicateSpaces(const string& s);
wstring RemoveDuplicateSpaces(const wstring& s);
// converts a string with upper case characters to be all lower case
// returns the string in all lower case
string MakeLowerCase(const string& s);
wstring MakeLowerCase(const wstring& s);
// converts a string with lower case characters to be all upper case
// returns the string in all upper case
string MakeUpperCase(const string& s);
wstring MakeUpperCase(const wstring& s);
// replace a specified character in a string with a specified replacement character
// returns string with specified character replaced
string Replace(const string& s, char oldChar, char newChar);
wstring Replace(const wstring& s, wchar_t oldChar, wchar_t newChar);
void ConvertStringByRef(string& out, const string& in);
void ConvertStringByRef(wstring& out, const string& in);
void ConvertStringByRef(string& out, const wstring& in);
void ConvertStringByRef(wstring& out, const wstring& in);
template <typename O, typename I>
O ConvertString(const I& in)
{
O out;
ConvertStringByRef(out, in);
return out;
}
void Split(const string& str, const string& separator, bool bReturnEmptyPartsToo, std::vector<string>& outParts);
void Split(const wstring& str, const wstring& separator, bool bReturnEmptyPartsToo, std::vector<wstring>& outParts);
void SplitByAnyOf(const string& str, const string& separators, bool bReturnEmptyPartsToo, std::vector<string>& outParts);
void SplitByAnyOf(const wstring& str, const wstring& separators, bool bReturnEmptyPartsToo, std::vector<wstring>& outParts);
string FormatVA(const char* const format, va_list parg);
wstring FormatVA(const wchar_t* const format, va_list parg);
inline string Format(const char* const format, ...)
{
if ((format == 0) || (format[0] == 0))
{
return string();
}
va_list parg;
va_start(parg, format);
const string result = FormatVA(format, parg);
va_end(parg);
return result;
}
inline wstring Format(const wchar_t* const format, ...)
{
if ((format == 0) || (format[0] == 0))
{
return wstring();
}
va_list parg;
va_start(parg, format);
const wstring result = FormatVA(format, parg);
va_end(parg);
return result;
}
//////////////////////////////////////////////////////////////////////////
void SafeCopy(char* const pDstBuffer, const size_t dstBufferSizeInBytes, const char* const pSrc);
void SafeCopy(wchar_t* const pDstBuffer, const size_t dstBufferSizeInBytes, const wchar_t* const pSrc);
void SafeCopyPadZeros(char* const pDstBuffer, const size_t dstBufferSizeInBytes, const char* const pSrc);
void SafeCopyPadZeros(wchar_t* const pDstBuffer, const size_t dstBufferSizeInBytes, const wchar_t* const pSrc);
//////////////////////////////////////////////////////////////////////////
// ASCII
// ANSI (system default Windows ANSI code page)
// UTF-8
// UTF-16
// ASCII -> UTF-16
bool Utf16ContainsAsciiOnly(const wchar_t* wstr);
string ConvertAsciiUtf16ToAscii(const wchar_t* wstr);
wstring ConvertAsciiToUtf16(const char* str);
// UTF-8 <-> UTF-16
#if defined(AZ_PLATFORM_WINDOWS)
wstring ConvertUtf8ToUtf16(const char* str);
string ConvertUtf16ToUtf8(const wchar_t* wstr);
inline string ConvertUtfToUtf8(const char* str)
{
return string(str);
}
inline string ConvertUtfToUtf8(const wchar_t* wstr)
{
return ConvertUtf16ToUtf8(wstr);
}
inline wstring ConvertUtfToUtf16(const char* str)
{
return ConvertUtf8ToUtf16(str);
}
inline wstring ConvertUtfToUtf16(const wchar_t* wstr)
{
return wstring(wstr);
}
// ANSI <-> UTF-8, UTF-16
wstring ConvertAnsiToUtf16(const char* str);
string ConvertUtf16ToAnsi(const wchar_t* wstr, char badChar);
inline string ConvertAnsiToUtf8(const char* str)
{
return ConvertUtf16ToUtf8(ConvertAnsiToUtf16(str).c_str());
}
inline string ConvertUtf8ToAnsi(const char* str, char badChar)
{
return ConvertUtf16ToAnsi(ConvertUtf8ToUtf16(str).c_str(), badChar);
}
#endif
// ANSI -> ASCII
string ConvertAnsiToAscii(const char* str, char badChar);
}
#endif // CRYINCLUDE_CRYCOMMONTOOLS_STRINGHELPERS_H
+61
View File
@@ -0,0 +1,61 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Utility class to help in STL algorithms on containers with
// CStrings when intending to use case insensitive searches or sorting for
// example.
#ifndef CRYINCLUDE_EDITOR_UTIL_STRINGNOCASEPREDICATE_H
#define CRYINCLUDE_EDITOR_UTIL_STRINGNOCASEPREDICATE_H
#pragma once
/*
Utility class to help in STL algorithms on containers with CStrings
when intending to use case insensitive searches or sorting for example.
e.g.
std::vector< CString > v;
...
std::sort( v.begin(), v.end(), CStringNoCasePredicate::LessThan() );
std::find_if( v.begin(), v.end(), CStringNoCasePredicate::Equal( stringImLookingFor ) );
*/
class CStringNoCasePredicate
{
public:
//////////////////////////////////////////////////////////////////////////
struct Equal
{
Equal(const QString& referenceString)
: m_referenceString(referenceString)
{
}
bool operator() (const QString& arg) const
{
return (m_referenceString.compare(arg, Qt::CaseInsensitive) == 0);
}
private:
const QString& m_referenceString;
};
//////////////////////////////////////////////////////////////////////////
//////////////////////////////////////////////////////////////////////////
struct LessThan
{
bool operator() (const QString& arg1, const QString& arg2) const
{
return (arg1.CompareNoCase(arg2) < 0);
}
};
//////////////////////////////////////////////////////////////////////////
};
#endif // CRYINCLUDE_EDITOR_UTIL_STRINGNOCASEPREDICATE_H
+55
View File
@@ -0,0 +1,55 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : Reference counted base object.
#ifndef CRYINCLUDE_EDITOR_UTIL_TREFCOUNTBASE_H
#define CRYINCLUDE_EDITOR_UTIL_TREFCOUNTBASE_H
#pragma once
AZ_PUSH_DISABLE_DLL_EXPORT_BASECLASS_WARNING
//////////////////////////////////////////////////////////////////////////
//! Derive from this class to get reference counting for your class.
//////////////////////////////////////////////////////////////////////////
template <class ParentClass>
class CRYEDIT_API TRefCountBase
: public ParentClass
{
AZ_POP_DISABLE_DLL_EXPORT_BASECLASS_WARNING
public:
TRefCountBase() { m_nRefCount = 0; };
//! Add new refrence to this object.
unsigned long AddRef()
{
m_nRefCount++;
return m_nRefCount;
};
//! Release refrence to this object.
//! when reference count reaches zero, object is deleted.
unsigned long Release()
{
int refs = --m_nRefCount;
if (m_nRefCount <= 0)
{
delete this;
}
return refs;
}
protected:
virtual ~TRefCountBase() {};
private:
int m_nRefCount;
};
#endif // CRYINCLUDE_EDITOR_UTIL_TREFCOUNTBASE_H
+68
View File
@@ -0,0 +1,68 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "Triangulate.h"
// this file is essentially a wrapper for a portion of the MIT-licenced
// ConvexDecomposition library by John W. Ratcliff mailto:jratcliffscarab@gmail.com.
// it contains no code from that library, it just provides it with the required types, then includes the
// portion we need.
static const float TRIANGULATION_EPSILON = 0.0000000001f;
#define MEMALLOC_MALLOC malloc
#define MEMALLOC_FREE free
namespace TriInternal
{
class TVec;
typedef double NxF64;
typedef float NxF32;
typedef unsigned char NxU8;
typedef unsigned int NxU32;
typedef int NxI32;
typedef unsigned int TU32;
typedef std::vector< TVec > TVecVector;
typedef std::vector< NxU32 > TU32Vector;
#include "Contrib/NvFloatMath.inl"
}
#undef MEMALLOC_MALLOC
#undef MEMALLOC_FREE
namespace Triangulator
{
// given the contour of a triangle, triangulate it, and return the result
// as a set of triangles
// return false if you fail to triangulate it.
bool Triangulate(const VectorOfVectors& contour, VectorOfVectors& result)
{
TriInternal::CTriangulator tri;
for (auto pt : contour)
{
tri.addPoint(pt.x, pt.y, pt.z);
}
TriInternal::NxU32 tricount = 0;
TriInternal::NxU32* indices = tri.triangulate(tricount, TRIANGULATION_EPSILON);
if (!indices)
{
return false;
}
for (TriInternal::NxU32 currentIdx = 0; currentIdx < tricount * 3; ++currentIdx)
{
TriInternal::NxU32 indexValue = *indices++;
result.push_back(contour[indexValue]);
}
return result.size() > 2;
}
}
+25
View File
@@ -0,0 +1,25 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_UTIL_TRIANGULATE_H
#define CRYINCLUDE_EDITOR_UTIL_TRIANGULATE_H
#pragma once
#include <vector>
#include "Cry_Vector3.h"
// you pass in a vector of vec3 (the contour of a shape) and it outputs a vector of vec3 (being the triangles)
namespace Triangulator
{
typedef std::vector< Vec3 > VectorOfVectors;
bool Triangulate(const VectorOfVectors& contour, VectorOfVectors& result);
};
#endif
+74
View File
@@ -0,0 +1,74 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : This file implements the container for the assotiaon of
// enumeration name to enumeration values
#include "EditorDefs.h"
#include "UIEnumerations.h"
//////////////////////////////////////////////////////////////////////////
CUIEnumerations& CUIEnumerations::GetUIEnumerationsInstance()
{
static CUIEnumerations oGeneralProxy;
return oGeneralProxy;
}
//////////////////////////////////////////////////////////////////////////
CUIEnumerations::TDValuesContainer& CUIEnumerations::GetStandardNameContainer()
{
static TDValuesContainer cValuesContainer;
static bool boInit(false);
if (!boInit)
{
boInit = true;
XmlNodeRef oRootNode;
XmlNodeRef oEnumaration;
XmlNodeRef oEnumerationItem;
int nNumberOfEnumarations(0);
int nCurrentEnumaration(0);
int nNumberOfEnumerationItems(0);
int nCurrentEnumarationItem(0);
oRootNode = GetISystem()->GetXmlUtils()->LoadXmlFromFile("Editor\\PropertyEnumerations.xml");
nNumberOfEnumarations = oRootNode ? oRootNode->getChildCount() : 0;
for (nCurrentEnumaration = 0; nCurrentEnumaration < nNumberOfEnumarations; ++nCurrentEnumaration)
{
TDValues cValues;
oEnumaration = oRootNode->getChild(nCurrentEnumaration);
nNumberOfEnumerationItems = oEnumaration->getChildCount();
for (nCurrentEnumarationItem = 0; nCurrentEnumarationItem < nNumberOfEnumerationItems; ++nCurrentEnumarationItem)
{
oEnumerationItem = oEnumaration->getChild(nCurrentEnumarationItem);
const char* szKey(NULL);
const char* szValue(NULL);
oEnumerationItem->getAttributeByIndex(0, &szKey, &szValue);
cValues.push_back(szValue);
}
const char* szKey(NULL);
const char* szValue(NULL);
oEnumaration->getAttributeByIndex(0, &szKey, &szValue);
cValuesContainer.insert(TDValuesContainer::value_type(szValue, cValues));
}
}
return cValuesContainer;
}
//////////////////////////////////////////////////////////////////////////
+36
View File
@@ -0,0 +1,36 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
// Description : This file declares the container for the assotiaon of
// enumeration name to enumeration values
#ifndef CRYINCLUDE_EDITOR_UTIL_UIENUMERATIONS_H
#define CRYINCLUDE_EDITOR_UTIL_UIENUMERATIONS_H
#pragma once
class CUIEnumerations
{
public:
// For XML standard values.
typedef QStringList TDValues;
typedef std::map<QString, TDValues> TDValuesContainer;
protected:
private:
public:
static CUIEnumerations& GetUIEnumerationsInstance();
TDValuesContainer& GetStandardNameContainer();
protected:
private:
};
#endif // CRYINCLUDE_EDITOR_UTIL_UIENUMERATIONS_H
+85
View File
@@ -0,0 +1,85 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "UndoUtil.h"
#include "Include/EditorCoreAPI.h"
CUndo::CUndo(const char* description)
: m_bCancelled(false)
{
if (!IsRecording())
{
GetIEditor()->BeginUndo();
azstrncpy(m_description, scDescSize, description, scDescSize);
m_description[scDescSize - 1] = '\0';
m_bStartedRecord = true;
}
else
{
m_bStartedRecord = false;
}
};
CUndo::~CUndo()
{
if (m_bStartedRecord)
{
if (m_bCancelled)
{
GetIEditor()->CancelUndo();
}
else
{
GetIEditor()->AcceptUndo(m_description);
}
}
};
bool CUndo::IsRecording()
{
if (IEditor* editor = GetIEditor())
{
return editor->IsUndoRecording();
}
return false;
}
bool CUndo::IsSuspended()
{
if (IEditor* editor = GetIEditor())
{
return editor->IsUndoSuspended();
}
return false;
}
void CUndo::Record(IUndoObject* undo)
{
if (IEditor* editor = GetIEditor())
{
editor->RecordUndo(undo);
}
}
CUndoSuspend::CUndoSuspend()
{
if (IEditor* editor = GetIEditor())
{
editor->SuspendUndo();
}
};
CUndoSuspend::~CUndoSuspend()
{
if (IEditor* editor = GetIEditor())
{
editor->ResumeUndo();
}
};
+49
View File
@@ -0,0 +1,49 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_EDITOR_CORE_UTIL_UNDO_UTIL_H
#define CRYINCLUDE_EDITOR_CORE_UTIL_UNDO_UTIL_H
#pragma once
#include "Include/EditorCoreAPI.h"
struct IUndoObject;
class EDITOR_CORE_API CUndo
{
public:
CUndo(const char* description);
~CUndo();
void Cancel() { m_bCancelled = true; }
//! Check if undo is recording.
static bool IsRecording();
//! Check if undo is suspended.
static bool IsSuspended();
//! Record specified object.
static void Record(IUndoObject* undo);
private:
static const uint32 scDescSize = 256;
char m_description[scDescSize];
bool m_bCancelled;
bool m_bStartedRecord;
};
//! CUndoSuspend is a utility undo class
//! Define instance of this class in block of code where you want to suspend undo operations
class EDITOR_CORE_API CUndoSuspend
{
public:
CUndoSuspend();
~CUndoSuspend();
};
#endif // CRYINCLUDE_EDITOR_CORE_UTIL_UNDO_UTIL_H
+197
View File
@@ -0,0 +1,197 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#ifndef CRYINCLUDE_CRYCOMMONTOOLS_UTIL_H
#define CRYINCLUDE_CRYCOMMONTOOLS_UTIL_H
#pragma once
#include <cstring> // memset()
#ifndef BIT
#define BIT(x) (1 << (x))
#endif
union IntOrPtr
{
int m_int;
unsigned int m_uint;
void* m_pVoid;
char* m_pChar;
void setZero()
{
memset(this, 0, sizeof(*this));
}
bool operator==(const IntOrPtr& a) const
{
return memcmp(this, &a, sizeof(*this)) == 0;
}
bool operator!=(const IntOrPtr& a) const
{
return memcmp(this, &a, sizeof(*this)) != 0;
}
};
namespace Util
{
// note: names 'getMin' and 'getMax' (instead of usual 'min' and 'max') are
// used to avoid conflicts with global and/or user's #define of 'min' and 'max'
template<class T>
inline const T& getMin(const T& a, const T& b)
{
return (a < b) ? a : b;
}
template<class T>
inline const T& getMax(const T& a, const T& b)
{
return (a < b) ? b : a;
}
template<class T>
inline const T& getMin(const T& a, const T& b, const T& c)
{
return (a < b)
? ((a < c) ? a : c)
: ((b < c) ? b : c);
}
template<class T>
inline const T& getMax(const T& a, const T& b, const T& c)
{
return (a < b)
? ((b < c) ? c : b)
: ((a < c) ? c : a);
}
template<class T>
inline const T& getClamped(const T& a, const T& a_min, const T& a_max)
{
if (a < a_min)
{
return a_min;
}
if (a_max < a)
{
return a_max;
}
return a;
}
// note: name 'clampMinMax' (instead of usual 'clamp') is used
// to avoid conflicts with global and/or user's #define of 'clamp'
template<class T>
inline void clampMinMax(T& a, const T& a_min, const T& a_max)
{
if (a < a_min)
{
a = a_min;
}
else if (a_max < a)
{
a = a_max;
}
}
template<class T>
inline void clampMin(T& a, const T& a_min)
{
if (a < a_min)
{
a = a_min;
}
}
template<class T>
inline void clampMax(T& a, const T& a_max)
{
if (a_max < a)
{
a = a_max;
}
}
template <class TInteger>
inline bool isPowerOfTwo(TInteger x)
{
return (x & (x - 1)) == 0;
}
template <class TInteger>
inline TInteger getCeiledPowerOfTwo(TInteger x)
{
x = x - 1;
#pragma warning(push)
#pragma warning(disable : 4293)
if (sizeof(TInteger) > 0)
{
x |= x >> 1;
}
if (sizeof(TInteger) > 0)
{
x |= x >> 2;
}
if (sizeof(TInteger) > 0)
{
x |= x >> 4;
}
if (sizeof(TInteger) > 1)
{
x |= x >> 8;
}
if (sizeof(TInteger) > 2)
{
x |= x >> 16;
}
if (sizeof(TInteger) > 4)
{
x |= x >> 32;
}
#pragma warning(pop)
return x + 1;
}
template <class TInteger>
inline TInteger getFlooredPowerOfTwo(TInteger x)
{
if (!isPowerOfTwo(x))
{
x = getCeiledPowerOfTwo(x) >> 1;
}
return x;
}
template <class T>
inline T square(T x)
{
return x * x;
}
template <class T>
inline T cube(T x)
{
return x * x * x;
}
} // namespace Util
#endif // CRYINCLUDE_CRYCOMMONTOOLS_UTIL_H
+543
View File
@@ -0,0 +1,543 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "Variable.h"
#include "UIEnumsDatabase.h"
#include "UsedResources.h" // for CUsedResources
//////////////////////////////////////////////////////////////////////////
CVarBlock* CVarBlock::Clone(bool bRecursive) const
{
CVarBlock* vb = new CVarBlock;
for (Variables::const_iterator it = m_vars.begin(); it != m_vars.end(); ++it)
{
IVariable* var = *it;
vb->AddVariable(var->Clone(bRecursive));
}
return vb;
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::CopyValues(const CVarBlock* fromVarBlock)
{
// Copy all variables.
int numSrc = fromVarBlock->GetNumVariables();
int numTrg = GetNumVariables();
for (int i = 0; i < numSrc && i < numTrg; i++)
{
GetVariable(i)->CopyValue(fromVarBlock->GetVariable(i));
}
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::CopyValuesByName(CVarBlock* fromVarBlock)
{
// Copy values using saving and loading to/from xml.
XmlNodeRef node = XmlHelpers::CreateXmlNode("Temp");
fromVarBlock->Serialize(node, false);
Serialize(node, true);
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::OnSetValues()
{
for (Variables::iterator it = m_vars.begin(); it != m_vars.end(); ++it)
{
IVariable* var = *it;
var->OnSetValue(true);
}
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::SetRecreateSplines()
{
for (Variables::iterator it = m_vars.begin(); it != m_vars.end(); ++it)
{
IVariable* var = *it;
var->SetFlagRecursive(IVariable::UI_CREATE_SPLINE);
}
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::AddVariable(IVariable* var)
{
//assert( !strstr(var->GetName(), " ") ); // spaces not allowed because of serialization
m_vars.push_back(var);
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::AddVariable(IVariable* pVar, const char* varName, unsigned char dataType)
{
if (varName)
{
pVar->SetName(varName);
}
pVar->SetDataType(dataType);
AddVariable(pVar);
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::AddVariable(CVariableBase& var, const char* varName, unsigned char dataType)
{
if (varName)
{
var.SetName(varName);
}
var.SetDataType(dataType);
AddVariable(&var);
}
//////////////////////////////////////////////////////////////////////////
bool CVarBlock::DeleteVariable(IVariable* var, bool bRecursive)
{
bool found = stl::find_and_erase(m_vars, var);
if (!found && bRecursive)
{
for (Variables::iterator it = m_vars.begin(); it != m_vars.end(); ++it)
{
if ((*it)->DeleteVariable(var, bRecursive))
{
return true;
}
}
}
return found;
}
//////////////////////////////////////////////////////////////////////////
bool CVarBlock::IsContainsVariable(IVariable* pVar, bool bRecursive) const
{
for (Variables::const_iterator it = m_vars.begin(); it != m_vars.end(); ++it)
{
if (*it == pVar)
{
return true;
}
}
// If not found search childs.
if (bRecursive)
{
// Search all top level variables.
for (Variables::const_iterator it = m_vars.begin(); it != m_vars.end(); ++it)
{
if ((*it)->IsContainsVariable(pVar))
{
return true;
}
}
}
return false;
}
namespace
{
IVariable* FindVariable(const char* name, bool bRecursive, bool bHumanName, const std::vector<IVariablePtr>& vars)
{
// Search all top level variables.
for (std::vector<IVariablePtr>::const_iterator it = vars.begin(); it != vars.end(); ++it)
{
IVariable* var = *it;
if (bHumanName && QString::compare(var->GetHumanName(), name, Qt::CaseInsensitive) == 0)
{
return var;
}
else if (!bHumanName && QString::compare(var->GetName(), name) == 0)
{
return var;
}
}
// If not found search childs.
if (bRecursive)
{
// Search all top level variables.
for (std::vector<IVariablePtr>::const_iterator it = vars.begin(); it != vars.end(); ++it)
{
IVariable* var = *it;
IVariable* found = var->FindVariable(name, bRecursive, bHumanName);
if (found)
{
return found;
}
}
}
return nullptr;
}
}
//////////////////////////////////////////////////////////////////////////
IVariable* CVarBlock::FindVariable(const char* name, bool bRecursive, bool bHumanName) const
{
return ::FindVariable(name, bRecursive, bHumanName, m_vars);
}
//////////////////////////////////////////////////////////////////////////
IVariable* CVariableArray::FindVariable(const char* name, bool bRecursive, bool bHumanName) const
{
return ::FindVariable(name, bRecursive, bHumanName, m_vars);
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::Serialize(XmlNodeRef vbNode, bool load)
{
if (load)
{
// Loading.
QString name;
for (Variables::iterator it = m_vars.begin(); it != m_vars.end(); ++it)
{
IVariable* var = *it;
if (var->GetNumVariables())
{
XmlNodeRef child = vbNode->findChild(var->GetName().toUtf8().data());
if (child)
{
var->Serialize(child, load);
}
}
else
{
var->Serialize(vbNode, load);
}
}
}
else
{
// Saving.
for (Variables::iterator it = m_vars.begin(); it != m_vars.end(); ++it)
{
IVariable* var = *it;
if (var->GetNumVariables())
{
XmlNodeRef child = vbNode->newChild(var->GetName().toUtf8().data());
var->Serialize(child, load);
}
else
{
var->Serialize(vbNode, load);
}
}
}
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::ReserveNumVariables(int numVars)
{
m_vars.reserve(numVars);
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::WireVar(IVariable* src, IVariable* trg, bool bWire)
{
if (bWire)
{
src->Wire(trg);
}
else
{
src->Unwire(trg);
}
int numSrcVars = src->GetNumVariables();
if (numSrcVars > 0)
{
int numTrgVars = trg->GetNumVariables();
for (int i = 0; i < numSrcVars && i < numTrgVars; i++)
{
WireVar(src->GetVariable(i), trg->GetVariable(i), bWire);
}
}
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::Wire(CVarBlock* toVarBlock)
{
Variables::iterator tit = toVarBlock->m_vars.begin();
Variables::iterator sit = m_vars.begin();
for (; sit != m_vars.end() && tit != toVarBlock->m_vars.end(); ++sit, ++tit)
{
IVariable* src = *sit;
IVariable* trg = *tit;
WireVar(src, trg, true);
}
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::Unwire(CVarBlock* toVarBlock)
{
Variables::iterator tit = toVarBlock->m_vars.begin();
Variables::iterator sit = m_vars.begin();
for (; sit != m_vars.end() && tit != toVarBlock->m_vars.end(); ++sit, ++tit)
{
IVariable* src = *sit;
IVariable* trg = *tit;
WireVar(src, trg, false);
}
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::AddOnSetCallback(IVariable::OnSetCallback* func)
{
for (Variables::iterator it = m_vars.begin(); it != m_vars.end(); ++it)
{
IVariable* var = *it;
SetCallbackToVar(func, var, true);
}
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::RemoveOnSetCallback(IVariable::OnSetCallback* func)
{
for (Variables::iterator it = m_vars.begin(); it != m_vars.end(); ++it)
{
IVariable* var = *it;
SetCallbackToVar(func, var, false);
}
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::SetCallbackToVar(IVariable::OnSetCallback* func, IVariable* pVar, bool bAdd)
{
if (bAdd)
{
pVar->AddOnSetCallback(func);
}
else
{
pVar->RemoveOnSetCallback(func);
}
int numVars = pVar->GetNumVariables();
if (numVars > 0)
{
for (int i = 0; i < numVars; i++)
{
SetCallbackToVar(func, pVar->GetVariable(i), bAdd);
}
}
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::GatherUsedResources(CUsedResources& resources)
{
for (int i = 0; i < GetNumVariables(); i++)
{
IVariable* pVar = GetVariable(i);
GatherUsedResourcesInVar(pVar, resources);
}
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::EnableUpdateCallbacks(bool boEnable)
{
for (int i = 0; i < GetNumVariables(); i++)
{
IVariable* pVar = GetVariable(i);
pVar->EnableUpdateCallbacks(boEnable);
}
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::GatherUsedResourcesInVar(IVariable* pVar, CUsedResources& resources)
{
int type = pVar->GetDataType();
if (type == IVariable::DT_FILE || type == IVariable::DT_OBJECT || type == IVariable::DT_TEXTURE)
{
// this is file.
QString filename;
pVar->Get(filename);
if (!filename.isEmpty())
{
resources.Add(filename.toUtf8().data());
}
}
for (int i = 0; i < pVar->GetNumVariables(); i++)
{
GatherUsedResourcesInVar(pVar->GetVariable(i), resources);
}
}
//////////////////////////////////////////////////////////////////////////
inline bool CompareNames(const IVariable* pVar1, const IVariable* pVar2)
{
return (QString::compare(pVar1->GetHumanName(), pVar2->GetHumanName(), Qt::CaseInsensitive) < 0);
}
//////////////////////////////////////////////////////////////////////////
void CVarBlock::Sort()
{
std::sort(m_vars.begin(), m_vars.end(), CompareNames);
}
//////////////////////////////////////////////////////////////////////////
CVarObject::CVarObject()
{}
//////////////////////////////////////////////////////////////////////////
CVarObject::~CVarObject()
{}
//////////////////////////////////////////////////////////////////////////
void CVarObject::AddVariable(CVariableBase& var, const QString& varName, VarOnSetCallback* cb, unsigned char dataType)
{
if (!m_vars)
{
m_vars = new CVarBlock;
}
var.AddRef(); // Variables are local and must not be released by CVarBlock.
var.SetName(varName);
var.SetDataType(dataType);
if (cb)
{
var.AddOnSetCallback(cb);
}
m_vars->AddVariable(&var);
}
//////////////////////////////////////////////////////////////////////////
void CVarObject::AddVariable(CVariableBase& var, const QString& varName, const QString& varHumanName, VarOnSetCallback* cb, unsigned char dataType)
{
if (!m_vars)
{
m_vars = new CVarBlock;
}
var.AddRef(); // Variables are local and must not be released by CVarBlock.
var.SetName(varName);
var.SetHumanName(varHumanName);
var.SetDataType(dataType);
if (cb)
{
var.AddOnSetCallback(cb);
}
m_vars->AddVariable(&var);
}
//////////////////////////////////////////////////////////////////////////
void CVarObject::AddVariable(CVariableArray& table, CVariableBase& var, const QString& varName, const QString& varHumanName, VarOnSetCallback* cb, unsigned char dataType)
{
if (!m_vars)
{
m_vars = new CVarBlock;
}
var.AddRef(); // Variables are local and must not be released by CVarBlock.
var.SetName(varName);
var.SetHumanName(varHumanName);
var.SetDataType(dataType);
if (cb)
{
var.AddOnSetCallback(cb);
}
table.AddVariable(&var);
}
//////////////////////////////////////////////////////////////////////////
void CVarObject::RemoveVariable(IVariable* var)
{
if (m_vars != NULL)
{
m_vars->DeleteVariable(var);
}
}
//////////////////////////////////////////////////////////////////////////
void CVarObject::EnableUpdateCallbacks(bool boEnable)
{
if (m_vars != NULL)
{
m_vars->EnableUpdateCallbacks(boEnable);
}
}
//////////////////////////////////////////////////////////////////////////
void CVarObject::OnSetValues()
{
if (m_vars != NULL)
{
m_vars->OnSetValues();
}
}
//////////////////////////////////////////////////////////////////////////
void CVarObject::ReserveNumVariables(int numVars)
{
if (m_vars != NULL)
{
m_vars->ReserveNumVariables(numVars);
}
}
//////////////////////////////////////////////////////////////////////////
void CVarObject::CopyVariableValues(CVarObject* sourceObject)
{
// Check if compatible types.
assert(metaObject() == sourceObject->metaObject());
if (m_vars != NULL && sourceObject->m_vars != NULL)
{
m_vars->CopyValues(sourceObject->m_vars);
}
}
//////////////////////////////////////////////////////////////////////////
void CVarObject::Serialize(XmlNodeRef node, bool load)
{
if (m_vars)
{
m_vars->Serialize(node, load);
}
}
CVarGlobalEnumList::CVarGlobalEnumList(CUIEnumsDatabase_SEnum* pEnum)
: m_pEnum(pEnum)
{
}
CVarGlobalEnumList::CVarGlobalEnumList(const QString& enumName)
{
m_pEnum = GetIEditor()->GetUIEnumsDatabase()->FindEnum(enumName);
}
//! Get the name of specified value in enumeration.
QString CVarGlobalEnumList::GetItemName(uint index)
{
if (!m_pEnum || index >= m_pEnum->strings.size())
{
return QString();
}
return m_pEnum->strings[index];
}
QString CVarGlobalEnumList::NameToValue(const QString& name)
{
if (m_pEnum)
{
return m_pEnum->NameToValue(name);
}
else
{
return name;
}
}
QString CVarGlobalEnumList::ValueToName(const QString& value)
{
if (m_pEnum)
{
return m_pEnum->ValueToName(value);
}
else
{
return value;
}
}
File diff suppressed because it is too large Load Diff
+330
View File
@@ -0,0 +1,330 @@
/*
* Copyright (c) Contributors to the Open 3D Engine Project
*
* SPDX-License-Identifier: Apache-2.0 OR MIT
*
*/
#include "EditorDefs.h"
#include "VariablePropertyType.h"
#include "Variable.h"
#include "UIEnumsDatabase.h"
#include "IEditor.h"
namespace Prop
{
struct
{
int dataType;
const char* name;
PropertyType type;
int image;
} static s_propertyTypeNames[] =
{
{ IVariable::DT_SIMPLE, "Bool", ePropertyBool, 2 },
{ IVariable::DT_SIMPLE, "Int", ePropertyInt, 0 },
{ IVariable::DT_SIMPLE, "Float", ePropertyFloat, 0 },
{ IVariable::DT_SIMPLE, "Vector", ePropertyVector2, 10 },
{ IVariable::DT_SIMPLE, "Vector", ePropertyVector, 10 },
{ IVariable::DT_SIMPLE, "Vector", ePropertyVector4, 10 },
{ IVariable::DT_SIMPLE, "String", ePropertyString, 3 },
{ IVariable::DT_PERCENT, "Float", ePropertyInt, 13 },
{ IVariable::DT_BOOLEAN, "Boolean", ePropertyBool, 2 },
{ IVariable::DT_COLOR, "Color", ePropertyColor, 1 },
{ IVariable::DT_COLORA, "ColorA", ePropertyColor, 1 },
{ IVariable::DT_CURVE | IVariable::DT_PERCENT, "FloatCurve", ePropertyFloatCurve, 13 },
{ IVariable::DT_CURVE | IVariable::DT_COLOR, "ColorCurve", ePropertyColorCurve, 1 },
{ IVariable::DT_ANGLE, "Angle", ePropertyAngle, 0 },
{ IVariable::DT_FILE, "File", ePropertyFile, 7 },
{ IVariable::DT_TEXTURE, "Texture", ePropertyTexture, 4 },
{ IVariable::DT_ANIMATION, "Animation", ePropertyAnimation, -1 },
{ IVariable::DT_MOTION, "Motion", ePropertyMotion, -1 },
{ IVariable::DT_OBJECT, "Model", ePropertyModel, 5 },
{ IVariable::DT_SIMPLE, "Selection", ePropertySelection, -1 },
{ IVariable::DT_SIMPLE, "List", ePropertyList, -1 },
{ IVariable::DT_SHADER, "Shader", ePropertyShader, 9 },
{ IVariable::DT_DEPRECATED0, "DEPRECATED", ePropertyDeprecated2, -1 },
{ IVariable::DT_EQUIP, "Equip", ePropertyEquip, 11 },
{ IVariable::DT_REVERBPRESET, "ReverbPreset", ePropertyReverbPreset, 11 },
{ IVariable::DT_LOCAL_STRING, "LocalString", ePropertyLocalString, 3 },
{ IVariable::DT_SEQUENCE, "Sequence", ePropertySequence, -1 },
{ IVariable::DT_MISSIONOBJ, "Mission Objective", ePropertyMissionObj, -1 },
{ IVariable::DT_USERITEMCB, "User", ePropertyUser, -1 },
{ IVariable::DT_SEQUENCE_ID, "SequenceId", ePropertySequenceId, -1 },
{ IVariable::DT_LIGHT_ANIMATION, "LightAnimation", ePropertyLightAnimation, -1 },
{ IVariable::DT_PARTICLE_EFFECT, "ParticleEffect", ePropertyParticleName, 3 },
{ IVariable::DT_GEOM_CACHE, "Geometry Cache", ePropertyGeomCache, 5 },
{ IVariable::DT_AUDIO_TRIGGER, "Audio Trigger", ePropertyAudioTrigger, 6 },
{ IVariable::DT_AUDIO_SWITCH, "Audio Switch", ePropertyAudioSwitch, 6 },
{ IVariable::DT_AUDIO_SWITCH_STATE, "Audio Switch", ePropertyAudioSwitchState, 6 },
{ IVariable::DT_AUDIO_RTPC, "Audio Realtime Parameter Control", ePropertyAudioRTPC, 6 },
{ IVariable::DT_AUDIO_ENVIRONMENT, "Audio Environment", ePropertyAudioEnvironment, 6 },
{ IVariable::DT_AUDIO_PRELOAD_REQUEST, "Audio Preload Request", ePropertyAudioPreloadRequest, 6 },
{ IVariable::DT_SIMPLE, "Custom", ePropertyFlowCustomData, -1 },
{ IVariable::DT_UI_ELEMENT, "UiElement", ePropertyUiElement, -1 }
};
static const int NumPropertyTypes = sizeof(s_propertyTypeNames) / sizeof(s_propertyTypeNames[0]);
Description::Description()
: m_type(ePropertyInvalid)
, m_numImages(-1)
, m_enumList(NULL)
, m_rangeMin(0)
, m_rangeMax(100)
, m_step(0)
, m_bHardMin(false)
, m_bHardMax(false)
, m_valueMultiplier(1)
, m_pEnumDBItem(NULL)
{
}
Description::Description(IVariable* pVar)
: m_type(ePropertyInvalid)
, m_numImages(-1)
, m_enumList(NULL)
, m_rangeMin(0)
, m_rangeMax(100)
, m_step(0)
, m_bHardMin(false)
, m_bHardMax(false)
, m_valueMultiplier(1)
, m_pEnumDBItem(NULL)
{
if (!pVar)
{
return;
}
const int type = (int)pVar->GetDataType();
if (type != IVariable::DT_SIMPLE)
{
m_type = GetType(type);
m_numImages = GetNumImages(pVar);
}
m_name = pVar->GetHumanName();
m_enumList = pVar->GetEnumList();
if (m_enumList != NULL)
{
m_type = ePropertySelection;
}
if (m_type == ePropertyInvalid)
{
switch (pVar->GetType())
{
case IVariable::INT:
m_type = ePropertyInt;
break;
case IVariable::BOOL:
m_type = ePropertyBool;
break;
case IVariable::FLOAT:
m_type = ePropertyFloat;
break;
case IVariable::VECTOR2:
m_type = ePropertyVector2;
break;
case IVariable::VECTOR4:
m_type = ePropertyVector4;
break;
case IVariable::VECTOR:
m_type = ePropertyVector;
break;
case IVariable::STRING:
m_type = ePropertyString;
break;
case IVariable::FLOW_CUSTOM_DATA:
m_type = ePropertyFlowCustomData;
break;
default:
break;
}
m_numImages = Prop::GetNumImages(m_type);
}
// Get variable limits.
pVar->GetLimits(m_rangeMin, m_rangeMax, m_step, m_bHardMin, m_bHardMax);
// Check if value is percents.
if (type == IVariable::DT_PERCENT)
{
// Scale all values by 100.
m_valueMultiplier = 100;
}
else if (type == IVariable::DT_ANGLE)
{
// Scale radians to degrees.
m_valueMultiplier = RAD2DEG(1);
m_rangeMin = max(-360.0f, m_rangeMin);
m_rangeMax = min(360.0f, m_rangeMax);
}
else if (type == IVariable::DT_UIENUM)
{
m_pEnumDBItem = GetIEditor()->GetUIEnumsDatabase()->FindEnum(m_name);
}
const bool useExplicitStep = (pVar->GetFlags() & IVariable::UI_EXPLICIT_STEP);
if (!useExplicitStep)
{
// Limit step size to 1000.
int nPrec = max(3 - int(log(m_rangeMax - m_rangeMin) / log(10.f)), 0);
m_step = max(m_step, powf(10.f, -nPrec));
}
}
const char* GetName(int dataType)
{
for (int i = 0; i < NumPropertyTypes; i++)
{
if (dataType == s_propertyTypeNames[i].type)
{
return s_propertyTypeNames[i].name;
}
}
return "";
}
PropertyType GetType(int dataType)
{
for (int i = 0; i < NumPropertyTypes; i++)
{
if (dataType == s_propertyTypeNames[i].dataType)
{
return s_propertyTypeNames[i].type;
}
}
return ePropertyInvalid;
}
PropertyType GetType(const IVariable* var)
{
assert(var);
if (!var)
{
return ePropertyInvalid;
}
const int dataType = (int)var->GetDataType();
for (int i = 0; i < NumPropertyTypes; i++)
{
if (dataType == s_propertyTypeNames[i].dataType)
{
return s_propertyTypeNames[i].type;
}
}
return ePropertyInvalid;
}
PropertyType GetType(const char* type)
{
assert(type);
if (!type)
{
return ePropertyInvalid;
}
for (int i = 0; i < NumPropertyTypes; i++)
{
if (azstricmp(type, s_propertyTypeNames[i].name) == 0)
{
return s_propertyTypeNames[i].type;
}
}
return ePropertyInvalid;
}
// look up image by property type
int GetNumImages(int propertyType)
{
for (int i = 0; i < NumPropertyTypes; i++)
{
if (propertyType == s_propertyTypeNames[i].type)
{
return s_propertyTypeNames[i].image;
}
}
return -1;
}
// look up image by data type
int GetNumImages(const IVariable* var)
{
assert(var);
if (!var)
{
return -1;
}
const int dataType = (int)var->GetDataType();
for (int i = 0; i < NumPropertyTypes; i++)
{
if (dataType == s_propertyTypeNames[i].dataType)
{
return s_propertyTypeNames[i].image;
}
}
return -1;
}
int GetNumImages(const char* type)
{
assert(type);
if (!type)
{
return -1;
}
for (int i = 0; i < NumPropertyTypes; i++)
{
if (azstricmp(type, s_propertyTypeNames[i].name) == 0)
{
return s_propertyTypeNames[i].image;
}
}
return -1;
}
const char* GetPropertyTypeToResourceType(PropertyType type)
{
// The strings below are names used together with
// REGISTER_RESOURCE_SELECTOR. See IResourceSelector.h.
switch (type)
{
case ePropertyModel:
return "Model";
case ePropertyGeomCache:
return "GeomCache";
case ePropertyAudioTrigger:
return "AudioTrigger";
case ePropertyAudioSwitch:
return "AudioSwitch";
case ePropertyAudioSwitchState:
return "AudioSwitchState";
case ePropertyAudioRTPC:
return "AudioRTPC";
case ePropertyAudioEnvironment:
return "AudioEnvironment";
case ePropertyAudioPreloadRequest:
return "AudioPreloadRequest";
default:
return 0;
}
}
}

Some files were not shown because too many files have changed in this diff Show More