git mv Code\Sandbox\Editor Code/Editor
Signed-off-by: Esteban Papp <81431996+amznestebanpapp@users.noreply.github.com>
This commit is contained in:
@@ -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
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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);
|
||||
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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>
|
||||
@@ -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
|
||||
@@ -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));
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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];
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
|
||||
@@ -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
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
@@ -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;
|
||||
};
|
||||
@@ -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);
|
||||
}
|
||||
|
||||
|
||||
@@ -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;
|
||||
};
|
||||
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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 }
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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);
|
||||
};
|
||||
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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);
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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;
|
||||
};
|
||||
@@ -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()
|
||||
{
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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('/'));
|
||||
}
|
||||
}
|
||||
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
@@ -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
|
||||
@@ -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
@@ -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
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
}
|
||||
@@ -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
|
||||
@@ -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;
|
||||
}
|
||||
//////////////////////////////////////////////////////////////////////////
|
||||
@@ -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
|
||||
@@ -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();
|
||||
}
|
||||
};
|
||||
@@ -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
|
||||
@@ -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
|
||||
@@ -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
@@ -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
Reference in New Issue
Block a user