|
|
@@ -2,13 +2,21 @@
|
|
|
|
|
|
#include "CRenderable.h"
|
|
|
|
|
|
+#include <algorithm>
|
|
|
+#include <cmath>
|
|
|
+#include <cstring>
|
|
|
+#include <iterator>
|
|
|
+#include <optional>
|
|
|
#include <sstream>
|
|
|
|
|
|
#include <glm/gtc/matrix_transform.hpp>
|
|
|
|
|
|
+#include "WallpaperEngine/Data/Model/DynamicValue.h"
|
|
|
#include "WallpaperEngine/Data/Model/Material.h"
|
|
|
#include "WallpaperEngine/Data/Model/Object.h"
|
|
|
+#include "WallpaperEngine/Data/Model/UserSetting.h"
|
|
|
#include "WallpaperEngine/Data/Parsers/MaterialParser.h"
|
|
|
+#include "WallpaperEngine/Logging/Log.h"
|
|
|
|
|
|
using namespace WallpaperEngine;
|
|
|
using namespace WallpaperEngine::Render::Objects;
|
|
|
@@ -16,6 +24,152 @@ using namespace WallpaperEngine::Render::Objects::Effects;
|
|
|
using namespace WallpaperEngine::Data::Parsers;
|
|
|
using namespace WallpaperEngine::Data::Builders;
|
|
|
|
|
|
+namespace {
|
|
|
+glm::vec2 rotateVec2 (const glm::vec2& value, float angle) {
|
|
|
+ const float cosAngle = std::cos (angle);
|
|
|
+ const float sinAngle = std::sin (angle);
|
|
|
+ return { value.x * cosAngle - value.y * sinAngle, value.x * sinAngle + value.y * cosAngle };
|
|
|
+}
|
|
|
+
|
|
|
+UserSettingUniquePtr makeStaticSetting (const glm::vec3& value) {
|
|
|
+ auto dynamicValue = std::make_unique<DynamicValue> (value);
|
|
|
+ return std::make_unique<UserSetting> (
|
|
|
+ UserSetting { .value = std::move (dynamicValue), .property = nullptr, .condition = std::nullopt }
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+UserSettingUniquePtr makeStaticSetting (float value) {
|
|
|
+ auto dynamicValue = std::make_unique<DynamicValue> (value);
|
|
|
+ return std::make_unique<UserSetting> (
|
|
|
+ UserSetting { .value = std::move (dynamicValue), .property = nullptr, .condition = std::nullopt }
|
|
|
+ );
|
|
|
+}
|
|
|
+
|
|
|
+bool isMagentaNeonTint (const glm::vec3& color) {
|
|
|
+ return color.r > 0.55f && color.g < 0.25f && color.b > 0.45f;
|
|
|
+}
|
|
|
+
|
|
|
+std::optional<glm::vec3> findMagentaCompositeTint (const Image& image, const std::vector<int>& skippedEffectIds) {
|
|
|
+ for (const auto& effect : image.effects) {
|
|
|
+ if (std::find (skippedEffectIds.begin (), skippedEffectIds.end (), static_cast<int> (effect->id))
|
|
|
+ != skippedEffectIds.end ()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+ if (!effect->visible->value->getBool ()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ for (const auto& passOverride : effect->passOverrides) {
|
|
|
+ const auto compositeCombo = passOverride->combos.find ("COMPOSITE");
|
|
|
+ if (compositeCombo == passOverride->combos.end () || compositeCombo->second != 2) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ const auto compositeColor = passOverride->constants.find ("compositecolor");
|
|
|
+ if (compositeColor == passOverride->constants.end () || compositeColor->second == nullptr
|
|
|
+ || compositeColor->second->value == nullptr) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ const auto tint = compositeColor->second->value->getVec3 ();
|
|
|
+ if (isMagentaNeonTint (tint)) {
|
|
|
+ return tint;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::nullopt;
|
|
|
+}
|
|
|
+
|
|
|
+uint32_t readU32 (const std::vector<char>& data, const size_t offset) {
|
|
|
+ uint32_t value = 0;
|
|
|
+ std::memcpy (&value, data.data () + offset, sizeof (value));
|
|
|
+ return value;
|
|
|
+}
|
|
|
+
|
|
|
+float readFloat (const std::vector<char>& data, const size_t offset) {
|
|
|
+ float value = 0.0f;
|
|
|
+ std::memcpy (&value, data.data () + offset, sizeof (value));
|
|
|
+ return value;
|
|
|
+}
|
|
|
+
|
|
|
+struct PuppetMeshBlock {
|
|
|
+ size_t headerOffset = 0;
|
|
|
+ uint32_t vertexBytes = 0;
|
|
|
+ uint32_t indexBytes = 0;
|
|
|
+};
|
|
|
+
|
|
|
+std::optional<PuppetMeshBlock> findPuppetMeshBlock (
|
|
|
+ const std::vector<char>& data, size_t markerSize, size_t mdlsOffset, size_t meshHeaderSize, size_t vertexStride
|
|
|
+) {
|
|
|
+ for (size_t offset = markerSize; offset + meshHeaderSize + sizeof (uint32_t) < mdlsOffset; offset++) {
|
|
|
+ const uint32_t candidateVertexBytes = readU32 (data, offset + sizeof (uint32_t));
|
|
|
+ const size_t verticesOffset = offset + meshHeaderSize;
|
|
|
+ const size_t indexLengthOffset = verticesOffset + candidateVertexBytes;
|
|
|
+
|
|
|
+ if (candidateVertexBytes == 0 || candidateVertexBytes % vertexStride != 0
|
|
|
+ || indexLengthOffset + sizeof (uint32_t) > mdlsOffset) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ const uint32_t candidateIndexBytes = readU32 (data, indexLengthOffset);
|
|
|
+ const size_t indicesOffset = indexLengthOffset + sizeof (uint32_t);
|
|
|
+ if (candidateIndexBytes == 0 || candidateIndexBytes % (sizeof (uint16_t) * 3) != 0
|
|
|
+ || indicesOffset + candidateIndexBytes > mdlsOffset) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
+ return PuppetMeshBlock { .headerOffset = offset, .vertexBytes = candidateVertexBytes, .indexBytes = candidateIndexBytes };
|
|
|
+ }
|
|
|
+
|
|
|
+ return std::nullopt;
|
|
|
+}
|
|
|
+}
|
|
|
+
|
|
|
+CImage::ResolvedTransform CImage::resolveTransform (const Object& object, const int depth) const {
|
|
|
+ constexpr int kMaxParentDepth = 32;
|
|
|
+ glm::vec3 origin = object.origin->value->getVec3 ();
|
|
|
+ glm::vec3 scale = glm::vec3 (1.0f);
|
|
|
+ float angle = 0.0f;
|
|
|
+
|
|
|
+ if (object.is<Image> ()) {
|
|
|
+ const auto* image = object.as<Image> ();
|
|
|
+ scale = image->scale->value->getVec3 ();
|
|
|
+ angle = image->angles->value->getVec3 ().z;
|
|
|
+ } else if (object.is<Text> ()) {
|
|
|
+ const auto* text = object.as<Text> ();
|
|
|
+ scale = text->scale->value->getVec3 ();
|
|
|
+ } else {
|
|
|
+ scale = object.groupScale->value->getVec3 ();
|
|
|
+ angle = object.groupAngles->value->getVec3 ().z;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!object.parent.has_value ()) {
|
|
|
+ return { origin, scale, angle };
|
|
|
+ }
|
|
|
+
|
|
|
+ if (depth >= kMaxParentDepth) {
|
|
|
+ sLog.error ("Parent transform chain is too deep; possible cycle at object id=", object.id);
|
|
|
+ return { origin, scale, angle };
|
|
|
+ }
|
|
|
+
|
|
|
+ const auto* parentObject = this->getScene ().getObject (object.parent.value ());
|
|
|
+ if (parentObject == nullptr) {
|
|
|
+ return { origin, scale, angle };
|
|
|
+ }
|
|
|
+
|
|
|
+ const auto& parent = parentObject->getObject ();
|
|
|
+ const auto parentTransform = this->resolveTransform (parent, depth + 1);
|
|
|
+ const glm::vec2 local = rotateVec2 ({ origin.x * parentTransform.scale.x, origin.y * parentTransform.scale.y }, parentTransform.angle);
|
|
|
+ origin.x = parentTransform.origin.x + local.x;
|
|
|
+ origin.y = parentTransform.origin.y + local.y;
|
|
|
+ origin.z = parentTransform.origin.z + origin.z * parentTransform.scale.z;
|
|
|
+ scale *= parentTransform.scale;
|
|
|
+ angle += parentTransform.angle;
|
|
|
+
|
|
|
+ return { origin, scale, angle };
|
|
|
+}
|
|
|
+
|
|
|
CImage::CImage (Wallpapers::CScene& scene, const Image& image) :
|
|
|
CRenderable (scene, image, *image.model->material), m_sceneSpacePosition (GL_NONE), m_copySpacePosition (GL_NONE),
|
|
|
m_passSpacePosition (GL_NONE), m_texcoordCopy (GL_NONE), m_texcoordPass (GL_NONE), m_modelViewProjectionScreen (),
|
|
|
@@ -27,15 +181,10 @@ CImage::CImage (Wallpapers::CScene& scene, const Image& image) :
|
|
|
auto scene_width = static_cast<float> (scene.getWidth ());
|
|
|
auto scene_height = static_cast<float> (scene.getHeight ());
|
|
|
|
|
|
- // TODO: MAKE USE OF THE USER PROPERTIES POINTER HERE TOO! SO EVERYTHING IS UPDATED ACCORDINGLY
|
|
|
- glm::vec3 origin = this->getImage ().origin->value->getVec3 ();
|
|
|
+ const auto transform = this->resolveTransform (this->getImage ());
|
|
|
+ glm::vec3 origin = transform.origin;
|
|
|
glm::vec2 size = this->getSize ();
|
|
|
- glm::vec3 scale = this->getImage ().scale->value->getVec3 ();
|
|
|
-
|
|
|
- // TODO: PROPERLY SUPPORT PARENTS, FOR NOW THIS SHOULD BE ENOUGH
|
|
|
- if (this->m_image.parent.has_value ()) {
|
|
|
- origin += this->getScene ().getObject (this->m_image.parent.value ())->getObject ().origin->value->getVec3 ();
|
|
|
- }
|
|
|
+ glm::vec3 scale = transform.scale;
|
|
|
|
|
|
this->detectTexture ();
|
|
|
|
|
|
@@ -72,6 +221,7 @@ CImage::CImage (Wallpapers::CScene& scene, const Image& image) :
|
|
|
|
|
|
// TODO: CHANGE ALIGNMENT TOO?
|
|
|
}
|
|
|
+ this->m_size = size;
|
|
|
|
|
|
glm::vec2 scaledSize = size * glm::vec2 (scale);
|
|
|
|
|
|
@@ -161,10 +311,16 @@ CImage::CImage (Wallpapers::CScene& scene, const Image& image) :
|
|
|
GLfloat realY = 0.0;
|
|
|
|
|
|
if (this->getImage ().model->passthrough) {
|
|
|
- x = -((this->m_pos.x + (scene_width / 2)) / size.x);
|
|
|
- y = -((this->m_pos.w + (scene_height / 2)) / size.y);
|
|
|
- height = (this->m_pos.y + (scene_height / 2)) / size.y;
|
|
|
- width = (this->m_pos.z + (scene_width / 2)) / size.x;
|
|
|
+ // Passthrough shaders fill the destination FBO from texcoords and sample the scene using positions.
|
|
|
+ // Keep the destination quad full-screen in local FBO space, but pass scene-space positions through.
|
|
|
+ x = 0.0f;
|
|
|
+ y = 0.0f;
|
|
|
+ width = 1.0f;
|
|
|
+ height = 1.0f;
|
|
|
+ realX = this->m_pos.x;
|
|
|
+ realY = this->m_pos.w;
|
|
|
+ realWidth = this->m_pos.z;
|
|
|
+ realHeight = this->m_pos.y;
|
|
|
|
|
|
if (this->getImage ().model->fullscreen) {
|
|
|
realX = -1.0;
|
|
|
@@ -206,6 +362,8 @@ CImage::CImage (Wallpapers::CScene& scene, const Image& image) :
|
|
|
glBindBuffer (GL_ARRAY_BUFFER, this->m_texcoordPass);
|
|
|
glBufferData (GL_ARRAY_BUFFER, sizeof (texcoordPass), texcoordPass, GL_STATIC_DRAW);
|
|
|
|
|
|
+ this->m_hasPuppetMesh = this->loadPuppetMesh (size);
|
|
|
+
|
|
|
// compute the center of the image in scene space for rotation
|
|
|
this->m_sceneCenter = glm::vec3 (
|
|
|
(this->m_pos.x + this->m_pos.z) / 2.0f,
|
|
|
@@ -216,7 +374,11 @@ CImage::CImage (Wallpapers::CScene& scene, const Image& image) :
|
|
|
this->m_modelViewProjectionScreen
|
|
|
= this->getScene ().getCamera ().getProjection () * this->getScene ().getCamera ().getLookAt ();
|
|
|
|
|
|
- this->m_modelViewProjectionCopy = glm::ortho<float> (0.0, size.x, 0.0, size.y);
|
|
|
+ if (this->getImage ().model->passthrough) {
|
|
|
+ this->m_modelViewProjectionCopy = this->m_modelViewProjectionScreen;
|
|
|
+ } else {
|
|
|
+ this->m_modelViewProjectionCopy = glm::ortho<float> (0.0, size.x, 0.0, size.y);
|
|
|
+ }
|
|
|
this->m_modelViewProjectionCopyInverse = glm::inverse (this->m_modelViewProjectionCopy);
|
|
|
this->m_modelMatrix = glm::ortho<float> (0.0, size.x, 0.0, size.y);
|
|
|
this->m_viewProjectionMatrix = glm::mat4 (1.0);
|
|
|
@@ -242,6 +404,173 @@ CImage::~CImage () {
|
|
|
glDeleteBuffers (1, &this->m_passSpacePosition);
|
|
|
glDeleteBuffers (1, &this->m_texcoordCopy);
|
|
|
glDeleteBuffers (1, &this->m_texcoordPass);
|
|
|
+ if (this->m_puppetSpacePosition != GL_NONE) {
|
|
|
+ glDeleteBuffers (1, &this->m_puppetSpacePosition);
|
|
|
+ }
|
|
|
+ if (this->m_puppetTexCoord != GL_NONE) {
|
|
|
+ glDeleteBuffers (1, &this->m_puppetTexCoord);
|
|
|
+ }
|
|
|
+ if (this->m_puppetIndices != GL_NONE) {
|
|
|
+ glDeleteBuffers (1, &this->m_puppetIndices);
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+bool CImage::loadPuppetMesh (const glm::vec2& size) {
|
|
|
+ if (!this->getImage ().model->puppet.has_value ()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ try {
|
|
|
+ const auto stream = this->getScene ().getScene ().project.assetLocator->read (*this->getImage ().model->puppet);
|
|
|
+ std::vector<char> data { std::istreambuf_iterator<char> (*stream), std::istreambuf_iterator<char> () };
|
|
|
+
|
|
|
+ constexpr size_t markerSize = 9;
|
|
|
+ constexpr size_t meshHeaderSize = sizeof (uint32_t) * 2;
|
|
|
+ constexpr size_t vertexStride = 80;
|
|
|
+ constexpr size_t positionOffset = 0;
|
|
|
+ constexpr size_t uvOffset = 72;
|
|
|
+
|
|
|
+ const std::string puppetVersion = data.size () >= markerSize ? std::string (data.data (), strlen ("MDLV0021")) : "";
|
|
|
+ if (puppetVersion != "MDLV0021" && puppetVersion != "MDLV0023") {
|
|
|
+ sLog.error ("Unsupported puppet model header ", puppetVersion, " in ", *this->getImage ().model->puppet);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const size_t mdlsOffset = [&data] () -> size_t {
|
|
|
+ for (size_t offset = markerSize; offset + strlen ("MDLS") < data.size (); offset++) {
|
|
|
+ if (std::memcmp (data.data () + offset, "MDLS", strlen ("MDLS")) == 0) {
|
|
|
+ return offset;
|
|
|
+ }
|
|
|
+ }
|
|
|
+ return data.size ();
|
|
|
+ } ();
|
|
|
+
|
|
|
+ const auto meshBlock = findPuppetMeshBlock (data, markerSize, mdlsOffset, meshHeaderSize, vertexStride);
|
|
|
+ if (!meshBlock.has_value ()) {
|
|
|
+ sLog.error ("Could not find a usable MDLV mesh block in ", *this->getImage ().model->puppet);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const size_t vertexCount = meshBlock->vertexBytes / vertexStride;
|
|
|
+ const size_t verticesOffset = meshBlock->headerOffset + meshHeaderSize;
|
|
|
+ const size_t indicesOffset = verticesOffset + meshBlock->vertexBytes + sizeof (uint32_t);
|
|
|
+ const size_t indexCount = meshBlock->indexBytes / sizeof (uint16_t);
|
|
|
+ std::vector<GLfloat> texcoords;
|
|
|
+ std::vector<GLushort> indices;
|
|
|
+
|
|
|
+ this->m_puppetRawPositions.clear ();
|
|
|
+ this->m_puppetRawPositions.reserve (vertexCount * 3);
|
|
|
+ texcoords.reserve (vertexCount * 2);
|
|
|
+ indices.reserve (indexCount);
|
|
|
+
|
|
|
+ for (size_t index = 0; index < vertexCount; index++) {
|
|
|
+ const size_t vertexOffset = verticesOffset + index * vertexStride;
|
|
|
+ const float x = readFloat (data, vertexOffset + positionOffset);
|
|
|
+ const float y = readFloat (data, vertexOffset + positionOffset + sizeof (float));
|
|
|
+ const float z = readFloat (data, vertexOffset + positionOffset + sizeof (float) * 2);
|
|
|
+ const float u = readFloat (data, vertexOffset + uvOffset);
|
|
|
+ const float v = readFloat (data, vertexOffset + uvOffset + sizeof (float));
|
|
|
+
|
|
|
+ this->m_puppetRawPositions.push_back (x);
|
|
|
+ this->m_puppetRawPositions.push_back (y);
|
|
|
+ this->m_puppetRawPositions.push_back (z);
|
|
|
+ texcoords.push_back (u);
|
|
|
+ texcoords.push_back (v);
|
|
|
+ }
|
|
|
+
|
|
|
+ for (size_t index = 0; index < indexCount; index++) {
|
|
|
+ uint16_t value = 0;
|
|
|
+ std::memcpy (&value, data.data () + indicesOffset + index * sizeof (uint16_t), sizeof (value));
|
|
|
+ if (value >= vertexCount) {
|
|
|
+ sLog.error ("Invalid puppet mesh index ", value, " in ", *this->getImage ().model->puppet);
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+ indices.push_back (value);
|
|
|
+ }
|
|
|
+
|
|
|
+ this->updatePuppetPositionBuffer (size);
|
|
|
+
|
|
|
+ glGenBuffers (1, &this->m_puppetTexCoord);
|
|
|
+ glBindBuffer (GL_ARRAY_BUFFER, this->m_puppetTexCoord);
|
|
|
+ glBufferData (GL_ARRAY_BUFFER, texcoords.size () * sizeof (GLfloat), texcoords.data (), GL_STATIC_DRAW);
|
|
|
+
|
|
|
+ glGenBuffers (1, &this->m_puppetIndices);
|
|
|
+ glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, this->m_puppetIndices);
|
|
|
+ glBufferData (GL_ELEMENT_ARRAY_BUFFER, indices.size () * sizeof (GLushort), indices.data (), GL_STATIC_DRAW);
|
|
|
+
|
|
|
+ this->m_puppetIndexCount = static_cast<GLsizei> (indices.size ());
|
|
|
+ sLog.out (
|
|
|
+ "Loaded puppet mesh ", *this->getImage ().model->puppet, " version=", puppetVersion, " vertices=", vertexCount,
|
|
|
+ " indices=", this->m_puppetIndexCount
|
|
|
+ );
|
|
|
+
|
|
|
+ return true;
|
|
|
+ } catch (const std::exception& ex) {
|
|
|
+ sLog.error ("Could not load puppet mesh ", *this->getImage ().model->puppet, ": ", ex.what ());
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+}
|
|
|
+
|
|
|
+void CImage::updatePuppetPositionBuffer (const glm::vec2& size) {
|
|
|
+ if (this->m_puppetRawPositions.empty ()) {
|
|
|
+ return;
|
|
|
+ }
|
|
|
+
|
|
|
+ std::vector<GLfloat> positions;
|
|
|
+ positions.reserve (this->m_puppetRawPositions.size ());
|
|
|
+ for (size_t index = 0; index + 2 < this->m_puppetRawPositions.size (); index += 3) {
|
|
|
+ positions.push_back (size.x / 2.0f + this->m_puppetRawPositions[index]);
|
|
|
+ positions.push_back (size.y / 2.0f - this->m_puppetRawPositions[index + 1]);
|
|
|
+ positions.push_back (this->m_puppetRawPositions[index + 2]);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->m_puppetSpacePosition == GL_NONE) {
|
|
|
+ glGenBuffers (1, &this->m_puppetSpacePosition);
|
|
|
+ }
|
|
|
+ glBindBuffer (GL_ARRAY_BUFFER, this->m_puppetSpacePosition);
|
|
|
+ glBufferData (GL_ARRAY_BUFFER, positions.size () * sizeof (GLfloat), positions.data (), GL_DYNAMIC_DRAW);
|
|
|
+}
|
|
|
+
|
|
|
+void CImage::setupPuppetGeometryCallback (Effects::CPass* pass) const {
|
|
|
+ pass->setGeometryCallback (
|
|
|
+ [this, pass] () {
|
|
|
+ const GLint position = glGetAttribLocation (pass->getProgramID (), "a_Position");
|
|
|
+ const GLint texCoord = glGetAttribLocation (pass->getProgramID (), "a_TexCoord");
|
|
|
+
|
|
|
+ if (position >= 0) {
|
|
|
+ glEnableVertexAttribArray (position);
|
|
|
+ glBindBuffer (GL_ARRAY_BUFFER, this->m_puppetSpacePosition);
|
|
|
+ glVertexAttribPointer (position, 3, GL_FLOAT, GL_FALSE, 0, nullptr);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (texCoord >= 0) {
|
|
|
+ glEnableVertexAttribArray (texCoord);
|
|
|
+ glBindBuffer (GL_ARRAY_BUFFER, this->m_puppetTexCoord);
|
|
|
+ glVertexAttribPointer (texCoord, 2, GL_FLOAT, GL_FALSE, 0, nullptr);
|
|
|
+ }
|
|
|
+ },
|
|
|
+ [this] () {
|
|
|
+ GLfloat previousClearColor[4] = {};
|
|
|
+ glGetFloatv (GL_COLOR_CLEAR_VALUE, previousClearColor);
|
|
|
+ glClearColor (0.0f, 0.0f, 0.0f, 0.0f);
|
|
|
+ glClear (GL_COLOR_BUFFER_BIT);
|
|
|
+ glClearColor (previousClearColor[0], previousClearColor[1], previousClearColor[2], previousClearColor[3]);
|
|
|
+ glBindBuffer (GL_ELEMENT_ARRAY_BUFFER, this->m_puppetIndices);
|
|
|
+ glDrawElements (GL_TRIANGLES, this->m_puppetIndexCount, GL_UNSIGNED_SHORT, nullptr);
|
|
|
+ },
|
|
|
+ [pass] () {
|
|
|
+ const GLint position = glGetAttribLocation (pass->getProgramID (), "a_Position");
|
|
|
+ const GLint texCoord = glGetAttribLocation (pass->getProgramID (), "a_TexCoord");
|
|
|
+
|
|
|
+ if (position >= 0) {
|
|
|
+ glDisableVertexAttribArray (position);
|
|
|
+ }
|
|
|
+
|
|
|
+ if (texCoord >= 0) {
|
|
|
+ glDisableVertexAttribArray (texCoord);
|
|
|
+ }
|
|
|
+ }
|
|
|
+ );
|
|
|
}
|
|
|
|
|
|
void CImage::setup () {
|
|
|
@@ -273,6 +602,8 @@ void CImage::setup () {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ const auto& debug = this->getScene ().getContext ().getApp ().getContext ().settings.render.debug;
|
|
|
+
|
|
|
// copy pass to the composite layer
|
|
|
for (const auto& cur : this->getImage ().model->material->passes) {
|
|
|
this->m_passes.push_back (
|
|
|
@@ -281,9 +612,14 @@ void CImage::setup () {
|
|
|
}
|
|
|
|
|
|
// prepare the passes list
|
|
|
- if (!this->getImage ().effects.empty ()) {
|
|
|
+ if (!debug.baseOnly && !this->getImage ().effects.empty ()) {
|
|
|
// generate the effects used by this material
|
|
|
for (const auto& cur : this->m_image.effects) {
|
|
|
+ if (std::find (debug.skipEffects.begin (), debug.skipEffects.end (), static_cast<int> (cur->id))
|
|
|
+ != debug.skipEffects.end ()) {
|
|
|
+ continue;
|
|
|
+ }
|
|
|
+
|
|
|
// do not add non-visible effects, this might need some adjustements tho as some effects might not be
|
|
|
// visible but affect the output of the image...
|
|
|
if (!cur->visible->value->getBool ()) {
|
|
|
@@ -364,8 +700,35 @@ void CImage::setup () {
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+ if (!debug.baseOnly) {
|
|
|
+ const auto magentaCompositeTint = findMagentaCompositeTint (this->m_image, debug.skipEffects);
|
|
|
+ if (magentaCompositeTint.has_value ()) {
|
|
|
+ auto tintOverride = std::make_unique<ImageEffectPassOverride> (ImageEffectPassOverride {
|
|
|
+ .id = -1,
|
|
|
+ .combos = {
|
|
|
+ { "BLENDMODE", 30 },
|
|
|
+ },
|
|
|
+ .constants = {},
|
|
|
+ .textures = {},
|
|
|
+ });
|
|
|
+ tintOverride->constants.emplace ("color", makeStaticSetting (magentaCompositeTint.value ()));
|
|
|
+ tintOverride->constants.emplace ("alpha", makeStaticSetting (1.0f));
|
|
|
+
|
|
|
+ this->m_materials.compatibilityMaterials.emplace_back (
|
|
|
+ MaterialParser::load (this->getScene ().getScene ().project, "materials/effects/tint.json")
|
|
|
+ );
|
|
|
+ this->m_materials.compatibilityOverrides.emplace_back (std::move (tintOverride));
|
|
|
+
|
|
|
+ this->m_passes.push_back (new CPass (
|
|
|
+ *this, std::make_shared<FBOProvider> (this),
|
|
|
+ **this->m_materials.compatibilityMaterials.back ()->passes.begin (),
|
|
|
+ *this->m_materials.compatibilityOverrides.back (), std::nullopt, std::nullopt
|
|
|
+ ));
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
// extra render pass if there's any blending to be done
|
|
|
- if (this->m_image.colorBlendMode->value->getInt() > 0) {
|
|
|
+ if (!debug.baseOnly && this->m_image.colorBlendMode->value->getInt() > 0) {
|
|
|
this->m_materials.colorBlending.material
|
|
|
= MaterialParser::load (this->getScene ().getScene ().project, "materials/util/effectpassthrough.json");
|
|
|
this->m_materials.colorBlending.override = std::make_unique<ImageEffectPassOverride> (ImageEffectPassOverride {
|
|
|
@@ -407,6 +770,8 @@ void CImage::setupPasses () {
|
|
|
auto cur = this->m_passes.begin ();
|
|
|
auto end = this->m_passes.end ();
|
|
|
bool first = true;
|
|
|
+ bool inTargetEffectSequence = false;
|
|
|
+ std::shared_ptr<const TextureProvider> effectInput = nullptr;
|
|
|
|
|
|
for (; cur != end; ++cur) {
|
|
|
// TODO: PROPERLY CHECK EFFECT'S VISIBILITY AND TAKE IT INTO ACCOUNT
|
|
|
@@ -414,31 +779,28 @@ void CImage::setupPasses () {
|
|
|
// TODO: WHICH ONE IS THE LAST + A FEW OTHER THINGS
|
|
|
Effects::CPass* pass = *cur;
|
|
|
std::shared_ptr<const CFBO> prevDrawTo = drawTo;
|
|
|
- GLuint spacePosition = (first) ? this->getCopySpacePosition () : this->getPassSpacePosition ();
|
|
|
- const glm::mat4* projection = (first) ? &this->m_modelViewProjectionCopy : &this->m_modelViewProjectionPass;
|
|
|
+ bool writesToTarget = false;
|
|
|
+ const bool isFirstPass = first;
|
|
|
+ GLuint spacePosition = (isFirstPass)
|
|
|
+ ? (this->m_hasPuppetMesh ? this->m_puppetSpacePosition : this->getCopySpacePosition ())
|
|
|
+ : this->getPassSpacePosition ();
|
|
|
+ const glm::mat4* projection = (isFirstPass) ? &this->m_modelViewProjectionCopy : &this->m_modelViewProjectionPass;
|
|
|
const glm::mat4* inverseProjection
|
|
|
- = (first) ? &this->m_modelViewProjectionCopyInverse : &this->m_modelViewProjectionPassInverse;
|
|
|
+ = (isFirstPass) ? &this->m_modelViewProjectionCopyInverse : &this->m_modelViewProjectionPassInverse;
|
|
|
first = false;
|
|
|
|
|
|
+ if (isFirstPass && this->m_hasPuppetMesh) {
|
|
|
+ pass->setBlendingMode (BlendingMode_Translucent);
|
|
|
+ this->setupPuppetGeometryCallback (pass);
|
|
|
+ }
|
|
|
+
|
|
|
pass->setModelMatrix (&this->m_modelMatrix);
|
|
|
pass->setViewProjectionMatrix (&this->m_viewProjectionMatrix);
|
|
|
|
|
|
- // set viewport and target texture if needed
|
|
|
- if (pass->getTarget ().has_value ()) {
|
|
|
- // setup target texture
|
|
|
- std::string target = pass->getTarget ().value ();
|
|
|
- drawTo = pass->getFBOProvider ()->find (target);
|
|
|
- // spacePosition = this->getPassSpacePosition ();
|
|
|
-
|
|
|
- // not a local fbo, try to find a scene fbo with the same name
|
|
|
- if (drawTo == nullptr) {
|
|
|
- // this one throws if no fbo was found
|
|
|
- drawTo = this->getScene ().findFBO (target);
|
|
|
- }
|
|
|
- }
|
|
|
+ writesToTarget = this->configurePassTarget (pass, drawTo, asInput, effectInput, inTargetEffectSequence);
|
|
|
// determine if it's the last element in the list as this is a screen-copy-like process
|
|
|
// TODO: PROPERLY CHECK IF THIS IS ALL THAT'S NEEDED
|
|
|
- else if (std::next (cur) == end && this->getImage ().visible->value->getBool ()) {
|
|
|
+ if (!writesToTarget && this->shouldRenderFinalPass (std::next (cur) == end)) {
|
|
|
// TODO: PROPERLY CHECK EFFECT'S VISIBILITY AND TAKE IT INTO ACCOUNT
|
|
|
spacePosition = this->getSceneSpacePosition ();
|
|
|
drawTo = this->getScene ().getFBO ();
|
|
|
@@ -448,20 +810,67 @@ void CImage::setupPasses () {
|
|
|
|
|
|
pass->setDestination (drawTo);
|
|
|
pass->setInput (asInput);
|
|
|
+ pass->setPreviousInput (inTargetEffectSequence ? effectInput : nullptr);
|
|
|
pass->setPosition (spacePosition);
|
|
|
pass->setTexCoord (texcoord);
|
|
|
pass->setModelViewProjectionMatrix (projection);
|
|
|
pass->setModelViewProjectionMatrixInverse (inverseProjection);
|
|
|
|
|
|
texcoord = this->getTexCoordPass ();
|
|
|
- drawTo = prevDrawTo;
|
|
|
|
|
|
- if (!pass->getTarget ().has_value ()) {
|
|
|
+ if (writesToTarget) {
|
|
|
+ asInput = drawTo;
|
|
|
+ drawTo = prevDrawTo;
|
|
|
+ } else {
|
|
|
+ drawTo = prevDrawTo;
|
|
|
this->pinpongFramebuffer (&drawTo, &asInput);
|
|
|
+ inTargetEffectSequence = false;
|
|
|
+ effectInput = nullptr;
|
|
|
}
|
|
|
}
|
|
|
}
|
|
|
|
|
|
+bool CImage::shouldRenderFinalPass (bool isLastPass) const {
|
|
|
+ if (!isLastPass || !this->getImage ().visible->value->getBool ()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const auto& debug = this->getScene ().getContext ().getApp ().getContext ().settings.render.debug;
|
|
|
+ return !(debug.noSolidFinal && this->getImage ().model->solidlayer);
|
|
|
+}
|
|
|
+
|
|
|
+bool CImage::configurePassTarget (
|
|
|
+ Effects::CPass* pass,
|
|
|
+ std::shared_ptr<const CFBO>& drawTo,
|
|
|
+ const std::shared_ptr<const TextureProvider>& asInput,
|
|
|
+ std::shared_ptr<const TextureProvider>& effectInput,
|
|
|
+ bool& inTargetEffectSequence
|
|
|
+) {
|
|
|
+ if (!pass->getTarget ().has_value ()) {
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ const std::string target = pass->getTarget ().value ();
|
|
|
+ std::shared_ptr<const CFBO> resolved = pass->getFBOProvider ()->find (target);
|
|
|
+ if (resolved == nullptr) {
|
|
|
+ resolved = this->getScene ().findFBO (target);
|
|
|
+ }
|
|
|
+ if (resolved == nullptr) {
|
|
|
+ sLog.error (
|
|
|
+ "Pass target FBO '", target, "' could not be resolved for object ", pass->getRenderable ().getId (),
|
|
|
+ " shader=", pass->getPass ().shader
|
|
|
+ );
|
|
|
+ return false;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (!inTargetEffectSequence) {
|
|
|
+ effectInput = asInput;
|
|
|
+ inTargetEffectSequence = true;
|
|
|
+ }
|
|
|
+ drawTo = resolved;
|
|
|
+ return true;
|
|
|
+}
|
|
|
+
|
|
|
void CImage::pinpongFramebuffer (std::shared_ptr<const CFBO>* drawTo, std::shared_ptr<const TextureProvider>* asInput) {
|
|
|
// temporarily store FBOs used
|
|
|
std::shared_ptr<const CFBO> currentMainFBO = this->m_currentMainFBO;
|
|
|
@@ -533,16 +942,146 @@ const glm::vec4& CImage::getColor4 () const { return this->m_image.color->value-
|
|
|
|
|
|
const glm::vec3& CImage::getCompositeColor () const { return this->m_image.color->value->getVec3 (); }
|
|
|
|
|
|
+glm::vec2 CImage::resolveGeometrySize (float sceneWidth, float sceneHeight, glm::vec3& origin) const {
|
|
|
+ glm::vec2 size = this->getSize ();
|
|
|
+
|
|
|
+ if ((size.x == 0.0f || size.y == 0.0f) && this->m_texture != nullptr) {
|
|
|
+ size.x = static_cast<float> (this->m_texture->getRealWidth ());
|
|
|
+ size.y = static_cast<float> (this->m_texture->getRealHeight ());
|
|
|
+ } else if ((size.x == 0.0f || size.y == 0.0f) && this->getImage ().model->width.has_value ()
|
|
|
+ && this->getImage ().model->height.has_value ()) {
|
|
|
+ size.x = static_cast<float> (this->getImage ().model->width.value ());
|
|
|
+ size.y = static_cast<float> (this->getImage ().model->height.value ());
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->getImage ().model->fullscreen) {
|
|
|
+ size = { sceneWidth, sceneHeight };
|
|
|
+ origin = { sceneWidth / 2.0f, sceneHeight / 2.0f, 0.0f };
|
|
|
+ }
|
|
|
+
|
|
|
+ return size;
|
|
|
+}
|
|
|
+
|
|
|
+void CImage::updateScenePosition (
|
|
|
+ const glm::vec3& origin, const glm::vec2& size, const glm::vec3& scale, float sceneWidth, float sceneHeight
|
|
|
+) {
|
|
|
+ const glm::vec2 scaledSize = size * glm::vec2 (scale);
|
|
|
+ this->m_pos.x = origin.x - (scaledSize.x / 2.0f);
|
|
|
+ this->m_pos.w = origin.y + (scaledSize.y / 2.0f);
|
|
|
+ this->m_pos.z = origin.x + (scaledSize.x / 2.0f);
|
|
|
+ this->m_pos.y = origin.y - (scaledSize.y / 2.0f);
|
|
|
+
|
|
|
+ if (this->getImage ().alignment.find ("top") != std::string::npos) {
|
|
|
+ this->m_pos.y -= scaledSize.y / 2.0f;
|
|
|
+ this->m_pos.w -= scaledSize.y / 2.0f;
|
|
|
+ } else if (this->getImage ().alignment.find ("bottom") != std::string::npos) {
|
|
|
+ this->m_pos.y += scaledSize.y / 2.0f;
|
|
|
+ this->m_pos.w += scaledSize.y / 2.0f;
|
|
|
+ }
|
|
|
+
|
|
|
+ if (this->getImage ().alignment.find ("left") != std::string::npos) {
|
|
|
+ this->m_pos.x += scaledSize.x / 2.0f;
|
|
|
+ this->m_pos.z += scaledSize.x / 2.0f;
|
|
|
+ } else if (this->getImage ().alignment.find ("right") != std::string::npos) {
|
|
|
+ this->m_pos.x -= scaledSize.x / 2.0f;
|
|
|
+ this->m_pos.z -= scaledSize.x / 2.0f;
|
|
|
+ }
|
|
|
+
|
|
|
+ this->m_pos.x -= sceneWidth / 2.0f;
|
|
|
+ this->m_pos.y = sceneHeight / 2.0f - this->m_pos.y;
|
|
|
+ this->m_pos.z -= sceneWidth / 2.0f;
|
|
|
+ this->m_pos.w = sceneHeight / 2.0f - this->m_pos.w;
|
|
|
+}
|
|
|
+
|
|
|
+void CImage::uploadGeometryBuffers (const glm::vec2& size) {
|
|
|
+ GLfloat sceneSpacePosition[] = { this->m_pos.x, this->m_pos.y, 0.0f, this->m_pos.x, this->m_pos.w, 0.0f,
|
|
|
+ this->m_pos.z, this->m_pos.y, 0.0f, this->m_pos.z, this->m_pos.y, 0.0f,
|
|
|
+ this->m_pos.x, this->m_pos.w, 0.0f, this->m_pos.z, this->m_pos.w, 0.0f };
|
|
|
+
|
|
|
+ float width = 1.0f;
|
|
|
+ float height = 1.0f;
|
|
|
+ if (
|
|
|
+ this->getTexture () != nullptr && !this->getTexture ()->isAnimated ()
|
|
|
+ && (
|
|
|
+ this->getTexture ()->getTextureWidth (0) != this->getTexture ()->getRealWidth ()
|
|
|
+ || this->getTexture ()->getTextureHeight (0) != this->getTexture ()->getRealHeight ()
|
|
|
+ )
|
|
|
+ ) {
|
|
|
+ width = static_cast<float> (this->getTexture ()->getRealWidth ())
|
|
|
+ / static_cast<float> (this->getTexture ()->getTextureWidth (0));
|
|
|
+ height = static_cast<float> (this->getTexture ()->getRealHeight ())
|
|
|
+ / static_cast<float> (this->getTexture ()->getTextureHeight (0));
|
|
|
+ }
|
|
|
+
|
|
|
+ float x = 0.0f;
|
|
|
+ float y = 0.0f;
|
|
|
+ GLfloat realWidth = size.x;
|
|
|
+ GLfloat realHeight = size.y;
|
|
|
+ GLfloat realX = 0.0f;
|
|
|
+ GLfloat realY = 0.0f;
|
|
|
+
|
|
|
+ if (this->getImage ().model->passthrough) {
|
|
|
+ width = 1.0f;
|
|
|
+ height = 1.0f;
|
|
|
+ realX = this->m_pos.x;
|
|
|
+ realY = this->m_pos.w;
|
|
|
+ realWidth = this->m_pos.z;
|
|
|
+ realHeight = this->m_pos.y;
|
|
|
+
|
|
|
+ if (this->getImage ().model->fullscreen) {
|
|
|
+ realX = -1.0f;
|
|
|
+ realY = -1.0f;
|
|
|
+ realWidth = 1.0f;
|
|
|
+ realHeight = 1.0f;
|
|
|
+ }
|
|
|
+ }
|
|
|
+
|
|
|
+ GLfloat texcoordCopy[] = { x, height, x, y, width, height, width, height, x, y, width, y };
|
|
|
+ GLfloat copySpacePosition[] = { realX, realHeight, 0.0f, realX, realY, 0.0f, realWidth, realHeight, 0.0f,
|
|
|
+ realWidth, realHeight, 0.0f, realX, realY, 0.0f, realWidth, realY, 0.0f };
|
|
|
+
|
|
|
+ glBindBuffer (GL_ARRAY_BUFFER, this->m_sceneSpacePosition);
|
|
|
+ glBufferData (GL_ARRAY_BUFFER, sizeof (sceneSpacePosition), sceneSpacePosition, GL_DYNAMIC_DRAW);
|
|
|
+ glBindBuffer (GL_ARRAY_BUFFER, this->m_copySpacePosition);
|
|
|
+ glBufferData (GL_ARRAY_BUFFER, sizeof (copySpacePosition), copySpacePosition, GL_DYNAMIC_DRAW);
|
|
|
+ glBindBuffer (GL_ARRAY_BUFFER, this->m_texcoordCopy);
|
|
|
+ glBufferData (GL_ARRAY_BUFFER, sizeof (texcoordCopy), texcoordCopy, GL_DYNAMIC_DRAW);
|
|
|
+
|
|
|
+ this->m_sceneCenter = glm::vec3 ((this->m_pos.x + this->m_pos.z) / 2.0f, (this->m_pos.y + this->m_pos.w) / 2.0f, 0.0f);
|
|
|
+ this->m_modelViewProjectionCopy = this->getImage ().model->passthrough
|
|
|
+ ? this->m_modelViewProjectionScreen
|
|
|
+ : glm::ortho<float> (0.0, size.x, 0.0, size.y);
|
|
|
+ this->m_modelViewProjectionCopyInverse = glm::inverse (this->m_modelViewProjectionCopy);
|
|
|
+ this->m_modelMatrix = glm::ortho<float> (0.0, size.x, 0.0, size.y);
|
|
|
+}
|
|
|
+
|
|
|
+void CImage::updateGeometryBuffers () {
|
|
|
+ auto sceneWidth = static_cast<float> (this->getScene ().getWidth ());
|
|
|
+ auto sceneHeight = static_cast<float> (this->getScene ().getHeight ());
|
|
|
+ const auto transform = this->resolveTransform (this->getImage ());
|
|
|
+ glm::vec3 origin = transform.origin;
|
|
|
+ const glm::vec3 scale = transform.scale;
|
|
|
+ const glm::vec2 size = this->resolveGeometrySize (sceneWidth, sceneHeight, origin);
|
|
|
+ const glm::vec2 previousSize = this->m_size;
|
|
|
+ this->m_size = size;
|
|
|
+ if (this->m_hasPuppetMesh && size != previousSize) {
|
|
|
+ this->updatePuppetPositionBuffer (size);
|
|
|
+ }
|
|
|
+
|
|
|
+ this->updateScenePosition (origin, size, scale, sceneWidth, sceneHeight);
|
|
|
+ this->uploadGeometryBuffers (size);
|
|
|
+}
|
|
|
+
|
|
|
void CImage::updateScreenSpacePosition () {
|
|
|
+ this->updateGeometryBuffers ();
|
|
|
+
|
|
|
// Build rotation from angles (already in radians from scene.json — see CParticle.cpp:2119)
|
|
|
// Negate X and Z rotations to account for Y-flipped coordinate system (CParticle.cpp:2120)
|
|
|
- glm::vec3 angles = this->getImage ().angles->value->getVec3 ();
|
|
|
+ const float angle = this->resolveTransform (this->getImage ()).angle;
|
|
|
glm::mat4 rotModel = glm::mat4 (1.0f);
|
|
|
- if (angles.x != 0.0f || angles.y != 0.0f || angles.z != 0.0f) {
|
|
|
+ if (angle != 0.0f) {
|
|
|
rotModel = glm::translate (rotModel, this->m_sceneCenter);
|
|
|
- rotModel = glm::rotate (rotModel, -angles.z, glm::vec3 (0.0f, 0.0f, 1.0f));
|
|
|
- rotModel = glm::rotate (rotModel, angles.y, glm::vec3 (0.0f, 1.0f, 0.0f));
|
|
|
- rotModel = glm::rotate (rotModel, -angles.x, glm::vec3 (1.0f, 0.0f, 0.0f));
|
|
|
+ rotModel = glm::rotate (rotModel, -angle, glm::vec3 (0.0f, 0.0f, 1.0f));
|
|
|
rotModel = glm::translate (rotModel, -this->m_sceneCenter);
|
|
|
}
|
|
|
|
|
|
@@ -552,13 +1091,13 @@ void CImage::updateScreenSpacePosition () {
|
|
|
|
|
|
// Apply parallax displacement if enabled
|
|
|
if (this->getScene ().getScene ().camera.parallax.enabled
|
|
|
- && !this->getImage ().model->fullscreen
|
|
|
&& !this->getScene ().getContext ().getApp ().getContext ().settings.mouse.disableparallax) {
|
|
|
const double parallaxAmount = this->getScene ().getScene ().camera.parallax.amount->value->getFloat ();
|
|
|
const glm::vec2 depth = this->getImage ().parallaxDepth->value->getVec2 ();
|
|
|
const glm::vec2* displacement = this->getScene ().getParallaxDisplacement ();
|
|
|
- float x = (depth.x + parallaxAmount) * displacement->x * this->getSize ().x;
|
|
|
- float y = (depth.y + parallaxAmount) * displacement->y * this->getSize ().x;
|
|
|
+ const float referenceSize = static_cast<float> (this->getScene ().getWidth ());
|
|
|
+ float x = (depth.x + parallaxAmount) * displacement->x * referenceSize;
|
|
|
+ float y = (depth.y + parallaxAmount) * displacement->y * referenceSize;
|
|
|
mvp = glm::translate (mvp, { x, y, 0.0f });
|
|
|
}
|
|
|
|