Merge branch 'experiments/directives'
This commit is contained in:
commit
cb76e4e444
@ -1,4 +1,5 @@
|
|||||||
#include "StarAssets.hpp"
|
#include "StarAssets.hpp"
|
||||||
|
#include "StarAssetPath.hpp"
|
||||||
#include "StarFile.hpp"
|
#include "StarFile.hpp"
|
||||||
#include "StarTime.hpp"
|
#include "StarTime.hpp"
|
||||||
#include "StarDirectoryAssetSource.hpp"
|
#include "StarDirectoryAssetSource.hpp"
|
||||||
@ -59,169 +60,6 @@ static void validatePath(AssetPath const& components, bool canContainSubPath, bo
|
|||||||
throw AssetException::format("Path '%s' cannot contain directives", components);
|
throw AssetException::format("Path '%s' cannot contain directives", components);
|
||||||
}
|
}
|
||||||
|
|
||||||
// The filename is everything after the last slash (excluding directives) and
|
|
||||||
// up to the first directive marker.
|
|
||||||
static Maybe<pair<size_t, size_t>> findFilenameRange(std::string const& pathUtf8) {
|
|
||||||
size_t firstDirectiveOrSubPath = pathUtf8.find_first_of(":?");
|
|
||||||
size_t filenameStart = 0;
|
|
||||||
while (true) {
|
|
||||||
size_t find = pathUtf8.find('/', filenameStart);
|
|
||||||
if (find >= firstDirectiveOrSubPath)
|
|
||||||
break;
|
|
||||||
filenameStart = find + 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (filenameStart == NPos) {
|
|
||||||
return {};
|
|
||||||
} else if (firstDirectiveOrSubPath == NPos) {
|
|
||||||
return {{filenameStart, pathUtf8.size()}};
|
|
||||||
} else {
|
|
||||||
return {{filenameStart, firstDirectiveOrSubPath}};
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
AssetPath AssetPath::split(String const& path) {
|
|
||||||
auto i = path.begin();
|
|
||||||
auto end = path.end();
|
|
||||||
|
|
||||||
AssetPath components;
|
|
||||||
|
|
||||||
// base paths cannot have any ':' or '?' characters, stop at the first one.
|
|
||||||
while (i != end) {
|
|
||||||
String::Char c = *i;
|
|
||||||
if (c == ':' || c == '?')
|
|
||||||
break;
|
|
||||||
|
|
||||||
components.basePath += c;
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Sub-paths must immediately follow base paths and must start with a ':',
|
|
||||||
// after this point any further ':' characters are not special.
|
|
||||||
if (i != end && *i == ':') {
|
|
||||||
++i;
|
|
||||||
while (i != end) {
|
|
||||||
String::Char c = *i;
|
|
||||||
if (c == '?')
|
|
||||||
break;
|
|
||||||
|
|
||||||
if (!components.subPath)
|
|
||||||
components.subPath.emplace();
|
|
||||||
|
|
||||||
*components.subPath += c;
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Directives must follow the base path and optional sub-path, and each
|
|
||||||
// directive is separated by one or more '?' characters.
|
|
||||||
while (i != end && *i == '?') {
|
|
||||||
++i;
|
|
||||||
String directive;
|
|
||||||
while (i != end) {
|
|
||||||
String::Char c = *i;
|
|
||||||
if (c == '?')
|
|
||||||
break;
|
|
||||||
|
|
||||||
directive += c;
|
|
||||||
++i;
|
|
||||||
}
|
|
||||||
if (!directive.empty())
|
|
||||||
components.directives.append(move(directive));
|
|
||||||
}
|
|
||||||
|
|
||||||
starAssert(i == end);
|
|
||||||
|
|
||||||
return components;
|
|
||||||
}
|
|
||||||
|
|
||||||
String AssetPath::join(AssetPath const& components) {
|
|
||||||
return toString(components);
|
|
||||||
}
|
|
||||||
|
|
||||||
String AssetPath::setSubPath(String const& path, String const& subPath) {
|
|
||||||
auto components = split(path);
|
|
||||||
components.subPath = subPath;
|
|
||||||
return join(components);
|
|
||||||
}
|
|
||||||
|
|
||||||
String AssetPath::removeSubPath(String const& path) {
|
|
||||||
auto components = split(path);
|
|
||||||
components.subPath.reset();
|
|
||||||
return join(components);
|
|
||||||
}
|
|
||||||
|
|
||||||
String AssetPath::getDirectives(String const& path) {
|
|
||||||
size_t firstDirective = path.find('?');
|
|
||||||
if (firstDirective == NPos)
|
|
||||||
return {};
|
|
||||||
return path.substr(firstDirective + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
String AssetPath::addDirectives(String const& path, String const& directives) {
|
|
||||||
return String::joinWith("?", path, directives);
|
|
||||||
}
|
|
||||||
|
|
||||||
String AssetPath::removeDirectives(String const& path) {
|
|
||||||
size_t firstDirective = path.find('?');
|
|
||||||
if (firstDirective == NPos)
|
|
||||||
return path;
|
|
||||||
return path.substr(0, firstDirective);
|
|
||||||
}
|
|
||||||
|
|
||||||
String AssetPath::directory(String const& path) {
|
|
||||||
if (auto p = findFilenameRange(path.utf8())) {
|
|
||||||
return String(path.utf8().substr(0, p->first));
|
|
||||||
} else {
|
|
||||||
return String();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String AssetPath::filename(String const& path) {
|
|
||||||
if (auto p = findFilenameRange(path.utf8())) {
|
|
||||||
return String(path.utf8().substr(p->first, p->second));
|
|
||||||
} else {
|
|
||||||
return String();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
String AssetPath::extension(String const& path) {
|
|
||||||
auto file = filename(path);
|
|
||||||
auto lastDot = file.findLast(".");
|
|
||||||
if (lastDot == NPos)
|
|
||||||
return "";
|
|
||||||
|
|
||||||
return file.substr(lastDot + 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
String AssetPath::relativeTo(String const& sourcePath, String const& givenPath) {
|
|
||||||
if (!givenPath.empty() && givenPath[0] == '/')
|
|
||||||
return givenPath;
|
|
||||||
|
|
||||||
auto path = directory(sourcePath);
|
|
||||||
path.append(givenPath);
|
|
||||||
return path;
|
|
||||||
}
|
|
||||||
|
|
||||||
bool AssetPath::operator==(AssetPath const& rhs) const {
|
|
||||||
return tie(basePath, subPath, directives) == tie(rhs.basePath, rhs.subPath, rhs.directives);
|
|
||||||
}
|
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, AssetPath const& rhs) {
|
|
||||||
os << rhs.basePath;
|
|
||||||
if (rhs.subPath) {
|
|
||||||
os << ":";
|
|
||||||
os << *rhs.subPath;
|
|
||||||
}
|
|
||||||
|
|
||||||
for (auto const& directive : rhs.directives) {
|
|
||||||
os << "?";
|
|
||||||
os << directive;
|
|
||||||
}
|
|
||||||
|
|
||||||
return os;
|
|
||||||
}
|
|
||||||
|
|
||||||
Maybe<RectU> FramesSpecification::getRect(String const& frame) const {
|
Maybe<RectU> FramesSpecification::getRect(String const& frame) const {
|
||||||
if (auto alias = aliases.ptr(frame)) {
|
if (auto alias = aliases.ptr(frame)) {
|
||||||
return frames.get(*alias);
|
return frames.get(*alias);
|
||||||
@ -396,11 +234,10 @@ void Assets::queueJsons(StringList const& paths) const {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageConstPtr Assets::image(String const& path) const {
|
ImageConstPtr Assets::image(AssetPath const& path) const {
|
||||||
auto components = AssetPath::split(path);
|
validatePath(path, true, true);
|
||||||
validatePath(components, true, true);
|
|
||||||
|
|
||||||
return as<ImageData>(getAsset(AssetId{AssetType::Image, move(components)}))->image;
|
return as<ImageData>(getAsset(AssetId{AssetType::Image, path}))->image;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Assets::queueImages(StringList const& paths) const {
|
void Assets::queueImages(StringList const& paths) const {
|
||||||
@ -412,11 +249,10 @@ void Assets::queueImages(StringList const& paths) const {
|
|||||||
}));
|
}));
|
||||||
}
|
}
|
||||||
|
|
||||||
ImageConstPtr Assets::tryImage(String const& path) const {
|
ImageConstPtr Assets::tryImage(AssetPath const& path) const {
|
||||||
auto components = AssetPath::split(path);
|
validatePath(path, true, true);
|
||||||
validatePath(components, true, true);
|
|
||||||
|
|
||||||
if (auto imageData = as<ImageData>(tryAsset(AssetId{AssetType::Image, move(components)})))
|
if (auto imageData = as<ImageData>(tryAsset(AssetId{AssetType::Image, path})))
|
||||||
return imageData->image;
|
return imageData->image;
|
||||||
else
|
else
|
||||||
return {};
|
return {};
|
||||||
@ -1006,9 +842,14 @@ shared_ptr<Assets::AssetData> Assets::loadImage(AssetPath const& path) const {
|
|||||||
as<ImageData>(loadAsset(AssetId{AssetType::Image, {path.basePath, path.subPath, {}}}));
|
as<ImageData>(loadAsset(AssetId{AssetType::Image, {path.basePath, path.subPath, {}}}));
|
||||||
if (!source)
|
if (!source)
|
||||||
return {};
|
return {};
|
||||||
List<ImageOperation> operations = path.directives.transformed(imageOperationFromString);
|
|
||||||
StringMap<ImageConstPtr> references;
|
StringMap<ImageConstPtr> references;
|
||||||
for (auto const& ref : imageOperationReferences(operations)) {
|
StringList referencePaths;
|
||||||
|
path.directives.forEach([&](auto const& entry) {
|
||||||
|
addImageOperationReferences(entry.operation, referencePaths);
|
||||||
|
}); // TODO: This can definitely be better, was changed quickly to support the new Directives.
|
||||||
|
|
||||||
|
|
||||||
|
for (auto const& ref : referencePaths) {
|
||||||
auto components = AssetPath::split(ref);
|
auto components = AssetPath::split(ref);
|
||||||
validatePath(components, true, false);
|
validatePath(components, true, false);
|
||||||
auto refImage = as<ImageData>(loadAsset(AssetId{AssetType::Image, move(components)}));
|
auto refImage = as<ImageData>(loadAsset(AssetId{AssetType::Image, move(components)}));
|
||||||
@ -1019,8 +860,11 @@ shared_ptr<Assets::AssetData> Assets::loadImage(AssetPath const& path) const {
|
|||||||
|
|
||||||
return unlockDuring([&]() {
|
return unlockDuring([&]() {
|
||||||
auto newData = make_shared<ImageData>();
|
auto newData = make_shared<ImageData>();
|
||||||
newData->image = make_shared<Image>(processImageOperations(
|
Image newImage = *source->image;
|
||||||
operations, *source->image, [&](String const& ref) { return references.get(ref).get(); }));
|
path.directives.forEach([&](auto const& entry) {
|
||||||
|
processImageOperation(entry.operation, newImage, [&](String const& ref) { return references.get(ref).get(); });
|
||||||
|
});
|
||||||
|
newData->image = make_shared<Image>(move(newImage));
|
||||||
return newData;
|
return newData;
|
||||||
});
|
});
|
||||||
|
|
||||||
|
@ -7,6 +7,7 @@
|
|||||||
#include "StarBiMap.hpp"
|
#include "StarBiMap.hpp"
|
||||||
#include "StarThread.hpp"
|
#include "StarThread.hpp"
|
||||||
#include "StarAssetSource.hpp"
|
#include "StarAssetSource.hpp"
|
||||||
|
#include "StarAssetPath.hpp"
|
||||||
|
|
||||||
namespace Star {
|
namespace Star {
|
||||||
|
|
||||||
@ -18,61 +19,6 @@ STAR_CLASS(Assets);
|
|||||||
|
|
||||||
STAR_EXCEPTION(AssetException, StarException);
|
STAR_EXCEPTION(AssetException, StarException);
|
||||||
|
|
||||||
// Asset paths are not filesystem paths. '/' is always the directory separator,
|
|
||||||
// and it is not possible to escape any asset source directory. '\' is never a
|
|
||||||
// valid directory separator. All asset paths are considered case-insensitive.
|
|
||||||
//
|
|
||||||
// In addition to the path portion of the asset path, some asset types may also
|
|
||||||
// have a sub-path, which is always separated from the path portion of the asset
|
|
||||||
// by ':'. There can be at most 1 sub-path component.
|
|
||||||
//
|
|
||||||
// Image paths may also have a directives portion of the full asset path, which
|
|
||||||
// must come after the path and optional sub-path comopnent. The directives
|
|
||||||
// portion of the path starts with a '?', and '?' separates each subsquent
|
|
||||||
// directive.
|
|
||||||
struct AssetPath {
|
|
||||||
static AssetPath split(String const& path);
|
|
||||||
static String join(AssetPath const& path);
|
|
||||||
|
|
||||||
// Get / modify sub-path directly on a joined path string
|
|
||||||
static String setSubPath(String const& joinedPath, String const& subPath);
|
|
||||||
static String removeSubPath(String const& joinedPath);
|
|
||||||
|
|
||||||
// Get / modify directives directly on a joined path string
|
|
||||||
static String getDirectives(String const& joinedPath);
|
|
||||||
static String addDirectives(String const& joinedPath, String const& directives);
|
|
||||||
static String removeDirectives(String const& joinedPath);
|
|
||||||
|
|
||||||
// The base directory name for any given path, including the trailing '/'.
|
|
||||||
// Ignores sub-path and directives.
|
|
||||||
static String directory(String const& path);
|
|
||||||
|
|
||||||
// The file part of any given path, ignoring sub-path and directives. Path
|
|
||||||
// must be a file not a directory.
|
|
||||||
static String filename(String const& path);
|
|
||||||
|
|
||||||
// The file extension of a given file path, ignoring directives and
|
|
||||||
// sub-paths.
|
|
||||||
static String extension(String const& path);
|
|
||||||
|
|
||||||
// Computes an absolute asset path from a relative path relative to another
|
|
||||||
// asset. The sourcePath must be an absolute path (may point to a directory
|
|
||||||
// or an asset in a directory, and ignores ':' sub-path or ? directives),
|
|
||||||
// and the givenPath may be either an absolute *or* a relative path. If it
|
|
||||||
// is an absolute path, it is returned unchanged. If it is a relative path,
|
|
||||||
// then it is computed as relative to the directory component of the
|
|
||||||
// sourcePath.
|
|
||||||
static String relativeTo(String const& sourcePath, String const& givenPath);
|
|
||||||
|
|
||||||
String basePath;
|
|
||||||
Maybe<String> subPath;
|
|
||||||
StringList directives;
|
|
||||||
|
|
||||||
bool operator==(AssetPath const& rhs) const;
|
|
||||||
};
|
|
||||||
|
|
||||||
std::ostream& operator<<(std::ostream& os, AssetPath const& rhs);
|
|
||||||
|
|
||||||
// The contents of an assets .frames file, which can be associated with one or
|
// The contents of an assets .frames file, which can be associated with one or
|
||||||
// more images, and specifies named sub-rects of those images.
|
// more images, and specifies named sub-rects of those images.
|
||||||
struct FramesSpecification {
|
struct FramesSpecification {
|
||||||
@ -173,12 +119,12 @@ public:
|
|||||||
// <full-path-minus-extension>.frames or default.frames, going up to assets
|
// <full-path-minus-extension>.frames or default.frames, going up to assets
|
||||||
// root. May return the same ImageConstPtr for different paths if the paths
|
// root. May return the same ImageConstPtr for different paths if the paths
|
||||||
// are equivalent or they are aliases of other image paths.
|
// are equivalent or they are aliases of other image paths.
|
||||||
ImageConstPtr image(String const& path) const;
|
ImageConstPtr image(AssetPath const& path) const;
|
||||||
// Load images using background processing
|
// Load images using background processing
|
||||||
void queueImages(StringList const& paths) const;
|
void queueImages(StringList const& paths) const;
|
||||||
// Return the given image *if* it is already loaded, otherwise queue it for
|
// Return the given image *if* it is already loaded, otherwise queue it for
|
||||||
// loading.
|
// loading.
|
||||||
ImageConstPtr tryImage(String const& path) const;
|
ImageConstPtr tryImage(AssetPath const& path) const;
|
||||||
|
|
||||||
// Returns the best associated FramesSpecification for a given image path, if
|
// Returns the best associated FramesSpecification for a given image path, if
|
||||||
// it exists. The given path must not contain sub-paths or directives, and
|
// it exists. The given path must not contain sub-paths or directives, and
|
||||||
|
@ -7,6 +7,7 @@ SET (star_core_HEADERS
|
|||||||
StarAStar.hpp
|
StarAStar.hpp
|
||||||
StarAlgorithm.hpp
|
StarAlgorithm.hpp
|
||||||
StarArray.hpp
|
StarArray.hpp
|
||||||
|
StarAssetPath.hpp
|
||||||
StarAtomicSharedPtr.hpp
|
StarAtomicSharedPtr.hpp
|
||||||
StarAudio.hpp
|
StarAudio.hpp
|
||||||
StarBTree.hpp
|
StarBTree.hpp
|
||||||
@ -23,6 +24,7 @@ SET (star_core_HEADERS
|
|||||||
StarDataStream.hpp
|
StarDataStream.hpp
|
||||||
StarDataStreamDevices.hpp
|
StarDataStreamDevices.hpp
|
||||||
StarDataStreamExtra.hpp
|
StarDataStreamExtra.hpp
|
||||||
|
StarDirectives.hpp
|
||||||
StarDynamicLib.hpp
|
StarDynamicLib.hpp
|
||||||
StarEither.hpp
|
StarEither.hpp
|
||||||
StarEncode.hpp
|
StarEncode.hpp
|
||||||
@ -121,6 +123,7 @@ SET (star_core_HEADERS
|
|||||||
|
|
||||||
SET (star_core_SOURCES
|
SET (star_core_SOURCES
|
||||||
StarAudio.cpp
|
StarAudio.cpp
|
||||||
|
StarAssetPath.cpp
|
||||||
StarBTreeDatabase.cpp
|
StarBTreeDatabase.cpp
|
||||||
StarBuffer.cpp
|
StarBuffer.cpp
|
||||||
StarByteArray.cpp
|
StarByteArray.cpp
|
||||||
@ -128,6 +131,7 @@ SET (star_core_SOURCES
|
|||||||
StarCompression.cpp
|
StarCompression.cpp
|
||||||
StarDataStream.cpp
|
StarDataStream.cpp
|
||||||
StarDataStreamDevices.cpp
|
StarDataStreamDevices.cpp
|
||||||
|
StarDirectives.cpp
|
||||||
StarEncode.cpp
|
StarEncode.cpp
|
||||||
StarFile.cpp
|
StarFile.cpp
|
||||||
StarFont.cpp
|
StarFont.cpp
|
||||||
|
205
source/core/StarAssetPath.cpp
Normal file
205
source/core/StarAssetPath.cpp
Normal file
@ -0,0 +1,205 @@
|
|||||||
|
#include "StarAssetPath.hpp"
|
||||||
|
#include "StarLexicalCast.hpp"
|
||||||
|
|
||||||
|
namespace Star {
|
||||||
|
|
||||||
|
// The filename is everything after the last slash (excluding directives) and
|
||||||
|
// up to the first directive marker.
|
||||||
|
static Maybe<pair<size_t, size_t>> findFilenameRange(std::string const& pathUtf8) {
|
||||||
|
size_t firstDirectiveOrSubPath = pathUtf8.find_first_of(":?");
|
||||||
|
size_t filenameStart = 0;
|
||||||
|
while (true) {
|
||||||
|
size_t find = pathUtf8.find('/', filenameStart);
|
||||||
|
if (find >= firstDirectiveOrSubPath)
|
||||||
|
break;
|
||||||
|
filenameStart = find + 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (filenameStart == NPos) {
|
||||||
|
return {};
|
||||||
|
} else if (firstDirectiveOrSubPath == NPos) {
|
||||||
|
return {{filenameStart, pathUtf8.size()}};
|
||||||
|
} else {
|
||||||
|
return {{filenameStart, firstDirectiveOrSubPath}};
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetPath AssetPath::split(String const& path) {
|
||||||
|
auto i = path.begin();
|
||||||
|
auto end = path.end();
|
||||||
|
|
||||||
|
AssetPath components;
|
||||||
|
|
||||||
|
// base paths cannot have any ':' or '?' characters, stop at the first one.
|
||||||
|
while (i != end) {
|
||||||
|
String::Char c = *i;
|
||||||
|
if (c == ':' || c == '?')
|
||||||
|
break;
|
||||||
|
|
||||||
|
components.basePath += c;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Sub-paths must immediately follow base paths and must start with a ':',
|
||||||
|
// after this point any further ':' characters are not special.
|
||||||
|
if (i != end && *i == ':') {
|
||||||
|
++i;
|
||||||
|
while (i != end) {
|
||||||
|
String::Char c = *i;
|
||||||
|
if (c == '?')
|
||||||
|
break;
|
||||||
|
|
||||||
|
if (!components.subPath)
|
||||||
|
components.subPath.emplace();
|
||||||
|
|
||||||
|
*components.subPath += c;
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
// Directives must follow the base path and optional sub-path, and each
|
||||||
|
// directive is separated by one or more '?' characters.
|
||||||
|
while (i != end && *i == '?') {
|
||||||
|
String directives;
|
||||||
|
while (i != end) {
|
||||||
|
directives.append(*i);
|
||||||
|
++i;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (!directives.empty())
|
||||||
|
components.directives.append(move(directives));
|
||||||
|
}
|
||||||
|
|
||||||
|
starAssert(i == end);
|
||||||
|
|
||||||
|
return components;
|
||||||
|
}
|
||||||
|
|
||||||
|
String AssetPath::join(AssetPath const& components) {
|
||||||
|
return toString(components);
|
||||||
|
}
|
||||||
|
|
||||||
|
String AssetPath::setSubPath(String const& path, String const& subPath) {
|
||||||
|
auto components = split(path);
|
||||||
|
components.subPath = subPath;
|
||||||
|
return join(components);
|
||||||
|
}
|
||||||
|
|
||||||
|
String AssetPath::removeSubPath(String const& path) {
|
||||||
|
auto components = split(path);
|
||||||
|
components.subPath.reset();
|
||||||
|
return join(components);
|
||||||
|
}
|
||||||
|
|
||||||
|
String AssetPath::getDirectives(String const& path) {
|
||||||
|
size_t firstDirective = path.find('?');
|
||||||
|
if (firstDirective == NPos)
|
||||||
|
return {};
|
||||||
|
return path.substr(firstDirective + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String AssetPath::addDirectives(String const& path, String const& directives) {
|
||||||
|
return String::joinWith("?", path, directives);
|
||||||
|
}
|
||||||
|
|
||||||
|
String AssetPath::removeDirectives(String const& path) {
|
||||||
|
size_t firstDirective = path.find('?');
|
||||||
|
if (firstDirective == NPos)
|
||||||
|
return path;
|
||||||
|
return path.substr(0, firstDirective);
|
||||||
|
}
|
||||||
|
|
||||||
|
String AssetPath::directory(String const& path) {
|
||||||
|
if (auto p = findFilenameRange(path.utf8())) {
|
||||||
|
return String(path.utf8().substr(0, p->first));
|
||||||
|
} else {
|
||||||
|
return String();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String AssetPath::filename(String const& path) {
|
||||||
|
if (auto p = findFilenameRange(path.utf8())) {
|
||||||
|
return String(path.utf8().substr(p->first, p->second));
|
||||||
|
} else {
|
||||||
|
return String();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
String AssetPath::extension(String const& path) {
|
||||||
|
auto file = filename(path);
|
||||||
|
auto lastDot = file.findLast(".");
|
||||||
|
if (lastDot == NPos)
|
||||||
|
return "";
|
||||||
|
|
||||||
|
return file.substr(lastDot + 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
String AssetPath::relativeTo(String const& sourcePath, String const& givenPath) {
|
||||||
|
if (!givenPath.empty() && givenPath[0] == '/')
|
||||||
|
return givenPath;
|
||||||
|
|
||||||
|
auto path = directory(sourcePath);
|
||||||
|
path.append(givenPath);
|
||||||
|
return path;
|
||||||
|
}
|
||||||
|
|
||||||
|
bool AssetPath::operator==(AssetPath const& rhs) const {
|
||||||
|
return tie(basePath, subPath, directives) == tie(rhs.basePath, rhs.subPath, rhs.directives);
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetPath::AssetPath(const char* path) {
|
||||||
|
*this = move(AssetPath::split(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
AssetPath::AssetPath(String const& path) {
|
||||||
|
*this = move(AssetPath::split(path));
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetPath::AssetPath(String&& basePath, Maybe<String>&& subPath, DirectivesGroup&& directives) {
|
||||||
|
this->basePath = move(basePath);
|
||||||
|
this->subPath = move(subPath);
|
||||||
|
this->directives = move(directives);
|
||||||
|
}
|
||||||
|
|
||||||
|
AssetPath::AssetPath(String const& basePath, Maybe<String> const& subPath, DirectivesGroup const& directives) {
|
||||||
|
this->basePath = basePath;
|
||||||
|
this->subPath = subPath;
|
||||||
|
this->directives = directives;
|
||||||
|
}
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, AssetPath const& rhs) {
|
||||||
|
os << rhs.basePath;
|
||||||
|
if (rhs.subPath) {
|
||||||
|
os << ":";
|
||||||
|
os << *rhs.subPath;
|
||||||
|
}
|
||||||
|
|
||||||
|
rhs.directives.forEach([&](auto const& entry) {
|
||||||
|
os << "?";
|
||||||
|
os << entry.string;
|
||||||
|
});
|
||||||
|
|
||||||
|
return os;
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t hash<AssetPath>::operator()(AssetPath const& s) const {
|
||||||
|
return hashOf(s.basePath, s.subPath, s.directives);
|
||||||
|
}
|
||||||
|
|
||||||
|
DataStream& operator>>(DataStream& ds, AssetPath& path) {
|
||||||
|
String string;
|
||||||
|
ds.read(string);
|
||||||
|
|
||||||
|
path = move(string);
|
||||||
|
|
||||||
|
return ds;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataStream& operator<<(DataStream& ds, AssetPath const& path) {
|
||||||
|
ds.write(AssetPath::join(path));
|
||||||
|
|
||||||
|
return ds;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
80
source/core/StarAssetPath.hpp
Normal file
80
source/core/StarAssetPath.hpp
Normal file
@ -0,0 +1,80 @@
|
|||||||
|
#ifndef STAR_ASSET_PATH_HPP
|
||||||
|
#define STAR_ASSET_PATH_HPP
|
||||||
|
|
||||||
|
#include "StarDirectives.hpp"
|
||||||
|
#include "StarHash.hpp"
|
||||||
|
#include "StarDataStream.hpp"
|
||||||
|
|
||||||
|
namespace Star {
|
||||||
|
|
||||||
|
// Asset paths are not filesystem paths. '/' is always the directory separator,
|
||||||
|
// and it is not possible to escape any asset source directory. '\' is never a
|
||||||
|
// valid directory separator. All asset paths are considered case-insensitive.
|
||||||
|
//
|
||||||
|
// In addition to the path portion of the asset path, some asset types may also
|
||||||
|
// have a sub-path, which is always separated from the path portion of the asset
|
||||||
|
// by ':'. There can be at most 1 sub-path component.
|
||||||
|
//
|
||||||
|
// Image paths may also have a directives portion of the full asset path, which
|
||||||
|
// must come after the path and optional sub-path comopnent. The directives
|
||||||
|
// portion of the path starts with a '?', and '?' separates each subsquent
|
||||||
|
// directive.
|
||||||
|
struct AssetPath {
|
||||||
|
static AssetPath split(String const& path);
|
||||||
|
static String join(AssetPath const& path);
|
||||||
|
|
||||||
|
// Get / modify sub-path directly on a joined path string
|
||||||
|
static String setSubPath(String const& joinedPath, String const& subPath);
|
||||||
|
static String removeSubPath(String const& joinedPath);
|
||||||
|
|
||||||
|
// Get / modify directives directly on a joined path string
|
||||||
|
static String getDirectives(String const& joinedPath);
|
||||||
|
static String addDirectives(String const& joinedPath, String const& directives);
|
||||||
|
static String removeDirectives(String const& joinedPath);
|
||||||
|
|
||||||
|
// The base directory name for any given path, including the trailing '/'.
|
||||||
|
// Ignores sub-path and directives.
|
||||||
|
static String directory(String const& path);
|
||||||
|
|
||||||
|
// The file part of any given path, ignoring sub-path and directives. Path
|
||||||
|
// must be a file not a directory.
|
||||||
|
static String filename(String const& path);
|
||||||
|
|
||||||
|
// The file extension of a given file path, ignoring directives and
|
||||||
|
// sub-paths.
|
||||||
|
static String extension(String const& path);
|
||||||
|
|
||||||
|
// Computes an absolute asset path from a relative path relative to another
|
||||||
|
// asset. The sourcePath must be an absolute path (may point to a directory
|
||||||
|
// or an asset in a directory, and ignores ':' sub-path or ? directives),
|
||||||
|
// and the givenPath may be either an absolute *or* a relative path. If it
|
||||||
|
// is an absolute path, it is returned unchanged. If it is a relative path,
|
||||||
|
// then it is computed as relative to the directory component of the
|
||||||
|
// sourcePath.
|
||||||
|
static String relativeTo(String const& sourcePath, String const& givenPath);
|
||||||
|
|
||||||
|
AssetPath() = default;
|
||||||
|
AssetPath(const char* path);
|
||||||
|
AssetPath(String const& path);
|
||||||
|
AssetPath(String&& basePath, Maybe<String>&& subPath, DirectivesGroup&& directives);
|
||||||
|
AssetPath(const String& basePath, const Maybe<String>& subPath, const DirectivesGroup& directives);
|
||||||
|
String basePath;
|
||||||
|
Maybe<String> subPath;
|
||||||
|
DirectivesGroup directives;
|
||||||
|
|
||||||
|
bool operator==(AssetPath const& rhs) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
DataStream& operator>>(DataStream& ds, AssetPath& path);
|
||||||
|
DataStream& operator<<(DataStream& ds, AssetPath const& path);
|
||||||
|
|
||||||
|
std::ostream& operator<<(std::ostream& os, AssetPath const& rhs);
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct hash<AssetPath> {
|
||||||
|
size_t operator()(AssetPath const& s) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
241
source/core/StarDirectives.cpp
Normal file
241
source/core/StarDirectives.cpp
Normal file
@ -0,0 +1,241 @@
|
|||||||
|
#include "StarImage.hpp"
|
||||||
|
#include "StarImageProcessing.hpp"
|
||||||
|
#include "StarDirectives.hpp"
|
||||||
|
#include "StarXXHash.hpp"
|
||||||
|
#include "StarLogging.hpp"
|
||||||
|
|
||||||
|
namespace Star {
|
||||||
|
|
||||||
|
Directives::Entry::Entry(ImageOperation&& newOperation, String&& newString) {
|
||||||
|
operation = move(newOperation);
|
||||||
|
string = move(newString);
|
||||||
|
}
|
||||||
|
|
||||||
|
Directives::Entry::Entry(ImageOperation const& newOperation, String const& newString) {
|
||||||
|
operation = newOperation;
|
||||||
|
string = newString;
|
||||||
|
}
|
||||||
|
|
||||||
|
Directives::Entry::Entry(Entry const& other) {
|
||||||
|
operation = other.operation;
|
||||||
|
string = other.string;
|
||||||
|
}
|
||||||
|
|
||||||
|
Directives::Directives() : hash(0) {}
|
||||||
|
Directives::Directives(String const& directives) : hash(0) {
|
||||||
|
string = directives;
|
||||||
|
parse(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
Directives::Directives(String&& directives) : hash(0) {
|
||||||
|
string = move(directives);
|
||||||
|
parse(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
Directives::Directives(const char* directives) : hash(0), string(directives) {
|
||||||
|
parse(string);
|
||||||
|
}
|
||||||
|
|
||||||
|
Directives::Directives(List<Entry>&& newEntries) {
|
||||||
|
entries = std::make_shared<List<Entry> const>(move(newEntries));
|
||||||
|
String newString;
|
||||||
|
string = move(buildString(newString));
|
||||||
|
hash = XXH3_64bits(string.utf8Ptr(), string.utf8Size());
|
||||||
|
}
|
||||||
|
|
||||||
|
void Directives::parse(String const& directives) {
|
||||||
|
if (directives.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
List<Entry> newList;
|
||||||
|
StringList split = directives.split('?');
|
||||||
|
newList.reserve(split.size());
|
||||||
|
for (String& str : split) {
|
||||||
|
if (!str.empty()) {
|
||||||
|
try {
|
||||||
|
ImageOperation operation = imageOperationFromString(str);
|
||||||
|
newList.emplace_back(move(operation), move(str));
|
||||||
|
} catch (StarException const& e) {
|
||||||
|
Logger::logf(LogLevel::Error, "Error parsing image operation: %s", e.what());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (newList.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
entries = std::make_shared<List<Entry> const>(move(newList));
|
||||||
|
hash = XXH3_64bits(directives.utf8Ptr(), directives.utf8Size());
|
||||||
|
//if (directives.utf8Size() > 1000)
|
||||||
|
// Logger::logf(LogLevel::Debug, "Directives: Parsed %u character long string", directives.utf8Size());
|
||||||
|
}
|
||||||
|
|
||||||
|
String& Directives::buildString(String& out) const {
|
||||||
|
if (entries) {
|
||||||
|
for (auto& entry : *entries) {
|
||||||
|
out += "?";
|
||||||
|
out += entry.string;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return out;
|
||||||
|
}
|
||||||
|
|
||||||
|
String Directives::toString() const {
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool Directives::empty() const {
|
||||||
|
return !entries || entries->empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Directives::operator bool() const {
|
||||||
|
return !empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
DataStream& operator>>(DataStream& ds, Directives& directives) {
|
||||||
|
String string;
|
||||||
|
ds.read(string);
|
||||||
|
|
||||||
|
directives.parse(string);
|
||||||
|
|
||||||
|
return ds;
|
||||||
|
}
|
||||||
|
|
||||||
|
DataStream& operator<<(DataStream& ds, Directives const& directives) {
|
||||||
|
ds.write(directives.toString());
|
||||||
|
|
||||||
|
return ds;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectivesGroup::DirectivesGroup() : m_count(0) {}
|
||||||
|
DirectivesGroup::DirectivesGroup(String const& directives) : m_count(0) {
|
||||||
|
if (directives.empty())
|
||||||
|
return;
|
||||||
|
|
||||||
|
Directives parsed(directives);
|
||||||
|
if (parsed) {
|
||||||
|
m_directives.emplace_back(move(parsed));
|
||||||
|
m_count = m_directives.back().entries->size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
DirectivesGroup::DirectivesGroup(String&& directives) : m_count(0) {
|
||||||
|
if (directives.empty()) {
|
||||||
|
directives.clear();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
Directives parsed(move(directives));
|
||||||
|
if (parsed) {
|
||||||
|
m_directives.emplace_back(move(parsed));
|
||||||
|
m_count = m_directives.back().entries->size();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool DirectivesGroup::empty() const {
|
||||||
|
return m_count == 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline DirectivesGroup::operator bool() const {
|
||||||
|
return empty();
|
||||||
|
}
|
||||||
|
|
||||||
|
inline bool DirectivesGroup::compare(DirectivesGroup const& other) const {
|
||||||
|
if (m_count != other.m_count)
|
||||||
|
return false;
|
||||||
|
|
||||||
|
if (empty())
|
||||||
|
return true;
|
||||||
|
|
||||||
|
return hash() == other.hash();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirectivesGroup::append(Directives const& directives) {
|
||||||
|
m_directives.emplace_back(directives);
|
||||||
|
if (directives.entries)
|
||||||
|
m_count += m_directives.back().entries->size();
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirectivesGroup::append(List<Directives::Entry>&& entries) {
|
||||||
|
size_t size = entries.size();
|
||||||
|
m_directives.emplace_back(move(entries));
|
||||||
|
m_count += size;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirectivesGroup::clear() {
|
||||||
|
m_directives.clear();
|
||||||
|
m_count = 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
DirectivesGroup& DirectivesGroup::operator+=(Directives const& other) {
|
||||||
|
append(other);
|
||||||
|
return *this;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline String DirectivesGroup::toString() const {
|
||||||
|
String string;
|
||||||
|
addToString(string);
|
||||||
|
return string;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirectivesGroup::addToString(String& string) const {
|
||||||
|
for (auto& directives : m_directives)
|
||||||
|
string += directives.string;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirectivesGroup::forEach(DirectivesCallback callback) const {
|
||||||
|
for (auto& directives : m_directives) {
|
||||||
|
if (directives.entries) {
|
||||||
|
for (auto& entry : *directives.entries)
|
||||||
|
callback(entry);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
bool DirectivesGroup::forEachAbortable(AbortableDirectivesCallback callback) const {
|
||||||
|
for (auto& directives : m_directives) {
|
||||||
|
if (directives.entries) {
|
||||||
|
for (auto& entry : *directives.entries) {
|
||||||
|
if (!callback(entry))
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
inline Image DirectivesGroup::applyNewImage(Image const& image) const {
|
||||||
|
Image result = image;
|
||||||
|
applyExistingImage(result);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
void DirectivesGroup::applyExistingImage(Image& image) const {
|
||||||
|
forEach([&](auto const& entry) {
|
||||||
|
processImageOperation(entry.operation, image);
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
inline size_t DirectivesGroup::hash() const {
|
||||||
|
XXHash3 hasher;
|
||||||
|
for (auto& directives : m_directives)
|
||||||
|
hasher.push((const char*)&directives.hash, sizeof(directives.hash));
|
||||||
|
|
||||||
|
return hasher.digest();
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator==(DirectivesGroup const& a, DirectivesGroup const& b) {
|
||||||
|
return a.compare(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
bool operator!=(DirectivesGroup const& a, DirectivesGroup const& b) {
|
||||||
|
return !a.compare(b);
|
||||||
|
}
|
||||||
|
|
||||||
|
size_t hash<DirectivesGroup>::operator()(DirectivesGroup const& s) const {
|
||||||
|
return s.hash();
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
96
source/core/StarDirectives.hpp
Normal file
96
source/core/StarDirectives.hpp
Normal file
@ -0,0 +1,96 @@
|
|||||||
|
#ifndef STAR_DIRECTIVES_HPP
|
||||||
|
#define STAR_DIRECTIVES_HPP
|
||||||
|
|
||||||
|
#include "StarImageProcessing.hpp"
|
||||||
|
#include "StarHash.hpp"
|
||||||
|
#include "StarDataStream.hpp"
|
||||||
|
|
||||||
|
namespace Star {
|
||||||
|
|
||||||
|
STAR_CLASS(Directives);
|
||||||
|
STAR_CLASS(DirectivesGroup);
|
||||||
|
STAR_EXCEPTION(DirectivesException, StarException);
|
||||||
|
|
||||||
|
// Kae: My attempt at reducing memory allocation and per-frame string parsing for extremely long directives
|
||||||
|
class Directives {
|
||||||
|
public:
|
||||||
|
struct Entry {
|
||||||
|
ImageOperation operation;
|
||||||
|
String string; // One day, we can make this a string_view pointing to Entry::string.
|
||||||
|
|
||||||
|
Entry(ImageOperation&& newOperation, String&& newString);
|
||||||
|
Entry(ImageOperation const& newOperation, String const& newString);
|
||||||
|
Entry(Entry const& other);
|
||||||
|
};
|
||||||
|
|
||||||
|
Directives();
|
||||||
|
Directives(String const& directives);
|
||||||
|
Directives(String&& directives);
|
||||||
|
Directives(const char* directives);
|
||||||
|
Directives(List<Entry>&& entries);
|
||||||
|
|
||||||
|
void parse(String const& directives);
|
||||||
|
String& buildString(String& out) const;
|
||||||
|
String toString() const;
|
||||||
|
inline bool empty() const;
|
||||||
|
inline operator bool() const;
|
||||||
|
|
||||||
|
friend DataStream& operator>>(DataStream& ds, Directives& directives);
|
||||||
|
friend DataStream& operator<<(DataStream& ds, Directives const& directives);
|
||||||
|
|
||||||
|
std::shared_ptr<List<Entry> const> entries;
|
||||||
|
size_t hash = 0;
|
||||||
|
String string;
|
||||||
|
};
|
||||||
|
|
||||||
|
class DirectivesGroup {
|
||||||
|
public:
|
||||||
|
DirectivesGroup();
|
||||||
|
DirectivesGroup(String const& directives);
|
||||||
|
DirectivesGroup(String&& directives);
|
||||||
|
|
||||||
|
void parseDirectivesIntoLeaf(String const& directives);
|
||||||
|
|
||||||
|
inline bool empty() const;
|
||||||
|
inline operator bool() const;
|
||||||
|
bool compare(DirectivesGroup const& other) const;
|
||||||
|
void append(Directives const& other);
|
||||||
|
void append(List<Directives::Entry>&& entries);
|
||||||
|
void clear();
|
||||||
|
|
||||||
|
DirectivesGroup& operator+=(Directives const& other);
|
||||||
|
|
||||||
|
inline String toString() const;
|
||||||
|
void addToString(String& string) const;
|
||||||
|
|
||||||
|
typedef function<void(Directives::Entry const&)> DirectivesCallback;
|
||||||
|
typedef function<bool(Directives::Entry const&)> AbortableDirectivesCallback;
|
||||||
|
|
||||||
|
void forEach(DirectivesCallback callback) const;
|
||||||
|
bool forEachAbortable(AbortableDirectivesCallback callback) const;
|
||||||
|
|
||||||
|
inline Image applyNewImage(const Image& image) const;
|
||||||
|
void applyExistingImage(Image& image) const;
|
||||||
|
|
||||||
|
inline size_t hash() const;
|
||||||
|
|
||||||
|
friend bool operator==(DirectivesGroup const& a, DirectivesGroup const& b);
|
||||||
|
friend bool operator!=(DirectivesGroup const& a, DirectivesGroup const& b);
|
||||||
|
private:
|
||||||
|
void buildString(String& string, const DirectivesGroup& directives) const;
|
||||||
|
|
||||||
|
List<Directives> m_directives;
|
||||||
|
size_t m_count;
|
||||||
|
};
|
||||||
|
|
||||||
|
|
||||||
|
template <>
|
||||||
|
struct hash<DirectivesGroup> {
|
||||||
|
size_t operator()(DirectivesGroup const& s) const;
|
||||||
|
};
|
||||||
|
|
||||||
|
typedef DirectivesGroup ImageDirectives;
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
#endif
|
@ -328,12 +328,23 @@ String imageOperationToString(ImageOperation const& operation) {
|
|||||||
return "";
|
return "";
|
||||||
}
|
}
|
||||||
|
|
||||||
List<ImageOperation> parseImageOperations(String const& params) {
|
void parseImageOperations(String const& params, function<void(ImageOperation&&)> outputter) {
|
||||||
List<ImageOperation> operations;
|
|
||||||
for (auto const& op : params.split('?')) {
|
for (auto const& op : params.split('?')) {
|
||||||
|
if (!op.empty())
|
||||||
|
outputter(imageOperationFromString(op));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
List<ImageOperation> parseImageOperations(String const& params) {
|
||||||
|
auto split = params.split('?');
|
||||||
|
List<ImageOperation> operations;
|
||||||
|
operations.reserve(split.size());
|
||||||
|
|
||||||
|
for (auto const& op : split) {
|
||||||
if (!op.empty())
|
if (!op.empty())
|
||||||
operations.append(imageOperationFromString(op));
|
operations.append(imageOperationFromString(op));
|
||||||
}
|
}
|
||||||
|
|
||||||
return operations;
|
return operations;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -341,227 +352,233 @@ String printImageOperations(List<ImageOperation> const& list) {
|
|||||||
return StringList(list.transformed(imageOperationToString)).join("?");
|
return StringList(list.transformed(imageOperationToString)).join("?");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
void addImageOperationReferences(ImageOperation const& operation, StringList& out) {
|
||||||
|
if (auto op = operation.ptr<AlphaMaskImageOperation>())
|
||||||
|
out.appendAll(op->maskImages);
|
||||||
|
else if (auto op = operation.ptr<BlendImageOperation>())
|
||||||
|
out.appendAll(op->blendImages);
|
||||||
|
}
|
||||||
|
|
||||||
StringList imageOperationReferences(List<ImageOperation> const& operations) {
|
StringList imageOperationReferences(List<ImageOperation> const& operations) {
|
||||||
StringList references;
|
StringList references;
|
||||||
for (auto const& operation : operations) {
|
for (auto const& operation : operations)
|
||||||
if (auto op = operation.ptr<AlphaMaskImageOperation>())
|
addImageOperationReferences(operation, references);
|
||||||
references.appendAll(op->maskImages);
|
|
||||||
else if (auto op = operation.ptr<BlendImageOperation>())
|
|
||||||
references.appendAll(op->blendImages);
|
|
||||||
}
|
|
||||||
return references;
|
return references;
|
||||||
}
|
}
|
||||||
|
|
||||||
Image processImageOperations(List<ImageOperation> const& operations, Image image, ImageReferenceCallback refCallback) {
|
void processImageOperation(ImageOperation const& operation, Image& image, ImageReferenceCallback refCallback) {
|
||||||
for (auto const& operation : operations) {
|
if (auto op = operation.ptr<HueShiftImageOperation>()) {
|
||||||
if (auto op = operation.ptr<HueShiftImageOperation>()) {
|
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
||||||
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
if (pixel[3] != 0)
|
||||||
if (pixel[3] != 0)
|
pixel = Color::hueShiftVec4B(pixel, op->hueShiftAmount);
|
||||||
pixel = Color::hueShiftVec4B(pixel, op->hueShiftAmount);
|
});
|
||||||
});
|
} else if (auto op = operation.ptr<SaturationShiftImageOperation>()) {
|
||||||
} else if (auto op = operation.ptr<SaturationShiftImageOperation>()) {
|
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
||||||
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
if (pixel[3] != 0) {
|
||||||
if (pixel[3] != 0) {
|
Color color = Color::rgba(pixel);
|
||||||
Color color = Color::rgba(pixel);
|
color.setSaturation(clamp(color.saturation() + op->saturationShiftAmount, 0.0f, 1.0f));
|
||||||
color.setSaturation(clamp(color.saturation() + op->saturationShiftAmount, 0.0f, 1.0f));
|
pixel = color.toRgba();
|
||||||
pixel = color.toRgba();
|
}
|
||||||
}
|
});
|
||||||
});
|
} else if (auto op = operation.ptr<BrightnessMultiplyImageOperation>()) {
|
||||||
} else if (auto op = operation.ptr<BrightnessMultiplyImageOperation>()) {
|
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
||||||
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
if (pixel[3] != 0) {
|
||||||
if (pixel[3] != 0) {
|
Color color = Color::rgba(pixel);
|
||||||
Color color = Color::rgba(pixel);
|
color.setValue(clamp(color.value() * op->brightnessMultiply, 0.0f, 1.0f));
|
||||||
color.setValue(clamp(color.value() * op->brightnessMultiply, 0.0f, 1.0f));
|
pixel = color.toRgba();
|
||||||
pixel = color.toRgba();
|
}
|
||||||
}
|
});
|
||||||
});
|
} else if (auto op = operation.ptr<FadeToColorImageOperation>()) {
|
||||||
} else if (auto op = operation.ptr<FadeToColorImageOperation>()) {
|
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
||||||
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
pixel[0] = op->rTable[pixel[0]];
|
||||||
pixel[0] = op->rTable[pixel[0]];
|
pixel[1] = op->gTable[pixel[1]];
|
||||||
pixel[1] = op->gTable[pixel[1]];
|
pixel[2] = op->bTable[pixel[2]];
|
||||||
pixel[2] = op->bTable[pixel[2]];
|
});
|
||||||
});
|
} else if (auto op = operation.ptr<ScanLinesImageOperation>()) {
|
||||||
} else if (auto op = operation.ptr<ScanLinesImageOperation>()) {
|
image.forEachPixel([&op](unsigned, unsigned y, Vec4B& pixel) {
|
||||||
image.forEachPixel([&op](unsigned, unsigned y, Vec4B& pixel) {
|
if (y % 2 == 0) {
|
||||||
if (y % 2 == 0) {
|
pixel[0] = op->fade1.rTable[pixel[0]];
|
||||||
pixel[0] = op->fade1.rTable[pixel[0]];
|
pixel[1] = op->fade1.gTable[pixel[1]];
|
||||||
pixel[1] = op->fade1.gTable[pixel[1]];
|
pixel[2] = op->fade1.bTable[pixel[2]];
|
||||||
pixel[2] = op->fade1.bTable[pixel[2]];
|
} else {
|
||||||
} else {
|
pixel[0] = op->fade2.rTable[pixel[0]];
|
||||||
pixel[0] = op->fade2.rTable[pixel[0]];
|
pixel[1] = op->fade2.gTable[pixel[1]];
|
||||||
pixel[1] = op->fade2.gTable[pixel[1]];
|
pixel[2] = op->fade2.bTable[pixel[2]];
|
||||||
pixel[2] = op->fade2.bTable[pixel[2]];
|
}
|
||||||
}
|
});
|
||||||
});
|
} else if (auto op = operation.ptr<SetColorImageOperation>()) {
|
||||||
} else if (auto op = operation.ptr<SetColorImageOperation>()) {
|
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
||||||
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
pixel[0] = op->color[0];
|
||||||
pixel[0] = op->color[0];
|
pixel[1] = op->color[1];
|
||||||
pixel[1] = op->color[1];
|
pixel[2] = op->color[2];
|
||||||
pixel[2] = op->color[2];
|
});
|
||||||
});
|
} else if (auto op = operation.ptr<ColorReplaceImageOperation>()) {
|
||||||
} else if (auto op = operation.ptr<ColorReplaceImageOperation>()) {
|
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
||||||
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
if (auto m = op->colorReplaceMap.maybe(pixel))
|
||||||
if (auto m = op->colorReplaceMap.maybe(pixel))
|
pixel = *m;
|
||||||
pixel = *m;
|
});
|
||||||
});
|
|
||||||
|
|
||||||
} else if (auto op = operation.ptr<AlphaMaskImageOperation>()) {
|
} else if (auto op = operation.ptr<AlphaMaskImageOperation>()) {
|
||||||
if (op->maskImages.empty())
|
if (op->maskImages.empty())
|
||||||
continue;
|
return;
|
||||||
|
|
||||||
if (!refCallback)
|
if (!refCallback)
|
||||||
throw StarException("Missing image ref callback during AlphaMaskImageOperation in ImageProcessor::process");
|
throw StarException("Missing image ref callback during AlphaMaskImageOperation in ImageProcessor::process");
|
||||||
|
|
||||||
List<Image const*> maskImages;
|
List<Image const*> maskImages;
|
||||||
for (auto const& reference : op->maskImages)
|
for (auto const& reference : op->maskImages)
|
||||||
maskImages.append(refCallback(reference));
|
maskImages.append(refCallback(reference));
|
||||||
|
|
||||||
image.forEachPixel([&op, &maskImages](unsigned x, unsigned y, Vec4B& pixel) {
|
image.forEachPixel([&op, &maskImages](unsigned x, unsigned y, Vec4B& pixel) {
|
||||||
uint8_t maskAlpha = 0;
|
uint8_t maskAlpha = 0;
|
||||||
Vec2U pos = Vec2U(Vec2I(x, y) + op->offset);
|
Vec2U pos = Vec2U(Vec2I(x, y) + op->offset);
|
||||||
for (auto mask : maskImages) {
|
for (auto mask : maskImages) {
|
||||||
if (pos[0] < mask->width() && pos[1] < mask->height()) {
|
if (pos[0] < mask->width() && pos[1] < mask->height()) {
|
||||||
if (op->mode == AlphaMaskImageOperation::Additive) {
|
if (op->mode == AlphaMaskImageOperation::Additive) {
|
||||||
// We produce our mask alpha from the maximum alpha of any of
|
// We produce our mask alpha from the maximum alpha of any of
|
||||||
// the
|
// the
|
||||||
// mask images.
|
// mask images.
|
||||||
maskAlpha = std::max(maskAlpha, mask->get(pos)[3]);
|
maskAlpha = std::max(maskAlpha, mask->get(pos)[3]);
|
||||||
} else if (op->mode == AlphaMaskImageOperation::Subtractive) {
|
} else if (op->mode == AlphaMaskImageOperation::Subtractive) {
|
||||||
// We produce our mask alpha from the minimum alpha of any of
|
// We produce our mask alpha from the minimum alpha of any of
|
||||||
// the
|
// the
|
||||||
// mask images.
|
// mask images.
|
||||||
maskAlpha = std::min(maskAlpha, mask->get(pos)[3]);
|
maskAlpha = std::min(maskAlpha, mask->get(pos)[3]);
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pixel[3] = std::min(pixel[3], maskAlpha);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else if (auto op = operation.ptr<BlendImageOperation>()) {
|
|
||||||
if (op->blendImages.empty())
|
|
||||||
continue;
|
|
||||||
|
|
||||||
if (!refCallback)
|
|
||||||
throw StarException("Missing image ref callback during BlendImageOperation in ImageProcessor::process");
|
|
||||||
|
|
||||||
List<Image const*> blendImages;
|
|
||||||
for (auto const& reference : op->blendImages)
|
|
||||||
blendImages.append(refCallback(reference));
|
|
||||||
|
|
||||||
image.forEachPixel([&op, &blendImages](unsigned x, unsigned y, Vec4B& pixel) {
|
|
||||||
Vec2U pos = Vec2U(Vec2I(x, y) + op->offset);
|
|
||||||
Vec4F fpixel = Color::v4bToFloat(pixel);
|
|
||||||
for (auto blend : blendImages) {
|
|
||||||
if (pos[0] < blend->width() && pos[1] < blend->height()) {
|
|
||||||
Vec4F blendPixel = Color::v4bToFloat(blend->get(pos));
|
|
||||||
if (op->mode == BlendImageOperation::Multiply)
|
|
||||||
fpixel = fpixel.piecewiseMultiply(blendPixel);
|
|
||||||
else if (op->mode == BlendImageOperation::Screen)
|
|
||||||
fpixel = Vec4F::filled(1.0f) - (Vec4F::filled(1.0f) - fpixel).piecewiseMultiply(Vec4F::filled(1.0f) - blendPixel);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pixel = Color::v4fToByte(fpixel);
|
|
||||||
});
|
|
||||||
|
|
||||||
} else if (auto op = operation.ptr<MultiplyImageOperation>()) {
|
|
||||||
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
|
||||||
pixel = pixel.combine(op->color, [](uint8_t a, uint8_t b) -> uint8_t {
|
|
||||||
return (uint8_t)(((int)a * (int)b) / 255);
|
|
||||||
});
|
|
||||||
});
|
|
||||||
|
|
||||||
} else if (auto op = operation.ptr<BorderImageOperation>()) {
|
|
||||||
Image borderImage(image.size() + Vec2U::filled(op->pixels * 2), PixelFormat::RGBA32);
|
|
||||||
borderImage.copyInto(Vec2U::filled(op->pixels), image);
|
|
||||||
Vec2I borderImageSize = Vec2I(borderImage.size());
|
|
||||||
|
|
||||||
borderImage.forEachPixel([&op, &image, &borderImageSize](int x, int y, Vec4B& pixel) {
|
|
||||||
int pixels = op->pixels;
|
|
||||||
bool includeTransparent = op->includeTransparent;
|
|
||||||
if (pixel[3] == 0 || (includeTransparent && pixel[3] != 255)) {
|
|
||||||
int dist = std::numeric_limits<int>::max();
|
|
||||||
for (int j = -pixels; j < pixels + 1; j++) {
|
|
||||||
for (int i = -pixels; i < pixels + 1; i++) {
|
|
||||||
if (i + x >= pixels && j + y >= pixels && i + x < borderImageSize[0] - pixels && j + y < borderImageSize[1] - pixels) {
|
|
||||||
Vec4B remotePixel = image.get(i + x - pixels, j + y - pixels);
|
|
||||||
if (remotePixel[3] != 0) {
|
|
||||||
dist = std::min(dist, abs(i) + abs(j));
|
|
||||||
if (dist == 1) // Early out, if dist is 1 it ain't getting shorter
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (dist < std::numeric_limits<int>::max()) {
|
|
||||||
float percent = (dist - 1) / (2.0f * pixels - 1);
|
|
||||||
Color color = Color::rgba(op->startColor).mix(Color::rgba(op->endColor), percent);
|
|
||||||
if (pixel[3] != 0) {
|
|
||||||
if (op->outlineOnly) {
|
|
||||||
float pixelA = byteToFloat(pixel[3]);
|
|
||||||
color.setAlphaF((1.0f - pixelA) * fminf(pixelA, 0.5f) * 2.0f);
|
|
||||||
}
|
|
||||||
else {
|
|
||||||
Color pixelF = Color::rgba(pixel);
|
|
||||||
float pixelA = pixelF.alphaF(), colorA = color.alphaF();
|
|
||||||
colorA += pixelA * (1.0f - colorA);
|
|
||||||
pixelF.convertToLinear(); //Mix in linear color space as it is more perceptually accurate
|
|
||||||
color.convertToLinear();
|
|
||||||
color = color.mix(pixelF, pixelA);
|
|
||||||
color.convertToSRGB();
|
|
||||||
color.setAlphaF(colorA);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
pixel = color.toRgba();
|
|
||||||
}
|
|
||||||
} else if (op->outlineOnly) {
|
|
||||||
pixel = Vec4B(0, 0, 0, 0);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
image = borderImage;
|
|
||||||
|
|
||||||
} else if (auto op = operation.ptr<ScaleImageOperation>()) {
|
|
||||||
if (op->mode == ScaleImageOperation::Nearest)
|
|
||||||
image = scaleNearest(image, op->scale);
|
|
||||||
else if (op->mode == ScaleImageOperation::Bilinear)
|
|
||||||
image = scaleBilinear(image, op->scale);
|
|
||||||
else if (op->mode == ScaleImageOperation::Bicubic)
|
|
||||||
image = scaleBicubic(image, op->scale);
|
|
||||||
|
|
||||||
} else if (auto op = operation.ptr<CropImageOperation>()) {
|
|
||||||
image = image.subImage(Vec2U(op->subset.min()), Vec2U(op->subset.size()));
|
|
||||||
|
|
||||||
} else if (auto op = operation.ptr<FlipImageOperation>()) {
|
|
||||||
if (op->mode == FlipImageOperation::FlipX || op->mode == FlipImageOperation::FlipXY) {
|
|
||||||
for (size_t y = 0; y < image.height(); ++y) {
|
|
||||||
for (size_t xLeft = 0; xLeft < image.width() / 2; ++xLeft) {
|
|
||||||
size_t xRight = image.width() - 1 - xLeft;
|
|
||||||
|
|
||||||
auto left = image.get(xLeft, y);
|
|
||||||
auto right = image.get(xRight, y);
|
|
||||||
|
|
||||||
image.set(xLeft, y, right);
|
|
||||||
image.set(xRight, y, left);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
pixel[3] = std::min(pixel[3], maskAlpha);
|
||||||
|
});
|
||||||
|
|
||||||
if (op->mode == FlipImageOperation::FlipY || op->mode == FlipImageOperation::FlipXY) {
|
} else if (auto op = operation.ptr<BlendImageOperation>()) {
|
||||||
for (size_t x = 0; x < image.width(); ++x) {
|
if (op->blendImages.empty())
|
||||||
for (size_t yTop = 0; yTop < image.height() / 2; ++yTop) {
|
return;
|
||||||
size_t yBottom = image.height() - 1 - yTop;
|
|
||||||
|
|
||||||
auto top = image.get(x, yTop);
|
if (!refCallback)
|
||||||
auto bottom = image.get(x, yBottom);
|
throw StarException("Missing image ref callback during BlendImageOperation in ImageProcessor::process");
|
||||||
|
|
||||||
image.set(x, yTop, bottom);
|
List<Image const*> blendImages;
|
||||||
image.set(x, yBottom, top);
|
for (auto const& reference : op->blendImages)
|
||||||
|
blendImages.append(refCallback(reference));
|
||||||
|
|
||||||
|
image.forEachPixel([&op, &blendImages](unsigned x, unsigned y, Vec4B& pixel) {
|
||||||
|
Vec2U pos = Vec2U(Vec2I(x, y) + op->offset);
|
||||||
|
Vec4F fpixel = Color::v4bToFloat(pixel);
|
||||||
|
for (auto blend : blendImages) {
|
||||||
|
if (pos[0] < blend->width() && pos[1] < blend->height()) {
|
||||||
|
Vec4F blendPixel = Color::v4bToFloat(blend->get(pos));
|
||||||
|
if (op->mode == BlendImageOperation::Multiply)
|
||||||
|
fpixel = fpixel.piecewiseMultiply(blendPixel);
|
||||||
|
else if (op->mode == BlendImageOperation::Screen)
|
||||||
|
fpixel = Vec4F::filled(1.0f) - (Vec4F::filled(1.0f) - fpixel).piecewiseMultiply(Vec4F::filled(1.0f) - blendPixel);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pixel = Color::v4fToByte(fpixel);
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (auto op = operation.ptr<MultiplyImageOperation>()) {
|
||||||
|
image.forEachPixel([&op](unsigned, unsigned, Vec4B& pixel) {
|
||||||
|
pixel = pixel.combine(op->color, [](uint8_t a, uint8_t b) -> uint8_t {
|
||||||
|
return (uint8_t)(((int)a * (int)b) / 255);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
|
||||||
|
} else if (auto op = operation.ptr<BorderImageOperation>()) {
|
||||||
|
Image borderImage(image.size() + Vec2U::filled(op->pixels * 2), PixelFormat::RGBA32);
|
||||||
|
borderImage.copyInto(Vec2U::filled(op->pixels), image);
|
||||||
|
Vec2I borderImageSize = Vec2I(borderImage.size());
|
||||||
|
|
||||||
|
borderImage.forEachPixel([&op, &image, &borderImageSize](int x, int y, Vec4B& pixel) {
|
||||||
|
int pixels = op->pixels;
|
||||||
|
bool includeTransparent = op->includeTransparent;
|
||||||
|
if (pixel[3] == 0 || (includeTransparent && pixel[3] != 255)) {
|
||||||
|
int dist = std::numeric_limits<int>::max();
|
||||||
|
for (int j = -pixels; j < pixels + 1; j++) {
|
||||||
|
for (int i = -pixels; i < pixels + 1; i++) {
|
||||||
|
if (i + x >= pixels && j + y >= pixels && i + x < borderImageSize[0] - pixels && j + y < borderImageSize[1] - pixels) {
|
||||||
|
Vec4B remotePixel = image.get(i + x - pixels, j + y - pixels);
|
||||||
|
if (remotePixel[3] != 0) {
|
||||||
|
dist = std::min(dist, abs(i) + abs(j));
|
||||||
|
if (dist == 1) // Early out, if dist is 1 it ain't getting shorter
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dist < std::numeric_limits<int>::max()) {
|
||||||
|
float percent = (dist - 1) / (2.0f * pixels - 1);
|
||||||
|
Color color = Color::rgba(op->startColor).mix(Color::rgba(op->endColor), percent);
|
||||||
|
if (pixel[3] != 0) {
|
||||||
|
if (op->outlineOnly) {
|
||||||
|
float pixelA = byteToFloat(pixel[3]);
|
||||||
|
color.setAlphaF((1.0f - pixelA) * fminf(pixelA, 0.5f) * 2.0f);
|
||||||
|
}
|
||||||
|
else {
|
||||||
|
Color pixelF = Color::rgba(pixel);
|
||||||
|
float pixelA = pixelF.alphaF(), colorA = color.alphaF();
|
||||||
|
colorA += pixelA * (1.0f - colorA);
|
||||||
|
pixelF.convertToLinear(); //Mix in linear color space as it is more perceptually accurate
|
||||||
|
color.convertToLinear();
|
||||||
|
color = color.mix(pixelF, pixelA);
|
||||||
|
color.convertToSRGB();
|
||||||
|
color.setAlphaF(colorA);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
pixel = color.toRgba();
|
||||||
|
}
|
||||||
|
} else if (op->outlineOnly) {
|
||||||
|
pixel = Vec4B(0, 0, 0, 0);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
image = borderImage;
|
||||||
|
|
||||||
|
} else if (auto op = operation.ptr<ScaleImageOperation>()) {
|
||||||
|
if (op->mode == ScaleImageOperation::Nearest)
|
||||||
|
image = scaleNearest(image, op->scale);
|
||||||
|
else if (op->mode == ScaleImageOperation::Bilinear)
|
||||||
|
image = scaleBilinear(image, op->scale);
|
||||||
|
else if (op->mode == ScaleImageOperation::Bicubic)
|
||||||
|
image = scaleBicubic(image, op->scale);
|
||||||
|
|
||||||
|
} else if (auto op = operation.ptr<CropImageOperation>()) {
|
||||||
|
image = image.subImage(Vec2U(op->subset.min()), Vec2U(op->subset.size()));
|
||||||
|
|
||||||
|
} else if (auto op = operation.ptr<FlipImageOperation>()) {
|
||||||
|
if (op->mode == FlipImageOperation::FlipX || op->mode == FlipImageOperation::FlipXY) {
|
||||||
|
for (size_t y = 0; y < image.height(); ++y) {
|
||||||
|
for (size_t xLeft = 0; xLeft < image.width() / 2; ++xLeft) {
|
||||||
|
size_t xRight = image.width() - 1 - xLeft;
|
||||||
|
|
||||||
|
auto left = image.get(xLeft, y);
|
||||||
|
auto right = image.get(xRight, y);
|
||||||
|
|
||||||
|
image.set(xLeft, y, right);
|
||||||
|
image.set(xRight, y, left);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
if (op->mode == FlipImageOperation::FlipY || op->mode == FlipImageOperation::FlipXY) {
|
||||||
|
for (size_t x = 0; x < image.width(); ++x) {
|
||||||
|
for (size_t yTop = 0; yTop < image.height() / 2; ++yTop) {
|
||||||
|
size_t yBottom = image.height() - 1 - yTop;
|
||||||
|
|
||||||
|
auto top = image.get(x, yTop);
|
||||||
|
auto bottom = image.get(x, yBottom);
|
||||||
|
|
||||||
|
image.set(x, yTop, bottom);
|
||||||
|
image.set(x, yBottom, top);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
Image processImageOperations(List<ImageOperation> const& operations, Image image, ImageReferenceCallback refCallback) {
|
||||||
|
for (auto const& operation : operations)
|
||||||
|
processImageOperation(operation, image, refCallback);
|
||||||
|
|
||||||
return image;
|
return image;
|
||||||
}
|
}
|
||||||
|
@ -136,6 +136,8 @@ typedef Variant<HueShiftImageOperation, SaturationShiftImageOperation, Brightnes
|
|||||||
ImageOperation imageOperationFromString(String const& string);
|
ImageOperation imageOperationFromString(String const& string);
|
||||||
String imageOperationToString(ImageOperation const& operation);
|
String imageOperationToString(ImageOperation const& operation);
|
||||||
|
|
||||||
|
void parseImageOperations(String const& params, function<void(ImageOperation&&)> outputter);
|
||||||
|
|
||||||
// Each operation is assumed to be separated by '?', with parameters
|
// Each operation is assumed to be separated by '?', with parameters
|
||||||
// separated by ';' or '='
|
// separated by ';' or '='
|
||||||
List<ImageOperation> parseImageOperations(String const& params);
|
List<ImageOperation> parseImageOperations(String const& params);
|
||||||
@ -143,10 +145,14 @@ List<ImageOperation> parseImageOperations(String const& params);
|
|||||||
// Each operation separated by '?', returns string with leading '?'
|
// Each operation separated by '?', returns string with leading '?'
|
||||||
String printImageOperations(List<ImageOperation> const& operations);
|
String printImageOperations(List<ImageOperation> const& operations);
|
||||||
|
|
||||||
|
void addImageOperationReferences(ImageOperation const& operation, StringList& out);
|
||||||
|
|
||||||
StringList imageOperationReferences(List<ImageOperation> const& operations);
|
StringList imageOperationReferences(List<ImageOperation> const& operations);
|
||||||
|
|
||||||
typedef function<Image const*(String const& refName)> ImageReferenceCallback;
|
typedef function<Image const*(String const& refName)> ImageReferenceCallback;
|
||||||
|
|
||||||
|
void processImageOperation(ImageOperation const& operation, Image& input, ImageReferenceCallback refCallback = {});
|
||||||
|
|
||||||
Image processImageOperations(List<ImageOperation> const& operations, Image input, ImageReferenceCallback refCallback = {});
|
Image processImageOperations(List<ImageOperation> const& operations, Image input, ImageReferenceCallback refCallback = {});
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -47,7 +47,7 @@ Type lexicalCast(std::string const& s, std::ios_base::fmtflags flags = std::ios_
|
|||||||
if (m)
|
if (m)
|
||||||
return m.take();
|
return m.take();
|
||||||
else
|
else
|
||||||
throw BadLexicalCast();
|
throw BadLexicalCast(strf("Lexical cast failed on '%s'", s));
|
||||||
}
|
}
|
||||||
|
|
||||||
template <typename Type>
|
template <typename Type>
|
||||||
|
@ -965,8 +965,8 @@ void MainInterface::renderBreath() {
|
|||||||
size_t blocks = round((10 * breath) / breathMax);
|
size_t blocks = round((10 * breath) / breathMax);
|
||||||
|
|
||||||
if (blocks < 10) {
|
if (blocks < 10) {
|
||||||
m_guiContext->drawQuad("/interface/breath/breath.png",
|
String breathPath = "/interface/breath/breath.png";
|
||||||
RectF::withCenter(breathBackgroundCenterPos, Vec2F(imgMetadata->imageSize("/interface/breath/breath.png")) * interfaceScale()));
|
m_guiContext->drawQuad(breathPath, RectF::withCenter(breathBackgroundCenterPos, Vec2F(imgMetadata->imageSize(breathPath)) * interfaceScale()));
|
||||||
for (size_t i = 0; i < 10; i++) {
|
for (size_t i = 0; i < 10; i++) {
|
||||||
if (i >= blocks) {
|
if (i >= blocks) {
|
||||||
if (blocks == 0 && Time::monotonicMilliseconds() % 500 > 250)
|
if (blocks == 0 && Time::monotonicMilliseconds() % 500 > 250)
|
||||||
|
@ -43,14 +43,10 @@ void Animation::setAngle(float angle) {
|
|||||||
m_angle = angle;
|
m_angle = angle;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::setProcessing(String processing) {
|
void Animation::setProcessing(Directives processing) {
|
||||||
m_processing = move(processing);
|
m_processing = move(processing);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Animation::addProcessing(String const& processing) {
|
|
||||||
m_processing = String::joinWith("?", m_processing, processing);
|
|
||||||
}
|
|
||||||
|
|
||||||
void Animation::setColor(Color color) {
|
void Animation::setColor(Color color) {
|
||||||
m_color = color;
|
m_color = color;
|
||||||
}
|
}
|
||||||
@ -73,9 +69,8 @@ Drawable Animation::drawable(float pixelSize) const {
|
|||||||
if (m_appendFrame)
|
if (m_appendFrame)
|
||||||
baseFrame += ":" + toString(m_frame);
|
baseFrame += ":" + toString(m_frame);
|
||||||
|
|
||||||
baseFrame = String::joinWith("?", baseFrame, m_processing);
|
|
||||||
|
|
||||||
Drawable drawable = Drawable::makeImage(move(baseFrame), pixelSize, m_centered, m_offset);
|
Drawable drawable = Drawable::makeImage(move(baseFrame), pixelSize, m_centered, m_offset);
|
||||||
|
drawable.imagePart().addDirectives(m_processing);
|
||||||
drawable.rotate(m_angle);
|
drawable.rotate(m_angle);
|
||||||
drawable.color = m_color;
|
drawable.color = m_color;
|
||||||
return drawable;
|
return drawable;
|
||||||
|
@ -15,8 +15,7 @@ public:
|
|||||||
|
|
||||||
void setAngle(float angle);
|
void setAngle(float angle);
|
||||||
|
|
||||||
void setProcessing(String processing);
|
void setProcessing(Directives processing);
|
||||||
void addProcessing(String const& processing);
|
|
||||||
|
|
||||||
void setColor(Color color);
|
void setColor(Color color);
|
||||||
|
|
||||||
@ -44,7 +43,7 @@ private:
|
|||||||
float m_angle;
|
float m_angle;
|
||||||
Vec2F m_offset;
|
Vec2F m_offset;
|
||||||
bool m_centered;
|
bool m_centered;
|
||||||
String m_processing;
|
Directives m_processing;
|
||||||
Color m_color;
|
Color m_color;
|
||||||
int m_variantOffset;
|
int m_variantOffset;
|
||||||
|
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
|
|
||||||
namespace Star {
|
namespace Star {
|
||||||
|
|
||||||
ArmorWearer::ArmorWearer() {
|
ArmorWearer::ArmorWearer() : m_lastNude(true), m_needsHumanoidSync(true) {
|
||||||
addNetElement(&m_headItemDataNetState);
|
addNetElement(&m_headItemDataNetState);
|
||||||
addNetElement(&m_chestItemDataNetState);
|
addNetElement(&m_chestItemDataNetState);
|
||||||
addNetElement(&m_legsItemDataNetState);
|
addNetElement(&m_legsItemDataNetState);
|
||||||
@ -27,7 +27,14 @@ ArmorWearer::ArmorWearer() {
|
|||||||
addNetElement(&m_backCosmeticItemDataNetState);
|
addNetElement(&m_backCosmeticItemDataNetState);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArmorWearer::setupHumanoidClothingDrawables(Humanoid& humanoid, bool forceNude) const {
|
void ArmorWearer::setupHumanoidClothingDrawables(Humanoid& humanoid, bool forceNude) {
|
||||||
|
if (m_lastNude != forceNude)
|
||||||
|
m_lastNude = forceNude;
|
||||||
|
else if (!m_needsHumanoidSync)
|
||||||
|
return;
|
||||||
|
|
||||||
|
m_needsHumanoidSync = false;
|
||||||
|
|
||||||
bool bodyHidden = false;
|
bool bodyHidden = false;
|
||||||
if (m_headCosmeticItem && !forceNude) {
|
if (m_headCosmeticItem && !forceNude) {
|
||||||
humanoid.setHeadArmorFrameset(m_headCosmeticItem->frameset(humanoid.identity().gender));
|
humanoid.setHeadArmorFrameset(m_headCosmeticItem->frameset(humanoid.identity().gender));
|
||||||
@ -161,35 +168,59 @@ List<PersistentStatusEffect> ArmorWearer::statusEffects() const {
|
|||||||
}
|
}
|
||||||
|
|
||||||
void ArmorWearer::setHeadItem(HeadArmorPtr headItem) {
|
void ArmorWearer::setHeadItem(HeadArmorPtr headItem) {
|
||||||
|
if (!Item::itemsEqual(m_headItem, headItem))
|
||||||
|
return;
|
||||||
m_headItem = headItem;
|
m_headItem = headItem;
|
||||||
|
m_needsHumanoidSync = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArmorWearer::setHeadCosmeticItem(HeadArmorPtr headCosmeticItem) {
|
void ArmorWearer::setHeadCosmeticItem(HeadArmorPtr headCosmeticItem) {
|
||||||
|
if (!Item::itemsEqual(m_headCosmeticItem, headCosmeticItem))
|
||||||
|
return;
|
||||||
m_headCosmeticItem = headCosmeticItem;
|
m_headCosmeticItem = headCosmeticItem;
|
||||||
|
m_needsHumanoidSync = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArmorWearer::setChestCosmeticItem(ChestArmorPtr chestCosmeticItem) {
|
void ArmorWearer::setChestCosmeticItem(ChestArmorPtr chestCosmeticItem) {
|
||||||
|
if (!Item::itemsEqual(m_chestCosmeticItem, chestCosmeticItem))
|
||||||
|
return;
|
||||||
m_chestCosmeticItem = chestCosmeticItem;
|
m_chestCosmeticItem = chestCosmeticItem;
|
||||||
|
m_needsHumanoidSync = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArmorWearer::setChestItem(ChestArmorPtr chestItem) {
|
void ArmorWearer::setChestItem(ChestArmorPtr chestItem) {
|
||||||
|
if (!Item::itemsEqual(m_chestItem, chestItem))
|
||||||
|
return;
|
||||||
m_chestItem = chestItem;
|
m_chestItem = chestItem;
|
||||||
|
m_needsHumanoidSync = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArmorWearer::setLegsItem(LegsArmorPtr legsItem) {
|
void ArmorWearer::setLegsItem(LegsArmorPtr legsItem) {
|
||||||
|
if (!Item::itemsEqual(m_legsItem, legsItem))
|
||||||
|
return;
|
||||||
m_legsItem = legsItem;
|
m_legsItem = legsItem;
|
||||||
|
m_needsHumanoidSync = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArmorWearer::setLegsCosmeticItem(LegsArmorPtr legsCosmeticItem) {
|
void ArmorWearer::setLegsCosmeticItem(LegsArmorPtr legsCosmeticItem) {
|
||||||
|
if (!Item::itemsEqual(m_legsCosmeticItem, legsCosmeticItem))
|
||||||
|
return;
|
||||||
m_legsCosmeticItem = legsCosmeticItem;
|
m_legsCosmeticItem = legsCosmeticItem;
|
||||||
|
m_needsHumanoidSync = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArmorWearer::setBackItem(BackArmorPtr backItem) {
|
void ArmorWearer::setBackItem(BackArmorPtr backItem) {
|
||||||
|
if (!Item::itemsEqual(m_backItem, backItem))
|
||||||
|
return;
|
||||||
m_backItem = backItem;
|
m_backItem = backItem;
|
||||||
|
m_needsHumanoidSync = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArmorWearer::setBackCosmeticItem(BackArmorPtr backCosmeticItem) {
|
void ArmorWearer::setBackCosmeticItem(BackArmorPtr backCosmeticItem) {
|
||||||
|
if (!Item::itemsEqual(m_backCosmeticItem, backCosmeticItem))
|
||||||
|
return;
|
||||||
m_backCosmeticItem = backCosmeticItem;
|
m_backCosmeticItem = backCosmeticItem;
|
||||||
|
m_needsHumanoidSync = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
HeadArmorPtr ArmorWearer::headItem() const {
|
HeadArmorPtr ArmorWearer::headItem() const {
|
||||||
@ -275,23 +306,26 @@ ItemDescriptor ArmorWearer::backCosmeticItemDescriptor() const {
|
|||||||
void ArmorWearer::netElementsNeedLoad(bool) {
|
void ArmorWearer::netElementsNeedLoad(bool) {
|
||||||
auto itemDatabase = Root::singleton().itemDatabase();
|
auto itemDatabase = Root::singleton().itemDatabase();
|
||||||
|
|
||||||
|
bool changed = false;
|
||||||
if (m_headItemDataNetState.pullUpdated())
|
if (m_headItemDataNetState.pullUpdated())
|
||||||
itemDatabase->loadItem(m_headItemDataNetState.get(), m_headItem);
|
changed |= itemDatabase->loadItem(m_headItemDataNetState.get(), m_headItem);
|
||||||
if (m_chestItemDataNetState.pullUpdated())
|
if (m_chestItemDataNetState.pullUpdated())
|
||||||
itemDatabase->loadItem(m_chestItemDataNetState.get(), m_chestItem);
|
changed |= itemDatabase->loadItem(m_chestItemDataNetState.get(), m_chestItem);
|
||||||
if (m_legsItemDataNetState.pullUpdated())
|
if (m_legsItemDataNetState.pullUpdated())
|
||||||
itemDatabase->loadItem(m_legsItemDataNetState.get(), m_legsItem);
|
changed |= itemDatabase->loadItem(m_legsItemDataNetState.get(), m_legsItem);
|
||||||
if (m_backItemDataNetState.pullUpdated())
|
if (m_backItemDataNetState.pullUpdated())
|
||||||
itemDatabase->loadItem(m_backItemDataNetState.get(), m_backItem);
|
changed |= itemDatabase->loadItem(m_backItemDataNetState.get(), m_backItem);
|
||||||
|
|
||||||
if (m_headCosmeticItemDataNetState.pullUpdated())
|
if (m_headCosmeticItemDataNetState.pullUpdated())
|
||||||
itemDatabase->loadItem(m_headCosmeticItemDataNetState.get(), m_headCosmeticItem);
|
changed |= itemDatabase->loadItem(m_headCosmeticItemDataNetState.get(), m_headCosmeticItem);
|
||||||
if (m_chestCosmeticItemDataNetState.pullUpdated())
|
if (m_chestCosmeticItemDataNetState.pullUpdated())
|
||||||
itemDatabase->loadItem(m_chestCosmeticItemDataNetState.get(), m_chestCosmeticItem);
|
changed |= itemDatabase->loadItem(m_chestCosmeticItemDataNetState.get(), m_chestCosmeticItem);
|
||||||
if (m_legsCosmeticItemDataNetState.pullUpdated())
|
if (m_legsCosmeticItemDataNetState.pullUpdated())
|
||||||
itemDatabase->loadItem(m_legsCosmeticItemDataNetState.get(), m_legsCosmeticItem);
|
changed |= itemDatabase->loadItem(m_legsCosmeticItemDataNetState.get(), m_legsCosmeticItem);
|
||||||
if (m_backCosmeticItemDataNetState.pullUpdated())
|
if (m_backCosmeticItemDataNetState.pullUpdated())
|
||||||
itemDatabase->loadItem(m_backCosmeticItemDataNetState.get(), m_backCosmeticItem);
|
changed |= itemDatabase->loadItem(m_backCosmeticItemDataNetState.get(), m_backCosmeticItem);
|
||||||
|
|
||||||
|
m_needsHumanoidSync = changed;
|
||||||
}
|
}
|
||||||
|
|
||||||
void ArmorWearer::netElementsNeedStore() {
|
void ArmorWearer::netElementsNeedStore() {
|
||||||
|
@ -26,7 +26,7 @@ class ArmorWearer : public NetElementSyncGroup {
|
|||||||
public:
|
public:
|
||||||
ArmorWearer();
|
ArmorWearer();
|
||||||
|
|
||||||
void setupHumanoidClothingDrawables(Humanoid& humanoid, bool forceNude) const;
|
void setupHumanoidClothingDrawables(Humanoid& humanoid, bool forceNude);
|
||||||
void effects(EffectEmitter& effectEmitter);
|
void effects(EffectEmitter& effectEmitter);
|
||||||
List<PersistentStatusEffect> statusEffects() const;
|
List<PersistentStatusEffect> statusEffects() const;
|
||||||
|
|
||||||
@ -82,6 +82,9 @@ private:
|
|||||||
NetElementData<ItemDescriptor> m_chestCosmeticItemDataNetState;
|
NetElementData<ItemDescriptor> m_chestCosmeticItemDataNetState;
|
||||||
NetElementData<ItemDescriptor> m_legsCosmeticItemDataNetState;
|
NetElementData<ItemDescriptor> m_legsCosmeticItemDataNetState;
|
||||||
NetElementData<ItemDescriptor> m_backCosmeticItemDataNetState;
|
NetElementData<ItemDescriptor> m_backCosmeticItemDataNetState;
|
||||||
|
|
||||||
|
bool m_lastNude;
|
||||||
|
bool m_needsHumanoidSync;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -9,37 +9,41 @@
|
|||||||
|
|
||||||
namespace Star {
|
namespace Star {
|
||||||
|
|
||||||
void Drawable::ImagePart::addDirectives(String const& directives, bool keepImageCenterPosition) {
|
Drawable::ImagePart& Drawable::ImagePart::addDirectives(Directives const& directives, bool keepImageCenterPosition) {
|
||||||
if (directives.empty())
|
if (!directives)
|
||||||
return;
|
return *this;
|
||||||
|
|
||||||
if (keepImageCenterPosition) {
|
if (keepImageCenterPosition) {
|
||||||
auto imageMetadata = Root::singleton().imageMetadataDatabase();
|
auto imageMetadata = Root::singleton().imageMetadataDatabase();
|
||||||
Vec2F imageSize = Vec2F(imageMetadata->imageSize(image));
|
Vec2F imageSize = Vec2F(imageMetadata->imageSize(image));
|
||||||
image = AssetPath::addDirectives(image, {directives});
|
image.directives += directives;
|
||||||
Vec2F newImageSize = Vec2F(imageMetadata->imageSize(image));
|
Vec2F newImageSize = Vec2F(imageMetadata->imageSize(image));
|
||||||
|
|
||||||
// If we are trying to maintain the image center, PRE translate the image by
|
// If we are trying to maintain the image center, PRE translate the image by
|
||||||
// the change in size / 2
|
// the change in size / 2
|
||||||
transformation *= Mat3F::translation((imageSize - newImageSize) / 2);
|
transformation *= Mat3F::translation((imageSize - newImageSize) / 2);
|
||||||
} else {
|
} else {
|
||||||
image = AssetPath::addDirectives(image, {directives});
|
image.directives += directives;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Drawable::ImagePart::removeDirectives(bool keepImageCenterPosition) {
|
Drawable::ImagePart& Drawable::ImagePart::removeDirectives(bool keepImageCenterPosition) {
|
||||||
if (keepImageCenterPosition) {
|
if (keepImageCenterPosition) {
|
||||||
auto imageMetadata = Root::singleton().imageMetadataDatabase();
|
auto imageMetadata = Root::singleton().imageMetadataDatabase();
|
||||||
Vec2F imageSize = Vec2F(imageMetadata->imageSize(image));
|
Vec2F imageSize = Vec2F(imageMetadata->imageSize(image));
|
||||||
image = AssetPath::removeDirectives(image);
|
image.directives.clear();
|
||||||
Vec2F newImageSize = Vec2F(imageMetadata->imageSize(image));
|
Vec2F newImageSize = Vec2F(imageMetadata->imageSize(image));
|
||||||
|
|
||||||
// If we are trying to maintain the image center, PRE translate the image by
|
// If we are trying to maintain the image center, PRE translate the image by
|
||||||
// the change in size / 2
|
// the change in size / 2
|
||||||
transformation *= Mat3F::translation((imageSize - newImageSize) / 2);
|
transformation *= Mat3F::translation((imageSize - newImageSize) / 2);
|
||||||
} else {
|
} else {
|
||||||
image = AssetPath::removeDirectives(image);
|
image.directives.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
return *this;
|
||||||
}
|
}
|
||||||
|
|
||||||
Drawable Drawable::makeLine(Line2F const& line, float lineWidth, Color const& color, Vec2F const& position) {
|
Drawable Drawable::makeLine(Line2F const& line, float lineWidth, Color const& color, Vec2F const& position) {
|
||||||
@ -60,7 +64,7 @@ Drawable Drawable::makePoly(PolyF poly, Color const& color, Vec2F const& positio
|
|||||||
return drawable;
|
return drawable;
|
||||||
}
|
}
|
||||||
|
|
||||||
Drawable Drawable::makeImage(String image, float pixelSize, bool centered, Vec2F const& position, Color const& color) {
|
Drawable Drawable::makeImage(AssetPath image, float pixelSize, bool centered, Vec2F const& position, Color const& color) {
|
||||||
Drawable drawable;
|
Drawable drawable;
|
||||||
Mat3F transformation = Mat3F::identity();
|
Mat3F transformation = Mat3F::identity();
|
||||||
if (centered) {
|
if (centered) {
|
||||||
@ -120,7 +124,7 @@ Json Drawable::toJson() const {
|
|||||||
} else if (auto poly = part.ptr<PolyPart>()) {
|
} else if (auto poly = part.ptr<PolyPart>()) {
|
||||||
json.set("poly", jsonFromPolyF(poly->poly));
|
json.set("poly", jsonFromPolyF(poly->poly));
|
||||||
} else if (auto image = part.ptr<ImagePart>()) {
|
} else if (auto image = part.ptr<ImagePart>()) {
|
||||||
json.set("image", image->image);
|
json.set("image", AssetPath::join(image->image));
|
||||||
json.set("transformation", jsonFromMat3F(image->transformation));
|
json.set("transformation", jsonFromMat3F(image->transformation));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -242,14 +246,15 @@ DataStream& operator<<(DataStream& ds, Drawable::PolyPart const& poly) {
|
|||||||
return ds;
|
return ds;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// I need to find out if this is for network serialization or not eventually
|
||||||
DataStream& operator>>(DataStream& ds, Drawable::ImagePart& image) {
|
DataStream& operator>>(DataStream& ds, Drawable::ImagePart& image) {
|
||||||
ds >> image.image;
|
ds >> AssetPath::join(image.image);
|
||||||
ds >> image.transformation;
|
ds >> image.transformation;
|
||||||
return ds;
|
return ds;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataStream& operator<<(DataStream& ds, Drawable::ImagePart const& image) {
|
DataStream& operator<<(DataStream& ds, Drawable::ImagePart const& image) {
|
||||||
ds << image.image;
|
ds << AssetPath::join(image.image);
|
||||||
ds << image.transformation;
|
ds << image.transformation;
|
||||||
return ds;
|
return ds;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include "StarPoly.hpp"
|
#include "StarPoly.hpp"
|
||||||
#include "StarColor.hpp"
|
#include "StarColor.hpp"
|
||||||
#include "StarJson.hpp"
|
#include "StarJson.hpp"
|
||||||
|
#include "StarAssetPath.hpp"
|
||||||
|
|
||||||
namespace Star {
|
namespace Star {
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ struct Drawable {
|
|||||||
};
|
};
|
||||||
|
|
||||||
struct ImagePart {
|
struct ImagePart {
|
||||||
String image;
|
AssetPath image;
|
||||||
// Transformation of the image in pixel space (0, 0) - (width, height) to
|
// Transformation of the image in pixel space (0, 0) - (width, height) to
|
||||||
// the final drawn space
|
// the final drawn space
|
||||||
Mat3F transformation;
|
Mat3F transformation;
|
||||||
@ -28,17 +29,17 @@ struct Drawable {
|
|||||||
// Add directives to this ImagePart, while optionally keeping the
|
// Add directives to this ImagePart, while optionally keeping the
|
||||||
// transformed center of the image the same if the directives change the
|
// transformed center of the image the same if the directives change the
|
||||||
// image size.
|
// image size.
|
||||||
void addDirectives(String const& directives, bool keepImageCenterPosition);
|
ImagePart& addDirectives(Directives const& directives, bool keepImageCenterPosition = false);
|
||||||
|
|
||||||
// Remove directives from this ImagePart, while optionally keeping the
|
// Remove directives from this ImagePart, while optionally keeping the
|
||||||
// transformed center of the image the same if the directives change the
|
// transformed center of the image the same if the directives change the
|
||||||
// image size.
|
// image size.
|
||||||
void removeDirectives(bool keepImageCenterPosition);
|
ImagePart& removeDirectives(bool keepImageCenterPosition = false);
|
||||||
};
|
};
|
||||||
|
|
||||||
static Drawable makeLine(Line2F const& line, float lineWidth, Color const& color, Vec2F const& position = Vec2F());
|
static Drawable makeLine(Line2F const& line, float lineWidth, Color const& color, Vec2F const& position = Vec2F());
|
||||||
static Drawable makePoly(PolyF poly, Color const& color, Vec2F const& position = Vec2F());
|
static Drawable makePoly(PolyF poly, Color const& color, Vec2F const& position = Vec2F());
|
||||||
static Drawable makeImage(String image, float pixelSize, bool centered, Vec2F const& position, Color const& color = Color::White);
|
static Drawable makeImage(AssetPath image, float pixelSize, bool centered, Vec2F const& position, Color const& color = Color::White);
|
||||||
|
|
||||||
template <typename DrawablesContainer>
|
template <typename DrawablesContainer>
|
||||||
static void translateAll(DrawablesContainer& drawables, Vec2F const& translation);
|
static void translateAll(DrawablesContainer& drawables, Vec2F const& translation);
|
||||||
|
@ -42,7 +42,11 @@ HumanoidIdentity::HumanoidIdentity(Json config) {
|
|||||||
hairType = config.getString("hairType", "male1");
|
hairType = config.getString("hairType", "male1");
|
||||||
hairDirectives = config.getString("hairDirectives", "");
|
hairDirectives = config.getString("hairDirectives", "");
|
||||||
bodyDirectives = config.getString("bodyDirectives", "");
|
bodyDirectives = config.getString("bodyDirectives", "");
|
||||||
emoteDirectives = config.getString("emoteDirectives", bodyDirectives);
|
if (auto jEmoteDirectives = config.optString("emoteDirectives")) // Passing Directives as a default arg would be inefficient
|
||||||
|
emoteDirectives = jEmoteDirectives.take();
|
||||||
|
else
|
||||||
|
emoteDirectives = bodyDirectives;
|
||||||
|
|
||||||
facialHairGroup = config.getString("facialHairGroup", "");
|
facialHairGroup = config.getString("facialHairGroup", "");
|
||||||
facialHairType = config.getString("facialHairType", "");
|
facialHairType = config.getString("facialHairType", "");
|
||||||
facialHairDirectives = config.getString("facialHairDirectives", "");
|
facialHairDirectives = config.getString("facialHairDirectives", "");
|
||||||
@ -67,15 +71,15 @@ Json HumanoidIdentity::toJson() const {
|
|||||||
{"gender", GenderNames.getRight(gender)},
|
{"gender", GenderNames.getRight(gender)},
|
||||||
{"hairGroup", hairGroup},
|
{"hairGroup", hairGroup},
|
||||||
{"hairType", hairType},
|
{"hairType", hairType},
|
||||||
{"hairDirectives", hairDirectives},
|
{"hairDirectives", hairDirectives.toString()},
|
||||||
{"bodyDirectives", bodyDirectives},
|
{"bodyDirectives", bodyDirectives.toString()},
|
||||||
{"emoteDirectives", emoteDirectives},
|
{"emoteDirectives", emoteDirectives.toString()},
|
||||||
{"facialHairGroup", facialHairGroup},
|
{"facialHairGroup", facialHairGroup},
|
||||||
{"facialHairType", facialHairType},
|
{"facialHairType", facialHairType},
|
||||||
{"facialHairDirectives", facialHairDirectives},
|
{"facialHairDirectives", facialHairDirectives.toString()},
|
||||||
{"facialMaskGroup", facialMaskGroup},
|
{"facialMaskGroup", facialMaskGroup},
|
||||||
{"facialMaskType", facialMaskType},
|
{"facialMaskType", facialMaskType},
|
||||||
{"facialMaskDirectives", facialMaskDirectives},
|
{"facialMaskDirectives", facialMaskDirectives.toString()},
|
||||||
{"personalityIdle", personality.idle},
|
{"personalityIdle", personality.idle},
|
||||||
{"personalityArmIdle", personality.armIdle},
|
{"personalityArmIdle", personality.armIdle},
|
||||||
{"personalityHeadOffset", jsonFromVec2F(personality.headOffset)},
|
{"personalityHeadOffset", jsonFromVec2F(personality.headOffset)},
|
||||||
@ -300,7 +304,7 @@ HumanoidIdentity const& Humanoid::identity() const {
|
|||||||
return m_identity;
|
return m_identity;
|
||||||
}
|
}
|
||||||
|
|
||||||
void Humanoid::setHeadArmorDirectives(String directives) {
|
void Humanoid::setHeadArmorDirectives(Directives directives) {
|
||||||
m_headArmorDirectives = move(directives);
|
m_headArmorDirectives = move(directives);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -308,7 +312,7 @@ void Humanoid::setHeadArmorFrameset(String headFrameset) {
|
|||||||
m_headArmorFrameset = move(headFrameset);
|
m_headArmorFrameset = move(headFrameset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Humanoid::setChestArmorDirectives(String directives) {
|
void Humanoid::setChestArmorDirectives(Directives directives) {
|
||||||
m_chestArmorDirectives = move(directives);
|
m_chestArmorDirectives = move(directives);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -324,7 +328,7 @@ void Humanoid::setFrontSleeveFrameset(String frontSleeveFrameset) {
|
|||||||
m_frontSleeveFrameset = move(frontSleeveFrameset);
|
m_frontSleeveFrameset = move(frontSleeveFrameset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Humanoid::setLegsArmorDirectives(String directives) {
|
void Humanoid::setLegsArmorDirectives(Directives directives) {
|
||||||
m_legsArmorDirectives = move(directives);
|
m_legsArmorDirectives = move(directives);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -332,7 +336,7 @@ void Humanoid::setLegsArmorFrameset(String legsFrameset) {
|
|||||||
m_legsArmorFrameset = move(legsFrameset);
|
m_legsArmorFrameset = move(legsFrameset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Humanoid::setBackArmorDirectives(String directives) {
|
void Humanoid::setBackArmorDirectives(Directives directives) {
|
||||||
m_backArmorDirectives = move(directives);
|
m_backArmorDirectives = move(directives);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -340,7 +344,7 @@ void Humanoid::setBackArmorFrameset(String backFrameset) {
|
|||||||
m_backArmorFrameset = move(backFrameset);
|
m_backArmorFrameset = move(backFrameset);
|
||||||
}
|
}
|
||||||
|
|
||||||
void Humanoid::setHelmetMaskDirectives(String helmetMaskDirectives) {
|
void Humanoid::setHelmetMaskDirectives(Directives helmetMaskDirectives) {
|
||||||
m_helmetMaskDirectives = move(helmetMaskDirectives);
|
m_helmetMaskDirectives = move(helmetMaskDirectives);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -464,7 +468,7 @@ List<Drawable> Humanoid::render() {
|
|||||||
|
|
||||||
int armStateSeq = getArmStateSequence();
|
int armStateSeq = getArmStateSequence();
|
||||||
int bodyStateSeq = getBodyStateSequence();
|
int bodyStateSeq = getBodyStateSequence();
|
||||||
int emoteStateSeq = m_timing.emoteStateSeq(m_emoteAnimationTimer, m_emoteState);
|
int emoteStateSeq = getEmoteStateSequence();
|
||||||
float bobYOffset = getBobYOffset();
|
float bobYOffset = getBobYOffset();
|
||||||
Maybe<DancePtr> dance = getDance();
|
Maybe<DancePtr> dance = getDance();
|
||||||
Maybe<DanceStep> danceStep = {};
|
Maybe<DanceStep> danceStep = {};
|
||||||
@ -492,9 +496,10 @@ List<Drawable> Humanoid::render() {
|
|||||||
drawables.append(move(drawable));
|
drawables.append(move(drawable));
|
||||||
};
|
};
|
||||||
|
|
||||||
auto backArmDrawable = [&](String const& frameSet, String const& directives) -> Drawable {
|
auto backArmDrawable = [&](String const& frameSet, Directives const& directives) -> Drawable {
|
||||||
String image = strf("%s:%s%s", frameSet, backHand.backFrame, directives);
|
String image = strf("%s:%s", frameSet, backHand.backFrame);
|
||||||
Drawable backArm = Drawable::makeImage(move(image), 1.0f / TilePixels, true, backArmFrameOffset);
|
Drawable backArm = Drawable::makeImage(move(image), 1.0f / TilePixels, true, backArmFrameOffset);
|
||||||
|
backArm.imagePart().addDirectives(directives);
|
||||||
backArm.rotate(backHand.angle, backArmFrameOffset + m_backArmRotationCenter + m_backArmOffset);
|
backArm.rotate(backHand.angle, backArmFrameOffset + m_backArmRotationCenter + m_backArmOffset);
|
||||||
return backArm;
|
return backArm;
|
||||||
};
|
};
|
||||||
@ -505,13 +510,15 @@ List<Drawable> Humanoid::render() {
|
|||||||
frameGroup = "runbackwards";
|
frameGroup = "runbackwards";
|
||||||
String image;
|
String image;
|
||||||
if (dance.isValid() && danceStep->bodyFrame)
|
if (dance.isValid() && danceStep->bodyFrame)
|
||||||
image = strf("%s:%s%s", m_backArmorFrameset, *danceStep->bodyFrame, getBackDirectives());
|
image = strf("%s:%s", m_backArmorFrameset, *danceStep->bodyFrame);
|
||||||
else if (m_state == Idle)
|
else if (m_state == Idle)
|
||||||
image = strf("%s:%s%s", m_backArmorFrameset, m_identity.personality.idle, getBackDirectives());
|
image = strf("%s:%s", m_backArmorFrameset, m_identity.personality.idle);
|
||||||
else
|
else
|
||||||
image = strf("%s:%s.%s%s", m_backArmorFrameset, frameGroup, bodyStateSeq, getBackDirectives());
|
image = strf("%s:%s.%s", m_backArmorFrameset, frameGroup, bodyStateSeq);
|
||||||
|
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, Vec2F()));
|
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, Vec2F());
|
||||||
|
drawable.imagePart().addDirectives(getBackDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (backHand.holdingItem && !dance.isValid()) {
|
if (backHand.holdingItem && !dance.isValid()) {
|
||||||
@ -535,33 +542,35 @@ List<Drawable> Humanoid::render() {
|
|||||||
String image;
|
String image;
|
||||||
Vec2F position;
|
Vec2F position;
|
||||||
if (dance.isValid() && danceStep->backArmFrame) {
|
if (dance.isValid() && danceStep->backArmFrame) {
|
||||||
image = strf("%s:%s%s", m_backArmFrameset, *danceStep->backArmFrame, getBodyDirectives());
|
image = strf("%s:%s", m_backArmFrameset, *danceStep->backArmFrame);
|
||||||
position = danceStep->backArmOffset / TilePixels;
|
position = danceStep->backArmOffset / TilePixels;
|
||||||
} else if (m_state == Idle) {
|
} else if (m_state == Idle) {
|
||||||
image = strf("%s:%s%s", m_backArmFrameset, m_identity.personality.armIdle, getBodyDirectives());
|
image = strf("%s:%s", m_backArmFrameset, m_identity.personality.armIdle);
|
||||||
position = m_identity.personality.armOffset / TilePixels;
|
position = m_identity.personality.armOffset / TilePixels;
|
||||||
} else
|
} else
|
||||||
image = strf("%s:%s.%s%s", m_backArmFrameset, frameBase(m_state), armStateSeq, getBodyDirectives());
|
image = strf("%s:%s.%s", m_backArmFrameset, frameBase(m_state), armStateSeq);
|
||||||
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position);
|
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position);
|
||||||
|
drawable.imagePart().addDirectives(getBodyDirectives());
|
||||||
if (dance.isValid())
|
if (dance.isValid())
|
||||||
drawable.rotate(danceStep->backArmRotation);
|
drawable.rotate(danceStep->backArmRotation);
|
||||||
addDrawable(drawable, m_bodyFullbright);
|
addDrawable(move(drawable), m_bodyFullbright);
|
||||||
}
|
}
|
||||||
if (!m_backSleeveFrameset.empty()) {
|
if (!m_backSleeveFrameset.empty()) {
|
||||||
String image;
|
String image;
|
||||||
Vec2F position;
|
Vec2F position;
|
||||||
if (dance.isValid() && danceStep->backArmFrame) {
|
if (dance.isValid() && danceStep->backArmFrame) {
|
||||||
image = strf("%s:%s%s", m_backSleeveFrameset, *danceStep->backArmFrame, getChestDirectives());
|
image = strf("%s:%s", m_backSleeveFrameset, *danceStep->backArmFrame);
|
||||||
position = danceStep->backArmOffset / TilePixels;
|
position = danceStep->backArmOffset / TilePixels;
|
||||||
} else if (m_state == Idle) {
|
} else if (m_state == Idle) {
|
||||||
image = strf("%s:%s%s", m_backSleeveFrameset, m_identity.personality.armIdle, getChestDirectives());
|
image = strf("%s:%s", m_backSleeveFrameset, m_identity.personality.armIdle);
|
||||||
position = m_identity.personality.armOffset / TilePixels;
|
position = m_identity.personality.armOffset / TilePixels;
|
||||||
} else
|
} else
|
||||||
image = strf("%s:%s.%s%s", m_backSleeveFrameset, frameBase(m_state), armStateSeq, getChestDirectives());
|
image = strf("%s:%s.%s", m_backSleeveFrameset, frameBase(m_state), armStateSeq);
|
||||||
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position);
|
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position);
|
||||||
|
drawable.imagePart().addDirectives(getChestDirectives());
|
||||||
if (dance.isValid())
|
if (dance.isValid())
|
||||||
drawable.rotate(danceStep->backArmRotation);
|
drawable.rotate(danceStep->backArmRotation);
|
||||||
addDrawable(drawable);
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -582,80 +591,99 @@ List<Drawable> Humanoid::render() {
|
|||||||
headPosition += m_headLayOffset;
|
headPosition += m_headLayOffset;
|
||||||
|
|
||||||
if (!m_headFrameset.empty() && !m_bodyHidden) {
|
if (!m_headFrameset.empty() && !m_bodyHidden) {
|
||||||
String image = strf("%s:normal%s", m_headFrameset, getBodyDirectives());
|
String image = strf("%s:normal", m_headFrameset);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition), m_bodyFullbright);
|
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition);
|
||||||
|
drawable.imagePart().addDirectives(getBodyDirectives());
|
||||||
|
addDrawable(move(drawable), m_bodyFullbright);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_emoteFrameset.empty() && !m_bodyHidden) {
|
if (!m_emoteFrameset.empty() && !m_bodyHidden) {
|
||||||
String image = strf("%s:%s.%s%s", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq, getEmoteDirectives());
|
String image = strf("%s:%s.%s", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition), m_bodyFullbright);
|
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition);
|
||||||
|
drawable.imagePart().addDirectives(getEmoteDirectives());
|
||||||
|
addDrawable(move(drawable), m_bodyFullbright);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_hairFrameset.empty() && !m_bodyHidden) {
|
if (!m_hairFrameset.empty() && !m_bodyHidden) {
|
||||||
String image = strf("%s:normal%s", m_hairFrameset, getHairDirectives() + getHelmetMaskDirectives());
|
String image = strf("%s:normal", m_hairFrameset);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition), m_bodyFullbright);
|
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition);
|
||||||
|
drawable.imagePart().addDirectives(getHairDirectives()).addDirectives(getHelmetMaskDirectives());
|
||||||
|
addDrawable(move(drawable), m_bodyFullbright);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_bodyFrameset.empty() && !m_bodyHidden) {
|
if (!m_bodyFrameset.empty() && !m_bodyHidden) {
|
||||||
String image;
|
String image;
|
||||||
if (dance.isValid() && danceStep->bodyFrame)
|
if (dance.isValid() && danceStep->bodyFrame)
|
||||||
image = strf("%s:%s%s", m_bodyFrameset, *danceStep->bodyFrame, getBodyDirectives());
|
image = strf("%s:%s", m_bodyFrameset, *danceStep->bodyFrame);
|
||||||
else if (m_state == Idle)
|
else if (m_state == Idle)
|
||||||
image = strf("%s:%s%s", m_bodyFrameset, m_identity.personality.idle, getBodyDirectives());
|
image = strf("%s:%s", m_bodyFrameset, m_identity.personality.idle);
|
||||||
else
|
else
|
||||||
image = strf("%s:%s.%s%s", m_bodyFrameset, frameBase(m_state), bodyStateSeq, getBodyDirectives());
|
image = strf("%s:%s.%s", m_bodyFrameset, frameBase(m_state), bodyStateSeq);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, {}), m_bodyFullbright);
|
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, {});
|
||||||
|
drawable.imagePart().addDirectives(getBodyDirectives());
|
||||||
|
addDrawable(move(drawable), m_bodyFullbright);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_legsArmorFrameset.empty()) {
|
if (!m_legsArmorFrameset.empty()) {
|
||||||
String image;
|
String image;
|
||||||
if (dance.isValid() && danceStep->bodyFrame)
|
if (dance.isValid() && danceStep->bodyFrame)
|
||||||
image = strf("%s:%s%s", m_legsArmorFrameset, *danceStep->bodyFrame, getLegsDirectives());
|
image = strf("%s:%s", m_legsArmorFrameset, *danceStep->bodyFrame);
|
||||||
else if (m_state == Idle)
|
else if (m_state == Idle)
|
||||||
image = strf("%s:%s%s", m_legsArmorFrameset, m_identity.personality.idle, getLegsDirectives());
|
image = strf("%s:%s", m_legsArmorFrameset, m_identity.personality.idle);
|
||||||
else
|
else
|
||||||
image = strf("%s:%s.%s%s", m_legsArmorFrameset, frameBase(m_state), bodyStateSeq, getLegsDirectives());
|
image = strf("%s:%s.%s", m_legsArmorFrameset, frameBase(m_state), bodyStateSeq);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, {}));
|
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, {});
|
||||||
|
drawable.imagePart().addDirectives(getLegsDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_chestArmorFrameset.empty()) {
|
if (!m_chestArmorFrameset.empty()) {
|
||||||
String image;
|
String image;
|
||||||
Vec2F position;
|
Vec2F position;
|
||||||
if (dance.isValid() && danceStep->bodyFrame)
|
if (dance.isValid() && danceStep->bodyFrame)
|
||||||
image = strf("%s:%s%s", m_chestArmorFrameset, *danceStep->bodyFrame, getChestDirectives());
|
image = strf("%s:%s", m_chestArmorFrameset, *danceStep->bodyFrame);
|
||||||
else if (m_state == Run)
|
else if (m_state == Run)
|
||||||
image = strf("%s:run%s", m_chestArmorFrameset, getChestDirectives());
|
image = strf("%s:run", m_chestArmorFrameset);
|
||||||
else if (m_state == Idle)
|
else if (m_state == Idle)
|
||||||
image = strf("%s:%s%s", m_chestArmorFrameset, m_identity.personality.idle, getChestDirectives());
|
image = strf("%s:%s", m_chestArmorFrameset, m_identity.personality.idle);
|
||||||
else if (m_state == Duck)
|
else if (m_state == Duck)
|
||||||
image = strf("%s:duck%s", m_chestArmorFrameset, getChestDirectives());
|
image = strf("%s:duck", m_chestArmorFrameset);
|
||||||
else if ((m_state == Swim) || (m_state == SwimIdle))
|
else if ((m_state == Swim) || (m_state == SwimIdle))
|
||||||
image = strf("%s:swim%s", m_chestArmorFrameset, getChestDirectives());
|
image = strf("%s:swim", m_chestArmorFrameset);
|
||||||
else
|
else
|
||||||
image = strf("%s:chest.1%s", m_chestArmorFrameset, getChestDirectives());
|
image = strf("%s:chest.1", m_chestArmorFrameset);
|
||||||
if (m_state != Duck)
|
if (m_state != Duck)
|
||||||
position[1] += bobYOffset;
|
position[1] += bobYOffset;
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, position));
|
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position);
|
||||||
|
drawable.imagePart().addDirectives(getChestDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_facialHairFrameset.empty() && !m_bodyHidden) {
|
if (!m_facialHairFrameset.empty() && !m_bodyHidden) {
|
||||||
String image = strf("%s:normal%s", m_facialHairFrameset, getFacialHairDirectives() + getHelmetMaskDirectives());
|
String image = strf("%s:normal", m_facialHairFrameset);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition), m_bodyFullbright);
|
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition);
|
||||||
|
drawable.imagePart().addDirectives(getFacialHairDirectives()).addDirectives(getHelmetMaskDirectives());
|
||||||
|
addDrawable(move(drawable), m_bodyFullbright);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_facialMaskFrameset.empty() && !m_bodyHidden) {
|
if (!m_facialMaskFrameset.empty() && !m_bodyHidden) {
|
||||||
String image = strf("%s:normal%s", m_facialMaskFrameset, getFacialMaskDirectives() + getHelmetMaskDirectives());
|
String image = strf("%s:normal", m_facialMaskFrameset);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition));
|
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition);
|
||||||
|
drawable.imagePart().addDirectives(getFacialMaskDirectives()).addDirectives(getHelmetMaskDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_headArmorFrameset.empty()) {
|
if (!m_headArmorFrameset.empty()) {
|
||||||
String image = strf("%s:normal%s", m_headArmorFrameset, getHeadDirectives());
|
String image = strf("%s:normal", m_headArmorFrameset);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition));
|
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, headPosition);
|
||||||
|
drawable.imagePart().addDirectives(getHeadDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
|
|
||||||
auto frontArmDrawable = [&](String const& frameSet, String const& directives) -> Drawable {
|
auto frontArmDrawable = [&](String const& frameSet, Directives const& directives) -> Drawable {
|
||||||
String image = strf("%s:%s%s", frameSet, frontHand.frontFrame, directives);
|
String image = strf("%s:%s", frameSet, frontHand.frontFrame);
|
||||||
Drawable frontArm = Drawable::makeImage(image, 1.0f / TilePixels, true, frontArmFrameOffset);
|
Drawable frontArm = Drawable::makeImage(image, 1.0f / TilePixels, true, frontArmFrameOffset);
|
||||||
|
frontArm.imagePart().addDirectives(directives);
|
||||||
frontArm.rotate(frontHand.angle, frontArmFrameOffset + m_frontArmRotationCenter);
|
frontArm.rotate(frontHand.angle, frontArmFrameOffset + m_frontArmRotationCenter);
|
||||||
return frontArm;
|
return frontArm;
|
||||||
};
|
};
|
||||||
@ -682,14 +710,15 @@ List<Drawable> Humanoid::render() {
|
|||||||
String image;
|
String image;
|
||||||
Vec2F position;
|
Vec2F position;
|
||||||
if (dance.isValid() && danceStep->frontArmFrame) {
|
if (dance.isValid() && danceStep->frontArmFrame) {
|
||||||
image = strf("%s:%s%s", m_frontArmFrameset, *danceStep->frontArmFrame, getBodyDirectives());
|
image = strf("%s:%s", m_frontArmFrameset, *danceStep->frontArmFrame);
|
||||||
position = danceStep->frontArmOffset / TilePixels;
|
position = danceStep->frontArmOffset / TilePixels;
|
||||||
} else if (m_state == Idle) {
|
} else if (m_state == Idle) {
|
||||||
image = strf("%s:%s%s", m_frontArmFrameset, m_identity.personality.armIdle, getBodyDirectives());
|
image = strf("%s:%s", m_frontArmFrameset, m_identity.personality.armIdle);
|
||||||
position = m_identity.personality.armOffset / TilePixels;
|
position = m_identity.personality.armOffset / TilePixels;
|
||||||
} else
|
} else
|
||||||
image = strf("%s:%s.%s%s", m_frontArmFrameset, frameBase(m_state), armStateSeq, getBodyDirectives());
|
image = strf("%s:%s.%s", m_frontArmFrameset, frameBase(m_state), armStateSeq);
|
||||||
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position);
|
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position);
|
||||||
|
drawable.imagePart().addDirectives(getBodyDirectives());
|
||||||
if (dance.isValid())
|
if (dance.isValid())
|
||||||
drawable.rotate(danceStep->frontArmRotation);
|
drawable.rotate(danceStep->frontArmRotation);
|
||||||
addDrawable(drawable, m_bodyFullbright);
|
addDrawable(drawable, m_bodyFullbright);
|
||||||
@ -699,14 +728,15 @@ List<Drawable> Humanoid::render() {
|
|||||||
String image;
|
String image;
|
||||||
Vec2F position;
|
Vec2F position;
|
||||||
if (dance.isValid() && danceStep->frontArmFrame) {
|
if (dance.isValid() && danceStep->frontArmFrame) {
|
||||||
image = strf("%s:%s%s", m_frontSleeveFrameset, *danceStep->frontArmFrame, getChestDirectives());
|
image = strf("%s:%s", m_frontSleeveFrameset, *danceStep->frontArmFrame);
|
||||||
position = danceStep->frontArmOffset / TilePixels;
|
position = danceStep->frontArmOffset / TilePixels;
|
||||||
} else if (m_state == Idle) {
|
} else if (m_state == Idle) {
|
||||||
image = strf("%s:%s%s", m_frontSleeveFrameset, m_identity.personality.armIdle, getChestDirectives());
|
image = strf("%s:%s", m_frontSleeveFrameset, m_identity.personality.armIdle);
|
||||||
position = m_identity.personality.armOffset / TilePixels;
|
position = m_identity.personality.armOffset / TilePixels;
|
||||||
} else
|
} else
|
||||||
image = strf("%s:%s.%s%s", m_frontSleeveFrameset, frameBase(m_state), armStateSeq, getChestDirectives());
|
image = strf("%s:%s.%s", m_frontSleeveFrameset, frameBase(m_state), armStateSeq);
|
||||||
auto drawable = Drawable::makeImage(move(image), 1.0f / TilePixels, true, position);
|
auto drawable = Drawable::makeImage(image, 1.0f / TilePixels, true, position);
|
||||||
|
drawable.imagePart().addDirectives(getChestDirectives());
|
||||||
if (dance.isValid())
|
if (dance.isValid())
|
||||||
drawable.rotate(danceStep->frontArmRotation);
|
drawable.rotate(danceStep->frontArmRotation);
|
||||||
addDrawable(drawable);
|
addDrawable(drawable);
|
||||||
@ -717,7 +747,7 @@ List<Drawable> Humanoid::render() {
|
|||||||
auto image = strf("%s:%d",
|
auto image = strf("%s:%d",
|
||||||
m_vaporTrailFrameset,
|
m_vaporTrailFrameset,
|
||||||
m_timing.genericSeq(m_animationTimer, m_vaporTrailCycle, m_vaporTrailFrames, true));
|
m_timing.genericSeq(m_animationTimer, m_vaporTrailCycle, m_vaporTrailFrames, true));
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f / TilePixels, true, {}));
|
addDrawable(Drawable::makeImage(AssetPath::split(image), 1.0f / TilePixels, true, {}));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_primaryHand.nonRotatedDrawables.size())
|
if (m_primaryHand.nonRotatedDrawables.size())
|
||||||
@ -736,18 +766,19 @@ List<Drawable> Humanoid::renderPortrait(PortraitMode mode) const {
|
|||||||
List<Drawable> drawables;
|
List<Drawable> drawables;
|
||||||
int emoteStateSeq = m_timing.emoteStateSeq(m_emoteAnimationTimer, m_emoteState);
|
int emoteStateSeq = m_timing.emoteStateSeq(m_emoteAnimationTimer, m_emoteState);
|
||||||
|
|
||||||
auto addDrawable = [&](Drawable drawable) {
|
auto addDrawable = [&](Drawable&& drawable) -> Drawable& {
|
||||||
if (mode != PortraitMode::Full && mode != PortraitMode::FullNeutral
|
if (mode != PortraitMode::Full && mode != PortraitMode::FullNeutral
|
||||||
&& mode != PortraitMode::FullNude && mode != PortraitMode::FullNeutralNude) {
|
&& mode != PortraitMode::FullNude && mode != PortraitMode::FullNeutralNude) {
|
||||||
// TODO: make this configurable
|
// TODO: make this configurable
|
||||||
drawable.imagePart().addDirectives("addmask=/humanoid/portraitMask.png;0;0", false);
|
drawable.imagePart().addDirectives(String("addmask=/humanoid/portraitMask.png;0;0"), false);
|
||||||
}
|
}
|
||||||
drawables.append(std::move(drawable));
|
drawables.append(std::move(drawable));
|
||||||
|
return drawables.back();
|
||||||
};
|
};
|
||||||
|
|
||||||
bool dressed = !(mode == PortraitMode::FullNude || mode == PortraitMode::FullNeutralNude);
|
bool dressed = !(mode == PortraitMode::FullNude || mode == PortraitMode::FullNeutralNude);
|
||||||
|
|
||||||
auto helmetMaskDirective = dressed ? getHelmetMaskDirectives() : "";
|
Directives helmetMaskDirective = dressed ? getHelmetMaskDirectives() : Directives();
|
||||||
|
|
||||||
auto personality = m_identity.personality;
|
auto personality = m_identity.personality;
|
||||||
if (mode == PortraitMode::FullNeutral || mode == PortraitMode::FullNeutralNude)
|
if (mode == PortraitMode::FullNeutral || mode == PortraitMode::FullNeutralNude)
|
||||||
@ -755,78 +786,105 @@ List<Drawable> Humanoid::renderPortrait(PortraitMode mode) const {
|
|||||||
|
|
||||||
if (mode != PortraitMode::Head) {
|
if (mode != PortraitMode::Head) {
|
||||||
if (!m_backArmFrameset.empty()) {
|
if (!m_backArmFrameset.empty()) {
|
||||||
String image = strf("%s:%s%s", m_backArmFrameset, personality.armIdle, getBodyDirectives());
|
String image = strf("%s:%s", m_backArmFrameset, personality.armIdle);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.armOffset));
|
Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.armOffset);
|
||||||
|
drawable.imagePart().addDirectives(getBodyDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
if (dressed && !m_backSleeveFrameset.empty()) {
|
if (dressed && !m_backSleeveFrameset.empty()) {
|
||||||
String image = strf("%s:%s%s", m_backSleeveFrameset, personality.armIdle, getChestDirectives());
|
String image = strf("%s:%s", m_backSleeveFrameset, personality.armIdle);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.armOffset));
|
Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.armOffset);
|
||||||
|
drawable.imagePart().addDirectives(getChestDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
if (mode != PortraitMode::Bust) {
|
if (mode != PortraitMode::Bust) {
|
||||||
if (dressed && !m_backArmorFrameset.empty()) {
|
if (dressed && !m_backArmorFrameset.empty()) {
|
||||||
String image = strf("%s:%s%s", m_backArmorFrameset, personality.idle, getBackDirectives());
|
String image = strf("%s:%s", m_backArmorFrameset, personality.idle);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f, true, {}));
|
Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {});
|
||||||
|
drawable.imagePart().addDirectives(getBackDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_headFrameset.empty()) {
|
if (!m_headFrameset.empty()) {
|
||||||
String image = strf("%s:normal%s", m_headFrameset, getBodyDirectives());
|
String image = strf("%s:normal", m_headFrameset);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset));
|
Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset);
|
||||||
|
drawable.imagePart().addDirectives(getBodyDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_emoteFrameset.empty()) {
|
if (!m_emoteFrameset.empty()) {
|
||||||
String image =
|
String image = strf("%s:%s.%s", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq);
|
||||||
strf("%s:%s.%s%s", m_emoteFrameset, emoteFrameBase(m_emoteState), emoteStateSeq, getEmoteDirectives());
|
Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset));
|
drawable.imagePart().addDirectives(getEmoteDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_hairFrameset.empty()) {
|
if (!m_hairFrameset.empty()) {
|
||||||
String image = strf("%s:normal%s", m_hairFrameset, getHairDirectives() + helmetMaskDirective);
|
String image = strf("%s:normal", m_hairFrameset);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset));
|
Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset);
|
||||||
|
drawable.imagePart().addDirectives(getHairDirectives()).addDirectives(helmetMaskDirective);
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_bodyFrameset.empty()) {
|
if (!m_bodyFrameset.empty()) {
|
||||||
String image = strf("%s:%s%s", m_bodyFrameset, personality.idle, getBodyDirectives());
|
String image = strf("%s:%s", m_bodyFrameset, personality.idle);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f, true, {}));
|
Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {});
|
||||||
|
drawable.imagePart().addDirectives(getBodyDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode != PortraitMode::Head) {
|
if (mode != PortraitMode::Head) {
|
||||||
if (dressed && !m_legsArmorFrameset.empty()) {
|
if (dressed && !m_legsArmorFrameset.empty()) {
|
||||||
String image = strf("%s:%s%s", m_legsArmorFrameset, personality.idle, getLegsDirectives());
|
String image = strf("%s:%s", m_legsArmorFrameset, personality.idle);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f, true, {}));
|
Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {});
|
||||||
|
drawable.imagePart().addDirectives(getLegsDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dressed && !m_chestArmorFrameset.empty()) {
|
if (dressed && !m_chestArmorFrameset.empty()) {
|
||||||
String image = strf("%s:%s%s", m_chestArmorFrameset, personality.idle, getChestDirectives());
|
String image = strf("%s:%s", m_chestArmorFrameset, personality.idle);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f, true, {}));
|
Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, {});
|
||||||
|
drawable.imagePart().addDirectives(getChestDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_facialHairFrameset.empty()) {
|
if (!m_facialHairFrameset.empty()) {
|
||||||
String image = strf("%s:normal%s", m_facialHairFrameset, getFacialHairDirectives() + helmetMaskDirective);
|
String image = strf("%s:normal", m_facialHairFrameset);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset));
|
Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset);
|
||||||
|
drawable.imagePart().addDirectives(getFacialHairDirectives()).addDirectives(helmetMaskDirective);
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!m_facialMaskFrameset.empty()) {
|
if (!m_facialMaskFrameset.empty()) {
|
||||||
String image = strf("%s:normal%s", m_facialMaskFrameset, getFacialMaskDirectives() + helmetMaskDirective);
|
String image = strf("%s:normal", m_facialMaskFrameset);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset));
|
Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset);
|
||||||
|
drawable.imagePart().addDirectives(getFacialMaskDirectives()).addDirectives(helmetMaskDirective);
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dressed && !m_headArmorFrameset.empty()) {
|
if (dressed && !m_headArmorFrameset.empty()) {
|
||||||
String image = strf("%s:normal%s", m_headArmorFrameset, getHeadDirectives());
|
String image = strf("%s:normal", m_headArmorFrameset);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.headOffset));
|
Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.headOffset);
|
||||||
|
drawable.imagePart().addDirectives(getHeadDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (mode != PortraitMode::Head) {
|
if (mode != PortraitMode::Head) {
|
||||||
if (!m_frontArmFrameset.empty()) {
|
if (!m_frontArmFrameset.empty()) {
|
||||||
String image = strf("%s:%s%s", m_frontArmFrameset, personality.armIdle, getBodyDirectives());
|
String image = strf("%s:%s", m_frontArmFrameset, personality.armIdle);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.armOffset));
|
Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.armOffset);
|
||||||
|
drawable.imagePart().addDirectives(getBodyDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (dressed && !m_frontSleeveFrameset.empty()) {
|
if (dressed && !m_frontSleeveFrameset.empty()) {
|
||||||
String image = strf("%s:%s%s", m_frontSleeveFrameset, personality.armIdle, getChestDirectives());
|
String image = strf("%s:%s", m_frontSleeveFrameset, personality.armIdle);
|
||||||
addDrawable(Drawable::makeImage(move(image), 1.0f, true, personality.armOffset));
|
Drawable drawable = Drawable::makeImage(move(image), 1.0f, true, personality.armOffset);
|
||||||
|
drawable.imagePart().addDirectives(getChestDirectives());
|
||||||
|
addDrawable(move(drawable));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1062,46 +1120,50 @@ String Humanoid::getVaporTrailFrameset() const {
|
|||||||
return "/humanoid/any/flames.png";
|
return "/humanoid/any/flames.png";
|
||||||
}
|
}
|
||||||
|
|
||||||
String Humanoid::getBodyDirectives() const {
|
Directives Humanoid::getBodyDirectives() const {
|
||||||
return m_identity.bodyDirectives;
|
return m_identity.bodyDirectives;
|
||||||
}
|
}
|
||||||
|
|
||||||
String Humanoid::getHairDirectives() const {
|
Directives Humanoid::getHairDirectives() const {
|
||||||
return m_identity.hairDirectives;
|
return m_identity.hairDirectives;
|
||||||
}
|
}
|
||||||
|
|
||||||
String Humanoid::getEmoteDirectives() const {
|
Directives Humanoid::getEmoteDirectives() const {
|
||||||
return m_identity.emoteDirectives;
|
return m_identity.emoteDirectives;
|
||||||
}
|
}
|
||||||
|
|
||||||
String Humanoid::getFacialHairDirectives() const {
|
Directives Humanoid::getFacialHairDirectives() const {
|
||||||
return m_identity.facialHairDirectives;
|
return m_identity.facialHairDirectives;
|
||||||
}
|
}
|
||||||
|
|
||||||
String Humanoid::getFacialMaskDirectives() const {
|
Directives Humanoid::getFacialMaskDirectives() const {
|
||||||
return m_identity.facialMaskDirectives;
|
return m_identity.facialMaskDirectives;
|
||||||
}
|
}
|
||||||
|
|
||||||
String Humanoid::getHelmetMaskDirectives() const {
|
Directives Humanoid::getHelmetMaskDirectives() const {
|
||||||
return m_helmetMaskDirectives;
|
return m_helmetMaskDirectives;
|
||||||
}
|
}
|
||||||
|
|
||||||
String Humanoid::getHeadDirectives() const {
|
Directives Humanoid::getHeadDirectives() const {
|
||||||
return m_headArmorDirectives;
|
return m_headArmorDirectives;
|
||||||
}
|
}
|
||||||
|
|
||||||
String Humanoid::getChestDirectives() const {
|
Directives Humanoid::getChestDirectives() const {
|
||||||
return m_chestArmorDirectives;
|
return m_chestArmorDirectives;
|
||||||
}
|
}
|
||||||
|
|
||||||
String Humanoid::getLegsDirectives() const {
|
Directives Humanoid::getLegsDirectives() const {
|
||||||
return m_legsArmorDirectives;
|
return m_legsArmorDirectives;
|
||||||
}
|
}
|
||||||
|
|
||||||
String Humanoid::getBackDirectives() const {
|
Directives Humanoid::getBackDirectives() const {
|
||||||
return m_backArmorDirectives;
|
return m_backArmorDirectives;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
int Humanoid::getEmoteStateSequence() const {
|
||||||
|
return m_timing.emoteStateSeq(m_emoteAnimationTimer, m_emoteState);
|
||||||
|
}
|
||||||
|
|
||||||
int Humanoid::getArmStateSequence() const {
|
int Humanoid::getArmStateSequence() const {
|
||||||
int stateSeq = m_timing.stateSeq(m_animationTimer, m_state);
|
int stateSeq = m_timing.stateSeq(m_animationTimer, m_state);
|
||||||
|
|
||||||
|
@ -60,15 +60,15 @@ struct HumanoidIdentity {
|
|||||||
String hairGroup;
|
String hairGroup;
|
||||||
// Must have :normal and :climb
|
// Must have :normal and :climb
|
||||||
String hairType;
|
String hairType;
|
||||||
String hairDirectives;
|
Directives hairDirectives;
|
||||||
String bodyDirectives;
|
Directives bodyDirectives;
|
||||||
String emoteDirectives;
|
Directives emoteDirectives;
|
||||||
String facialHairGroup;
|
String facialHairGroup;
|
||||||
String facialHairType;
|
String facialHairType;
|
||||||
String facialHairDirectives;
|
Directives facialHairDirectives;
|
||||||
String facialMaskGroup;
|
String facialMaskGroup;
|
||||||
String facialMaskType;
|
String facialMaskType;
|
||||||
String facialMaskDirectives;
|
Directives facialMaskDirectives;
|
||||||
|
|
||||||
Personality personality;
|
Personality personality;
|
||||||
Vec4B color;
|
Vec4B color;
|
||||||
@ -125,11 +125,11 @@ public:
|
|||||||
// empty string, it is disabled.
|
// empty string, it is disabled.
|
||||||
|
|
||||||
// Asset directives for the head armor.
|
// Asset directives for the head armor.
|
||||||
void setHeadArmorDirectives(String directives);
|
void setHeadArmorDirectives(Directives directives);
|
||||||
// Must have :normal, climb
|
// Must have :normal, climb
|
||||||
void setHeadArmorFrameset(String headFrameset);
|
void setHeadArmorFrameset(String headFrameset);
|
||||||
// Asset directives for the chest, back and front arms armor.
|
// Asset directives for the chest, back and front arms armor.
|
||||||
void setChestArmorDirectives(String directives);
|
void setChestArmorDirectives(Directives directives);
|
||||||
// Will have :run, :normal, and :duck
|
// Will have :run, :normal, and :duck
|
||||||
void setChestArmorFrameset(String chest);
|
void setChestArmorFrameset(String chest);
|
||||||
// Same as back arm image frames
|
// Same as back arm image frames
|
||||||
@ -138,16 +138,16 @@ public:
|
|||||||
void setFrontSleeveFrameset(String frontSleeveFrameset);
|
void setFrontSleeveFrameset(String frontSleeveFrameset);
|
||||||
|
|
||||||
// Asset directives for the legs armor.
|
// Asset directives for the legs armor.
|
||||||
void setLegsArmorDirectives(String directives);
|
void setLegsArmorDirectives(Directives directives);
|
||||||
// Must have :idle, :duck, :walk[1-8], :run[1-8], :jump[1-4], :fall[1-4]
|
// Must have :idle, :duck, :walk[1-8], :run[1-8], :jump[1-4], :fall[1-4]
|
||||||
void setLegsArmorFrameset(String legsFrameset);
|
void setLegsArmorFrameset(String legsFrameset);
|
||||||
|
|
||||||
// Asset directives for the back armor.
|
// Asset directives for the back armor.
|
||||||
void setBackArmorDirectives(String directives);
|
void setBackArmorDirectives(Directives directives);
|
||||||
// Must have :idle, :duck, :walk[1-8], :run[1-8], :jump[1-4], :fall[1-4]
|
// Must have :idle, :duck, :walk[1-8], :run[1-8], :jump[1-4], :fall[1-4]
|
||||||
void setBackArmorFrameset(String backFrameset);
|
void setBackArmorFrameset(String backFrameset);
|
||||||
|
|
||||||
void setHelmetMaskDirectives(String helmetMaskDirectives);
|
void setHelmetMaskDirectives(Directives helmetMaskDirectives);
|
||||||
|
|
||||||
void setBodyHidden(bool hidden);
|
void setBodyHidden(bool hidden);
|
||||||
|
|
||||||
@ -260,16 +260,16 @@ private:
|
|||||||
String getFrontArmFromIdentity() const;
|
String getFrontArmFromIdentity() const;
|
||||||
String getVaporTrailFrameset() const;
|
String getVaporTrailFrameset() const;
|
||||||
|
|
||||||
String getBodyDirectives() const;
|
Directives getBodyDirectives() const;
|
||||||
String getHairDirectives() const;
|
Directives getHairDirectives() const;
|
||||||
String getEmoteDirectives() const;
|
Directives getEmoteDirectives() const;
|
||||||
String getFacialHairDirectives() const;
|
Directives getFacialHairDirectives() const;
|
||||||
String getFacialMaskDirectives() const;
|
Directives getFacialMaskDirectives() const;
|
||||||
String getHelmetMaskDirectives() const;
|
Directives getHelmetMaskDirectives() const;
|
||||||
String getHeadDirectives() const;
|
Directives getHeadDirectives() const;
|
||||||
String getChestDirectives() const;
|
Directives getChestDirectives() const;
|
||||||
String getLegsDirectives() const;
|
Directives getLegsDirectives() const;
|
||||||
String getBackDirectives() const;
|
Directives getBackDirectives() const;
|
||||||
|
|
||||||
int getEmoteStateSequence() const;
|
int getEmoteStateSequence() const;
|
||||||
int getArmStateSequence() const;
|
int getArmStateSequence() const;
|
||||||
@ -327,14 +327,14 @@ private:
|
|||||||
String m_backSleeveFrameset;
|
String m_backSleeveFrameset;
|
||||||
String m_frontSleeveFrameset;
|
String m_frontSleeveFrameset;
|
||||||
String m_headArmorFrameset;
|
String m_headArmorFrameset;
|
||||||
String m_headArmorDirectives;
|
Directives m_headArmorDirectives;
|
||||||
String m_chestArmorFrameset;
|
String m_chestArmorFrameset;
|
||||||
String m_chestArmorDirectives;
|
Directives m_chestArmorDirectives;
|
||||||
String m_legsArmorFrameset;
|
String m_legsArmorFrameset;
|
||||||
String m_legsArmorDirectives;
|
Directives m_legsArmorDirectives;
|
||||||
String m_backArmorFrameset;
|
String m_backArmorFrameset;
|
||||||
String m_backArmorDirectives;
|
Directives m_backArmorDirectives;
|
||||||
String m_helmetMaskDirectives;
|
Directives m_helmetMaskDirectives;
|
||||||
|
|
||||||
State m_state;
|
State m_state;
|
||||||
HumanoidEmote m_emoteState;
|
HumanoidEmote m_emoteState;
|
||||||
|
@ -10,7 +10,7 @@
|
|||||||
|
|
||||||
namespace Star {
|
namespace Star {
|
||||||
|
|
||||||
Vec2U ImageMetadataDatabase::imageSize(String const& path) const {
|
Vec2U ImageMetadataDatabase::imageSize(AssetPath const& path) const {
|
||||||
MutexLocker locker(m_mutex);
|
MutexLocker locker(m_mutex);
|
||||||
auto i = m_sizeCache.find(path);
|
auto i = m_sizeCache.find(path);
|
||||||
if (i != m_sizeCache.end())
|
if (i != m_sizeCache.end())
|
||||||
@ -24,7 +24,7 @@ Vec2U ImageMetadataDatabase::imageSize(String const& path) const {
|
|||||||
return size;
|
return size;
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Vec2I> ImageMetadataDatabase::imageSpaces(String const& path, Vec2F position, float fillLimit, bool flip) const {
|
List<Vec2I> ImageMetadataDatabase::imageSpaces(AssetPath const& path, Vec2F position, float fillLimit, bool flip) const {
|
||||||
SpacesEntry key = make_tuple(path, Vec2I::round(position), fillLimit, flip);
|
SpacesEntry key = make_tuple(path, Vec2I::round(position), fillLimit, flip);
|
||||||
|
|
||||||
MutexLocker locker(m_mutex);
|
MutexLocker locker(m_mutex);
|
||||||
@ -33,7 +33,7 @@ List<Vec2I> ImageMetadataDatabase::imageSpaces(String const& path, Vec2F positio
|
|||||||
return i->second;
|
return i->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
String filteredPath = filterProcessing(path);
|
auto filteredPath = filterProcessing(path);
|
||||||
SpacesEntry filteredKey = make_tuple(filteredPath, Vec2I::round(position), fillLimit, flip);
|
SpacesEntry filteredKey = make_tuple(filteredPath, Vec2I::round(position), fillLimit, flip);
|
||||||
|
|
||||||
auto j = m_spacesCache.find(filteredKey);
|
auto j = m_spacesCache.find(filteredKey);
|
||||||
@ -88,14 +88,14 @@ List<Vec2I> ImageMetadataDatabase::imageSpaces(String const& path, Vec2F positio
|
|||||||
return spaces;
|
return spaces;
|
||||||
}
|
}
|
||||||
|
|
||||||
RectU ImageMetadataDatabase::nonEmptyRegion(String const& path) const {
|
RectU ImageMetadataDatabase::nonEmptyRegion(AssetPath const& path) const {
|
||||||
MutexLocker locker(m_mutex);
|
MutexLocker locker(m_mutex);
|
||||||
auto i = m_regionCache.find(path);
|
auto i = m_regionCache.find(path);
|
||||||
if (i != m_regionCache.end()) {
|
if (i != m_regionCache.end()) {
|
||||||
return i->second;
|
return i->second;
|
||||||
}
|
}
|
||||||
|
|
||||||
String filteredPath = filterProcessing(path);
|
auto filteredPath = filterProcessing(path);
|
||||||
auto j = m_regionCache.find(filteredPath);
|
auto j = m_regionCache.find(filteredPath);
|
||||||
if (j != m_regionCache.end()) {
|
if (j != m_regionCache.end()) {
|
||||||
m_regionCache[path] = j->second;
|
m_regionCache[path] = j->second;
|
||||||
@ -117,29 +117,27 @@ RectU ImageMetadataDatabase::nonEmptyRegion(String const& path) const {
|
|||||||
return region;
|
return region;
|
||||||
}
|
}
|
||||||
|
|
||||||
String ImageMetadataDatabase::filterProcessing(String const& path) {
|
AssetPath ImageMetadataDatabase::filterProcessing(AssetPath const& path) {
|
||||||
AssetPath components = AssetPath::split(path);
|
AssetPath newPath = { path.basePath, path.subPath, {} };
|
||||||
|
|
||||||
components.directives.filter([](String const& directive) {
|
List<Directives::Entry> filtered;
|
||||||
ImageOperation operation;
|
path.directives.forEach([&](auto const& entry) {
|
||||||
try {
|
ImageOperation const& operation = entry.operation;
|
||||||
operation = imageOperationFromString(directive);
|
if (!(operation.is<HueShiftImageOperation>() ||
|
||||||
} catch (StarException const&) {
|
operation.is<SaturationShiftImageOperation>() ||
|
||||||
return true;
|
operation.is<BrightnessMultiplyImageOperation>() ||
|
||||||
}
|
operation.is<FadeToColorImageOperation>() ||
|
||||||
|
operation.is<ScanLinesImageOperation>() ||
|
||||||
|
operation.is<SetColorImageOperation>())) {
|
||||||
|
filtered.emplace_back(entry);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
return !(operation.is<HueShiftImageOperation>() ||
|
newPath.directives.append(move(filtered));
|
||||||
operation.is<SaturationShiftImageOperation>() ||
|
return newPath;
|
||||||
operation.is<BrightnessMultiplyImageOperation>() ||
|
|
||||||
operation.is<FadeToColorImageOperation>() ||
|
|
||||||
operation.is<ScanLinesImageOperation>() ||
|
|
||||||
operation.is<SetColorImageOperation>());
|
|
||||||
});
|
|
||||||
|
|
||||||
return AssetPath::join(components);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec2U ImageMetadataDatabase::calculateImageSize(String const& path) const {
|
Vec2U ImageMetadataDatabase::calculateImageSize(AssetPath const& path) const {
|
||||||
// Carefully calculate an image's size while trying not to actually load it.
|
// Carefully calculate an image's size while trying not to actually load it.
|
||||||
// In error cases, this will fall back to calling Assets::image, so that image
|
// In error cases, this will fall back to calling Assets::image, so that image
|
||||||
// can possibly produce a missing image asset or properly report the error.
|
// can possibly produce a missing image asset or properly report the error.
|
||||||
@ -150,18 +148,17 @@ Vec2U ImageMetadataDatabase::calculateImageSize(String const& path) const {
|
|||||||
return assets->image(path)->size();
|
return assets->image(path)->size();
|
||||||
};
|
};
|
||||||
|
|
||||||
AssetPath components = AssetPath::split(path);
|
if (!assets->assetExists(path.basePath)) {
|
||||||
if (!assets->assetExists(components.basePath)) {
|
|
||||||
return fallback();
|
return fallback();
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec2U imageSize;
|
Vec2U imageSize;
|
||||||
if (components.subPath) {
|
if (path.subPath) {
|
||||||
auto frames = assets->imageFrames(components.basePath);
|
auto frames = assets->imageFrames(path.basePath);
|
||||||
if (!frames)
|
if (!frames)
|
||||||
return fallback();
|
return fallback();
|
||||||
|
|
||||||
if (auto rect = frames->getRect(*components.subPath))
|
if (auto rect = frames->getRect(*path.subPath))
|
||||||
imageSize = rect->size();
|
imageSize = rect->size();
|
||||||
else
|
else
|
||||||
return fallback();
|
return fallback();
|
||||||
@ -170,13 +167,13 @@ Vec2U ImageMetadataDatabase::calculateImageSize(String const& path) const {
|
|||||||
// so we don't have to call Image::readPngMetadata on the same file more
|
// so we don't have to call Image::readPngMetadata on the same file more
|
||||||
// than once.
|
// than once.
|
||||||
MutexLocker locker(m_mutex);
|
MutexLocker locker(m_mutex);
|
||||||
if (auto size = m_sizeCache.maybe(components.basePath)) {
|
if (auto size = m_sizeCache.maybe(path.basePath)) {
|
||||||
imageSize = *size;
|
imageSize = *size;
|
||||||
} else {
|
} else {
|
||||||
locker.unlock();
|
locker.unlock();
|
||||||
imageSize = get<0>(Image::readPngMetadata(assets->openFile(components.basePath)));
|
imageSize = get<0>(Image::readPngMetadata(assets->openFile(path.basePath)));
|
||||||
locker.lock();
|
locker.lock();
|
||||||
m_sizeCache[components.basePath] = imageSize;
|
m_sizeCache[path.basePath] = imageSize;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -231,19 +228,13 @@ Vec2U ImageMetadataDatabase::calculateImageSize(String const& path) const {
|
|||||||
|
|
||||||
OperationSizeAdjust osa(imageSize);
|
OperationSizeAdjust osa(imageSize);
|
||||||
|
|
||||||
for (auto const& directive : components.directives) {
|
bool complete = path.directives.forEachAbortable([&](auto const& entry) -> bool {
|
||||||
ImageOperation operation;
|
entry.operation.call(osa);
|
||||||
try {
|
return !osa.hasError;
|
||||||
operation = imageOperationFromString(directive);
|
});
|
||||||
} catch (StarException const&) {
|
|
||||||
return fallback();
|
|
||||||
}
|
|
||||||
|
|
||||||
operation.call(osa);
|
if (!complete)
|
||||||
if (osa.hasError) {
|
return fallback();
|
||||||
return fallback();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return imageSize;
|
return imageSize;
|
||||||
}
|
}
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "StarMap.hpp"
|
#include "StarMap.hpp"
|
||||||
#include "StarString.hpp"
|
#include "StarString.hpp"
|
||||||
#include "StarThread.hpp"
|
#include "StarThread.hpp"
|
||||||
|
#include "StarAssetPath.hpp"
|
||||||
|
|
||||||
namespace Star {
|
namespace Star {
|
||||||
|
|
||||||
@ -15,24 +16,24 @@ STAR_CLASS(ImageMetadataDatabase);
|
|||||||
// because they are expensive to compute and cheap to keep around.
|
// because they are expensive to compute and cheap to keep around.
|
||||||
class ImageMetadataDatabase {
|
class ImageMetadataDatabase {
|
||||||
public:
|
public:
|
||||||
Vec2U imageSize(String const& path) const;
|
Vec2U imageSize(AssetPath const& path) const;
|
||||||
List<Vec2I> imageSpaces(String const& path, Vec2F position, float fillLimit, bool flip) const;
|
List<Vec2I> imageSpaces(AssetPath const& path, Vec2F position, float fillLimit, bool flip) const;
|
||||||
RectU nonEmptyRegion(String const& path) const;
|
RectU nonEmptyRegion(AssetPath const& path) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
// Removes image processing directives that don't affect image spaces /
|
// Removes image processing directives that don't affect image spaces /
|
||||||
// non-empty regions.
|
// non-empty regions.
|
||||||
static String filterProcessing(String const& path);
|
static AssetPath filterProcessing(AssetPath const& path);
|
||||||
|
|
||||||
Vec2U calculateImageSize(String const& path) const;
|
Vec2U calculateImageSize(AssetPath const& path) const;
|
||||||
|
|
||||||
// Path, position, fillLimit, and flip
|
// Path, position, fillLimit, and flip
|
||||||
typedef tuple<String, Vec2I, float, bool> SpacesEntry;
|
typedef tuple<AssetPath, Vec2I, float, bool> SpacesEntry;
|
||||||
|
|
||||||
mutable Mutex m_mutex;
|
mutable Mutex m_mutex;
|
||||||
mutable StringMap<Vec2U> m_sizeCache;
|
mutable HashMap<AssetPath, Vec2U> m_sizeCache;
|
||||||
mutable HashMap<SpacesEntry, List<Vec2I>> m_spacesCache;
|
mutable HashMap<SpacesEntry, List<Vec2I>> m_spacesCache;
|
||||||
mutable StringMap<RectU> m_regionCache;
|
mutable HashMap<AssetPath, RectU> m_regionCache;
|
||||||
};
|
};
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -285,4 +285,13 @@ ItemPtr GenericItem::clone() const {
|
|||||||
return make_shared<GenericItem>(*this);
|
return make_shared<GenericItem>(*this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
bool Item::itemsEqual(ItemConstPtr const& a, ItemConstPtr const& b) {
|
||||||
|
if (!a && !b) // Both are null
|
||||||
|
return true;
|
||||||
|
if (a && b) // Both aren't null, compare
|
||||||
|
return a->stackableWith(b);
|
||||||
|
else // One is null, so not equal
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -114,6 +114,8 @@ public:
|
|||||||
// Returns just the dynamic parameters
|
// Returns just the dynamic parameters
|
||||||
Json parameters() const;
|
Json parameters() const;
|
||||||
|
|
||||||
|
static bool itemsEqual(ItemConstPtr const& a, ItemConstPtr const& b);
|
||||||
|
|
||||||
protected:
|
protected:
|
||||||
void setMaxStack(uint64_t maxStack);
|
void setMaxStack(uint64_t maxStack);
|
||||||
void setDescription(String const& description);
|
void setDescription(String const& description);
|
||||||
|
@ -1204,8 +1204,9 @@ List<Drawable> Object::orientationDrawables(size_t orientationIndex) const {
|
|||||||
if (!m_orientationDrawablesCache || orientationIndex != m_orientationDrawablesCache->first) {
|
if (!m_orientationDrawablesCache || orientationIndex != m_orientationDrawablesCache->first) {
|
||||||
m_orientationDrawablesCache = make_pair(orientationIndex, List<Drawable>());
|
m_orientationDrawablesCache = make_pair(orientationIndex, List<Drawable>());
|
||||||
for (auto const& layer : orientation->imageLayers) {
|
for (auto const& layer : orientation->imageLayers) {
|
||||||
auto drawable = layer;
|
Drawable drawable = layer;
|
||||||
drawable.imagePart().image = drawable.imagePart().image.replaceTags(m_imageKeys, true, "default");
|
auto& image = drawable.imagePart().image;
|
||||||
|
image = AssetPath::join(image).replaceTags(m_imageKeys, true, "default");
|
||||||
if (orientation->flipImages)
|
if (orientation->flipImages)
|
||||||
drawable.scale(Vec2F(-1, 1), drawable.boundBox(false).center() - drawable.position);
|
drawable.scale(Vec2F(-1, 1), drawable.boundBox(false).center() - drawable.position);
|
||||||
m_orientationDrawablesCache->second.append(move(drawable));
|
m_orientationDrawablesCache->second.append(move(drawable));
|
||||||
|
@ -157,10 +157,11 @@ List<ObjectOrientationPtr> ObjectDatabase::parseOrientations(String const& path,
|
|||||||
orientation->config = orientationSettings;
|
orientation->config = orientationSettings;
|
||||||
|
|
||||||
if (orientationSettings.contains("imageLayers")) {
|
if (orientationSettings.contains("imageLayers")) {
|
||||||
for (auto layer : orientationSettings.get("imageLayers").iterateArray()) {
|
for (Json layer : orientationSettings.get("imageLayers").iterateArray()) {
|
||||||
|
if (auto image = layer.opt("image"))
|
||||||
|
layer = layer.set("image", AssetPath::relativeTo(path, image->toString()));
|
||||||
Drawable drawable(layer.set("centered", layer.getBool("centered", false)));
|
Drawable drawable(layer.set("centered", layer.getBool("centered", false)));
|
||||||
drawable.scale(1.0f / TilePixels);
|
drawable.scale(1.0f / TilePixels);
|
||||||
drawable.imagePart().image = AssetPath::relativeTo(path, drawable.imagePart().image);
|
|
||||||
orientation->imageLayers.append(drawable);
|
orientation->imageLayers.append(drawable);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
@ -191,7 +192,7 @@ List<ObjectOrientationPtr> ObjectDatabase::parseOrientations(String const& path,
|
|||||||
auto spaceScanSpaces = Set<Vec2I>::from(orientation->spaces);
|
auto spaceScanSpaces = Set<Vec2I>::from(orientation->spaces);
|
||||||
for (auto const& layer : orientation->imageLayers) {
|
for (auto const& layer : orientation->imageLayers) {
|
||||||
spaceScanSpaces.addAll(root.imageMetadataDatabase()->imageSpaces(
|
spaceScanSpaces.addAll(root.imageMetadataDatabase()->imageSpaces(
|
||||||
layer.imagePart().image.replaceTags(StringMap<String>(), true, "default"),
|
AssetPath::join(layer.imagePart().image).replaceTags(StringMap<String>(), true, "default"),
|
||||||
imagePosition,
|
imagePosition,
|
||||||
orientationSettings.getDouble("spaceScan"),
|
orientationSettings.getDouble("spaceScan"),
|
||||||
orientation->flipImages));
|
orientation->flipImages));
|
||||||
@ -578,8 +579,9 @@ List<Drawable> ObjectDatabase::cursorHintDrawables(World const* world, String co
|
|||||||
|
|
||||||
auto orientation = config->orientations.at(orientationIndex);
|
auto orientation = config->orientations.at(orientationIndex);
|
||||||
for (auto const& layer : orientation->imageLayers) {
|
for (auto const& layer : orientation->imageLayers) {
|
||||||
auto drawable = layer;
|
Drawable drawable = layer;
|
||||||
drawable.imagePart().image = drawable.imagePart().image.replaceTags(StringMap<String>(), true, "default");
|
auto& image = drawable.imagePart().image;
|
||||||
|
image = AssetPath::join(image).replaceTags(StringMap<String>(), true, "default");
|
||||||
if (orientation->flipImages)
|
if (orientation->flipImages)
|
||||||
drawable.scale(Vec2F(-1, 1), drawable.boundBox(false).center() - drawable.position);
|
drawable.scale(Vec2F(-1, 1), drawable.boundBox(false).center() - drawable.position);
|
||||||
drawables.append(move(drawable));
|
drawables.append(move(drawable));
|
||||||
|
@ -45,7 +45,6 @@ Particle::Particle() {
|
|||||||
trail = false;
|
trail = false;
|
||||||
flippable = true;
|
flippable = true;
|
||||||
flip = false;
|
flip = false;
|
||||||
directives = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Particle::Particle(Json const& config, String const& path) {
|
Particle::Particle(Json const& config, String const& path) {
|
||||||
@ -69,6 +68,12 @@ Particle::Particle(Json const& config, String const& path) {
|
|||||||
if (type == Type::Animated)
|
if (type == Type::Animated)
|
||||||
initializeAnimation();
|
initializeAnimation();
|
||||||
|
|
||||||
|
auto pathEnd = string.find('?');
|
||||||
|
if (pathEnd == NPos)
|
||||||
|
directives = "";
|
||||||
|
else
|
||||||
|
directives.parse(string.substr(pathEnd));
|
||||||
|
|
||||||
if (config.contains("color"))
|
if (config.contains("color"))
|
||||||
color = jsonToColor(config.get("color"));
|
color = jsonToColor(config.get("color"));
|
||||||
|
|
||||||
@ -96,9 +101,11 @@ Particle::Particle(Json const& config, String const& path) {
|
|||||||
length = config.getFloat("length", 10.0f);
|
length = config.getFloat("length", 10.0f);
|
||||||
|
|
||||||
destructionAction = DestructionActionNames.getLeft(config.getString("destructionAction", "None"));
|
destructionAction = DestructionActionNames.getLeft(config.getString("destructionAction", "None"));
|
||||||
destructionImage = config.getString("destructionImage", "");
|
String destructionImagePath = config.getString("destructionImage", "");
|
||||||
if (destructionAction == DestructionAction::Image)
|
if (destructionAction == DestructionAction::Image)
|
||||||
destructionImage = AssetPath::relativeTo(path, destructionImage);
|
destructionImagePath = AssetPath::relativeTo(path, destructionImagePath);
|
||||||
|
destructionImage = destructionImagePath;
|
||||||
|
|
||||||
destructionTime = config.getFloat("destructionTime", 0.0f);
|
destructionTime = config.getFloat("destructionTime", 0.0f);
|
||||||
|
|
||||||
timeToLive = config.getFloat("timeToLive", 0.0f);
|
timeToLive = config.getFloat("timeToLive", 0.0f);
|
||||||
@ -113,7 +120,6 @@ Particle::Particle(Json const& config, String const& path) {
|
|||||||
ignoreWind = config.getBool("ignoreWind", true);
|
ignoreWind = config.getBool("ignoreWind", true);
|
||||||
|
|
||||||
trail = config.getBool("trail", false);
|
trail = config.getBool("trail", false);
|
||||||
directives = "";
|
|
||||||
}
|
}
|
||||||
|
|
||||||
Json Particle::toJson() const {
|
Json Particle::toJson() const {
|
||||||
@ -134,7 +140,7 @@ Json Particle::toJson() const {
|
|||||||
{"angularVelocity", angularVelocity * 180.0f / Constants::pi},
|
{"angularVelocity", angularVelocity * 180.0f / Constants::pi},
|
||||||
{"length", length},
|
{"length", length},
|
||||||
{"destructionAction", DestructionActionNames.getRight(destructionAction)},
|
{"destructionAction", DestructionActionNames.getRight(destructionAction)},
|
||||||
{"destructionImage", destructionImage},
|
{"destructionImage", AssetPath::join(destructionImage)},
|
||||||
{"destructionTime", destructionTime},
|
{"destructionTime", destructionTime},
|
||||||
{"timeToLive", timeToLive},
|
{"timeToLive", timeToLive},
|
||||||
{"layer", LayerNames.getRight(layer)},
|
{"layer", LayerNames.getRight(layer)},
|
||||||
@ -221,7 +227,7 @@ void Particle::destructionUpdate() {
|
|||||||
size = 1.0f;
|
size = 1.0f;
|
||||||
color = Color::White;
|
color = Color::White;
|
||||||
type = Particle::Type::Textured;
|
type = Particle::Type::Textured;
|
||||||
string = destructionImage;
|
image = destructionImage;
|
||||||
angularVelocity = 0.0f;
|
angularVelocity = 0.0f;
|
||||||
length = 0.0f;
|
length = 0.0f;
|
||||||
rotation = 0.0f;
|
rotation = 0.0f;
|
||||||
@ -232,7 +238,7 @@ void Particle::destructionUpdate() {
|
|||||||
void Particle::initializeAnimation() {
|
void Particle::initializeAnimation() {
|
||||||
if (!animation) {
|
if (!animation) {
|
||||||
animation = Animation(AssetPath::removeDirectives(string));
|
animation = Animation(AssetPath::removeDirectives(string));
|
||||||
animation->addProcessing(AssetPath::getDirectives(string));
|
animation->setProcessing(directives);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -5,6 +5,7 @@
|
|||||||
#include "StarColor.hpp"
|
#include "StarColor.hpp"
|
||||||
#include "StarBiMap.hpp"
|
#include "StarBiMap.hpp"
|
||||||
#include "StarAnimation.hpp"
|
#include "StarAnimation.hpp"
|
||||||
|
#include "StarAssetPath.hpp"
|
||||||
|
|
||||||
namespace Star {
|
namespace Star {
|
||||||
|
|
||||||
@ -75,6 +76,8 @@ struct Particle {
|
|||||||
|
|
||||||
// Used differently depending on the type of the particle.
|
// Used differently depending on the type of the particle.
|
||||||
String string;
|
String string;
|
||||||
|
AssetPath image;
|
||||||
|
Directives directives;
|
||||||
|
|
||||||
Color color;
|
Color color;
|
||||||
Color light;
|
Color light;
|
||||||
@ -95,7 +98,7 @@ struct Particle {
|
|||||||
float length;
|
float length;
|
||||||
|
|
||||||
DestructionAction destructionAction;
|
DestructionAction destructionAction;
|
||||||
String destructionImage;
|
AssetPath destructionImage;
|
||||||
float destructionTime;
|
float destructionTime;
|
||||||
|
|
||||||
float timeToLive;
|
float timeToLive;
|
||||||
@ -110,7 +113,6 @@ struct Particle {
|
|||||||
bool trail;
|
bool trail;
|
||||||
|
|
||||||
Maybe<Animation> animation;
|
Maybe<Animation> animation;
|
||||||
String directives;
|
|
||||||
};
|
};
|
||||||
|
|
||||||
DataStream& operator<<(DataStream& ds, Particle const& particle);
|
DataStream& operator<<(DataStream& ds, Particle const& particle);
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include "StarRoot.hpp"
|
#include "StarRoot.hpp"
|
||||||
#include "StarStoredFunctions.hpp"
|
#include "StarStoredFunctions.hpp"
|
||||||
#include "StarPlayer.hpp"
|
#include "StarPlayer.hpp"
|
||||||
|
#include "StarDirectives.hpp"
|
||||||
|
|
||||||
namespace Star {
|
namespace Star {
|
||||||
|
|
||||||
@ -20,7 +21,7 @@ ArmorItem::ArmorItem(Json const& config, String const& directory, Json const& da
|
|||||||
|
|
||||||
m_directives = instanceValue("directives", "").toString();
|
m_directives = instanceValue("directives", "").toString();
|
||||||
m_colorOptions = colorDirectivesFromConfig(config.getArray("colorOptions", JsonArray{""}));
|
m_colorOptions = colorDirectivesFromConfig(config.getArray("colorOptions", JsonArray{""}));
|
||||||
if (m_directives.empty())
|
if (!m_directives)
|
||||||
m_directives = "?" + m_colorOptions.wrap(instanceValue("colorIndex", 0).toUInt());
|
m_directives = "?" + m_colorOptions.wrap(instanceValue("colorIndex", 0).toUInt());
|
||||||
refreshIconDrawables();
|
refreshIconDrawables();
|
||||||
|
|
||||||
@ -39,7 +40,7 @@ List<String> const& ArmorItem::colorOptions() {
|
|||||||
return m_colorOptions;
|
return m_colorOptions;
|
||||||
}
|
}
|
||||||
|
|
||||||
String const& ArmorItem::directives() const {
|
Directives const& ArmorItem::directives() const {
|
||||||
return m_directives;
|
return m_directives;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,9 +89,11 @@ HeadArmor::HeadArmor(Json const& config, String const& directory, Json const& da
|
|||||||
m_maleImage = AssetPath::relativeTo(directory, config.getString("maleFrames"));
|
m_maleImage = AssetPath::relativeTo(directory, config.getString("maleFrames"));
|
||||||
m_femaleImage = AssetPath::relativeTo(directory, config.getString("femaleFrames"));
|
m_femaleImage = AssetPath::relativeTo(directory, config.getString("femaleFrames"));
|
||||||
|
|
||||||
m_maskDirectives = instanceValue("mask").toString();
|
String maskDirectivesStr = instanceValue("mask").toString();
|
||||||
if (!m_maskDirectives.empty() && !m_maskDirectives.contains("?"))
|
if (!maskDirectivesStr.empty() && !maskDirectivesStr.contains("?"))
|
||||||
m_maskDirectives = "?addmask=" + AssetPath::relativeTo(directory, m_maskDirectives) + ";0;0";
|
m_maskDirectives = "?addmask=" + AssetPath::relativeTo(directory, maskDirectivesStr) + ";0;0";
|
||||||
|
else
|
||||||
|
m_maskDirectives = maskDirectivesStr;
|
||||||
}
|
}
|
||||||
|
|
||||||
ItemPtr HeadArmor::clone() const {
|
ItemPtr HeadArmor::clone() const {
|
||||||
@ -104,7 +107,7 @@ String const& HeadArmor::frameset(Gender gender) const {
|
|||||||
return m_femaleImage;
|
return m_femaleImage;
|
||||||
}
|
}
|
||||||
|
|
||||||
String const& HeadArmor::maskDirectives() const {
|
Directives const& HeadArmor::maskDirectives() const {
|
||||||
return m_maskDirectives;
|
return m_maskDirectives;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,7 +25,7 @@ public:
|
|||||||
|
|
||||||
List<String> const& colorOptions();
|
List<String> const& colorOptions();
|
||||||
|
|
||||||
String const& directives() const;
|
Directives const& directives() const;
|
||||||
|
|
||||||
bool hideBody() const;
|
bool hideBody() const;
|
||||||
|
|
||||||
@ -38,7 +38,7 @@ private:
|
|||||||
List<String> m_colorOptions;
|
List<String> m_colorOptions;
|
||||||
List<PersistentStatusEffect> m_statusEffects;
|
List<PersistentStatusEffect> m_statusEffects;
|
||||||
StringSet m_effectSources;
|
StringSet m_effectSources;
|
||||||
String m_directives;
|
Directives m_directives;
|
||||||
bool m_hideBody;
|
bool m_hideBody;
|
||||||
Maybe<String> m_techModule;
|
Maybe<String> m_techModule;
|
||||||
};
|
};
|
||||||
@ -51,14 +51,14 @@ public:
|
|||||||
virtual ItemPtr clone() const;
|
virtual ItemPtr clone() const;
|
||||||
|
|
||||||
String const& frameset(Gender gender) const;
|
String const& frameset(Gender gender) const;
|
||||||
String const& maskDirectives() const;
|
Directives const& maskDirectives() const;
|
||||||
|
|
||||||
virtual List<Drawable> preview(PlayerPtr const& viewer = {}) const;
|
virtual List<Drawable> preview(PlayerPtr const& viewer = {}) const;
|
||||||
|
|
||||||
private:
|
private:
|
||||||
String m_maleImage;
|
String m_maleImage;
|
||||||
String m_femaleImage;
|
String m_femaleImage;
|
||||||
String m_maskDirectives;
|
Directives m_maskDirectives;
|
||||||
};
|
};
|
||||||
|
|
||||||
class ChestArmor : public ArmorItem, public PreviewableItem {
|
class ChestArmor : public ArmorItem, public PreviewableItem {
|
||||||
|
@ -17,8 +17,10 @@ MaterialItem::MaterialItem(Json const& config, String const& directory, Json con
|
|||||||
if (materialHueShift() != MaterialHue()) {
|
if (materialHueShift() != MaterialHue()) {
|
||||||
auto drawables = iconDrawables();
|
auto drawables = iconDrawables();
|
||||||
for (auto& d : drawables) {
|
for (auto& d : drawables) {
|
||||||
if (d.isImage())
|
if (d.isImage()) {
|
||||||
d.imagePart().addDirectives(strf("?hueshift=%s", materialHueToDegrees(m_materialHueShift)), false);
|
String image = strf("?hueshift=%s", materialHueToDegrees(m_materialHueShift));
|
||||||
|
d.imagePart().addDirectives(image, false);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
setIconDrawables(move(drawables));
|
setIconDrawables(move(drawables));
|
||||||
}
|
}
|
||||||
|
@ -610,7 +610,7 @@ List<Drawable> PaintingBeamTool::drawables() const {
|
|||||||
auto result = BeamItem::drawables();
|
auto result = BeamItem::drawables();
|
||||||
for (auto& entry : result) {
|
for (auto& entry : result) {
|
||||||
if (entry.isImage())
|
if (entry.isImage())
|
||||||
entry.imagePart().image = entry.imagePart().image + m_colorKeys[m_colorIndex];
|
entry.imagePart().image.directives += m_colorKeys[m_colorIndex];
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -13,16 +13,16 @@ AssetTextureGroup::AssetTextureGroup(TextureGroupPtr textureGroup)
|
|||||||
Root::singleton().registerReloadListener(m_reloadTracker);
|
Root::singleton().registerReloadListener(m_reloadTracker);
|
||||||
}
|
}
|
||||||
|
|
||||||
TexturePtr AssetTextureGroup::loadTexture(String const& imageName) {
|
TexturePtr AssetTextureGroup::loadTexture(AssetPath const& imagePath) {
|
||||||
return loadTexture(imageName, false);
|
return loadTexture(imagePath, false);
|
||||||
}
|
}
|
||||||
|
|
||||||
TexturePtr AssetTextureGroup::tryTexture(String const& imageName) {
|
TexturePtr AssetTextureGroup::tryTexture(AssetPath const& imagePath) {
|
||||||
return loadTexture(imageName, true);
|
return loadTexture(imagePath, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
bool AssetTextureGroup::textureLoaded(String const& imageName) const {
|
bool AssetTextureGroup::textureLoaded(AssetPath const& imagePath) const {
|
||||||
return m_textureMap.contains(imageName);
|
return m_textureMap.contains(imagePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
void AssetTextureGroup::cleanup(int64_t textureTimeout) {
|
void AssetTextureGroup::cleanup(int64_t textureTimeout) {
|
||||||
@ -50,8 +50,8 @@ void AssetTextureGroup::cleanup(int64_t textureTimeout) {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
TexturePtr AssetTextureGroup::loadTexture(String const& imageName, bool tryTexture) {
|
TexturePtr AssetTextureGroup::loadTexture(AssetPath const& imagePath, bool tryTexture) {
|
||||||
if (auto p = m_textureMap.ptr(imageName)) {
|
if (auto p = m_textureMap.ptr(imagePath)) {
|
||||||
p->second = Time::monotonicMilliseconds();
|
p->second = Time::monotonicMilliseconds();
|
||||||
return p->first;
|
return p->first;
|
||||||
}
|
}
|
||||||
@ -60,9 +60,9 @@ TexturePtr AssetTextureGroup::loadTexture(String const& imageName, bool tryTextu
|
|||||||
|
|
||||||
ImageConstPtr image;
|
ImageConstPtr image;
|
||||||
if (tryTexture)
|
if (tryTexture)
|
||||||
image = assets->tryImage(imageName);
|
image = assets->tryImage(imagePath);
|
||||||
else
|
else
|
||||||
image = assets->image(imageName);
|
image = assets->image(imagePath);
|
||||||
|
|
||||||
if (!image)
|
if (!image)
|
||||||
return {};
|
return {};
|
||||||
@ -72,11 +72,11 @@ TexturePtr AssetTextureGroup::loadTexture(String const& imageName, bool tryTextu
|
|||||||
// in the texture group for these, so we keep track of the image pointers
|
// in the texture group for these, so we keep track of the image pointers
|
||||||
// returned to deduplicate them.
|
// returned to deduplicate them.
|
||||||
if (auto existingTexture = m_textureDeduplicationMap.value(image)) {
|
if (auto existingTexture = m_textureDeduplicationMap.value(image)) {
|
||||||
m_textureMap.add(imageName, {existingTexture, Time::monotonicMilliseconds()});
|
m_textureMap.add(imagePath, {existingTexture, Time::monotonicMilliseconds()});
|
||||||
return existingTexture;
|
return existingTexture;
|
||||||
} else {
|
} else {
|
||||||
auto texture = m_textureGroup->create(*image);
|
auto texture = m_textureGroup->create(*image);
|
||||||
m_textureMap.add(imageName, {texture, Time::monotonicMilliseconds()});
|
m_textureMap.add(imagePath, {texture, Time::monotonicMilliseconds()});
|
||||||
m_textureDeduplicationMap.add(image, texture);
|
m_textureDeduplicationMap.add(image, texture);
|
||||||
return texture;
|
return texture;
|
||||||
}
|
}
|
||||||
|
@ -6,6 +6,7 @@
|
|||||||
#include "StarBiMap.hpp"
|
#include "StarBiMap.hpp"
|
||||||
#include "StarListener.hpp"
|
#include "StarListener.hpp"
|
||||||
#include "StarRenderer.hpp"
|
#include "StarRenderer.hpp"
|
||||||
|
#include "StarAssetPath.hpp"
|
||||||
|
|
||||||
namespace Star {
|
namespace Star {
|
||||||
|
|
||||||
@ -20,14 +21,14 @@ public:
|
|||||||
|
|
||||||
// Load the given texture into the texture group if it is not loaded, and
|
// Load the given texture into the texture group if it is not loaded, and
|
||||||
// return the texture pointer.
|
// return the texture pointer.
|
||||||
TexturePtr loadTexture(String const& imageName);
|
TexturePtr loadTexture(AssetPath const& imagePath);
|
||||||
|
|
||||||
// If the texture is loaded and ready, returns the texture pointer, otherwise
|
// If the texture is loaded and ready, returns the texture pointer, otherwise
|
||||||
// queues the texture using Assets::tryImage and returns nullptr.
|
// queues the texture using Assets::tryImage and returns nullptr.
|
||||||
TexturePtr tryTexture(String const& imageName);
|
TexturePtr tryTexture(AssetPath const& imagePath);
|
||||||
|
|
||||||
// Has the texture been loaded?
|
// Has the texture been loaded?
|
||||||
bool textureLoaded(String const& imageName) const;
|
bool textureLoaded(AssetPath const& imagePath) const;
|
||||||
|
|
||||||
// Frees textures that haven't been used in more than 'textureTimeout' time.
|
// Frees textures that haven't been used in more than 'textureTimeout' time.
|
||||||
// If Root has been reloaded, will simply clear the texture group.
|
// If Root has been reloaded, will simply clear the texture group.
|
||||||
@ -37,10 +38,10 @@ private:
|
|||||||
// Returns the texture parameters. If tryTexture is true, then returns none
|
// Returns the texture parameters. If tryTexture is true, then returns none
|
||||||
// if the texture is not loaded, and queues it, otherwise loads texture
|
// if the texture is not loaded, and queues it, otherwise loads texture
|
||||||
// immediately
|
// immediately
|
||||||
TexturePtr loadTexture(String const& imageName, bool tryTexture);
|
TexturePtr loadTexture(AssetPath const& imagePath, bool tryTexture);
|
||||||
|
|
||||||
TextureGroupPtr m_textureGroup;
|
TextureGroupPtr m_textureGroup;
|
||||||
StringMap<pair<TexturePtr, int64_t>> m_textureMap;
|
HashMap<AssetPath, pair<TexturePtr, int64_t>> m_textureMap;
|
||||||
HashMap<ImageConstPtr, TexturePtr> m_textureDeduplicationMap;
|
HashMap<ImageConstPtr, TexturePtr> m_textureDeduplicationMap;
|
||||||
TrackerListenerPtr m_reloadTracker;
|
TrackerListenerPtr m_reloadTracker;
|
||||||
};
|
};
|
||||||
|
@ -135,7 +135,7 @@ void GuiContext::resetInterfaceScissorRect() {
|
|||||||
renderer()->setScissorRect({});
|
renderer()->setScissorRect({});
|
||||||
}
|
}
|
||||||
|
|
||||||
Vec2U GuiContext::textureSize(String const& texName) {
|
Vec2U GuiContext::textureSize(AssetPath const& texName) {
|
||||||
return assetTextureGroup()->loadTexture(texName)->size();
|
return assetTextureGroup()->loadTexture(texName)->size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -143,15 +143,15 @@ void GuiContext::drawQuad(RectF const& screenCoords, Vec4B const& color) {
|
|||||||
renderer()->render(renderFlatRect(screenCoords, color, 0.0f));
|
renderer()->render(renderFlatRect(screenCoords, color, 0.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiContext::drawQuad(String const& texName, RectF const& screenCoords, Vec4B const& color) {
|
void GuiContext::drawQuad(AssetPath const& texName, RectF const& screenCoords, Vec4B const& color) {
|
||||||
renderer()->render(renderTexturedRect(assetTextureGroup()->loadTexture(texName), screenCoords, color, 0.0f));
|
renderer()->render(renderTexturedRect(assetTextureGroup()->loadTexture(texName), screenCoords, color, 0.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiContext::drawQuad(String const& texName, Vec2F const& screenPos, int pixelRatio, Vec4B const& color) {
|
void GuiContext::drawQuad(AssetPath const& texName, Vec2F const& screenPos, int pixelRatio, Vec4B const& color) {
|
||||||
renderer()->render(renderTexturedRect(assetTextureGroup()->loadTexture(texName), screenPos, pixelRatio, color, 0.0f));
|
renderer()->render(renderTexturedRect(assetTextureGroup()->loadTexture(texName), screenPos, pixelRatio, color, 0.0f));
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiContext::drawQuad(String const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color) {
|
void GuiContext::drawQuad(AssetPath const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color) {
|
||||||
renderer()->render(RenderQuad{assetTextureGroup()->loadTexture(texName),
|
renderer()->render(RenderQuad{assetTextureGroup()->loadTexture(texName),
|
||||||
RenderVertex{Vec2F(screenCoords.xMin(), screenCoords.yMin()), Vec2F(texCoords.xMin(), texCoords.yMin()), color, 0.0f},
|
RenderVertex{Vec2F(screenCoords.xMin(), screenCoords.yMin()), Vec2F(texCoords.xMin(), texCoords.yMin()), color, 0.0f},
|
||||||
RenderVertex{Vec2F(screenCoords.xMax(), screenCoords.yMin()), Vec2F(texCoords.xMax(), texCoords.yMin()), color, 0.0f},
|
RenderVertex{Vec2F(screenCoords.xMax(), screenCoords.yMin()), Vec2F(texCoords.xMax(), texCoords.yMin()), color, 0.0f},
|
||||||
@ -209,15 +209,15 @@ void GuiContext::drawInterfaceQuad(RectF const& screenCoords, Vec4B const& color
|
|||||||
drawQuad(screenCoords.scaled(interfaceScale()), color);
|
drawQuad(screenCoords.scaled(interfaceScale()), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiContext::drawInterfaceQuad(String const& texName, Vec2F const& screenCoords, Vec4B const& color) {
|
void GuiContext::drawInterfaceQuad(AssetPath const& texName, Vec2F const& screenCoords, Vec4B const& color) {
|
||||||
drawQuad(texName, screenCoords * interfaceScale(), interfaceScale(), color);
|
drawQuad(texName, screenCoords * interfaceScale(), interfaceScale(), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiContext::drawInterfaceQuad(String const& texName, Vec2F const& screenCoords, float scale, Vec4B const& color) {
|
void GuiContext::drawInterfaceQuad(AssetPath const& texName, Vec2F const& screenCoords, float scale, Vec4B const& color) {
|
||||||
drawQuad(texName, screenCoords * interfaceScale(), interfaceScale() * scale, color);
|
drawQuad(texName, screenCoords * interfaceScale(), interfaceScale() * scale, color);
|
||||||
}
|
}
|
||||||
|
|
||||||
void GuiContext::drawInterfaceQuad(String const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color) {
|
void GuiContext::drawInterfaceQuad(AssetPath const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color) {
|
||||||
drawQuad(texName, texCoords, screenCoords.scaled(interfaceScale()), color);
|
drawQuad(texName, texCoords, screenCoords.scaled(interfaceScale()), color);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -313,7 +313,7 @@ bool GuiContext::trySetCursor(Drawable const& drawable, Vec2I const& offset, int
|
|||||||
|
|
||||||
auto assets = Root::singleton().assets();
|
auto assets = Root::singleton().assets();
|
||||||
auto& imagePath = drawable.imagePart().image;
|
auto& imagePath = drawable.imagePart().image;
|
||||||
return applicationController()->setCursorImage(imagePath, assets->image(imagePath), pixelRatio, offset);
|
return applicationController()->setCursorImage(AssetPath::join(imagePath), assets->image(imagePath), pixelRatio, offset);
|
||||||
}
|
}
|
||||||
|
|
||||||
RectF GuiContext::renderText(String const& s, TextPositioning const& position) {
|
RectF GuiContext::renderText(String const& s, TextPositioning const& position) {
|
||||||
|
@ -62,12 +62,12 @@ public:
|
|||||||
void setInterfaceScissorRect(RectI const& scissor);
|
void setInterfaceScissorRect(RectI const& scissor);
|
||||||
void resetInterfaceScissorRect();
|
void resetInterfaceScissorRect();
|
||||||
|
|
||||||
Vec2U textureSize(String const& texName);
|
Vec2U textureSize(AssetPath const& texName);
|
||||||
|
|
||||||
void drawQuad(RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
void drawQuad(RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
||||||
void drawQuad(String const& texName, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
void drawQuad(AssetPath const& texName, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
||||||
void drawQuad(String const& texName, Vec2F const& screenPos, int pixelRatio, Vec4B const& color = Vec4B::filled(255));
|
void drawQuad(AssetPath const& texName, Vec2F const& screenPos, int pixelRatio, Vec4B const& color = Vec4B::filled(255));
|
||||||
void drawQuad(String const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
void drawQuad(AssetPath const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
||||||
|
|
||||||
void drawDrawable(Drawable drawable, Vec2F const& screenPos, int pixelRatio, Vec4B const& color = Vec4B::filled(255));
|
void drawDrawable(Drawable drawable, Vec2F const& screenPos, int pixelRatio, Vec4B const& color = Vec4B::filled(255));
|
||||||
|
|
||||||
@ -84,9 +84,9 @@ public:
|
|||||||
void drawInterfaceTriangles(List<tuple<Vec2F, Vec2F, Vec2F>> const& triangles, Vec4B const& color);
|
void drawInterfaceTriangles(List<tuple<Vec2F, Vec2F, Vec2F>> const& triangles, Vec4B const& color);
|
||||||
|
|
||||||
void drawInterfaceQuad(RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
void drawInterfaceQuad(RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
||||||
void drawInterfaceQuad(String const& texName, Vec2F const& screenPos, Vec4B const& color = Vec4B::filled(255));
|
void drawInterfaceQuad(AssetPath const& texName, Vec2F const& screenPos, Vec4B const& color = Vec4B::filled(255));
|
||||||
void drawInterfaceQuad(String const& texName, Vec2F const& screenPos, float scale, Vec4B const& color = Vec4B::filled(255));
|
void drawInterfaceQuad(AssetPath const& texName, Vec2F const& screenPos, float scale, Vec4B const& color = Vec4B::filled(255));
|
||||||
void drawInterfaceQuad(String const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
void drawInterfaceQuad(AssetPath const& texName, RectF const& texCoords, RectF const& screenCoords, Vec4B const& color = Vec4B::filled(255));
|
||||||
|
|
||||||
void drawImageStretchSet(ImageStretchSet const& imageSet, RectF const& screenPos, GuiDirection direction = GuiDirection::Horizontal, Vec4B const& color = Vec4B::filled(255));
|
void drawImageStretchSet(ImageStretchSet const& imageSet, RectF const& screenPos, GuiDirection direction = GuiDirection::Horizontal, Vec4B const& color = Vec4B::filled(255));
|
||||||
|
|
||||||
|
@ -41,9 +41,10 @@ void ImageWidget::setRotation(float rotation) {
|
|||||||
}
|
}
|
||||||
|
|
||||||
String ImageWidget::image() const {
|
String ImageWidget::image() const {
|
||||||
if (!m_drawables.size())
|
if (m_drawables.empty())
|
||||||
return "";
|
return "";
|
||||||
return m_drawables[0].imagePart().image;
|
else
|
||||||
|
return AssetPath::join(m_drawables[0].imagePart().image);
|
||||||
}
|
}
|
||||||
|
|
||||||
void ImageWidget::setDrawables(List<Drawable> drawables) {
|
void ImageWidget::setDrawables(List<Drawable> drawables) {
|
||||||
|
@ -153,8 +153,7 @@ void ItemSlotWidget::renderImpl() {
|
|||||||
|
|
||||||
if (m_showLinkIndicator) {
|
if (m_showLinkIndicator) {
|
||||||
// TODO: Hardcoded
|
// TODO: Hardcoded
|
||||||
context()->drawInterfaceQuad("/interface/inventory/itemlinkindicator.png",
|
context()->drawInterfaceQuad(String("/interface/inventory/itemlinkindicator.png"), Vec2F(screenPosition() - Vec2I(1, 1)));
|
||||||
Vec2F(screenPosition() - Vec2I(1, 1)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
for (auto i : iconDrawables)
|
for (auto i : iconDrawables)
|
||||||
@ -179,7 +178,7 @@ void ItemSlotWidget::renderImpl() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
int frame = (int)roundf(m_progress * 18); // TODO: Hardcoded lol
|
int frame = (int)roundf(m_progress * 18); // TODO: Hardcoded lol
|
||||||
context()->drawInterfaceQuad(strf("/interface/cooldown.png:%d", frame), Vec2F(screenPosition()));
|
context()->drawInterfaceQuad(String(strf("/interface/cooldown.png:%d", frame)), Vec2F(screenPosition()));
|
||||||
|
|
||||||
if (m_item->count() > 1 && m_showCount) { // we don't need to tell people that there's only 1 of something
|
if (m_item->count() > 1 && m_showCount) { // we don't need to tell people that there's only 1 of something
|
||||||
context()->setFont(m_font);
|
context()->setFont(m_font);
|
||||||
@ -192,7 +191,7 @@ void ItemSlotWidget::renderImpl() {
|
|||||||
} else if (m_drawBackingImageWhenEmpty && m_backingImage != "") {
|
} else if (m_drawBackingImageWhenEmpty && m_backingImage != "") {
|
||||||
context()->drawInterfaceQuad(m_backingImage, Vec2F(screenPosition()));
|
context()->drawInterfaceQuad(m_backingImage, Vec2F(screenPosition()));
|
||||||
int frame = (int)roundf(m_progress * 18); // TODO: Hardcoded lol
|
int frame = (int)roundf(m_progress * 18); // TODO: Hardcoded lol
|
||||||
context()->drawInterfaceQuad(strf("/interface/cooldown.png:%d", frame), Vec2F(screenPosition()));
|
context()->drawInterfaceQuad(String(strf("/interface/cooldown.png:%d", frame)), Vec2F(screenPosition()));
|
||||||
}
|
}
|
||||||
|
|
||||||
if (m_highlightEnabled) {
|
if (m_highlightEnabled) {
|
||||||
|
@ -38,7 +38,7 @@ void PortraitWidget::renderImpl() {
|
|||||||
}
|
}
|
||||||
if (m_entity) {
|
if (m_entity) {
|
||||||
List<Drawable> portrait = m_entity->portrait(m_portraitMode);
|
List<Drawable> portrait = m_entity->portrait(m_portraitMode);
|
||||||
for (auto i : portrait) {
|
for (auto& i : portrait) {
|
||||||
i.scale(m_scale);
|
i.scale(m_scale);
|
||||||
context()->drawInterfaceDrawable(i, Vec2F(screenPosition() + offset));
|
context()->drawInterfaceDrawable(i, Vec2F(screenPosition() + offset));
|
||||||
}
|
}
|
||||||
|
@ -31,12 +31,12 @@ private:
|
|||||||
|
|
||||||
PortraitEntityPtr m_entity;
|
PortraitEntityPtr m_entity;
|
||||||
PortraitMode m_portraitMode;
|
PortraitMode m_portraitMode;
|
||||||
String m_noEntityImageFull;
|
AssetPath m_noEntityImageFull;
|
||||||
String m_noEntityImagePart;
|
AssetPath m_noEntityImagePart;
|
||||||
float m_scale;
|
float m_scale;
|
||||||
|
|
||||||
bool m_iconMode;
|
bool m_iconMode;
|
||||||
String m_iconImage;
|
AssetPath m_iconImage;
|
||||||
Vec2I m_iconOffset;
|
Vec2I m_iconOffset;
|
||||||
};
|
};
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user