init
This commit is contained in:
commit
444f800536
122 changed files with 17137 additions and 0 deletions
121
shared/Bitmap.h
Normal file
121
shared/Bitmap.h
Normal file
|
|
@ -0,0 +1,121 @@
|
|||
#pragma once
|
||||
|
||||
#include <string.h>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
enum eBitmapType
|
||||
{
|
||||
eBitmapType_2D,
|
||||
eBitmapType_Cube
|
||||
};
|
||||
|
||||
enum eBitmapFormat
|
||||
{
|
||||
eBitmapFormat_UnsignedByte,
|
||||
eBitmapFormat_Float,
|
||||
};
|
||||
|
||||
/// R/RG/RGB/RGBA bitmaps
|
||||
struct Bitmap
|
||||
{
|
||||
Bitmap() = default;
|
||||
Bitmap(int w, int h, int comp, eBitmapFormat fmt)
|
||||
:w_(w), h_(h), comp_(comp), fmt_(fmt), data_(w * h * comp * getBytesPerComponent(fmt))
|
||||
{
|
||||
initGetSetFuncs();
|
||||
}
|
||||
Bitmap(int w, int h, int d, int comp, eBitmapFormat fmt)
|
||||
:w_(w), h_(h), d_(d), comp_(comp), fmt_(fmt), data_(w * h * d * comp * getBytesPerComponent(fmt))
|
||||
{
|
||||
initGetSetFuncs();
|
||||
}
|
||||
Bitmap(int w, int h, int comp, eBitmapFormat fmt, const void* ptr)
|
||||
:w_(w), h_(h), comp_(comp), fmt_(fmt), data_(w * h * comp * getBytesPerComponent(fmt))
|
||||
{
|
||||
initGetSetFuncs();
|
||||
memcpy(data_.data(), ptr, data_.size());
|
||||
}
|
||||
int w_ = 0;
|
||||
int h_ = 0;
|
||||
int d_ = 1;
|
||||
int comp_ = 3;
|
||||
eBitmapFormat fmt_ = eBitmapFormat_UnsignedByte;
|
||||
eBitmapType type_ = eBitmapType_2D;
|
||||
std::vector<uint8_t> data_;
|
||||
|
||||
static int getBytesPerComponent(eBitmapFormat fmt)
|
||||
{
|
||||
if (fmt == eBitmapFormat_UnsignedByte) return 1;
|
||||
if (fmt == eBitmapFormat_Float) return 4;
|
||||
return 0;
|
||||
}
|
||||
|
||||
void setPixel(int x, int y, const glm::vec4& c)
|
||||
{
|
||||
(*this.*setPixelFunc)(x, y, c);
|
||||
}
|
||||
glm::vec4 getPixel(int x, int y) const
|
||||
{
|
||||
return ((*this.*getPixelFunc)(x, y));
|
||||
}
|
||||
private:
|
||||
using setPixel_t = void(Bitmap::*)(int, int, const glm::vec4&);
|
||||
using getPixel_t = glm::vec4(Bitmap::*)(int, int) const;
|
||||
setPixel_t setPixelFunc = &Bitmap::setPixelUnsignedByte;
|
||||
getPixel_t getPixelFunc = &Bitmap::getPixelUnsignedByte;
|
||||
|
||||
void initGetSetFuncs()
|
||||
{
|
||||
switch (fmt_)
|
||||
{
|
||||
case eBitmapFormat_UnsignedByte:
|
||||
setPixelFunc = &Bitmap::setPixelUnsignedByte;
|
||||
getPixelFunc = &Bitmap::getPixelUnsignedByte;
|
||||
break;
|
||||
case eBitmapFormat_Float:
|
||||
setPixelFunc = &Bitmap::setPixelFloat;
|
||||
getPixelFunc = &Bitmap::getPixelFloat;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
void setPixelFloat(int x, int y, const glm::vec4& c)
|
||||
{
|
||||
const int ofs = comp_ * (y * w_ + x);
|
||||
float* data = reinterpret_cast<float*>(data_.data());
|
||||
if (comp_ > 0) data[ofs + 0] = c.x;
|
||||
if (comp_ > 1) data[ofs + 1] = c.y;
|
||||
if (comp_ > 2) data[ofs + 2] = c.z;
|
||||
if (comp_ > 3) data[ofs + 3] = c.w;
|
||||
}
|
||||
glm::vec4 getPixelFloat(int x, int y) const
|
||||
{
|
||||
const int ofs = comp_ * (y * w_ + x);
|
||||
const float* data = reinterpret_cast<const float*>(data_.data());
|
||||
return glm::vec4(
|
||||
comp_ > 0 ? data[ofs + 0] : 0.0f,
|
||||
comp_ > 1 ? data[ofs + 1] : 0.0f,
|
||||
comp_ > 2 ? data[ofs + 2] : 0.0f,
|
||||
comp_ > 3 ? data[ofs + 3] : 0.0f);
|
||||
}
|
||||
|
||||
void setPixelUnsignedByte(int x, int y, const glm::vec4& c)
|
||||
{
|
||||
const int ofs = comp_ * (y * w_ + x);
|
||||
if (comp_ > 0) data_[ofs + 0] = uint8_t(c.x * 255.0f);
|
||||
if (comp_ > 1) data_[ofs + 1] = uint8_t(c.y * 255.0f);
|
||||
if (comp_ > 2) data_[ofs + 2] = uint8_t(c.z * 255.0f);
|
||||
if (comp_ > 3) data_[ofs + 3] = uint8_t(c.w * 255.0f);
|
||||
}
|
||||
glm::vec4 getPixelUnsignedByte(int x, int y) const
|
||||
{
|
||||
const int ofs = comp_ * (y * w_ + x);
|
||||
return glm::vec4(
|
||||
comp_ > 0 ? float(data_[ofs + 0]) / 255.0f : 0.0f,
|
||||
comp_ > 1 ? float(data_[ofs + 1]) / 255.0f : 0.0f,
|
||||
comp_ > 2 ? float(data_[ofs + 2]) / 255.0f : 0.0f,
|
||||
comp_ > 3 ? float(data_[ofs + 3]) / 255.0f : 0.0f);
|
||||
}
|
||||
};
|
||||
33
shared/CMakeLists.txt
Normal file
33
shared/CMakeLists.txt
Normal file
|
|
@ -0,0 +1,33 @@
|
|||
cmake_minimum_required(VERSION 3.19)
|
||||
|
||||
project(SharedUtils CXX C)
|
||||
|
||||
include(../CMake/CommonMacros.txt)
|
||||
|
||||
file(GLOB_RECURSE SRC_FILES LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.c??)
|
||||
file(GLOB_RECURSE HEADER_FILES LIST_DIRECTORIES false RELATIVE ${CMAKE_CURRENT_SOURCE_DIR} *.h)
|
||||
|
||||
|
||||
add_library(SharedUtils ${SRC_FILES} ${HEADER_FILES})
|
||||
|
||||
set_property(TARGET SharedUtils PROPERTY CXX_STANDARD 20)
|
||||
set_property(TARGET SharedUtils PROPERTY CXX_STANDARD_REQUIRED ON)
|
||||
|
||||
# Add include directory for the root of the project to fix include paths
|
||||
target_include_directories(SharedUtils PUBLIC ${CMAKE_SOURCE_DIR})
|
||||
|
||||
target_link_libraries(SharedUtils PUBLIC assimp)
|
||||
target_link_libraries(SharedUtils PUBLIC glslang)
|
||||
target_link_libraries(SharedUtils PUBLIC glfw)
|
||||
target_link_libraries(SharedUtils PUBLIC LUtils)
|
||||
target_link_libraries(SharedUtils PUBLIC LVKLibrary)
|
||||
target_link_libraries(SharedUtils PUBLIC LVKstb)
|
||||
target_link_libraries(SharedUtils PUBLIC ktx)
|
||||
|
||||
if(WIN32)
|
||||
target_compile_definitions(SharedUtils PUBLIC "NOMINMAX")
|
||||
endif()
|
||||
|
||||
if(UNIX AND NOT APPLE)
|
||||
target_link_libraries(SharedUtils PUBLIC tbb)
|
||||
endif()
|
||||
232
shared/Camera.h
Normal file
232
shared/Camera.h
Normal file
|
|
@ -0,0 +1,232 @@
|
|||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <algorithm>
|
||||
|
||||
#include "shared/UtilsMath.h"
|
||||
#include "shared/Trackball.h"
|
||||
|
||||
#include "glm/gtx/euler_angles.hpp"
|
||||
|
||||
class CameraPositionerInterface
|
||||
{
|
||||
public:
|
||||
virtual ~CameraPositionerInterface() = default;
|
||||
virtual glm::mat4 getViewMatrix() const = 0;
|
||||
virtual glm::vec3 getPosition() const = 0;
|
||||
};
|
||||
|
||||
class Camera final
|
||||
{
|
||||
public:
|
||||
explicit Camera(CameraPositionerInterface& positioner)
|
||||
: positioner_(&positioner)
|
||||
{}
|
||||
|
||||
Camera(const Camera&) = default;
|
||||
Camera& operator = (const Camera&) = default;
|
||||
|
||||
glm::mat4 getViewMatrix() const { return positioner_->getViewMatrix(); }
|
||||
glm::vec3 getPosition() const { return positioner_->getPosition(); }
|
||||
glm::mat4 getProjMatrix() const { return proj_; }
|
||||
|
||||
private:
|
||||
const CameraPositionerInterface* positioner_;
|
||||
glm::mat4 proj_;
|
||||
};
|
||||
|
||||
class CameraPositioner_FirstPerson final: public CameraPositionerInterface
|
||||
{
|
||||
public:
|
||||
CameraPositioner_FirstPerson() = default;
|
||||
CameraPositioner_FirstPerson(const glm::vec3& pos, const glm::vec3& target, const glm::vec3& up)
|
||||
: cameraPosition_(pos)
|
||||
, cameraOrientation_(glm::lookAt(pos, target, up))
|
||||
, up_(up)
|
||||
{}
|
||||
|
||||
void update(double deltaSeconds, const glm::vec2& mousePos, bool mousePressed)
|
||||
{
|
||||
if (mousePressed)
|
||||
{
|
||||
const glm::vec2 delta = mousePos - mousePos_;
|
||||
const glm::quat deltaQuat = glm::quat(glm::vec3(-mouseSpeed_ * delta.y, mouseSpeed_ * delta.x, 0.0f));
|
||||
cameraOrientation_ = deltaQuat * cameraOrientation_;
|
||||
cameraOrientation_ = glm::normalize(cameraOrientation_);
|
||||
setUpVector(up_);
|
||||
}
|
||||
mousePos_ = mousePos;
|
||||
|
||||
const glm::mat4 v = glm::mat4_cast(cameraOrientation_);
|
||||
|
||||
const glm::vec3 forward = -glm::vec3(v[0][2], v[1][2], v[2][2]);
|
||||
const glm::vec3 right = glm::vec3(v[0][0], v[1][0], v[2][0]);
|
||||
const glm::vec3 up = glm::cross(right, forward);
|
||||
|
||||
glm::vec3 accel(0.0f);
|
||||
|
||||
if (movement_.forward_) accel += forward;
|
||||
if (movement_.backward_) accel -= forward;
|
||||
|
||||
if (movement_.left_) accel -= right;
|
||||
if (movement_.right_) accel += right;
|
||||
|
||||
if (movement_.up_) accel += up;
|
||||
if (movement_.down_) accel -= up;
|
||||
|
||||
if (movement_.fastSpeed_) accel *= fastCoef_;
|
||||
|
||||
if (accel == glm::vec3(0))
|
||||
{
|
||||
// decelerate naturally according to the damping value
|
||||
moveSpeed_ -= moveSpeed_ * std::min((1.0f / damping_) * static_cast<float>(deltaSeconds), 1.0f);
|
||||
}
|
||||
else
|
||||
{
|
||||
// acceleration
|
||||
moveSpeed_ += accel * acceleration_ * static_cast<float>(deltaSeconds);
|
||||
const float maxSpeed = movement_.fastSpeed_ ? maxSpeed_ * fastCoef_ : maxSpeed_;
|
||||
if (glm::length(moveSpeed_) > maxSpeed) moveSpeed_ = glm::normalize(moveSpeed_) * maxSpeed;
|
||||
}
|
||||
|
||||
cameraPosition_ += moveSpeed_ * static_cast<float>(deltaSeconds);
|
||||
}
|
||||
|
||||
virtual glm::mat4 getViewMatrix() const override
|
||||
{
|
||||
const glm::mat4 t = glm::translate(glm::mat4(1.0f), -cameraPosition_);
|
||||
const glm::mat4 r = glm::mat4_cast(cameraOrientation_);
|
||||
return r * t;
|
||||
}
|
||||
|
||||
virtual glm::vec3 getPosition() const override
|
||||
{
|
||||
return cameraPosition_;
|
||||
}
|
||||
|
||||
void setPosition(const glm::vec3& pos)
|
||||
{
|
||||
cameraPosition_ = pos;
|
||||
}
|
||||
|
||||
void setSpeed(const glm::vec3& speed) {
|
||||
moveSpeed_ = speed;
|
||||
}
|
||||
|
||||
void resetMousePosition(const glm::vec2& p) { mousePos_ = p; };
|
||||
|
||||
void setUpVector(const glm::vec3& up)
|
||||
{
|
||||
const glm::mat4 view = getViewMatrix();
|
||||
const glm::vec3 dir = -glm::vec3(view[0][2], view[1][2], view[2][2]);
|
||||
cameraOrientation_ = glm::lookAt(cameraPosition_, cameraPosition_ + dir, up);
|
||||
}
|
||||
|
||||
inline void lookAt(const glm::vec3& pos, const glm::vec3& target, const glm::vec3& up) {
|
||||
cameraPosition_ = pos;
|
||||
cameraOrientation_ = glm::lookAt(pos, target, up);
|
||||
}
|
||||
|
||||
public:
|
||||
struct Movement
|
||||
{
|
||||
bool forward_ = false;
|
||||
bool backward_ = false;
|
||||
bool left_ = false;
|
||||
bool right_ = false;
|
||||
bool up_ = false;
|
||||
bool down_ = false;
|
||||
//
|
||||
bool fastSpeed_ = false;
|
||||
} movement_;
|
||||
|
||||
public:
|
||||
float mouseSpeed_ = 4.0f;
|
||||
float acceleration_ = 150.0f;
|
||||
float damping_ = 0.2f;
|
||||
float maxSpeed_ = 10.0f;
|
||||
float fastCoef_ = 10.0f;
|
||||
|
||||
private:
|
||||
glm::vec2 mousePos_ = glm::vec2(0);
|
||||
glm::vec3 cameraPosition_ = glm::vec3(0.0f, 10.0f, 10.0f);
|
||||
glm::quat cameraOrientation_ = glm::quat(glm::vec3(0));
|
||||
glm::vec3 moveSpeed_ = glm::vec3(0.0f);
|
||||
glm::vec3 up_ = glm::vec3(0.0f, 0.0f, 1.0f);
|
||||
};
|
||||
|
||||
class CameraPositioner_MoveTo final : public CameraPositionerInterface
|
||||
{
|
||||
public:
|
||||
CameraPositioner_MoveTo(const glm::vec3& pos, const glm::vec3& angles)
|
||||
: positionCurrent_(pos)
|
||||
, positionDesired_(pos)
|
||||
, anglesCurrent_(angles)
|
||||
, anglesDesired_(angles)
|
||||
{}
|
||||
|
||||
void update(float deltaSeconds, const glm::vec2& mousePos, bool mousePressed)
|
||||
{
|
||||
positionCurrent_ += dampingLinear_ * deltaSeconds * (positionDesired_ - positionCurrent_);
|
||||
|
||||
// normalization is required to avoid "spinning" around the object 2pi times
|
||||
anglesCurrent_ = clipAngles(anglesCurrent_);
|
||||
anglesDesired_ = clipAngles(anglesDesired_);
|
||||
|
||||
// update angles
|
||||
anglesCurrent_ -= angleDelta(anglesCurrent_, anglesDesired_) * dampingEulerAngles_ * deltaSeconds;
|
||||
|
||||
// normalize new angles
|
||||
anglesCurrent_ = clipAngles(anglesCurrent_);
|
||||
|
||||
const glm::vec3 a = glm::radians(anglesCurrent_);
|
||||
|
||||
currentTransform_ = glm::translate(glm::yawPitchRoll(a.y, a.x, a.z), -positionCurrent_);
|
||||
}
|
||||
|
||||
void setPosition(const glm::vec3& p) { positionCurrent_ = p; }
|
||||
void setAngles(float pitch, float pan, float roll) { anglesCurrent_ = glm::vec3(pitch, pan, roll); }
|
||||
void setAngles(const glm::vec3& angles) { anglesCurrent_ = angles; }
|
||||
void setDesiredPosition(const glm::vec3& p) { positionDesired_ = p; }
|
||||
void setDesiredAngles(float pitch, float pan, float roll) { anglesDesired_ = glm::vec3(pitch, pan, roll); }
|
||||
void setDesiredAngles(const glm::vec3& angles) { anglesDesired_ = angles; }
|
||||
|
||||
virtual glm::vec3 getPosition() const override { return positionCurrent_; }
|
||||
virtual glm::mat4 getViewMatrix() const override { return currentTransform_; }
|
||||
|
||||
public:
|
||||
float dampingLinear_ = 10.0f;
|
||||
glm::vec3 dampingEulerAngles_ = glm::vec3(5.0f, 5.0f, 5.0f);
|
||||
|
||||
private:
|
||||
glm::vec3 positionCurrent_ = glm::vec3(0.0f);
|
||||
glm::vec3 positionDesired_ = glm::vec3(0.0f);
|
||||
|
||||
/// pitch, pan, roll
|
||||
glm::vec3 anglesCurrent_ = glm::vec3(0.0f);
|
||||
glm::vec3 anglesDesired_ = glm::vec3(0.0f);
|
||||
|
||||
glm::mat4 currentTransform_ = glm::mat4(1.0f);
|
||||
|
||||
static inline float clipAngle(float d)
|
||||
{
|
||||
if (d < -180.0f) return d + 360.0f;
|
||||
if (d > +180.0f) return d - 360.f;
|
||||
return d;
|
||||
}
|
||||
|
||||
static inline glm::vec3 clipAngles(const glm::vec3& angles)
|
||||
{
|
||||
return glm::vec3(
|
||||
std::fmod(angles.x, 360.0f),
|
||||
std::fmod(angles.y, 360.0f),
|
||||
std::fmod(angles.z, 360.0f)
|
||||
);
|
||||
}
|
||||
|
||||
static inline glm::vec3 angleDelta(const glm::vec3& anglesCurrent, const glm::vec3& anglesDesired)
|
||||
{
|
||||
const glm::vec3 d = clipAngles(anglesCurrent) - clipAngles(anglesDesired);
|
||||
return glm::vec3(clipAngle(d.x), clipAngle(d.y), clipAngle(d.z));
|
||||
}
|
||||
};
|
||||
77
shared/Graph.h
Normal file
77
shared/Graph.h
Normal file
|
|
@ -0,0 +1,77 @@
|
|||
#pragma once
|
||||
|
||||
#include <lvk/LVK.h>
|
||||
|
||||
#include <deque>
|
||||
#include <vector>
|
||||
|
||||
class LinearGraph
|
||||
{
|
||||
public:
|
||||
explicit LinearGraph(const char* name, size_t maxGraphPoints = 256)
|
||||
: name_(name)
|
||||
, maxPoints_(maxGraphPoints)
|
||||
{
|
||||
}
|
||||
|
||||
void addPoint(float value)
|
||||
{
|
||||
graph_.push_back(value);
|
||||
if (graph_.size() > maxPoints_)
|
||||
graph_.erase(graph_.begin());
|
||||
}
|
||||
|
||||
void renderGraph(uint32_t x, uint32_t y, uint32_t width, uint32_t height, const glm::vec4& color = vec4(1.0)) const
|
||||
{
|
||||
LVK_PROFILER_FUNCTION();
|
||||
|
||||
float minVal = std::numeric_limits<float>::max();
|
||||
float maxVal = std::numeric_limits<float>::min();
|
||||
|
||||
for (float f : graph_) {
|
||||
if (f < minVal)
|
||||
minVal = f;
|
||||
if (f > maxVal)
|
||||
maxVal = f;
|
||||
}
|
||||
|
||||
const float range = maxVal - minVal;
|
||||
|
||||
float valX = 0.0;
|
||||
|
||||
std::vector<float> dataX_;
|
||||
std::vector<float> dataY_;
|
||||
dataX_.reserve(graph_.size());
|
||||
dataY_.reserve(graph_.size());
|
||||
|
||||
for (float f : graph_) {
|
||||
const float valY = (f - minVal) / range;
|
||||
valX += 1.0f / maxPoints_;
|
||||
dataX_.push_back(valX);
|
||||
dataY_.push_back(valY);
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(x, y));
|
||||
ImGui::SetNextWindowSize(ImVec2(width, height));
|
||||
ImGui::Begin(
|
||||
name_, nullptr,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs);
|
||||
|
||||
if (ImPlot::BeginPlot(name_, ImVec2(width, height), ImPlotFlags_CanvasOnly | ImPlotFlags_NoFrame | ImPlotFlags_NoInputs)) {
|
||||
ImPlot::SetupAxes(nullptr, nullptr, ImPlotAxisFlags_NoDecorations, ImPlotAxisFlags_NoDecorations);
|
||||
ImPlot::PushStyleColor(ImPlotCol_Line, ImVec4(color.r, color.g, color.b, color.a));
|
||||
ImPlot::PushStyleColor(ImPlotCol_PlotBg, ImVec4(0, 0, 0, 0));
|
||||
ImPlot::PlotLine("#line", dataX_.data(), dataY_.data(), (int)graph_.size(), ImPlotLineFlags_None);
|
||||
ImPlot::PopStyleColor(2);
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
private:
|
||||
const char* name_ = nullptr;
|
||||
const size_t maxPoints_;
|
||||
std::deque<float> graph_;
|
||||
};
|
||||
55
shared/HelpersGLFW.cpp
Normal file
55
shared/HelpersGLFW.cpp
Normal file
|
|
@ -0,0 +1,55 @@
|
|||
#include "HelpersGLFW.h"
|
||||
|
||||
GLFWwindow* initWindow(const char* windowTitle, uint32_t& outWidth, uint32_t& outHeight)
|
||||
{
|
||||
glfwSetErrorCallback([](int error, const char* description) { printf("GLFW Error (%i): %s\n", error, description); });
|
||||
|
||||
if (!glfwInit()) {
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
const bool wantsWholeArea = !outWidth || !outHeight;
|
||||
|
||||
glfwWindowHint(GLFW_CLIENT_API, GLFW_NO_API);
|
||||
glfwWindowHint(GLFW_RESIZABLE, wantsWholeArea ? GLFW_FALSE : GLFW_TRUE);
|
||||
|
||||
// render full screen without overlapping taskbar
|
||||
GLFWmonitor* monitor = glfwGetPrimaryMonitor();
|
||||
const GLFWvidmode* mode = glfwGetVideoMode(monitor);
|
||||
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
int w = mode->width;
|
||||
int h = mode->height;
|
||||
|
||||
if (wantsWholeArea) {
|
||||
glfwGetMonitorWorkarea(monitor, &x, &y, &w, &h);
|
||||
} else {
|
||||
w = outWidth;
|
||||
h = outHeight;
|
||||
}
|
||||
|
||||
GLFWwindow* window = glfwCreateWindow(w, h, windowTitle, nullptr, nullptr);
|
||||
|
||||
if (!window) {
|
||||
glfwTerminate();
|
||||
return nullptr;
|
||||
}
|
||||
|
||||
if (wantsWholeArea) {
|
||||
glfwSetWindowPos(window, x, y);
|
||||
}
|
||||
|
||||
glfwGetWindowSize(window, &w, &h);
|
||||
|
||||
glfwSetKeyCallback(window, [](GLFWwindow* window, int key, int, int action, int) {
|
||||
if (key == GLFW_KEY_ESCAPE && action == GLFW_PRESS) {
|
||||
glfwSetWindowShouldClose(window, GLFW_TRUE);
|
||||
}
|
||||
});
|
||||
|
||||
outWidth = (uint32_t)w;
|
||||
outHeight = (uint32_t)h;
|
||||
|
||||
return window;
|
||||
}
|
||||
24
shared/HelpersGLFW.h
Normal file
24
shared/HelpersGLFW.h
Normal file
|
|
@ -0,0 +1,24 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdio.h>
|
||||
|
||||
#define GLFW_INCLUDE_NONE
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
// clang-format off
|
||||
#ifdef _WIN32
|
||||
# define GLFW_EXPOSE_NATIVE_WIN32
|
||||
# define GLFW_EXPOSE_NATIVE_WGL
|
||||
#elif __APPLE__
|
||||
# define GLFW_EXPOSE_NATIVE_COCOA
|
||||
#elif defined(__linux__)
|
||||
# define GLFW_EXPOSE_NATIVE_X11
|
||||
#else
|
||||
# error Unsupported OS
|
||||
#endif
|
||||
// clang-format on
|
||||
|
||||
#include <GLFW/glfw3native.h>
|
||||
|
||||
GLFWwindow *initWindow(const char *windowTitle, uint32_t &outWidth,
|
||||
uint32_t &outHeight);
|
||||
239
shared/LineCanvas.cpp
Normal file
239
shared/LineCanvas.cpp
Normal file
|
|
@ -0,0 +1,239 @@
|
|||
#include "LineCanvas.h"
|
||||
|
||||
static const char* codeVS = R"(
|
||||
layout (location = 0) out vec4 out_color;
|
||||
|
||||
struct Vertex {
|
||||
vec4 pos;
|
||||
vec4 rgba;
|
||||
};
|
||||
|
||||
layout(std430, buffer_reference) readonly buffer VertexBuffer {
|
||||
Vertex vertices[];
|
||||
};
|
||||
|
||||
layout(push_constant) uniform PushConstants {
|
||||
mat4 mvp;
|
||||
VertexBuffer vb;
|
||||
};
|
||||
|
||||
void main() {
|
||||
// Vertex v = vb.vertices[gl_VertexIndex]; <--- does not work on Snapdragon Adreno
|
||||
out_color = vb.vertices[gl_VertexIndex].rgba;
|
||||
gl_Position = mvp * vb.vertices[gl_VertexIndex].pos;
|
||||
})";
|
||||
|
||||
static const char* codeFS = R"(
|
||||
layout (location = 0) in vec4 in_color;
|
||||
layout (location = 0) out vec4 out_color;
|
||||
|
||||
void main() {
|
||||
out_color = in_color;
|
||||
})";
|
||||
|
||||
void LineCanvas2D::render(const char* nameImGuiWindow)
|
||||
{
|
||||
LVK_PROFILER_FUNCTION();
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(0, 0));
|
||||
ImGui::SetNextWindowSize(ImGui::GetMainViewport()->Size);
|
||||
ImGui::Begin(
|
||||
nameImGuiWindow, nullptr,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoBackground | ImGuiWindowFlags_NoInputs);
|
||||
|
||||
ImDrawList* drawList = ImGui::GetBackgroundDrawList();
|
||||
|
||||
for (const LineData& l : lines_) {
|
||||
drawList->AddLine(ImVec2(l.p1.x, l.p1.y), ImVec2(l.p2.x, l.p2.y), ImColor(l.color.r, l.color.g, l.color.b, l.color.a));
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void LineCanvas3D::line(const vec3& p1, const vec3& p2, const vec4& c)
|
||||
{
|
||||
lines_.push_back({ .pos = vec4(p1, 1.0f), .color = c });
|
||||
lines_.push_back({ .pos = vec4(p2, 1.0f), .color = c });
|
||||
}
|
||||
|
||||
void LineCanvas3D::plane(
|
||||
const vec3& o, const vec3& v1, const vec3& v2, int n1, int n2, float s1, float s2, const vec4& color, const vec4& outlineColor)
|
||||
{
|
||||
line(o - s1 / 2.0f * v1 - s2 / 2.0f * v2, o - s1 / 2.0f * v1 + s2 / 2.0f * v2, outlineColor);
|
||||
line(o + s1 / 2.0f * v1 - s2 / 2.0f * v2, o + s1 / 2.0f * v1 + s2 / 2.0f * v2, outlineColor);
|
||||
|
||||
line(o - s1 / 2.0f * v1 + s2 / 2.0f * v2, o + s1 / 2.0f * v1 + s2 / 2.0f * v2, outlineColor);
|
||||
line(o - s1 / 2.0f * v1 - s2 / 2.0f * v2, o + s1 / 2.0f * v1 - s2 / 2.0f * v2, outlineColor);
|
||||
|
||||
for (int i = 1; i < n1; i++) {
|
||||
float t = ((float)i - (float)n1 / 2.0f) * s1 / (float)n1;
|
||||
const vec3 o1 = o + t * v1;
|
||||
line(o1 - s2 / 2.0f * v2, o1 + s2 / 2.0f * v2, color);
|
||||
}
|
||||
|
||||
for (int i = 1; i < n2; i++) {
|
||||
const float t = ((float)i - (float)n2 / 2.0f) * s2 / (float)n2;
|
||||
const vec3 o2 = o + t * v2;
|
||||
line(o2 - s1 / 2.0f * v1, o2 + s1 / 2.0f * v1, color);
|
||||
}
|
||||
}
|
||||
|
||||
void LineCanvas3D::box(const mat4& m, const vec3& size, const vec4& c)
|
||||
{
|
||||
vec3 pts[8] = {
|
||||
vec3(+size.x, +size.y, +size.z), vec3(+size.x, +size.y, -size.z), vec3(+size.x, -size.y, +size.z), vec3(+size.x, -size.y, -size.z),
|
||||
vec3(-size.x, +size.y, +size.z), vec3(-size.x, +size.y, -size.z), vec3(-size.x, -size.y, +size.z), vec3(-size.x, -size.y, -size.z),
|
||||
};
|
||||
|
||||
for (auto& p : pts)
|
||||
p = vec3(m * vec4(p, 1.f));
|
||||
|
||||
line(pts[0], pts[1], c);
|
||||
line(pts[2], pts[3], c);
|
||||
line(pts[4], pts[5], c);
|
||||
line(pts[6], pts[7], c);
|
||||
|
||||
line(pts[0], pts[2], c);
|
||||
line(pts[1], pts[3], c);
|
||||
line(pts[4], pts[6], c);
|
||||
line(pts[5], pts[7], c);
|
||||
|
||||
line(pts[0], pts[4], c);
|
||||
line(pts[1], pts[5], c);
|
||||
line(pts[2], pts[6], c);
|
||||
line(pts[3], pts[7], c);
|
||||
}
|
||||
|
||||
void LineCanvas3D::box(const mat4& m, const BoundingBox& box, const glm::vec4& color)
|
||||
{
|
||||
this->box(m * glm::translate(mat4(1.f), .5f * (box.min_ + box.max_)), 0.5f * vec3(box.max_ - box.min_), color);
|
||||
}
|
||||
|
||||
void LineCanvas3D::frustum(const mat4& camView, const mat4& camProj, const vec4& color)
|
||||
{
|
||||
const vec3 corners[] = { vec3(-1, -1, -1), vec3(+1, -1, -1), vec3(+1, +1, -1), vec3(-1, +1, -1),
|
||||
vec3(-1, -1, +1), vec3(+1, -1, +1), vec3(+1, +1, +1), vec3(-1, +1, +1) };
|
||||
|
||||
vec3 pp[8];
|
||||
|
||||
for (int i = 0; i < 8; i++) {
|
||||
vec4 q = glm::inverse(camView) * glm::inverse(camProj) * vec4(corners[i], 1.0f);
|
||||
pp[i] = vec3(q.x / q.w, q.y / q.w, q.z / q.w);
|
||||
}
|
||||
line(pp[0], pp[4], color);
|
||||
line(pp[1], pp[5], color);
|
||||
line(pp[2], pp[6], color);
|
||||
line(pp[3], pp[7], color);
|
||||
// near
|
||||
line(pp[0], pp[1], color);
|
||||
line(pp[1], pp[2], color);
|
||||
line(pp[2], pp[3], color);
|
||||
line(pp[3], pp[0], color);
|
||||
// x
|
||||
line(pp[0], pp[2], color);
|
||||
line(pp[1], pp[3], color);
|
||||
// far
|
||||
line(pp[4], pp[5], color);
|
||||
line(pp[5], pp[6], color);
|
||||
line(pp[6], pp[7], color);
|
||||
line(pp[7], pp[4], color);
|
||||
// x
|
||||
line(pp[4], pp[6], color);
|
||||
line(pp[5], pp[7], color);
|
||||
|
||||
const vec4 gridColor = color * 0.7f;
|
||||
const int gridLines = 100;
|
||||
|
||||
// bottom
|
||||
{
|
||||
vec3 p1 = pp[0];
|
||||
vec3 p2 = pp[1];
|
||||
const vec3 s1 = (pp[4] - pp[0]) / float(gridLines);
|
||||
const vec3 s2 = (pp[5] - pp[1]) / float(gridLines);
|
||||
for (int i = 0; i != gridLines; i++, p1 += s1, p2 += s2)
|
||||
line(p1, p2, gridColor);
|
||||
}
|
||||
// top
|
||||
{
|
||||
vec3 p1 = pp[2];
|
||||
vec3 p2 = pp[3];
|
||||
const vec3 s1 = (pp[6] - pp[2]) / float(gridLines);
|
||||
const vec3 s2 = (pp[7] - pp[3]) / float(gridLines);
|
||||
for (int i = 0; i != gridLines; i++, p1 += s1, p2 += s2)
|
||||
line(p1, p2, gridColor);
|
||||
}
|
||||
// left
|
||||
{
|
||||
vec3 p1 = pp[0];
|
||||
vec3 p2 = pp[3];
|
||||
const vec3 s1 = (pp[4] - pp[0]) / float(gridLines);
|
||||
const vec3 s2 = (pp[7] - pp[3]) / float(gridLines);
|
||||
for (int i = 0; i != gridLines; i++, p1 += s1, p2 += s2)
|
||||
line(p1, p2, gridColor);
|
||||
}
|
||||
// right
|
||||
{
|
||||
vec3 p1 = pp[1];
|
||||
vec3 p2 = pp[2];
|
||||
const vec3 s1 = (pp[5] - pp[1]) / float(gridLines);
|
||||
const vec3 s2 = (pp[6] - pp[2]) / float(gridLines);
|
||||
for (int i = 0; i != gridLines; i++, p1 += s1, p2 += s2)
|
||||
line(p1, p2, gridColor);
|
||||
}
|
||||
}
|
||||
|
||||
void LineCanvas3D::render(lvk::IContext& ctx, const lvk::Framebuffer& desc, lvk::ICommandBuffer& buf, uint32_t numSamples)
|
||||
{
|
||||
LVK_PROFILER_FUNCTION();
|
||||
|
||||
if (lines_.empty()) {
|
||||
return;
|
||||
}
|
||||
|
||||
const uint32_t requiredSize = lines_.size() * sizeof(LineData);
|
||||
|
||||
if (currentBufferSize_[currentFrame_] < requiredSize) {
|
||||
linesBuffer_[currentFrame_] = ctx.createBuffer(
|
||||
{ .usage = lvk::BufferUsageBits_Storage, .storage = lvk::StorageType_HostVisible, .size = requiredSize, .data = lines_.data() });
|
||||
currentBufferSize_[currentFrame_] = requiredSize;
|
||||
} else {
|
||||
ctx.upload(linesBuffer_[currentFrame_], lines_.data(), requiredSize);
|
||||
}
|
||||
|
||||
if (pipeline_.empty() || pipelineSamples_ != numSamples) {
|
||||
pipelineSamples_ = numSamples;
|
||||
|
||||
vert_ = ctx.createShaderModule({ codeVS, lvk::Stage_Vert, "Shader Module: imgui (vert)" });
|
||||
frag_ = ctx.createShaderModule({ codeFS, lvk::Stage_Frag, "Shader Module: imgui (frag)" });
|
||||
pipeline_ = ctx.createRenderPipeline(
|
||||
{
|
||||
.topology = lvk::Topology_Line,
|
||||
.smVert = vert_,
|
||||
.smFrag = frag_,
|
||||
.color = { {
|
||||
.format = ctx.getFormat(desc.color[0].texture),
|
||||
.blendEnabled = true,
|
||||
.srcRGBBlendFactor = lvk::BlendFactor_SrcAlpha,
|
||||
.dstRGBBlendFactor = lvk::BlendFactor_OneMinusSrcAlpha,
|
||||
} },
|
||||
.depthFormat = desc.depthStencil.texture ? ctx.getFormat(desc.depthStencil.texture) : lvk::Format_Invalid,
|
||||
.cullMode = lvk::CullMode_None,
|
||||
.samplesCount = numSamples,
|
||||
},
|
||||
nullptr);
|
||||
}
|
||||
|
||||
struct {
|
||||
mat4 mvp;
|
||||
uint64_t addr;
|
||||
} pc{
|
||||
.mvp = mvp_,
|
||||
.addr = ctx.gpuAddress(linesBuffer_[currentFrame_]),
|
||||
};
|
||||
buf.cmdBindRenderPipeline(pipeline_);
|
||||
buf.cmdPushConstants(pc);
|
||||
buf.cmdDraw(lines_.size());
|
||||
|
||||
currentFrame_ = (currentFrame_ + 1) % LVK_ARRAY_NUM_ELEMENTS(linesBuffer_);
|
||||
}
|
||||
54
shared/LineCanvas.h
Normal file
54
shared/LineCanvas.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
|
||||
#include "shared/VulkanApp.h"
|
||||
|
||||
class LineCanvas2D
|
||||
{
|
||||
public:
|
||||
void clear() { lines_.clear(); }
|
||||
void line(const vec2& p1, const vec2& p2, const vec4& c) { lines_.push_back({ .p1 = p1, .p2 = p2, .color = c }); }
|
||||
void render(const char* nameImGuiWindow);
|
||||
|
||||
private:
|
||||
struct LineData {
|
||||
vec2 p1, p2;
|
||||
vec4 color;
|
||||
};
|
||||
std::vector<LineData> lines_;
|
||||
};
|
||||
|
||||
class LineCanvas3D
|
||||
{
|
||||
public:
|
||||
void clear() { lines_.clear(); }
|
||||
void line(const vec3& p1, const vec3& p2, const vec4& c);
|
||||
void plane(
|
||||
const vec3& orig, const vec3& v1, const vec3& v2, int n1, int n2, float s1, float s2, const vec4& color, const vec4& outlineColor);
|
||||
void box(const mat4& m, const BoundingBox& box, const vec4& color);
|
||||
void box(const mat4& m, const vec3& size, const vec4& color);
|
||||
void frustum(const mat4& camView, const mat4& camProj, const vec4& color);
|
||||
|
||||
void setMatrix(const mat4& mvp) { mvp_ = mvp; }
|
||||
void render(lvk::IContext& ctx, const lvk::Framebuffer& desc, lvk::ICommandBuffer& buf, uint32_t numSamples = 1);
|
||||
|
||||
private:
|
||||
mat4 mvp_ = mat4(1.0f);
|
||||
|
||||
struct LineData {
|
||||
vec4 pos;
|
||||
vec4 color;
|
||||
};
|
||||
std::vector<LineData> lines_;
|
||||
|
||||
lvk::Holder<lvk::ShaderModuleHandle> vert_;
|
||||
lvk::Holder<lvk::ShaderModuleHandle> frag_;
|
||||
lvk::Holder<lvk::RenderPipelineHandle> pipeline_;
|
||||
lvk::Holder<lvk::BufferHandle> linesBuffer_[3] = {};
|
||||
|
||||
uint32_t pipelineSamples_ = 1;
|
||||
|
||||
uint32_t currentBufferSize_[3] = {};
|
||||
uint32_t currentFrame_ = 0;
|
||||
|
||||
static_assert(LVK_ARRAY_NUM_ELEMENTS(linesBuffer_) == LVK_ARRAY_NUM_ELEMENTS(currentBufferSize_));
|
||||
};
|
||||
151
shared/Scene/MergeUtil.cpp
Normal file
151
shared/Scene/MergeUtil.cpp
Normal file
|
|
@ -0,0 +1,151 @@
|
|||
#include "shared/Scene/MergeUtil.h"
|
||||
#include "shared/Scene/Scene.h"
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
static uint32_t shiftMeshIndices(MeshData& meshData, const std::vector<uint32_t>& meshesToMerge)
|
||||
{
|
||||
uint32_t minVtxOffset = std::numeric_limits<uint32_t>::max();
|
||||
|
||||
for (uint32_t i : meshesToMerge)
|
||||
minVtxOffset = std::min(meshData.meshes[i].vertexOffset, minVtxOffset);
|
||||
|
||||
uint32_t mergeCount = 0u; // calculated by summing index counts in meshesToMerge
|
||||
|
||||
// now shift all the indices in individual index blocks [use minVtxOffset]
|
||||
for (uint32_t i : meshesToMerge) {
|
||||
Mesh& m = meshData.meshes[i];
|
||||
// for how much should we shift the indices in mesh [m]
|
||||
const uint32_t delta = m.vertexOffset - minVtxOffset;
|
||||
const uint32_t idxCount = m.getLODIndicesCount(0);
|
||||
for (uint32_t ii = 0u; ii < idxCount; ii++)
|
||||
meshData.indexData[m.indexOffset + ii] += delta;
|
||||
|
||||
m.vertexOffset = minVtxOffset;
|
||||
|
||||
// sum all the deleted meshes' indices
|
||||
mergeCount += idxCount;
|
||||
}
|
||||
|
||||
return meshData.indexData.size() - mergeCount;
|
||||
}
|
||||
|
||||
// All the meshesToMerge now have the same vertexOffset and individual index values are shifted by appropriate amount
|
||||
// Here we move all the indices to appropriate places in the new index array
|
||||
static void mergeIndexArray(MeshData& md, const std::vector<uint32_t>& meshesToMerge, std::unordered_map<uint32_t, uint32_t>& oldToNew)
|
||||
{
|
||||
std::vector<uint32_t> newIndices(md.indexData.size());
|
||||
// Two offsets in the new indices array (one begins at the start, the second one after all the copied indices)
|
||||
uint32_t copyOffset = 0;
|
||||
uint32_t mergeOffset = shiftMeshIndices(md, meshesToMerge);
|
||||
|
||||
const size_t mergedMeshIndex = md.meshes.size() - meshesToMerge.size();
|
||||
uint32_t newIndex = 0u;
|
||||
for (size_t midx = 0u; midx < md.meshes.size(); midx++) {
|
||||
const bool shouldMerge = std::binary_search(meshesToMerge.begin(), meshesToMerge.end(), midx);
|
||||
|
||||
oldToNew[midx] = shouldMerge ? mergedMeshIndex : newIndex;
|
||||
newIndex += shouldMerge ? 0 : 1;
|
||||
|
||||
Mesh& mesh = md.meshes[midx];
|
||||
const uint32_t idxCount = mesh.getLODIndicesCount(0);
|
||||
// move all indices to the new array at mergeOffset
|
||||
const auto start = md.indexData.begin() + mesh.indexOffset;
|
||||
mesh.indexOffset = copyOffset;
|
||||
uint32_t* const offsetPtr = shouldMerge ? &mergeOffset : ©Offset;
|
||||
std::copy(start, start + idxCount, newIndices.begin() + *offsetPtr);
|
||||
*offsetPtr += idxCount;
|
||||
}
|
||||
|
||||
md.indexData = newIndices;
|
||||
|
||||
// all the merged indices are now in lastMesh
|
||||
Mesh lastMesh = md.meshes[meshesToMerge[0]];
|
||||
lastMesh.indexOffset = copyOffset;
|
||||
lastMesh.lodOffset[0] = copyOffset;
|
||||
lastMesh.lodOffset[1] = mergeOffset;
|
||||
lastMesh.lodCount = 1;
|
||||
md.meshes.push_back(lastMesh);
|
||||
}
|
||||
|
||||
void mergeNodesWithMaterial(Scene& scene, MeshData& meshData, const std::string& materialName)
|
||||
{
|
||||
// Find material index
|
||||
const int oldMaterial = (int)std::distance(
|
||||
std::begin(scene.materialNames), std::find(std::begin(scene.materialNames), std::end(scene.materialNames), materialName));
|
||||
|
||||
std::vector<uint32_t> toDelete;
|
||||
|
||||
for (size_t i = 0u; i < scene.hierarchy.size(); i++)
|
||||
if (scene.meshForNode.contains(i) && scene.materialForNode.contains(i) && (scene.materialForNode.at(i) == oldMaterial))
|
||||
toDelete.push_back(i);
|
||||
|
||||
std::vector<uint32_t> meshesToMerge(toDelete.size());
|
||||
|
||||
// Convert toDelete indices to mesh indices
|
||||
std::transform(toDelete.begin(), toDelete.end(), meshesToMerge.begin(), [&scene](uint32_t i) { return scene.meshForNode.at(i); });
|
||||
|
||||
// TODO: if merged mesh transforms are non-zero, then we should pre-transform individual mesh vertices in meshData using local transform
|
||||
|
||||
// old-to-new mesh indices
|
||||
std::unordered_map<uint32_t, uint32_t> oldToNew;
|
||||
|
||||
// now move all the meshesToMerge to the end of array
|
||||
mergeIndexArray(meshData, meshesToMerge, oldToNew);
|
||||
|
||||
// cutoff all but one of the merged meshes (insert the last saved mesh from meshesToMerge - they are all the same)
|
||||
eraseSelected(meshData.meshes, meshesToMerge);
|
||||
|
||||
for (auto& n : scene.meshForNode)
|
||||
n.second = oldToNew[n.second];
|
||||
|
||||
// reattach the node with merged meshes [identity transforms are assumed]
|
||||
int newNode = addNode(scene, 0, 1);
|
||||
scene.meshForNode[newNode] = (int)meshData.meshes.size() - 1;
|
||||
scene.materialForNode[newNode] = (uint32_t)oldMaterial;
|
||||
|
||||
deleteSceneNodes(scene, toDelete);
|
||||
}
|
||||
|
||||
void mergeMaterialLists(
|
||||
const std::vector<std::vector<Material>*>& oldMaterials, const std::vector<std::vector<std::string>*>& oldTextures,
|
||||
std::vector<Material>& allMaterials, std::vector<std::string>& newTextures)
|
||||
{
|
||||
// map texture names to indices in newTexturesList (calculated as we fill the newTexturesList)
|
||||
std::unordered_map<std::string, int> newTextureNames;
|
||||
std::unordered_map<size_t, size_t> materialToTextureList; // use the index of Material in the allMaterials array
|
||||
|
||||
// create a combined material list [no hashing of materials, just straightforward merging of all lists]
|
||||
for (size_t midx = 0; midx != oldMaterials.size(); midx++) {
|
||||
for (const Material& m : *oldMaterials[midx]) {
|
||||
allMaterials.push_back(m);
|
||||
materialToTextureList[allMaterials.size() - 1] = midx;
|
||||
}
|
||||
}
|
||||
|
||||
// create one combined texture list
|
||||
for (const std::vector<std::string>* tl : oldTextures) {
|
||||
for (const std::string& file : *tl) {
|
||||
newTextureNames[file] = addUnique(newTextures, file);
|
||||
}
|
||||
}
|
||||
|
||||
// a lambda to replace textureID by a new "version" (from the global list)
|
||||
auto replaceTexture = [&materialToTextureList, &oldTextures, &newTextureNames](int mtlId, int* textureID) {
|
||||
if (*textureID == -1)
|
||||
return;
|
||||
|
||||
const size_t listIdx = materialToTextureList[mtlId];
|
||||
const std::vector<std::string>& texList = *oldTextures[listIdx];
|
||||
const std::string& texFile = texList[*textureID];
|
||||
*textureID = newTextureNames[texFile];
|
||||
};
|
||||
|
||||
for (size_t i = 0; i < allMaterials.size(); i++) {
|
||||
Material& m = allMaterials[i];
|
||||
replaceTexture(i, &m.baseColorTexture);
|
||||
replaceTexture(i, &m.emissiveTexture);
|
||||
replaceTexture(i, &m.normalTexture);
|
||||
replaceTexture(i, &m.opacityTexture);
|
||||
}
|
||||
}
|
||||
16
shared/Scene/MergeUtil.h
Normal file
16
shared/Scene/MergeUtil.h
Normal file
|
|
@ -0,0 +1,16 @@
|
|||
#pragma once
|
||||
|
||||
#include "shared/Scene/Scene.h"
|
||||
#include "shared/Scene/VtxData.h"
|
||||
|
||||
void mergeNodesWithMaterial(Scene& scene, MeshData& meshData, const std::string& materialName);
|
||||
|
||||
// Merge material lists from multiple scenes (follows the logic of merging in mergeScenes)
|
||||
void mergeMaterialLists(
|
||||
// Input:
|
||||
const std::vector<std::vector<Material>*>& oldMaterials, // all materials
|
||||
const std::vector<std::vector<std::string>*>& oldTextures, // all textures from all material lists
|
||||
// Output:
|
||||
std::vector<Material>& allMaterials,
|
||||
std::vector<std::string>& newTextures // all textures (merged from oldTextures, only unique items)
|
||||
);
|
||||
493
shared/Scene/Scene.cpp
Normal file
493
shared/Scene/Scene.cpp
Normal file
|
|
@ -0,0 +1,493 @@
|
|||
#include "shared/Scene/Scene.h"
|
||||
#include "shared/Utils.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <numeric>
|
||||
|
||||
int addNode(Scene &scene, int parent, int level) {
|
||||
const int node = (int)scene.hierarchy.size();
|
||||
{
|
||||
// TODO: resize aux arrays (local/global etc.)
|
||||
scene.localTransform.push_back(glm::mat4(1.0f));
|
||||
scene.globalTransform.push_back(glm::mat4(1.0f));
|
||||
}
|
||||
scene.hierarchy.push_back({.parent = parent, .lastSibling = -1});
|
||||
if (parent > -1) {
|
||||
// find first item (sibling)
|
||||
const int s = scene.hierarchy[parent].firstChild;
|
||||
if (s == -1) {
|
||||
scene.hierarchy[parent].firstChild = node;
|
||||
scene.hierarchy[node].lastSibling = node;
|
||||
} else {
|
||||
int dest = scene.hierarchy[s].lastSibling;
|
||||
if (dest <= -1) {
|
||||
// no cached lastSibling, iterate nextSibling indices
|
||||
for (dest = s; scene.hierarchy[dest].nextSibling != -1;
|
||||
dest = scene.hierarchy[dest].nextSibling)
|
||||
;
|
||||
}
|
||||
scene.hierarchy[dest].nextSibling = node;
|
||||
scene.hierarchy[s].lastSibling = node;
|
||||
}
|
||||
}
|
||||
scene.hierarchy[node].level = level;
|
||||
scene.hierarchy[node].nextSibling = -1;
|
||||
scene.hierarchy[node].firstChild = -1;
|
||||
return node;
|
||||
}
|
||||
|
||||
void markAsChanged(Scene &scene, int node) {
|
||||
const int level = scene.hierarchy[node].level;
|
||||
scene.changedAtThisFrame[level].push_back(node);
|
||||
|
||||
// TODO: use non-recursive iteration with aux stack
|
||||
for (int s = scene.hierarchy[node].firstChild; s != -1;
|
||||
s = scene.hierarchy[s].nextSibling) {
|
||||
markAsChanged(scene, s);
|
||||
}
|
||||
}
|
||||
|
||||
int findNodeByName(const Scene &scene, const std::string &name) {
|
||||
// Extremely simple linear search without any hierarchy reference
|
||||
// To support DFS/BFS searches separate traversal routines are needed
|
||||
|
||||
for (size_t i = 0; i < scene.localTransform.size(); i++)
|
||||
if (scene.nameForNode.contains(i)) {
|
||||
int strID = scene.nameForNode.at(i);
|
||||
if (strID > -1)
|
||||
if (scene.nodeNames[strID] == name)
|
||||
return (int)i;
|
||||
}
|
||||
|
||||
return -1;
|
||||
}
|
||||
|
||||
bool mat4IsIdentity(const glm::mat4 &m);
|
||||
void fprintfMat4(FILE *f, const glm::mat4 &m);
|
||||
|
||||
// CPU version of global transform update []
|
||||
bool recalculateGlobalTransforms(Scene &scene) {
|
||||
bool wasUpdated = false;
|
||||
|
||||
if (!scene.changedAtThisFrame[0].empty()) {
|
||||
const int c = scene.changedAtThisFrame[0][0];
|
||||
scene.globalTransform[c] = scene.localTransform[c];
|
||||
scene.changedAtThisFrame[0].clear();
|
||||
wasUpdated = true;
|
||||
}
|
||||
|
||||
for (int i = 1; i < MAX_NODE_LEVEL; i++) {
|
||||
for (int c : scene.changedAtThisFrame[i]) {
|
||||
const int p = scene.hierarchy[c].parent;
|
||||
scene.globalTransform[c] =
|
||||
scene.globalTransform[p] * scene.localTransform[c];
|
||||
}
|
||||
wasUpdated |= !scene.changedAtThisFrame[i].empty();
|
||||
scene.changedAtThisFrame[i].clear();
|
||||
}
|
||||
|
||||
return wasUpdated;
|
||||
}
|
||||
|
||||
void loadMap(FILE *f, std::unordered_map<uint32_t, uint32_t> &map) {
|
||||
std::vector<uint32_t> ms;
|
||||
|
||||
uint32_t sz = 0;
|
||||
fread(&sz, 1, sizeof(sz), f);
|
||||
|
||||
ms.resize(sz);
|
||||
fread(ms.data(), sizeof(uint32_t), sz, f);
|
||||
|
||||
for (size_t i = 0; i < (sz / 2); i++)
|
||||
map[ms[i * 2 + 0]] = ms[i * 2 + 1];
|
||||
}
|
||||
|
||||
void loadScene(const char *fileName, Scene &scene) {
|
||||
FILE *f = fopen(fileName, "rb");
|
||||
|
||||
if (!f) {
|
||||
printf("Cannot open scene file '%s'. Please run SceneConverter from "
|
||||
"Chapter7 and/or MergeMeshes from Chapter 9",
|
||||
fileName);
|
||||
return;
|
||||
}
|
||||
|
||||
uint32_t sz = 0;
|
||||
fread(&sz, sizeof(sz), 1, f);
|
||||
|
||||
scene.hierarchy.resize(sz);
|
||||
scene.globalTransform.resize(sz);
|
||||
scene.localTransform.resize(sz);
|
||||
// TODO: check > -1
|
||||
// TODO: recalculate changedAtThisLevel() - find max depth of a node [or save
|
||||
// scene.maxLevel]
|
||||
fread(scene.localTransform.data(), sizeof(glm::mat4), sz, f);
|
||||
fread(scene.globalTransform.data(), sizeof(glm::mat4), sz, f);
|
||||
fread(scene.hierarchy.data(), sizeof(Hierarchy), sz, f);
|
||||
|
||||
// Mesh for node [index to some list of buffers]
|
||||
loadMap(f, scene.materialForNode);
|
||||
loadMap(f, scene.meshForNode);
|
||||
|
||||
if (!feof(f)) {
|
||||
loadMap(f, scene.nameForNode);
|
||||
loadStringList(f, scene.nodeNames);
|
||||
loadStringList(f, scene.materialNames);
|
||||
}
|
||||
|
||||
fclose(f);
|
||||
|
||||
markAsChanged(scene, 0);
|
||||
recalculateGlobalTransforms(scene);
|
||||
}
|
||||
|
||||
void saveMap(FILE *f, const std::unordered_map<uint32_t, uint32_t> &map) {
|
||||
std::vector<uint32_t> ms;
|
||||
ms.reserve(map.size() * 2);
|
||||
for (const auto &m : map) {
|
||||
ms.push_back(m.first);
|
||||
ms.push_back(m.second);
|
||||
}
|
||||
const uint32_t sz = static_cast<uint32_t>(ms.size());
|
||||
fwrite(&sz, sizeof(sz), 1, f);
|
||||
fwrite(ms.data(), sizeof(uint32_t), ms.size(), f);
|
||||
}
|
||||
|
||||
void saveScene(const char *fileName, const Scene &scene) {
|
||||
FILE *f = fopen(fileName, "wb");
|
||||
|
||||
const uint32_t sz = (uint32_t)scene.hierarchy.size();
|
||||
fwrite(&sz, sizeof(sz), 1, f);
|
||||
|
||||
fwrite(scene.localTransform.data(), sizeof(glm::mat4), sz, f);
|
||||
fwrite(scene.globalTransform.data(), sizeof(glm::mat4), sz, f);
|
||||
fwrite(scene.hierarchy.data(), sizeof(Hierarchy), sz, f);
|
||||
|
||||
// Mesh for node [index to some list of buffers]
|
||||
saveMap(f, scene.materialForNode);
|
||||
saveMap(f, scene.meshForNode);
|
||||
|
||||
if (!scene.nodeNames.empty() && !scene.nameForNode.empty()) {
|
||||
saveMap(f, scene.nameForNode);
|
||||
saveStringList(f, scene.nodeNames);
|
||||
saveStringList(f, scene.materialNames);
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
bool mat4IsIdentity(const glm::mat4 &m) {
|
||||
return (m[0][0] == 1 && m[0][1] == 0 && m[0][2] == 0 && m[0][3] == 0 &&
|
||||
m[1][0] == 0 && m[1][1] == 1 && m[1][2] == 0 && m[1][3] == 0 &&
|
||||
m[2][0] == 0 && m[2][1] == 0 && m[2][2] == 1 && m[2][3] == 0 &&
|
||||
m[3][0] == 0 && m[3][1] == 0 && m[3][2] == 0 && m[3][3] == 1);
|
||||
}
|
||||
|
||||
void fprintfMat4(FILE *f, const glm::mat4 &m) {
|
||||
if (mat4IsIdentity(m)) {
|
||||
fprintf(f, "Identity\n");
|
||||
} else {
|
||||
fprintf(f, "\n");
|
||||
for (int i = 0; i < 4; i++) {
|
||||
for (int j = 0; j < 4; j++)
|
||||
fprintf(f, "%f ;", m[i][j]);
|
||||
fprintf(f, "\n");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void dumpTransforms(const char *fileName, const Scene &scene) {
|
||||
FILE *f = fopen(fileName, "a+");
|
||||
for (size_t i = 0; i < scene.localTransform.size(); i++) {
|
||||
fprintf(f, "Node[%d].localTransform: ", (int)i);
|
||||
fprintfMat4(f, scene.localTransform[i]);
|
||||
fprintf(f, "Node[%d].globalTransform: ", (int)i);
|
||||
fprintfMat4(f, scene.globalTransform[i]);
|
||||
fprintf(f, "Node[%d].globalDet = %f; localDet = %f\n", (int)i,
|
||||
glm::determinant(scene.globalTransform[i]),
|
||||
glm::determinant(scene.localTransform[i]));
|
||||
}
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void printChangedNodes(const Scene &scene) {
|
||||
for (int i = 0; i < MAX_NODE_LEVEL && (!scene.changedAtThisFrame[i].empty());
|
||||
i++) {
|
||||
printf("Changed at level(%d):\n", i);
|
||||
|
||||
for (const int &c : scene.changedAtThisFrame[i]) {
|
||||
int p = scene.hierarchy[c].parent;
|
||||
// scene.globalTransform_[c] = scene.globalTransform_[p] *
|
||||
// scene.localTransform_[c];
|
||||
printf(" Node %d. Parent = %d; LocalTransform: ", c, p);
|
||||
fprintfMat4(stdout, scene.localTransform[i]);
|
||||
if (p > -1) {
|
||||
printf(" ParentGlobalTransform: ");
|
||||
fprintfMat4(stdout, scene.globalTransform[p]);
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// Shift all hierarchy components in the nodes
|
||||
void shiftNodes(Scene &scene, int startOffset, int nodeCount, int shiftAmount) {
|
||||
auto shiftNode = [shiftAmount](Hierarchy &node) {
|
||||
if (node.parent > -1)
|
||||
node.parent += shiftAmount;
|
||||
if (node.firstChild > -1)
|
||||
node.firstChild += shiftAmount;
|
||||
if (node.nextSibling > -1)
|
||||
node.nextSibling += shiftAmount;
|
||||
if (node.lastSibling > -1)
|
||||
node.lastSibling += shiftAmount;
|
||||
// node->level does not require to be shifted
|
||||
};
|
||||
|
||||
// If there are too many nodes, we can use std::execution::par with
|
||||
// std::transform:
|
||||
// std::transform(scene.hierarchy_.begin() + startOffset,
|
||||
// scene.hierarchy_.begin() + nodeCount,
|
||||
// scene.hierarchy_.begin() + startOffset,
|
||||
// shiftNode);
|
||||
// for (auto i = scene.hierarchy_.begin() + startOffset ; i !=
|
||||
// scene.hierarchy_.begin() + nodeCount ; i++) shiftNode(*i);
|
||||
|
||||
for (int i = 0; i < nodeCount; i++)
|
||||
shiftNode(scene.hierarchy[i + startOffset]);
|
||||
}
|
||||
|
||||
using ItemMap = std::unordered_map<uint32_t, uint32_t>;
|
||||
|
||||
// Add the items from otherMap shifting indices and values along the way
|
||||
void mergeMaps(ItemMap &m, const ItemMap &otherMap, int indexOffset,
|
||||
int itemOffset) {
|
||||
for (const auto &i : otherMap)
|
||||
m[i.first + indexOffset] = i.second + itemOffset;
|
||||
}
|
||||
|
||||
/**
|
||||
There are different use cases for scene merging.
|
||||
The simplest one is the direct "gluing" of multiple scenes into one [all the
|
||||
material lists and mesh lists are merged and indices in all scene nodes are
|
||||
shifted appropriately] The second one is creating a "grid" of objects (or
|
||||
scenes) with the same material and mesh sets. For the second use case we need
|
||||
two flags: 'mergeMeshes' and 'mergeMaterials' to avoid shifting mesh indices
|
||||
*/
|
||||
void mergeScenes(Scene &scene, const std::vector<Scene *> &scenes,
|
||||
const std::vector<glm::mat4> &rootTransforms,
|
||||
const std::vector<uint32_t> &meshCounts, bool mergeMeshes,
|
||||
bool mergeMaterials) {
|
||||
// Create new root node
|
||||
scene.hierarchy = {{
|
||||
.parent = -1,
|
||||
.firstChild = 1,
|
||||
.nextSibling = -1,
|
||||
.lastSibling = -1,
|
||||
.level = 0,
|
||||
}};
|
||||
|
||||
scene.nameForNode[0] = 0;
|
||||
scene.nodeNames = {"NewRoot"};
|
||||
|
||||
scene.localTransform.push_back(glm::mat4(1.f));
|
||||
scene.globalTransform.push_back(glm::mat4(1.f));
|
||||
|
||||
if (scenes.empty())
|
||||
return;
|
||||
|
||||
int offs = 1;
|
||||
int meshOffs = 0;
|
||||
int nameOffs = (int)scene.nodeNames.size();
|
||||
int materialOfs = 0;
|
||||
auto meshCount = meshCounts.begin();
|
||||
|
||||
if (!mergeMaterials)
|
||||
scene.materialNames = scenes[0]->materialNames;
|
||||
|
||||
// FIXME: too much logic (for all the components in a scene, though mesh data
|
||||
// and materials go separately - they're dedicated data lists)
|
||||
for (const Scene *s : scenes) {
|
||||
mergeVectors(scene.localTransform, s->localTransform);
|
||||
mergeVectors(scene.globalTransform, s->globalTransform);
|
||||
|
||||
mergeVectors(scene.hierarchy, s->hierarchy);
|
||||
|
||||
mergeVectors(scene.nodeNames, s->nodeNames);
|
||||
if (mergeMaterials)
|
||||
mergeVectors(scene.materialNames, s->materialNames);
|
||||
|
||||
const int nodeCount = (int)s->hierarchy.size();
|
||||
|
||||
shiftNodes(scene, offs, nodeCount, offs);
|
||||
|
||||
mergeMaps(scene.meshForNode, s->meshForNode, offs,
|
||||
mergeMeshes ? meshOffs : 0);
|
||||
mergeMaps(scene.materialForNode, s->materialForNode, offs,
|
||||
mergeMaterials ? materialOfs : 0);
|
||||
mergeMaps(scene.nameForNode, s->nameForNode, offs, nameOffs);
|
||||
|
||||
offs += nodeCount;
|
||||
|
||||
materialOfs += (int)s->materialNames.size();
|
||||
nameOffs += (int)s->nodeNames.size();
|
||||
|
||||
if (mergeMeshes) {
|
||||
meshOffs += *meshCount;
|
||||
meshCount++;
|
||||
}
|
||||
}
|
||||
|
||||
// fixing 'nextSibling' fields in the old roots (zero-index in all the scenes)
|
||||
offs = 1;
|
||||
int idx = 0;
|
||||
for (const Scene *s : scenes) {
|
||||
const int nodeCount = (int)s->hierarchy.size();
|
||||
const bool isLast = (idx == scenes.size() - 1);
|
||||
// calculate new next sibling for the old scene roots
|
||||
const int next = isLast ? -1 : offs + nodeCount;
|
||||
|
||||
scene.hierarchy[offs].nextSibling = next;
|
||||
// attach to new root
|
||||
scene.hierarchy[offs].parent = 0;
|
||||
|
||||
// transform old root nodes, if the transforms are given
|
||||
if (!rootTransforms.empty())
|
||||
scene.localTransform[offs] =
|
||||
rootTransforms[idx] * scene.localTransform[offs];
|
||||
|
||||
offs += nodeCount;
|
||||
idx++;
|
||||
}
|
||||
|
||||
// now, shift levels of all nodes below the root
|
||||
for (auto i = scene.hierarchy.begin() + 1; i != scene.hierarchy.end(); i++)
|
||||
i->level++;
|
||||
}
|
||||
|
||||
void dumpSceneToDot(const char *fileName, const Scene &scene, int *visited) {
|
||||
FILE *f = fopen(fileName, "w");
|
||||
fprintf(f, "digraph G\n{\n");
|
||||
for (size_t i = 0; i < scene.globalTransform.size(); i++) {
|
||||
std::string name = "";
|
||||
std::string extra = "";
|
||||
if (scene.nameForNode.contains(i)) {
|
||||
int strID = scene.nameForNode.at(i);
|
||||
name = scene.nodeNames[strID];
|
||||
}
|
||||
if (visited) {
|
||||
if (visited[i])
|
||||
extra = ", color = red";
|
||||
}
|
||||
fprintf(f, "n%d [label=\"%s\" %s]\n", (int)i, name.c_str(), extra.c_str());
|
||||
}
|
||||
for (size_t i = 0; i < scene.hierarchy.size(); i++) {
|
||||
int p = scene.hierarchy[i].parent;
|
||||
if (p > -1)
|
||||
fprintf(f, "\t n%d -> n%d\n", p, (int)i);
|
||||
}
|
||||
fprintf(f, "}\n");
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
// A rather long algorithm (and the auxiliary routines) to delete a number of
|
||||
// scene nodes from the hierarchy
|
||||
|
||||
// Add an index to a sorted index array
|
||||
static void addUniqueIdx(std::vector<uint32_t> &v, uint32_t index) {
|
||||
if (!std::binary_search(v.begin(), v.end(), index))
|
||||
v.push_back(index);
|
||||
}
|
||||
|
||||
// Recurse down from a node and collect all nodes which are already marked for
|
||||
// deletion
|
||||
static void collectNodesToDelete(const Scene &scene, int node,
|
||||
std::vector<uint32_t> &nodes) {
|
||||
for (int n = scene.hierarchy[node].firstChild; n != -1;
|
||||
n = scene.hierarchy[n].nextSibling) {
|
||||
addUniqueIdx(nodes, n);
|
||||
collectNodesToDelete(scene, n, nodes);
|
||||
}
|
||||
}
|
||||
|
||||
int findLastNonDeletedItem(const Scene &scene,
|
||||
const std::vector<int> &newIndices, int node) {
|
||||
// we have to be more subtle:
|
||||
// if the (newIndices[firstChild_] == -1), we should follow the link and
|
||||
// extract the last non-removed item
|
||||
// ..
|
||||
if (node == -1)
|
||||
return -1;
|
||||
|
||||
return (newIndices[node] == -1)
|
||||
? findLastNonDeletedItem(scene, newIndices,
|
||||
scene.hierarchy[node].nextSibling)
|
||||
: newIndices[node];
|
||||
}
|
||||
|
||||
void shiftMapIndices(std::unordered_map<uint32_t, uint32_t> &items,
|
||||
const std::vector<int> &newIndices) {
|
||||
std::unordered_map<uint32_t, uint32_t> newItems;
|
||||
for (const auto &m : items) {
|
||||
int newIndex = newIndices[m.first];
|
||||
if (newIndex != -1)
|
||||
newItems[newIndex] = m.second;
|
||||
}
|
||||
items = newItems;
|
||||
}
|
||||
|
||||
// Approximately an O ( N * Log(N) * Log(M)) algorithm (N = scene.size, M =
|
||||
// nodesToDelete.size) to delete a collection of nodes from scene graph
|
||||
void deleteSceneNodes(Scene &scene,
|
||||
const std::vector<uint32_t> &nodesToDelete) {
|
||||
// 0) Add all the nodes down below in the hierarchy
|
||||
auto indicesToDelete = nodesToDelete;
|
||||
for (uint32_t i : indicesToDelete)
|
||||
collectNodesToDelete(scene, i, indicesToDelete);
|
||||
|
||||
// aux array with node indices to keep track of the moved ones [moved =
|
||||
// [](node) { return (node != nodes[node]); ]
|
||||
std::vector<int> nodes(scene.hierarchy.size());
|
||||
std::iota(nodes.begin(), nodes.end(), 0);
|
||||
|
||||
// 1.a) Move all the indicesToDelete to the end of 'nodes' array (and cut them
|
||||
// off, a variation of swap'n'pop for multiple elements)
|
||||
const size_t oldSize = nodes.size();
|
||||
eraseSelected(nodes, indicesToDelete);
|
||||
|
||||
// 1.b) Make a newIndices[oldIndex] mapping table
|
||||
std::vector<int> newIndices(oldSize, -1);
|
||||
for (int i = 0; i < nodes.size(); i++)
|
||||
newIndices[nodes[i]] = i;
|
||||
|
||||
// 2) Replace all non-null parent/firstChild/nextSibling pointers in all the
|
||||
// nodes by new positions
|
||||
auto nodeMover = [&scene, &newIndices](Hierarchy &h) {
|
||||
return Hierarchy{
|
||||
.parent = (h.parent != -1) ? newIndices[h.parent] : -1,
|
||||
.firstChild = findLastNonDeletedItem(scene, newIndices, h.firstChild),
|
||||
.nextSibling = findLastNonDeletedItem(scene, newIndices, h.nextSibling),
|
||||
.lastSibling = findLastNonDeletedItem(scene, newIndices, h.lastSibling),
|
||||
};
|
||||
};
|
||||
std::transform(scene.hierarchy.begin(), scene.hierarchy.end(),
|
||||
scene.hierarchy.begin(), nodeMover);
|
||||
|
||||
// 3) Finally throw away the hierarchy items
|
||||
eraseSelected(scene.hierarchy, indicesToDelete);
|
||||
|
||||
// 4) As in mergeScenes() routine we also have to adjust all the "components"
|
||||
// (i.e., meshes, materials, names and transformations)
|
||||
|
||||
// 4a) Transformations are stored in arrays, so we just erase the items as we
|
||||
// did with the scene.hierarchy_
|
||||
eraseSelected(scene.localTransform, indicesToDelete);
|
||||
eraseSelected(scene.globalTransform, indicesToDelete);
|
||||
|
||||
// 4b) All the maps should change the key values with the newIndices[] array
|
||||
shiftMapIndices(scene.meshForNode, newIndices);
|
||||
shiftMapIndices(scene.materialForNode, newIndices);
|
||||
shiftMapIndices(scene.nameForNode, newIndices);
|
||||
|
||||
// 5) scene node names list is not modified, but in principle it can be
|
||||
// (remove all non-used items and adjust the nameForNode_ map) 6) Material
|
||||
// names list is not modified also, but if some materials fell out of use
|
||||
}
|
||||
98
shared/Scene/Scene.h
Normal file
98
shared/Scene/Scene.h
Normal file
|
|
@ -0,0 +1,98 @@
|
|||
#pragma once
|
||||
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
#include <glm/ext.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
using glm::mat4;
|
||||
|
||||
// we do not define std::vector<Node*> Children - this is already present in the
|
||||
// aiNode from assimp
|
||||
|
||||
constexpr const int MAX_NODE_LEVEL = 16;
|
||||
|
||||
struct Hierarchy {
|
||||
// parent for this node (or -1 for root)
|
||||
int parent = -1;
|
||||
// first child for a node (or -1)
|
||||
int firstChild = -1;
|
||||
// next sibling for a node (or -1)
|
||||
int nextSibling = -1;
|
||||
// last added node (or -1)
|
||||
int lastSibling = -1;
|
||||
// cached node level
|
||||
int level = 0;
|
||||
};
|
||||
|
||||
/* This scene is converted into a descriptorSet(s) in MultiRenderer class
|
||||
This structure is also used as a storage type in SceneExporter tool
|
||||
*/
|
||||
struct Scene {
|
||||
// local transformations for each node and global transforms
|
||||
// + an array of 'dirty/changed' local transforms
|
||||
std::vector<mat4> localTransform; // indexed by node
|
||||
std::vector<mat4> globalTransform; // indexed by node
|
||||
|
||||
// list of nodes that need their global transforms recalculated
|
||||
std::vector<int> changedAtThisFrame[MAX_NODE_LEVEL];
|
||||
|
||||
// Hierarchy component
|
||||
std::vector<Hierarchy> hierarchy;
|
||||
|
||||
// Mesh component: which Mesh belongs to which node (Node -> Mesh)
|
||||
std::unordered_map<uint32_t, uint32_t> meshForNode;
|
||||
|
||||
// Material component: which material belongs to which node (Node -> Material)
|
||||
std::unordered_map<uint32_t, uint32_t> materialForNode;
|
||||
|
||||
// Node name component: which name is assigned to the node (Node -> Name)
|
||||
std::unordered_map<uint32_t, uint32_t> nameForNode;
|
||||
|
||||
// List of scene node names
|
||||
std::vector<std::string> nodeNames;
|
||||
|
||||
// Debug list of material names
|
||||
std::vector<std::string> materialNames;
|
||||
};
|
||||
|
||||
int addNode(Scene &scene, int parent, int level);
|
||||
|
||||
void markAsChanged(Scene &scene, int node);
|
||||
|
||||
int findNodeByName(const Scene &scene, const std::string &name);
|
||||
|
||||
inline std::string getNodeName(const Scene &scene, int node) {
|
||||
int strID =
|
||||
scene.nameForNode.contains(node) ? scene.nameForNode.at(node) : -1;
|
||||
return (strID > -1) ? scene.nodeNames[strID] : std::string();
|
||||
}
|
||||
|
||||
inline void setNodeName(Scene &scene, int node, const std::string &name) {
|
||||
uint32_t stringID = (uint32_t)scene.nodeNames.size();
|
||||
scene.nodeNames.push_back(name);
|
||||
scene.nameForNode[node] = stringID;
|
||||
}
|
||||
|
||||
int getNodeLevel(const Scene &scene, int n);
|
||||
|
||||
bool recalculateGlobalTransforms(Scene &scene);
|
||||
|
||||
void loadScene(const char *fileName, Scene &scene);
|
||||
void saveScene(const char *fileName, const Scene &scene);
|
||||
|
||||
void dumpTransforms(const char *fileName, const Scene &scene);
|
||||
void printChangedNodes(const Scene &scene);
|
||||
|
||||
void dumpSceneToDot(const char *fileName, const Scene &scene,
|
||||
int *visited = nullptr);
|
||||
|
||||
void mergeScenes(Scene &scene, const std::vector<Scene *> &scenes,
|
||||
const std::vector<glm::mat4> &rootTransforms,
|
||||
const std::vector<uint32_t> &meshCounts,
|
||||
bool mergeMeshes = true, bool mergeMaterials = true);
|
||||
|
||||
// Delete a collection of nodes from a scenegraph
|
||||
void deleteSceneNodes(Scene &scene, const std::vector<uint32_t> &nodesToDelete);
|
||||
342
shared/Scene/VtxData.cpp
Normal file
342
shared/Scene/VtxData.cpp
Normal file
|
|
@ -0,0 +1,342 @@
|
|||
#include "shared/Scene/VtxData.h"
|
||||
|
||||
#include <algorithm>
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
bool isMeshDataValid(const char* fileName)
|
||||
{
|
||||
FILE* f = fopen(fileName, "rb");
|
||||
|
||||
if (!f)
|
||||
return false;
|
||||
|
||||
SCOPE_EXIT
|
||||
{
|
||||
fclose(f);
|
||||
};
|
||||
|
||||
MeshFileHeader header;
|
||||
|
||||
if (fread(&header, 1, sizeof(header), f) != sizeof(header))
|
||||
return false;
|
||||
|
||||
if (fseek(f, sizeof(Mesh) * header.meshCount, SEEK_CUR))
|
||||
return false;
|
||||
|
||||
if (fseek(f, sizeof(BoundingBox) * header.meshCount, SEEK_CUR))
|
||||
return false;
|
||||
|
||||
if (fseek(f, header.indexDataSize, SEEK_CUR))
|
||||
return false;
|
||||
|
||||
if (fseek(f, header.vertexDataSize, SEEK_CUR))
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isMeshHierarchyValid(const char* fileName)
|
||||
{
|
||||
FILE* f = fopen(fileName, "rb");
|
||||
|
||||
if (!f)
|
||||
return false;
|
||||
|
||||
SCOPE_EXIT
|
||||
{
|
||||
fclose(f);
|
||||
};
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
bool isMeshMaterialsValid(const char* fileName)
|
||||
{
|
||||
FILE* f = fopen(fileName, "rb");
|
||||
|
||||
if (!f)
|
||||
return false;
|
||||
|
||||
SCOPE_EXIT
|
||||
{
|
||||
fclose(f);
|
||||
};
|
||||
|
||||
uint64_t numMaterials = 0;
|
||||
uint64_t materialsSize = 0;
|
||||
|
||||
if (fread(&numMaterials, 1, sizeof(numMaterials), f) != sizeof(numMaterials))
|
||||
return false;
|
||||
if (fread(&materialsSize, 1, sizeof(materialsSize), f) != sizeof(materialsSize))
|
||||
return false;
|
||||
if (numMaterials * sizeof(Material) != materialsSize)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
MeshFileHeader loadMeshData(const char* meshFile, MeshData& out)
|
||||
{
|
||||
FILE* f = fopen(meshFile, "rb");
|
||||
|
||||
assert(f);
|
||||
|
||||
if (!f) {
|
||||
printf("Cannot open '%s'.\n", meshFile);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
SCOPE_EXIT
|
||||
{
|
||||
fclose(f);
|
||||
};
|
||||
|
||||
MeshFileHeader header;
|
||||
|
||||
if (fread(&header, 1, sizeof(header), f) != sizeof(header)) {
|
||||
printf("Unable to read mesh file header.\n");
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (fread(&out.streams, 1, sizeof(out.streams), f) != sizeof(out.streams)) {
|
||||
printf("Unable to read vertex streams description.\n");
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
out.meshes.resize(header.meshCount);
|
||||
if (fread(out.meshes.data(), sizeof(Mesh), header.meshCount, f) != header.meshCount) {
|
||||
printf("Could not read mesh descriptors.\n");
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
out.boxes.resize(header.meshCount);
|
||||
if (fread(out.boxes.data(), sizeof(BoundingBox), header.meshCount, f) != header.meshCount) {
|
||||
printf("Could not read bounding boxes.\n");
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
out.indexData.resize(header.indexDataSize / sizeof(uint32_t));
|
||||
out.vertexData.resize(header.vertexDataSize);
|
||||
|
||||
if (fread(out.indexData.data(), 1, header.indexDataSize, f) != header.indexDataSize) {
|
||||
printf("Unable to read index data.\n");
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (fread(out.vertexData.data(), 1, header.vertexDataSize, f) != header.vertexDataSize) {
|
||||
printf("Unable to read vertex data.\n");
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
return header;
|
||||
}
|
||||
|
||||
void loadMeshDataMaterials(const char* fileName, MeshData& out)
|
||||
{
|
||||
FILE* f = fopen(fileName, "rb");
|
||||
|
||||
if (!f) {
|
||||
printf("Cannot open '%s'.\n", fileName);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
uint64_t numMaterials = 0;
|
||||
uint64_t materialsSize = 0;
|
||||
|
||||
if (fread(&numMaterials, 1, sizeof(numMaterials), f) != sizeof(numMaterials)) {
|
||||
printf("Unable to read numMaterials.\n");
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
if (fread(&materialsSize, 1, sizeof(materialsSize), f) != sizeof(materialsSize)) {
|
||||
printf("Unable to read materialsSize.\n");
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
if (numMaterials * sizeof(Material) != materialsSize) {
|
||||
printf("Corrupted material file '%s'.\n", fileName);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
out.materials.resize(numMaterials);
|
||||
if (fread(out.materials.data(), 1, materialsSize, f) != materialsSize) {
|
||||
printf("Unable to read material data.\n");
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
loadStringList(f, out.textureFiles);
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void saveMeshData(const char* fileName, const MeshData& m)
|
||||
{
|
||||
FILE* f = fopen(fileName, "wb");
|
||||
|
||||
if (!f) {
|
||||
printf("Error opening file '%s' for writing.\n", fileName);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
const MeshFileHeader header = {
|
||||
.meshCount = (uint32_t)m.meshes.size(),
|
||||
.indexDataSize = (uint32_t)(m.indexData.size() * sizeof(uint32_t)),
|
||||
.vertexDataSize = (uint32_t)(m.vertexData.size()),
|
||||
};
|
||||
|
||||
fwrite(&header, 1, sizeof(header), f);
|
||||
fwrite(&m.streams, 1, sizeof(m.streams), f);
|
||||
fwrite(m.meshes.data(), sizeof(Mesh), header.meshCount, f);
|
||||
fwrite(m.boxes.data(), sizeof(BoundingBox), header.meshCount, f);
|
||||
fwrite(m.indexData.data(), 1, header.indexDataSize, f);
|
||||
fwrite(m.vertexData.data(), 1, header.vertexDataSize, f);
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void saveMeshDataMaterials(const char* fileName, const MeshData& m)
|
||||
{
|
||||
FILE* f = fopen(fileName, "wb");
|
||||
|
||||
if (!f) {
|
||||
printf("Error opening file '%s' for writing.\n", fileName);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
const uint64_t numMaterials = m.materials.size();
|
||||
const uint64_t materialsSize = m.materials.size() * sizeof(Material);
|
||||
|
||||
fwrite(&numMaterials, 1, sizeof(numMaterials), f);
|
||||
fwrite(&materialsSize, 1, sizeof(materialsSize), f);
|
||||
fwrite(m.materials.data(), sizeof(Material), numMaterials, f);
|
||||
|
||||
saveStringList(f, m.textureFiles);
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void saveBoundingBoxes(const char* fileName, const std::vector<BoundingBox>& boxes)
|
||||
{
|
||||
FILE* f = fopen(fileName, "wb");
|
||||
|
||||
if (!f) {
|
||||
printf("Error opening bounding boxes file '%s' for writing.\n", fileName);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
const uint32_t sz = (uint32_t)boxes.size();
|
||||
fwrite(&sz, 1, sizeof(sz), f);
|
||||
fwrite(boxes.data(), sz, sizeof(BoundingBox), f);
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
void loadBoundingBoxes(const char* fileName, std::vector<BoundingBox>& boxes)
|
||||
{
|
||||
FILE* f = fopen(fileName, "rb");
|
||||
|
||||
if (!f) {
|
||||
printf("Error opening bounding boxes file '%s'\n", fileName);
|
||||
assert(false);
|
||||
exit(EXIT_FAILURE);
|
||||
}
|
||||
|
||||
uint32_t sz;
|
||||
fread(&sz, 1, sizeof(sz), f);
|
||||
|
||||
// TODO: check file size, divide by bounding box size
|
||||
boxes.resize(sz);
|
||||
fread(boxes.data(), sz, sizeof(BoundingBox), f);
|
||||
|
||||
fclose(f);
|
||||
}
|
||||
|
||||
// combine a collection of meshes into a single MeshData container
|
||||
MeshFileHeader mergeMeshData(MeshData& m, const std::vector<MeshData*> md)
|
||||
{
|
||||
uint32_t numTotalVertices = 0;
|
||||
uint32_t numTotalIndices = 0;
|
||||
|
||||
if (!md.empty()) {
|
||||
m.streams = md[0]->streams;
|
||||
}
|
||||
|
||||
const uint32_t vertexSize = m.streams.getVertexSize();
|
||||
|
||||
uint32_t offset = 0;
|
||||
uint32_t mtlOffset = 0;
|
||||
|
||||
for (const MeshData* i : md) {
|
||||
LVK_ASSERT(m.streams == i->streams);
|
||||
mergeVectors(m.indexData, i->indexData);
|
||||
mergeVectors(m.vertexData, i->vertexData);
|
||||
mergeVectors(m.meshes, i->meshes);
|
||||
mergeVectors(m.boxes, i->boxes);
|
||||
|
||||
for (size_t j = 0; j != i->meshes.size(); j++) {
|
||||
// m.vertexCount, m.lodCount and m.streamCount do not change
|
||||
// m.vertexOffset also does not change, because vertex offsets are local (i.e., baked into the indices)
|
||||
m.meshes[offset + j].indexOffset += numTotalIndices;
|
||||
m.meshes[offset + j].materialID += mtlOffset;
|
||||
}
|
||||
|
||||
// shift individual indices
|
||||
for (size_t j = 0; j != i->indexData.size(); j++) {
|
||||
m.indexData[numTotalIndices + j] += numTotalVertices;
|
||||
}
|
||||
|
||||
offset += (uint32_t)i->meshes.size();
|
||||
mtlOffset += (uint32_t)i->materials.size();
|
||||
|
||||
numTotalIndices += (uint32_t)i->indexData.size();
|
||||
numTotalVertices += (uint32_t)i->vertexData.size() / vertexSize;
|
||||
}
|
||||
|
||||
return MeshFileHeader{
|
||||
.magicValue = 0x12345678,
|
||||
.meshCount = (uint32_t)offset,
|
||||
.indexDataSize = static_cast<uint32_t>(numTotalIndices * sizeof(uint32_t)),
|
||||
.vertexDataSize = static_cast<uint32_t>(m.vertexData.size()),
|
||||
};
|
||||
}
|
||||
|
||||
void recalculateBoundingBoxes(MeshData& m)
|
||||
{
|
||||
LVK_ASSERT(m.streams.attributes[0].format == lvk::VertexFormat::Float3);
|
||||
|
||||
const uint32_t stride = m.streams.getVertexSize();
|
||||
|
||||
m.boxes.clear();
|
||||
m.boxes.reserve(m.meshes.size());
|
||||
|
||||
for (const Mesh& mesh : m.meshes) {
|
||||
const uint32_t numIndices = mesh.getLODIndicesCount(0);
|
||||
|
||||
glm::vec3 vmin(std::numeric_limits<float>::max());
|
||||
glm::vec3 vmax(std::numeric_limits<float>::lowest());
|
||||
|
||||
for (uint32_t i = 0; i != numIndices; i++) {
|
||||
const uint32_t vtxOffset = m.indexData[mesh.indexOffset + i] + mesh.vertexOffset;
|
||||
const float* vf = (const float*)&m.vertexData[vtxOffset * stride];
|
||||
|
||||
vmin = glm::min(vmin, vec3(vf[0], vf[1], vf[2]));
|
||||
vmax = glm::max(vmax, vec3(vf[0], vf[1], vf[2]));
|
||||
}
|
||||
|
||||
m.boxes.emplace_back(vmin, vmax);
|
||||
}
|
||||
}
|
||||
111
shared/Scene/VtxData.h
Normal file
111
shared/Scene/VtxData.h
Normal file
|
|
@ -0,0 +1,111 @@
|
|||
#pragma once
|
||||
|
||||
#include <stdint.h>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "shared/Utils.h"
|
||||
#include "shared/UtilsMath.h"
|
||||
|
||||
constexpr const uint32_t kMaxLODs = 7;
|
||||
|
||||
// All offsets are relative to the beginning of the data block (excluding headers with a Mesh list)
|
||||
struct Mesh final {
|
||||
// Number of LODs in this mesh. Strictly less than MAX_LODS, last LOD offset is used as a marker only
|
||||
uint32_t lodCount = 1;
|
||||
|
||||
// The total count of all previous vertices in this mesh file
|
||||
uint32_t indexOffset = 0;
|
||||
|
||||
uint32_t vertexOffset = 0;
|
||||
|
||||
// Vertex count (for all LODs)
|
||||
uint32_t vertexCount = 0;
|
||||
|
||||
// Offsets to LOD indices data. The last offset is used as a marker to calculate the size
|
||||
uint32_t lodOffset[kMaxLODs + 1] = { 0 };
|
||||
|
||||
uint32_t materialID = 0;
|
||||
|
||||
inline uint32_t getLODIndicesCount(uint32_t lod) const { return lod < lodCount ? lodOffset[lod + 1] - lodOffset[lod] : 0; }
|
||||
|
||||
// Any additional information, such as mesh name, can be added here...
|
||||
};
|
||||
|
||||
struct MeshFileHeader {
|
||||
// Unique 64-bit value to check integrity of the file
|
||||
uint32_t magicValue = 0x12345678;
|
||||
|
||||
// Number of mesh descriptors following this header
|
||||
uint32_t meshCount = 0;
|
||||
|
||||
// How much space index data takes in bytes
|
||||
uint32_t indexDataSize = 0;
|
||||
|
||||
// How much space vertex data takes in bytes
|
||||
uint32_t vertexDataSize = 0;
|
||||
|
||||
// According to your needs, you may add additional metadata fields...
|
||||
};
|
||||
|
||||
enum MaterialFlags {
|
||||
sMaterialFlags_CastShadow = 0x1,
|
||||
sMaterialFlags_ReceiveShadow = 0x2,
|
||||
sMaterialFlags_Transparent = 0x4,
|
||||
};
|
||||
|
||||
struct Material {
|
||||
vec4 emissiveFactor = vec4(0.0f, 0.0f, 0.0f, 0.0f);
|
||||
vec4 baseColorFactor = vec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
float roughness = 1.0f;
|
||||
float transparencyFactor = 1.0f;
|
||||
float alphaTest = 0.0f;
|
||||
float metallicFactor = 0.0f;
|
||||
// index into MeshData::textureFiles
|
||||
int baseColorTexture = -1;
|
||||
int emissiveTexture = -1;
|
||||
int normalTexture = -1;
|
||||
int opacityTexture = -1;
|
||||
uint32_t flags = sMaterialFlags_CastShadow | sMaterialFlags_ReceiveShadow;
|
||||
};
|
||||
|
||||
struct MeshData {
|
||||
lvk::VertexInput streams = {};
|
||||
std::vector<uint32_t> indexData;
|
||||
std::vector<uint8_t> vertexData;
|
||||
std::vector<Mesh> meshes;
|
||||
std::vector<BoundingBox> boxes;
|
||||
std::vector<Material> materials;
|
||||
std::vector<std::string> textureFiles;
|
||||
MeshFileHeader getMeshFileHeader() const
|
||||
{
|
||||
return {
|
||||
.meshCount = (uint32_t)meshes.size(),
|
||||
.indexDataSize = (uint32_t)(indexData.size() * sizeof(uint32_t)),
|
||||
.vertexDataSize = (uint32_t)vertexData.size(),
|
||||
};
|
||||
}
|
||||
};
|
||||
|
||||
static_assert(sizeof(BoundingBox) == sizeof(float) * 6);
|
||||
|
||||
bool isMeshDataValid(const char* fileName);
|
||||
bool isMeshMaterialsValid(const char* fileName);
|
||||
bool isMeshHierarchyValid(const char* fileName);
|
||||
MeshFileHeader loadMeshData(const char* meshFile, MeshData& out);
|
||||
void loadMeshDataMaterials(const char* meshFile, MeshData& out);
|
||||
void saveMeshData(const char* fileName, const MeshData& m);
|
||||
void saveMeshDataMaterials(const char* fileName, const MeshData& m);
|
||||
|
||||
void recalculateBoundingBoxes(MeshData& m);
|
||||
|
||||
// combine a list of meshes to a single mesh container
|
||||
MeshFileHeader mergeMeshData(MeshData& m, const std::vector<MeshData*> md);
|
||||
|
||||
// use to write values into MeshData::vertexData
|
||||
template <typename T> inline void put(std::vector<uint8_t>& v, const T& value)
|
||||
{
|
||||
const size_t pos = v.size();
|
||||
v.resize(v.size() + sizeof(value));
|
||||
memcpy(v.data() + pos, &value, sizeof(value));
|
||||
}
|
||||
63
shared/Tonemap.h
Normal file
63
shared/Tonemap.h
Normal file
|
|
@ -0,0 +1,63 @@
|
|||
#pragma once
|
||||
|
||||
#include <glm/ext.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
enum ToneMappingMode {
|
||||
ToneMapping_None = 0,
|
||||
ToneMapping_Reinhard = 1,
|
||||
ToneMapping_Uchimura = 2,
|
||||
ToneMapping_KhronosPBR = 3,
|
||||
};
|
||||
|
||||
// Uchimura 2017, "HDR theory and practice"
|
||||
// Math: https://www.desmos.com/calculator/gslcdxvipg
|
||||
// Source: https://www.slideshare.net/nikuque/hdr-theory-and-practicce-jp
|
||||
float uchimura(float x, float P, float a, float m, float l, float c, float b)
|
||||
{
|
||||
float l0 = ((P - m) * l) / a;
|
||||
float L0 = m - m / a;
|
||||
float L1 = m + (1.0f - m) / a;
|
||||
float S0 = m + l0;
|
||||
float S1 = m + a * l0;
|
||||
float C2 = (a * P) / (P - S1);
|
||||
float CP = -C2 / P;
|
||||
|
||||
float w0 = float(1.0f - glm::smoothstep(0.0f, m, x));
|
||||
float w2 = float(glm::step(m + l0, x));
|
||||
float w1 = float(1.0f - w0 - w2);
|
||||
|
||||
float T = float(m * pow(x / m, float(c)) + b);
|
||||
float S = float(P - (P - S1) * exp(CP * (x - S0)));
|
||||
float L = float(m + a * (x - m));
|
||||
|
||||
return T * w0 + L * w1 + S * w2;
|
||||
}
|
||||
|
||||
float reinhard2(float v, float maxWhite)
|
||||
{
|
||||
return v * (1.0f + (v / (maxWhite * maxWhite))) / (1.0f + v);
|
||||
}
|
||||
|
||||
// Khronos PBR Neutral Tone Mapper
|
||||
// https://github.com/KhronosGroup/ToneMapping/blob/main/PBR_Neutral/README.md#pbr-neutral-specification
|
||||
// https://github.com/KhronosGroup/ToneMapping/blob/main/PBR_Neutral/pbrNeutral.glsl
|
||||
float PBRNeutralToneMapping(float color, float startCompression, float desaturation)
|
||||
{
|
||||
startCompression -= 0.04f;
|
||||
|
||||
float x = color;
|
||||
float offset = x < 0.08f ? x - 6.25f * x * x : 0.04f;
|
||||
color -= offset;
|
||||
|
||||
float peak = color;
|
||||
if (peak < startCompression)
|
||||
return color;
|
||||
|
||||
const float d = 1. - startCompression;
|
||||
float newPeak = 1. - d * d / (peak + d - startCompression);
|
||||
color *= newPeak / peak;
|
||||
|
||||
float g = 1.0f - 1.0f / (desaturation * (peak - newPeak) + 1.0f);
|
||||
return glm::mix(color, newPeak, g);
|
||||
}
|
||||
126
shared/Trackball.h
Normal file
126
shared/Trackball.h
Normal file
|
|
@ -0,0 +1,126 @@
|
|||
/*
|
||||
* Copyright (C) 2013-2015 Sergey Kosarevsky (sk@linderdaum.com)
|
||||
* Copyright (C) 2013-2015 Viktor Latypov (vl@linderdaum.com)
|
||||
* Based on Linderdaum Engine http://www.linderdaum.com
|
||||
* All rights reserved.
|
||||
*
|
||||
* Redistribution and use in source and binary forms, with or without
|
||||
* modification, are permitted provided that the following conditions are met:
|
||||
*
|
||||
* 1. Redistributions of source code must retain the above copyright notice,
|
||||
* this list of conditions and the following disclaimer.
|
||||
*
|
||||
* 2. Redistributions in binary form must display the names 'Sergey Kosarevsky' and
|
||||
* 'Viktor Latypov' in the credits of the application, if such credits exist.
|
||||
* The authors of this work must be notified via email (sk@linderdaum.com) in
|
||||
* this case of redistribution.
|
||||
*
|
||||
* 3. Neither the name of copyright holders nor the names of its contributors
|
||||
* may be used to endorse or promote products derived from this software
|
||||
* without specific prior written permission.
|
||||
*
|
||||
* THIS SOFTWARE IS PROVIDED BY THE COPYRIGHT HOLDERS AND CONTRIBUTORS ``AS
|
||||
* IS'' AND ANY EXPRESS OR IMPLIED WARRANTIES, INCLUDING, BUT NOT LIMITED TO,
|
||||
* THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR
|
||||
* PURPOSE ARE DISCLAIMED. IN NO EVENT SHALL COPYRIGHT HOLDERS OR CONTRIBUTORS
|
||||
* BE LIABLE FOR ANY DIRECT, INDIRECT, INCIDENTAL, SPECIAL, EXEMPLARY, OR
|
||||
* CONSEQUENTIAL DAMAGES (INCLUDING, BUT NOT LIMITED TO, PROCUREMENT OF
|
||||
* SUBSTITUTE GOODS OR SERVICES; LOSS OF USE, DATA, OR PROFITS; OR BUSINESS
|
||||
* INTERRUPTION) HOWEVER CAUSED AND ON ANY THEORY OF LIABILITY, WHETHER IN
|
||||
* CONTRACT, STRICT LIABILITY, OR TORT (INCLUDING NEGLIGENCE OR OTHERWISE)
|
||||
* ARISING IN ANY WAY OUT OF THE USE OF THIS SOFTWARE, EVEN IF ADVISED OF THE
|
||||
* POSSIBILITY OF SUCH DAMAGE.
|
||||
*/
|
||||
|
||||
#pragma once
|
||||
|
||||
#include <numeric>
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/ext.hpp>
|
||||
|
||||
/// Virtual trackball for user interaction with rotations
|
||||
class VirtualTrackball
|
||||
{
|
||||
public:
|
||||
VirtualTrackball() = default;
|
||||
|
||||
/**
|
||||
Get rotation matrix for new mouse point
|
||||
**/
|
||||
glm::mat4 dragTo(glm::vec2 screenPoint, float speed, bool keyPressed)
|
||||
{
|
||||
if (keyPressed && !isDraggingActive_)
|
||||
{
|
||||
startDragging(screenPoint);
|
||||
|
||||
isDraggingActive_ = keyPressed;
|
||||
|
||||
return glm::mat4(1.0f);
|
||||
}
|
||||
|
||||
isDraggingActive_ = keyPressed;
|
||||
|
||||
if (!keyPressed) return glm::mat4(1.0f);
|
||||
|
||||
pointCur_ = projectOnSphere(screenPoint);
|
||||
|
||||
const glm::vec3 direction = pointCur_ - pointPrev_;
|
||||
const float shift = glm::length(direction);
|
||||
|
||||
glm::mat4 rotMatrix = glm::mat4(1.0f);
|
||||
|
||||
if (shift > std::numeric_limits<float>::epsilon())
|
||||
{
|
||||
const glm::vec3 axis = glm::cross( pointPrev_, pointCur_);
|
||||
rotMatrix = glm::rotate(glm::mat4(1.0f), shift * speed, axis);
|
||||
}
|
||||
|
||||
rotationDelta_ = rotMatrix;
|
||||
|
||||
return rotMatrix;
|
||||
}
|
||||
|
||||
const glm::mat4& getRotationDelta() const
|
||||
{
|
||||
return rotationDelta_;
|
||||
};
|
||||
|
||||
/**
|
||||
Get current rotation matrix
|
||||
**/
|
||||
glm::mat4 getRotationMatrix() const
|
||||
{
|
||||
return rotation_ * rotationDelta_;
|
||||
}
|
||||
|
||||
private:
|
||||
void startDragging(glm::vec2 screenPoint)
|
||||
{
|
||||
rotation_ = rotation_ * rotationDelta_;
|
||||
rotationDelta_ = glm::mat4(1.0f);
|
||||
pointCur_ = projectOnSphere(screenPoint);
|
||||
pointPrev_ = pointCur_;
|
||||
}
|
||||
|
||||
glm::vec3 projectOnSphere(glm::vec2 ScreenPoint)
|
||||
{
|
||||
// convert to -1.0...1.0 range
|
||||
glm::vec3 proj(
|
||||
+(2.0f * ScreenPoint.x - 1.0f),
|
||||
-(2.0f * ScreenPoint.y - 1.0f),
|
||||
0.0f
|
||||
);
|
||||
|
||||
const float Length = std::min(glm::length(proj), 1.0f);
|
||||
|
||||
proj.z = sqrtf(1.001f - Length * Length);
|
||||
|
||||
return glm::normalize(proj);
|
||||
}
|
||||
glm::vec3 pointCur_ = glm::vec3(0.0f);
|
||||
glm::vec3 pointPrev_ = glm::vec3(0.0f);
|
||||
glm::mat4 rotation_ = glm::mat4(1.0f);
|
||||
glm::mat4 rotationDelta_ = glm::mat4(1.0f);
|
||||
bool isDraggingActive_ = false;
|
||||
};
|
||||
286
shared/Utils.cpp
Normal file
286
shared/Utils.cpp
Normal file
|
|
@ -0,0 +1,286 @@
|
|||
#include <string.h>
|
||||
#include <string>
|
||||
#if !defined(__APPLE__)
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
#include "Utils.h"
|
||||
|
||||
#include <stb/stb_image.h>
|
||||
#include <ktx.h>
|
||||
#include <ktx-software/lib/gl_format.h>
|
||||
|
||||
#include <unordered_map>
|
||||
|
||||
// lvk::ShaderModuleHandle -> GLSL source code
|
||||
std::unordered_map<uint32_t, std::string> debugGLSLSourceCode;
|
||||
|
||||
bool endsWith(const char* s, const char* part)
|
||||
{
|
||||
const size_t sLength = strlen(s);
|
||||
const size_t partLength = strlen(part);
|
||||
return sLength < partLength ? false : strcmp(s + sLength - partLength, part) == 0;
|
||||
}
|
||||
|
||||
std::string readShaderFile(const char* fileName)
|
||||
{
|
||||
FILE* file = fopen(fileName, "r");
|
||||
|
||||
if (!file) {
|
||||
LLOGW("I/O error. Cannot open shader file '%s'\n", fileName);
|
||||
return std::string();
|
||||
}
|
||||
|
||||
fseek(file, 0L, SEEK_END);
|
||||
const size_t bytesinfile = ftell(file);
|
||||
fseek(file, 0L, SEEK_SET);
|
||||
|
||||
char* buffer = (char*)alloca(bytesinfile + 1);
|
||||
const size_t bytesread = fread(buffer, 1, bytesinfile, file);
|
||||
fclose(file);
|
||||
|
||||
buffer[bytesread] = 0;
|
||||
|
||||
static constexpr unsigned char BOM[] = { 0xEF, 0xBB, 0xBF };
|
||||
|
||||
if (bytesread > 3) {
|
||||
if (!memcmp(buffer, BOM, 3))
|
||||
memset(buffer, ' ', 3);
|
||||
}
|
||||
|
||||
std::string code(buffer);
|
||||
|
||||
while (code.find("#include ") != code.npos) {
|
||||
const auto pos = code.find("#include ");
|
||||
const auto p1 = code.find('<', pos);
|
||||
const auto p2 = code.find('>', pos);
|
||||
if (p1 == code.npos || p2 == code.npos || p2 <= p1) {
|
||||
LLOGW("Error while loading shader program: %s\n", code.c_str());
|
||||
return std::string();
|
||||
}
|
||||
const std::string name = code.substr(p1 + 1, p2 - p1 - 1);
|
||||
const std::string include = readShaderFile(name.c_str());
|
||||
code.replace(pos, p2 - pos + 1, include.c_str());
|
||||
}
|
||||
|
||||
return code;
|
||||
}
|
||||
|
||||
VkShaderStageFlagBits vkShaderStageFromFileName(const char* fileName)
|
||||
{
|
||||
if (endsWith(fileName, ".vert"))
|
||||
return VK_SHADER_STAGE_VERTEX_BIT;
|
||||
|
||||
if (endsWith(fileName, ".frag"))
|
||||
return VK_SHADER_STAGE_FRAGMENT_BIT;
|
||||
|
||||
if (endsWith(fileName, ".geom"))
|
||||
return VK_SHADER_STAGE_GEOMETRY_BIT;
|
||||
|
||||
if (endsWith(fileName, ".comp"))
|
||||
return VK_SHADER_STAGE_COMPUTE_BIT;
|
||||
|
||||
if (endsWith(fileName, ".tesc"))
|
||||
return VK_SHADER_STAGE_TESSELLATION_CONTROL_BIT;
|
||||
|
||||
if (endsWith(fileName, ".tese"))
|
||||
return VK_SHADER_STAGE_TESSELLATION_EVALUATION_BIT;
|
||||
|
||||
return VK_SHADER_STAGE_VERTEX_BIT;
|
||||
}
|
||||
|
||||
lvk::ShaderStage lvkShaderStageFromFileName(const char* fileName)
|
||||
{
|
||||
if (endsWith(fileName, ".vert"))
|
||||
return lvk::Stage_Vert;
|
||||
|
||||
if (endsWith(fileName, ".frag"))
|
||||
return lvk::Stage_Frag;
|
||||
|
||||
if (endsWith(fileName, ".geom"))
|
||||
return lvk::Stage_Geom;
|
||||
|
||||
if (endsWith(fileName, ".comp"))
|
||||
return lvk::Stage_Comp;
|
||||
|
||||
if (endsWith(fileName, ".tesc"))
|
||||
return lvk::Stage_Tesc;
|
||||
|
||||
if (endsWith(fileName, ".tese"))
|
||||
return lvk::Stage_Tese;
|
||||
|
||||
return lvk::Stage_Vert;
|
||||
}
|
||||
|
||||
lvk::Holder<lvk::ShaderModuleHandle> loadShaderModule(const std::unique_ptr<lvk::IContext>& ctx, const char* fileName) {
|
||||
const std::string code = readShaderFile(fileName);
|
||||
const lvk::ShaderStage stage = lvkShaderStageFromFileName(fileName);
|
||||
|
||||
if (code.empty()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
lvk::Result res;
|
||||
|
||||
lvk::Holder<lvk::ShaderModuleHandle> handle =
|
||||
ctx->createShaderModule({ code.c_str(), stage, (std::string("Shader Module: ") + fileName).c_str() }, &res);
|
||||
|
||||
if (!res.isOk()) {
|
||||
return {};
|
||||
}
|
||||
|
||||
debugGLSLSourceCode[handle.index()] = code;
|
||||
|
||||
return handle;
|
||||
}
|
||||
|
||||
lvk::Holder<lvk::TextureHandle> loadTexture(
|
||||
const std::unique_ptr<lvk::IContext>& ctx, const char* fileName, lvk::TextureType textureType, bool sRGB)
|
||||
{
|
||||
const bool isKTX = endsWith(fileName, ".ktx") || endsWith(fileName, ".KTX");
|
||||
|
||||
lvk::Result result;
|
||||
|
||||
lvk::Holder<lvk::TextureHandle> texture;
|
||||
|
||||
if (isKTX) {
|
||||
ktxTexture1* ktxTex = nullptr;
|
||||
|
||||
if (!LVK_VERIFY(ktxTexture1_CreateFromNamedFile(fileName, KTX_TEXTURE_CREATE_LOAD_IMAGE_DATA_BIT, &ktxTex) == KTX_SUCCESS)) {
|
||||
LLOGW("Failed to load %s\n", fileName);
|
||||
assert(0);
|
||||
return {};
|
||||
}
|
||||
SCOPE_EXIT
|
||||
{
|
||||
ktxTexture_Destroy(ktxTexture(ktxTex));
|
||||
};
|
||||
|
||||
lvk::Result result;
|
||||
|
||||
const lvk::Format format = [](uint32_t glInternalFormat) {
|
||||
switch (glInternalFormat) {
|
||||
case GL_COMPRESSED_RGBA_BPTC_UNORM:
|
||||
return lvk::Format_BC7_RGBA;
|
||||
case GL_RGBA8:
|
||||
return lvk::Format_RGBA_UN8;
|
||||
case GL_RG16F:
|
||||
return lvk::Format_RG_F16;
|
||||
case GL_RGBA16F:
|
||||
return lvk::Format_RGBA_F16;
|
||||
case GL_RGBA32F:
|
||||
return lvk::Format_RGBA_F32;
|
||||
default:
|
||||
LLOGW("Unsupported pixel format (%u)\n", glInternalFormat);
|
||||
assert(0);
|
||||
}
|
||||
return lvk::Format_Invalid;
|
||||
}(ktxTex->glInternalformat);
|
||||
|
||||
texture = ctx->createTexture(
|
||||
{
|
||||
.type = textureType,
|
||||
.format = format,
|
||||
.dimensions = {ktxTex->baseWidth, ktxTex->baseHeight, 1},
|
||||
.usage = lvk::TextureUsageBits_Sampled,
|
||||
.numMipLevels = ktxTex->numLevels,
|
||||
.data = ktxTex->pData,
|
||||
.dataNumMipLevels = ktxTex->numLevels,
|
||||
.debugName = fileName,
|
||||
},
|
||||
fileName, &result);
|
||||
} else {
|
||||
LVK_ASSERT(textureType == lvk::TextureType_2D);
|
||||
|
||||
int w, h, comp;
|
||||
const uint8_t* img = stbi_load(fileName, &w, &h, &comp, 4);
|
||||
|
||||
SCOPE_EXIT
|
||||
{
|
||||
if (img)
|
||||
stbi_image_free((void*)img);
|
||||
};
|
||||
|
||||
if (!img) {
|
||||
printf("Unable to load %s. File not found.\n", fileName);
|
||||
return {};
|
||||
};
|
||||
|
||||
texture = ctx->createTexture(
|
||||
{
|
||||
.type = lvk::TextureType_2D,
|
||||
.format = sRGB ? lvk::Format_RGBA_SRGB8 : lvk::Format_RGBA_UN8,
|
||||
.dimensions = {(uint32_t)w, (uint32_t)h},
|
||||
.usage = lvk::TextureUsageBits_Sampled,
|
||||
.data = img,
|
||||
.debugName = fileName,
|
||||
},
|
||||
fileName, &result);
|
||||
}
|
||||
|
||||
if (!result.isOk()) {
|
||||
printf("Unable to load %s. Reason: %s\n", fileName, result.message);
|
||||
}
|
||||
|
||||
return texture;
|
||||
}
|
||||
|
||||
void saveStringList(FILE* f, const std::vector<std::string>& lines)
|
||||
{
|
||||
uint32_t sz = (uint32_t)lines.size();
|
||||
fwrite(&sz, sizeof(uint32_t), 1, f);
|
||||
for (const std::string& s : lines) {
|
||||
sz = (uint32_t)s.length();
|
||||
fwrite(&sz, sizeof(uint32_t), 1, f);
|
||||
fwrite(s.c_str(), sz + 1, 1, f);
|
||||
}
|
||||
}
|
||||
|
||||
void loadStringList(FILE* f, std::vector<std::string>& lines)
|
||||
{
|
||||
{
|
||||
uint32_t sz = 0;
|
||||
fread(&sz, sizeof(uint32_t), 1, f);
|
||||
lines.resize(sz);
|
||||
}
|
||||
std::vector<char> inBytes;
|
||||
for (std::string& s : lines) {
|
||||
uint32_t sz = 0;
|
||||
fread(&sz, sizeof(uint32_t), 1, f);
|
||||
inBytes.resize(sz + 1);
|
||||
fread(inBytes.data(), sz + 1, 1, f);
|
||||
s = std::string(inBytes.data());
|
||||
}
|
||||
}
|
||||
|
||||
int addUnique(std::vector<std::string>& files, const std::string& file)
|
||||
{
|
||||
if (file.empty())
|
||||
return -1;
|
||||
|
||||
const auto i = std::find(std::begin(files), std::end(files), file);
|
||||
|
||||
if (i != files.end())
|
||||
return (int)std::distance(files.begin(), i);
|
||||
|
||||
files.push_back(file);
|
||||
return (int)files.size() - 1;
|
||||
}
|
||||
|
||||
std::string replaceAll(const std::string& str, const std::string& oldSubStr, const std::string& newSubStr)
|
||||
{
|
||||
std::string result = str;
|
||||
|
||||
for (size_t p = result.find(oldSubStr); p != std::string::npos; p = result.find(oldSubStr))
|
||||
result.replace(p, oldSubStr.length(), newSubStr);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
// Convert 8-bit ASCII string to upper case
|
||||
std::string lowercaseString(const std::string& s)
|
||||
{
|
||||
std::string out(s.length(), ' ');
|
||||
std::transform(s.begin(), s.end(), out.begin(), tolower);
|
||||
return out;
|
||||
}
|
||||
54
shared/Utils.h
Normal file
54
shared/Utils.h
Normal file
|
|
@ -0,0 +1,54 @@
|
|||
#pragma once
|
||||
|
||||
#include <algorithm>
|
||||
#include <memory>
|
||||
#include <string.h>
|
||||
#include <string>
|
||||
#include <vector>
|
||||
#if !defined(__APPLE__)
|
||||
#include <malloc.h>
|
||||
#endif
|
||||
|
||||
#include <glslang/Include/glslang_c_interface.h>
|
||||
#include <ldrutils/lutils/ScopeExit.h>
|
||||
#include <lvk/LVK.h>
|
||||
#include <lvk/vulkan/VulkanUtils.h>
|
||||
|
||||
bool endsWith(const char* s, const char* part);
|
||||
|
||||
std::string readShaderFile(const char* fileName);
|
||||
|
||||
VkShaderStageFlagBits vkShaderStageFromFileName(const char* fileName);
|
||||
|
||||
lvk::Holder<lvk::ShaderModuleHandle> loadShaderModule(const std::unique_ptr<lvk::IContext>& ctx, const char* fileName);
|
||||
lvk::Holder<lvk::TextureHandle> loadTexture(
|
||||
const std::unique_ptr<lvk::IContext>& ctx, const char* fileName, lvk::TextureType textureType = lvk::TextureType_2D, bool sRGB = false);
|
||||
|
||||
template <typename T> inline void mergeVectors(std::vector<T>& v1, const std::vector<T>& v2)
|
||||
{
|
||||
v1.insert(v1.end(), v2.begin(), v2.end());
|
||||
}
|
||||
|
||||
// From https://stackoverflow.com/a/64152990/1182653
|
||||
// Delete a list of items from std::vector with indices in 'selection'
|
||||
// e.g., eraseSelected({1, 2, 3, 4, 5}, {1, 3}) -> {1, 3, 5}
|
||||
// ^ ^ 2 and 4 get deleted
|
||||
template <typename T, typename Index = int> inline void eraseSelected(std::vector<T>& v, const std::vector<Index>& selection)
|
||||
{
|
||||
// cut off the elements moved to the end of the vector by std::stable_partition
|
||||
v.resize(std::distance(
|
||||
v.begin(),
|
||||
// std::stable_partition moves any element whose index is in 'selection' to the end
|
||||
std::stable_partition(v.begin(), v.end(), [&selection, &v](const T& item) {
|
||||
return !std::binary_search(
|
||||
selection.begin(), selection.end(),
|
||||
// std::distance(std::find(v.begin(), v.end(), item), v.begin()) - if you don't like the pointer arithmetic below
|
||||
static_cast<Index>(static_cast<const T*>(&item) - &v[0]));
|
||||
})));
|
||||
}
|
||||
|
||||
void saveStringList(FILE* f, const std::vector<std::string>& lines);
|
||||
void loadStringList(FILE* f, std::vector<std::string>& lines);
|
||||
int addUnique(std::vector<std::string>& files, const std::string& file);
|
||||
std::string replaceAll(const std::string& str, const std::string& oldSubStr, const std::string& newSubStr);
|
||||
std::string lowercaseString(const std::string& s); // convert 8-bit ASCII string to upper case
|
||||
319
shared/UtilsAnim.cpp
Normal file
319
shared/UtilsAnim.cpp
Normal file
|
|
@ -0,0 +1,319 @@
|
|||
#include "UtilsAnim.h"
|
||||
|
||||
#include "UtilsGLTF.h"
|
||||
#include "VulkanApp.h"
|
||||
#include <assimp/GltfMaterial.h>
|
||||
#include <assimp/cimport.h>
|
||||
#include <assimp/postprocess.h>
|
||||
#include <assimp/scene.h>
|
||||
#include <assimp/types.h>
|
||||
#include <cmath>
|
||||
|
||||
using glm::quat;
|
||||
|
||||
AnimationChannel initChannel(const aiNodeAnim* anim)
|
||||
{
|
||||
AnimationChannel channel;
|
||||
channel.pos.resize(anim->mNumPositionKeys);
|
||||
|
||||
for (uint32_t i = 0; i < anim->mNumPositionKeys; ++i) {
|
||||
channel.pos[i] = { .pos = aiVector3DToVec3(anim->mPositionKeys[i].mValue), .time = (float)anim->mPositionKeys[i].mTime };
|
||||
}
|
||||
|
||||
channel.rot.resize(anim->mNumRotationKeys);
|
||||
for (uint32_t i = 0; i < anim->mNumRotationKeys; ++i) {
|
||||
channel.rot[i] = { .rot = aiQuaternionToQuat(anim->mRotationKeys[i].mValue), .time = (float)anim->mRotationKeys[i].mTime };
|
||||
}
|
||||
|
||||
channel.scale.resize(anim->mNumScalingKeys);
|
||||
for (uint32_t i = 0; i < anim->mNumScalingKeys; ++i) {
|
||||
channel.scale[i] = { .scale = aiVector3DToVec3(anim->mScalingKeys[i].mValue), .time = (float)anim->mScalingKeys[i].mTime };
|
||||
}
|
||||
|
||||
return channel;
|
||||
}
|
||||
|
||||
template <typename T> uint32_t getTimeIndex(const std::vector<T>& t, float time)
|
||||
{
|
||||
return std::max(
|
||||
0,
|
||||
(int)std::distance(t.begin(), std::lower_bound(t.begin(), t.end(), time, [&](const T& lhs, float rhs) { return lhs.time < rhs; })) -
|
||||
1);
|
||||
}
|
||||
|
||||
float interpolationVal(float lastTimeStamp, float nextTimeStamp, float animationTime)
|
||||
{
|
||||
return (animationTime - lastTimeStamp) / (nextTimeStamp - lastTimeStamp);
|
||||
}
|
||||
|
||||
vec3 interpolatePosition(const AnimationChannel& channel, float time)
|
||||
{
|
||||
if (channel.pos.size() == 1)
|
||||
return channel.pos[0].pos;
|
||||
|
||||
uint32_t start = getTimeIndex<>(channel.pos, time);
|
||||
uint32_t end = start + 1;
|
||||
float mix = interpolationVal(channel.pos[start].time, channel.pos[end].time, time);
|
||||
return glm::mix(channel.pos[start].pos, channel.pos[end].pos, mix);
|
||||
}
|
||||
|
||||
glm::quat interpolateRotation(const AnimationChannel& channel, float time)
|
||||
{
|
||||
if (channel.rot.size() == 1)
|
||||
return channel.rot[0].rot;
|
||||
|
||||
uint32_t start = getTimeIndex<>(channel.rot, time);
|
||||
uint32_t end = start + 1;
|
||||
float mix = interpolationVal(channel.rot[start].time, channel.rot[end].time, time);
|
||||
return glm::slerp(channel.rot[start].rot, channel.rot[end].rot, mix);
|
||||
}
|
||||
|
||||
vec3 interpolateScaling(const AnimationChannel& channel, float time)
|
||||
{
|
||||
if (channel.scale.size() == 1)
|
||||
return channel.scale[0].scale;
|
||||
|
||||
uint32_t start = getTimeIndex<>(channel.scale, time);
|
||||
uint32_t end = start + 1;
|
||||
float coef = interpolationVal(channel.scale[start].time, channel.scale[end].time, time);
|
||||
return glm::mix(channel.scale[start].scale, channel.scale[end].scale, coef);
|
||||
}
|
||||
|
||||
mat4 animationTransform(const AnimationChannel& channel, float time)
|
||||
{
|
||||
mat4 translation = glm::translate(mat4(1.0f), interpolatePosition(channel, time));
|
||||
mat4 rotation = glm::toMat4(glm::normalize(interpolateRotation(channel, time)));
|
||||
mat4 scale = glm::scale(mat4(1.0f), interpolateScaling(channel, time));
|
||||
return translation * rotation * scale;
|
||||
}
|
||||
|
||||
mat4 animationTransformBlending(const AnimationChannel& channel1, float time1, const AnimationChannel& channel2, float time2, float weight)
|
||||
{
|
||||
mat4 trans1 = glm::translate(mat4(1.0f), interpolatePosition(channel1, time1));
|
||||
mat4 trans2 = glm::translate(mat4(1.0f), interpolatePosition(channel2, time2));
|
||||
mat4 translation = glm::mix(trans1, trans2, weight);
|
||||
|
||||
quat rot1 = interpolateRotation(channel1, time1);
|
||||
quat rot2 = interpolateRotation(channel2, time2);
|
||||
|
||||
mat4 rotation = glm::toMat4(glm::normalize(glm::slerp(rot1, rot2, weight)));
|
||||
|
||||
vec3 scl1 = interpolateScaling(channel1, time1);
|
||||
vec3 scl2 = interpolateScaling(channel2, time2);
|
||||
mat4 scale = glm::scale(mat4(1.0f), glm::mix(scl1, scl2, weight));
|
||||
|
||||
return translation * rotation * scale;
|
||||
}
|
||||
|
||||
MorphState morphTransform(const MorphTarget& target, const MorphingChannel& channel, float time)
|
||||
{
|
||||
MorphState ms;
|
||||
ms.meshId = target.meshId;
|
||||
|
||||
float mix = 0.0f;
|
||||
int start = 0;
|
||||
int end = 0;
|
||||
|
||||
if (channel.key.size() > 0) {
|
||||
start = getTimeIndex(channel.key, time);
|
||||
end = start + 1;
|
||||
mix = interpolationVal(channel.key[start].time, channel.key[end].time, time);
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < std::min((uint32_t)target.offset.size(), (uint32_t)MAX_MORPH_WEIGHTS); ++i) {
|
||||
ms.morphTarget[i] = target.offset[channel.key[start].mesh[i]];
|
||||
ms.weights[i] = glm::mix(channel.key[start].weight[i], channel.key[end].weight[i], mix);
|
||||
}
|
||||
|
||||
return ms;
|
||||
}
|
||||
|
||||
void initAnimations(GLTFContext& glTF, const aiScene* scene)
|
||||
{
|
||||
glTF.animations.resize(scene->mNumAnimations);
|
||||
|
||||
for (uint32_t i = 0; i < scene->mNumAnimations; ++i) {
|
||||
Animation& anim = glTF.animations[i];
|
||||
anim.name = scene->mAnimations[i]->mName.C_Str();
|
||||
anim.duration = scene->mAnimations[i]->mDuration;
|
||||
anim.ticksPerSecond = scene->mAnimations[i]->mTicksPerSecond;
|
||||
for (uint32_t c = 0; c < scene->mAnimations[i]->mNumChannels; c++) {
|
||||
const aiNodeAnim* channel = scene->mAnimations[i]->mChannels[c];
|
||||
const char* boneName = channel->mNodeName.data;
|
||||
uint32_t boneId = glTF.bonesByName[boneName].boneId;
|
||||
if (boneId == ~0u) {
|
||||
for (const GLTFNode& node : glTF.nodesStorage) {
|
||||
if (node.name != boneName)
|
||||
continue;
|
||||
boneId = node.modelMtxId;
|
||||
glTF.bonesByName[boneName] = {
|
||||
.boneId = boneId,
|
||||
.transform = glTF.hasBones ? glm::inverse(node.transform) : mat4(1),
|
||||
};
|
||||
break;
|
||||
}
|
||||
}
|
||||
assert(boneId != ~0u);
|
||||
anim.channels[boneId] = initChannel(channel);
|
||||
}
|
||||
|
||||
const uint32_t numMorphTargetChannels = scene->mAnimations[i]->mNumMorphMeshChannels;
|
||||
anim.morphChannels.resize(numMorphTargetChannels);
|
||||
|
||||
for (uint32_t c = 0; c < numMorphTargetChannels; c++) {
|
||||
const aiMeshMorphAnim* channel = scene->mAnimations[i]->mMorphMeshChannels[c];
|
||||
|
||||
MorphingChannel& morphChannel = anim.morphChannels[c];
|
||||
|
||||
morphChannel.name = channel->mName.C_Str();
|
||||
morphChannel.key.resize(channel->mNumKeys);
|
||||
|
||||
for (uint32_t k = 0; k < channel->mNumKeys; ++k) {
|
||||
MorphingChannelKey& key = morphChannel.key[k];
|
||||
key.time = channel->mKeys[k].mTime;
|
||||
for (uint32_t v = 0; v < std::min((uint32_t)MAX_MORPH_WEIGHTS, channel->mKeys[k].mNumValuesAndWeights); ++v) {
|
||||
key.mesh[v] = channel->mKeys[k].mValues[v];
|
||||
key.weight[v] = channel->mKeys[k].mWeights[v];
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateAnimation(GLTFContext& glTF, AnimationState& anim, float dt)
|
||||
{
|
||||
if (!anim.active || (anim.animId == ~0u)) {
|
||||
glTF.morphing = false;
|
||||
glTF.skinning = false;
|
||||
return;
|
||||
}
|
||||
|
||||
const Animation& activeAnim = glTF.animations[anim.animId];
|
||||
anim.currentTime += activeAnim.ticksPerSecond * dt;
|
||||
|
||||
if (anim.playOnce && anim.currentTime > activeAnim.duration) {
|
||||
anim.currentTime = activeAnim.duration;
|
||||
anim.active = false;
|
||||
} else
|
||||
anim.currentTime = fmodf(anim.currentTime, activeAnim.duration);
|
||||
|
||||
// Apply animations
|
||||
std::function<void(GLTFNodeRef gltfNode, const mat4& parentTransform)> traverseTree = [&](GLTFNodeRef gltfNode,
|
||||
const mat4& parentTransform) {
|
||||
const GLTFBone& bone = glTF.bonesByName[glTF.nodesStorage[gltfNode].name];
|
||||
const uint32_t boneId = bone.boneId;
|
||||
|
||||
if (boneId != ~0u) {
|
||||
assert(boneId == glTF.nodesStorage[gltfNode].modelMtxId);
|
||||
auto channel = activeAnim.channels.find(boneId);
|
||||
const bool hasActiveChannel = channel != activeAnim.channels.end();
|
||||
|
||||
glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId] =
|
||||
parentTransform *
|
||||
(hasActiveChannel ? animationTransform(channel->second, anim.currentTime) : glTF.nodesStorage[gltfNode].transform);
|
||||
|
||||
glTF.skinning = true;
|
||||
} else {
|
||||
glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId] = parentTransform * glTF.nodesStorage[gltfNode].transform;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < glTF.nodesStorage[gltfNode].children.size(); i++) {
|
||||
const GLTFNodeRef child = glTF.nodesStorage[gltfNode].children[i];
|
||||
|
||||
traverseTree(child, glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId]);
|
||||
}
|
||||
};
|
||||
|
||||
traverseTree(glTF.root, mat4(1.0f));
|
||||
|
||||
for (const std::pair<std::string, GLTFBone>& b : glTF.bonesByName) {
|
||||
if (b.second.boneId != ~0u) {
|
||||
glTF.matrices[b.second.boneId] = glTF.matrices[b.second.boneId] * b.second.transform;
|
||||
}
|
||||
}
|
||||
|
||||
glTF.morphStates.resize(glTF.meshesStorage.size());
|
||||
// update morphing
|
||||
if (glTF.enableMorphing) {
|
||||
if (!activeAnim.morphChannels.empty()) {
|
||||
for (size_t i = 0; i < activeAnim.morphChannels.size(); ++i) {
|
||||
const MorphingChannel& channel = activeAnim.morphChannels[i];
|
||||
const uint32_t meshId = glTF.meshesRemap[channel.name];
|
||||
const MorphTarget& morphTarget = glTF.morphTargets[meshId];
|
||||
|
||||
if (morphTarget.meshId != ~0u) {
|
||||
glTF.morphStates[morphTarget.meshId] = morphTransform(morphTarget, channel, anim.currentTime);
|
||||
}
|
||||
}
|
||||
|
||||
glTF.morphing = true;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void updateAnimationBlending(GLTFContext& glTF, AnimationState& anim1, AnimationState& anim2, float weight, float dt)
|
||||
{
|
||||
if (anim1.active && anim2.active) {
|
||||
const Animation& activeAnim1 = glTF.animations[anim1.animId];
|
||||
anim1.currentTime += activeAnim1.ticksPerSecond * dt;
|
||||
|
||||
if (anim1.playOnce && anim1.currentTime > activeAnim1.duration) {
|
||||
anim1.currentTime = activeAnim1.duration;
|
||||
anim1.active = false;
|
||||
} else {
|
||||
anim1.currentTime = fmodf(anim1.currentTime, activeAnim1.duration);
|
||||
}
|
||||
|
||||
const Animation& activeAnim2 = glTF.animations[anim2.animId];
|
||||
anim2.currentTime += activeAnim2.ticksPerSecond * dt;
|
||||
|
||||
if (anim2.playOnce && anim2.currentTime > activeAnim2.duration) {
|
||||
anim2.currentTime = activeAnim2.duration;
|
||||
anim2.active = false;
|
||||
} else {
|
||||
anim2.currentTime = fmodf(anim2.currentTime, activeAnim2.duration);
|
||||
}
|
||||
|
||||
// Update skinning
|
||||
std::function<void(GLTFNodeRef gltfNode, const mat4& parentTransform)> traverseTree = [&](GLTFNodeRef gltfNode,
|
||||
const mat4& parentTransform) {
|
||||
const GLTFBone& bone = glTF.bonesByName[glTF.nodesStorage[gltfNode].name];
|
||||
const uint32_t boneId = bone.boneId;
|
||||
if (boneId != ~0u) {
|
||||
auto channel1 = activeAnim1.channels.find(boneId);
|
||||
auto channel2 = activeAnim2.channels.find(boneId);
|
||||
|
||||
if (channel1 != activeAnim1.channels.end() && channel2 != activeAnim2.channels.end()) {
|
||||
glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId] =
|
||||
parentTransform *
|
||||
animationTransformBlending(channel1->second, anim1.currentTime, channel2->second, anim2.currentTime, weight);
|
||||
} else if (channel1 != activeAnim1.channels.end()) {
|
||||
glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId] = parentTransform * animationTransform(channel1->second, anim1.currentTime);
|
||||
} else if (channel2 != activeAnim2.channels.end()) {
|
||||
glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId] = parentTransform * animationTransform(channel2->second, anim2.currentTime);
|
||||
} else {
|
||||
glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId] = parentTransform * glTF.nodesStorage[gltfNode].transform;
|
||||
}
|
||||
glTF.skinning = true;
|
||||
}
|
||||
|
||||
for (uint32_t i = 0; i < glTF.nodesStorage[gltfNode].children.size(); i++) {
|
||||
const uint32_t child = glTF.nodesStorage[gltfNode].children[i];
|
||||
|
||||
traverseTree(child, glTF.matrices[glTF.nodesStorage[gltfNode].modelMtxId]);
|
||||
}
|
||||
};
|
||||
|
||||
traverseTree(glTF.root, mat4(1.0f));
|
||||
|
||||
for (const std::pair<std::string, GLTFBone>& b : glTF.bonesByName) {
|
||||
if (b.second.boneId != ~0u) {
|
||||
glTF.matrices[b.second.boneId] = glTF.matrices[b.second.boneId] * b.second.transform;
|
||||
}
|
||||
}
|
||||
|
||||
} else {
|
||||
glTF.morphing = false;
|
||||
glTF.skinning = false;
|
||||
}
|
||||
}
|
||||
76
shared/UtilsAnim.h
Normal file
76
shared/UtilsAnim.h
Normal file
|
|
@ -0,0 +1,76 @@
|
|||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/gtc/matrix_transform.hpp>
|
||||
#include <glm/gtc/quaternion.hpp>
|
||||
#include <glm/gtc/type_ptr.hpp>
|
||||
#include <string>
|
||||
#include <unordered_map>
|
||||
#include <vector>
|
||||
|
||||
using glm::quat;
|
||||
using glm::vec3;
|
||||
using glm::vec4;
|
||||
|
||||
struct AnimationKeyPosition {
|
||||
vec3 pos;
|
||||
float time;
|
||||
};
|
||||
|
||||
struct AnimationKeyRotation {
|
||||
quat rot;
|
||||
float time;
|
||||
};
|
||||
|
||||
struct AnimationKeyScale {
|
||||
vec3 scale;
|
||||
float time;
|
||||
};
|
||||
|
||||
struct AnimationChannel {
|
||||
std::vector<AnimationKeyPosition> pos;
|
||||
std::vector<AnimationKeyRotation> rot;
|
||||
std::vector<AnimationKeyScale> scale;
|
||||
};
|
||||
|
||||
#define MAX_MORPH_WEIGHTS 8
|
||||
#define MAX_MORPHS 100
|
||||
|
||||
struct MorphingChannelKey {
|
||||
float time = 0.0f;
|
||||
uint32_t mesh[MAX_MORPH_WEIGHTS] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
float weight[MAX_MORPH_WEIGHTS] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
};
|
||||
|
||||
struct MorphingChannel {
|
||||
std::string name;
|
||||
std::vector<MorphingChannelKey> key;
|
||||
};
|
||||
|
||||
struct Animation {
|
||||
std::unordered_map<int, AnimationChannel> channels;
|
||||
std::vector<MorphingChannel> morphChannels;
|
||||
float duration; // In seconds
|
||||
float ticksPerSecond;
|
||||
std::string name;
|
||||
};
|
||||
|
||||
struct MorphState {
|
||||
uint32_t meshId = ~0u;
|
||||
uint32_t morphTarget[MAX_MORPH_WEIGHTS] = { 0, 0, 0, 0, 0, 0, 0, 0 };
|
||||
float weights[MAX_MORPH_WEIGHTS] = { 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f, 0.0f };
|
||||
};
|
||||
|
||||
struct AnimationState {
|
||||
uint32_t animId = ~0u;
|
||||
float currentTime = 0.0f;
|
||||
bool playOnce = false;
|
||||
bool active = false;
|
||||
};
|
||||
|
||||
struct GLTFContext;
|
||||
struct aiScene;
|
||||
|
||||
void initAnimations(GLTFContext& glTF, const aiScene* scene);
|
||||
void updateAnimation(GLTFContext& glTF, AnimationState& anim, float dt);
|
||||
void updateAnimationBlending(GLTFContext& glTF, AnimationState& anim1, AnimationState& anim2, float weight, float dt);
|
||||
281
shared/UtilsCubemap.cpp
Normal file
281
shared/UtilsCubemap.cpp
Normal file
|
|
@ -0,0 +1,281 @@
|
|||
#include "UtilsMath.h"
|
||||
#include "UtilsCubemap.h"
|
||||
|
||||
#include <cstdio>
|
||||
#include <glm/glm.hpp>
|
||||
#include <glm/ext.hpp>
|
||||
|
||||
#include "stb_image_resize2.h"
|
||||
|
||||
using glm::vec2;
|
||||
using glm::vec3;
|
||||
using glm::vec4;
|
||||
using glm::ivec2;
|
||||
|
||||
/// From Henry J. Warren's "Hacker's Delight"
|
||||
float radicalInverse_VdC(uint32_t bits)
|
||||
{
|
||||
bits = (bits << 16u) | (bits >> 16u);
|
||||
bits = ((bits & 0x55555555u) << 1u) | ((bits & 0xAAAAAAAAu) >> 1u);
|
||||
bits = ((bits & 0x33333333u) << 2u) | ((bits & 0xCCCCCCCCu) >> 2u);
|
||||
bits = ((bits & 0x0F0F0F0Fu) << 4u) | ((bits & 0xF0F0F0F0u) >> 4u);
|
||||
bits = ((bits & 0x00FF00FFu) << 8u) | ((bits & 0xFF00FF00u) >> 8u);
|
||||
return float(bits) * 2.3283064365386963e-10f; // / 0x100000000
|
||||
}
|
||||
|
||||
// The i-th point is then computed by
|
||||
|
||||
/// From http://holger.dammertz.org/stuff/notes_HammersleyOnHemisphere.html
|
||||
|
||||
vec2 hammersley2d(uint32_t i, uint32_t N)
|
||||
{
|
||||
return vec2(float(i)/float(N), radicalInverse_VdC(i));
|
||||
}
|
||||
|
||||
void convolveLambertian(const vec3* data, int srcW, int srcH, int dstW, int dstH, vec3* output, int numMonteCarloSamples)
|
||||
{
|
||||
// only equirectangular maps are supported
|
||||
assert(srcW == 2 * srcH);
|
||||
|
||||
if (srcW != 2 * srcH) return;
|
||||
|
||||
std::vector<vec3> tmp(dstW * dstH);
|
||||
|
||||
stbir_resize(
|
||||
reinterpret_cast<const float*>(data), srcW, srcH, 0, reinterpret_cast<float*>(tmp.data()), dstW, dstH, 0, STBIR_RGB, STBIR_TYPE_FLOAT,
|
||||
STBIR_EDGE_WRAP, STBIR_FILTER_CUBICBSPLINE);
|
||||
|
||||
const vec3* scratch = tmp.data();
|
||||
srcW = dstW;
|
||||
srcH = dstH;
|
||||
|
||||
for (int y = 0; y != dstH; y++)
|
||||
{
|
||||
printf("Line %i...\n", y);
|
||||
const float theta1 = float(y) / float(dstH) * Math::PI;
|
||||
for (int x = 0; x != dstW; x++)
|
||||
{
|
||||
const float phi1 = float(x) / float(dstW) * Math::TWOPI;
|
||||
const vec3 V1 = vec3(sin(theta1) * cos(phi1), sin(theta1) * sin(phi1), cos(theta1));
|
||||
vec3 color = vec3(0.0f);
|
||||
float weight = 0.0f;
|
||||
for (int i = 0; i != numMonteCarloSamples; i++)
|
||||
{
|
||||
const vec2 h = hammersley2d(i, numMonteCarloSamples);
|
||||
const int x1 = int(floor(h.x * srcW));
|
||||
const int y1 = int(floor(h.y * srcH));
|
||||
const float theta2 = float(y1) / float(srcH) * Math::PI;
|
||||
const float phi2 = float(x1) / float(srcW) * Math::TWOPI;
|
||||
const vec3 V2 = vec3(sin(theta2) * cos(phi2), sin(theta2) * sin(phi2), cos(theta2));
|
||||
const float D = std::max(0.0f, glm::dot(V1, V2));
|
||||
if (D > 0.01f)
|
||||
{
|
||||
color += scratch[y1 * srcW + x1] * D;
|
||||
weight += D;
|
||||
}
|
||||
}
|
||||
output[y * dstW + x] = color / weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void convolveGGX(const vec3* data, int srcW, int srcH, int dstW, int dstH, vec3* output, int numMonteCarloSamples)
|
||||
{
|
||||
// only equirectangular maps are supported
|
||||
assert(srcW == 2 * srcH);
|
||||
|
||||
if (srcW != 2 * srcH)
|
||||
return;
|
||||
|
||||
std::vector<vec3> tmp(dstW * dstH);
|
||||
|
||||
stbir_resize(
|
||||
reinterpret_cast<const float*>(data), srcW, srcH, 0, reinterpret_cast<float*>(tmp.data()), dstW, dstH, 0, STBIR_RGB, STBIR_TYPE_FLOAT,
|
||||
STBIR_EDGE_WRAP, STBIR_FILTER_CUBICBSPLINE);
|
||||
|
||||
const vec3* scratch = tmp.data();
|
||||
srcW = dstW;
|
||||
srcH = dstH;
|
||||
|
||||
for (int y = 0; y != dstH; y++) {
|
||||
printf("Line %i...\n", y);
|
||||
const float theta1 = float(y) / float(dstH) * Math::PI;
|
||||
for (int x = 0; x != dstW; x++) {
|
||||
const float phi1 = float(x) / float(dstW) * Math::TWOPI;
|
||||
const vec3 V1 = vec3(sin(theta1) * cos(phi1), sin(theta1) * sin(phi1), cos(theta1));
|
||||
vec3 color = vec3(0.0f);
|
||||
float weight = 0.0f;
|
||||
for (int i = 0; i != numMonteCarloSamples; i++) {
|
||||
const vec2 h = hammersley2d(i, numMonteCarloSamples);
|
||||
const int x1 = int(floor(h.x * srcW));
|
||||
const int y1 = int(floor(h.y * srcH));
|
||||
|
||||
|
||||
const float theta2 = float(y1) / float(srcH) * Math::PI;
|
||||
const float phi2 = float(x1) / float(srcW) * Math::TWOPI;
|
||||
const vec3 V2 = vec3(sin(theta2) * cos(phi2), sin(theta2) * sin(phi2), cos(theta2));
|
||||
const float D = std::max(0.0f, glm::dot(V1, V2));
|
||||
if (D > 0.01f) {
|
||||
color += scratch[y1 * srcW + x1] * D;
|
||||
weight += D;
|
||||
}
|
||||
}
|
||||
output[y * dstW + x] = color / weight;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
vec3 faceCoordsToXYZ(int i, int j, int faceID, int faceSize)
|
||||
{
|
||||
const float A = 2.0f * float(i) / faceSize;
|
||||
const float B = 2.0f * float(j) / faceSize;
|
||||
|
||||
if (faceID == 0) return vec3(-1.0f, A - 1.0f, B - 1.0f);
|
||||
if (faceID == 1) return vec3(A - 1.0f, -1.0f, 1.0f - B);
|
||||
if (faceID == 2) return vec3(1.0f, A - 1.0f, 1.0f - B);
|
||||
if (faceID == 3) return vec3(1.0f - A, 1.0f, 1.0f - B);
|
||||
if (faceID == 4) return vec3(B - 1.0f, A - 1.0f, 1.0f);
|
||||
if (faceID == 5) return vec3(1.0f - B, A - 1.0f, -1.0f);
|
||||
|
||||
return vec3();
|
||||
}
|
||||
|
||||
Bitmap convertEquirectangularMapToVerticalCross(const Bitmap& b)
|
||||
{
|
||||
if (b.type_ != eBitmapType_2D) return Bitmap();
|
||||
|
||||
const int faceSize = b.w_ / 4;
|
||||
|
||||
const int w = faceSize * 3;
|
||||
const int h = faceSize * 4;
|
||||
|
||||
Bitmap result(w, h, b.comp_, b.fmt_);
|
||||
|
||||
const ivec2 kFaceOffsets[] =
|
||||
{
|
||||
ivec2(faceSize, faceSize * 3),
|
||||
ivec2(0, faceSize),
|
||||
ivec2(faceSize, faceSize),
|
||||
ivec2(faceSize * 2, faceSize),
|
||||
ivec2(faceSize, 0),
|
||||
ivec2(faceSize, faceSize * 2)
|
||||
};
|
||||
|
||||
const int clampW = b.w_ - 1;
|
||||
const int clampH = b.h_ - 1;
|
||||
|
||||
for (int face = 0; face != 6; face++)
|
||||
{
|
||||
for (int i = 0; i != faceSize; i++)
|
||||
{
|
||||
for (int j = 0; j != faceSize; j++)
|
||||
{
|
||||
const vec3 P = faceCoordsToXYZ(i, j, face, faceSize);
|
||||
const float R = hypot(P.x, P.y);
|
||||
const float theta = atan2(P.y, P.x);
|
||||
const float phi = atan2(P.z, R);
|
||||
// float point source coordinates
|
||||
const float Uf = float(2.0f * faceSize * (theta + M_PI) / M_PI);
|
||||
const float Vf = float(2.0f * faceSize * (M_PI / 2.0f - phi) / M_PI);
|
||||
// 4-samples for bilinear interpolation
|
||||
const int U1 = clamp(int(floor(Uf)), 0, clampW);
|
||||
const int V1 = clamp(int(floor(Vf)), 0, clampH);
|
||||
const int U2 = clamp(U1 + 1, 0, clampW);
|
||||
const int V2 = clamp(V1 + 1, 0, clampH);
|
||||
// fractional part
|
||||
const float s = Uf - U1;
|
||||
const float t = Vf - V1;
|
||||
// fetch 4-samples
|
||||
const vec4 A = b.getPixel(U1, V1);
|
||||
const vec4 B = b.getPixel(U2, V1);
|
||||
const vec4 C = b.getPixel(U1, V2);
|
||||
const vec4 D = b.getPixel(U2, V2);
|
||||
// bilinear interpolation
|
||||
const vec4 color = A * (1 - s) * (1 - t) + B * (s) * (1 - t) + C * (1 - s) * t + D * (s) * (t);
|
||||
result.setPixel(i + kFaceOffsets[face].x, j + kFaceOffsets[face].y, color);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Bitmap convertVerticalCrossToCubeMapFaces(const Bitmap& b)
|
||||
{
|
||||
const int faceWidth = b.w_ / 3;
|
||||
const int faceHeight = b.h_ / 4;
|
||||
|
||||
Bitmap cubemap(faceWidth, faceHeight, 6, b.comp_, b.fmt_);
|
||||
cubemap.type_ = eBitmapType_Cube;
|
||||
|
||||
const uint8_t* src = b.data_.data();
|
||||
uint8_t* dst = cubemap.data_.data();
|
||||
|
||||
/*
|
||||
------
|
||||
| +Y |
|
||||
----------------
|
||||
| -X | -Z | +X |
|
||||
----------------
|
||||
| -Y |
|
||||
------
|
||||
| +Z |
|
||||
------
|
||||
*/
|
||||
|
||||
const int pixelSize = cubemap.comp_ * Bitmap::getBytesPerComponent(cubemap.fmt_);
|
||||
|
||||
for (int face = 0; face != 6; ++face) {
|
||||
for (int j = 0; j != faceHeight; ++j) {
|
||||
for (int i = 0; i != faceWidth; ++i) {
|
||||
int x = 0;
|
||||
int y = 0;
|
||||
|
||||
switch (face) {
|
||||
// CUBE_MAP_POSITIVE_X
|
||||
case 0:
|
||||
x = 2 * faceWidth + i;
|
||||
y = 1 * faceHeight + j;
|
||||
break;
|
||||
|
||||
// CUBE_MAP_NEGATIVE_X
|
||||
case 1:
|
||||
x = i;
|
||||
y = faceHeight + j;
|
||||
break;
|
||||
|
||||
// CUBE_MAP_POSITIVE_Y
|
||||
case 2:
|
||||
x = 1 * faceWidth + i;
|
||||
y = j;
|
||||
break;
|
||||
|
||||
// CUBE_MAP_NEGATIVE_Y
|
||||
case 3:
|
||||
x = 1 * faceWidth + i;
|
||||
y = 2 * faceHeight + j;
|
||||
break;
|
||||
|
||||
// CUBE_MAP_POSITIVE_Z
|
||||
case 4:
|
||||
x = faceWidth + i;
|
||||
y = faceHeight + j;
|
||||
break;
|
||||
|
||||
// CUBE_MAP_NEGATIVE_Z
|
||||
case 5:
|
||||
x = 2 * faceWidth - (i + 1);
|
||||
y = b.h_ - (j + 1);
|
||||
break;
|
||||
}
|
||||
|
||||
memcpy(dst, src + (y * b.w_ + x) * pixelSize, pixelSize);
|
||||
|
||||
dst += pixelSize;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return cubemap;
|
||||
}
|
||||
15
shared/UtilsCubemap.h
Normal file
15
shared/UtilsCubemap.h
Normal file
|
|
@ -0,0 +1,15 @@
|
|||
#pragma once
|
||||
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include "shared/Bitmap.h"
|
||||
|
||||
Bitmap convertEquirectangularMapToVerticalCross(const Bitmap& b);
|
||||
Bitmap convertVerticalCrossToCubeMapFaces(const Bitmap& b);
|
||||
|
||||
inline Bitmap convertEquirectangularMapToCubeMapFaces(const Bitmap& b) {
|
||||
return convertVerticalCrossToCubeMapFaces(convertEquirectangularMapToVerticalCross(b));
|
||||
}
|
||||
|
||||
void convolveLambertian(const glm::vec3* data, int srcW, int srcH, int dstW, int dstH, glm::vec3* output, int numMonteCarloSamples);
|
||||
void convolveGGX(const glm::vec3* data, int srcW, int srcH, int dstW, int dstH, glm::vec3* output, int numMonteCarloSamples);
|
||||
43
shared/UtilsFPS.h
Normal file
43
shared/UtilsFPS.h
Normal file
|
|
@ -0,0 +1,43 @@
|
|||
#pragma once
|
||||
|
||||
#include <assert.h>
|
||||
#include <stdio.h>
|
||||
|
||||
class FramesPerSecondCounter
|
||||
{
|
||||
public:
|
||||
explicit FramesPerSecondCounter(float avgInterval = 0.5f)
|
||||
: avgInterval_(avgInterval)
|
||||
{
|
||||
assert(avgInterval > 0.0f);
|
||||
}
|
||||
|
||||
bool tick(float deltaSeconds, bool frameRendered = true)
|
||||
{
|
||||
if (frameRendered)
|
||||
numFrames_++;
|
||||
|
||||
accumulatedTime_ += deltaSeconds;
|
||||
|
||||
if (accumulatedTime_ > avgInterval_) {
|
||||
currentFPS_ = static_cast<float>(numFrames_ / accumulatedTime_);
|
||||
if (printFPS_)
|
||||
printf("FPS: %.1f\n", currentFPS_);
|
||||
numFrames_ = 0;
|
||||
accumulatedTime_ = 0;
|
||||
return true;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
inline float getFPS() const { return currentFPS_; }
|
||||
|
||||
bool printFPS_ = true;
|
||||
|
||||
public:
|
||||
float avgInterval_ = 0.5f;
|
||||
unsigned int numFrames_ = 0;
|
||||
double accumulatedTime_ = 0;
|
||||
float currentFPS_ = 0.0f;
|
||||
};
|
||||
1257
shared/UtilsGLTF.cpp
Normal file
1257
shared/UtilsGLTF.cpp
Normal file
File diff suppressed because it is too large
Load diff
528
shared/UtilsGLTF.h
Normal file
528
shared/UtilsGLTF.h
Normal file
|
|
@ -0,0 +1,528 @@
|
|||
#pragma once
|
||||
|
||||
#include "VulkanApp.h"
|
||||
|
||||
#include <assimp/GltfMaterial.h>
|
||||
#include <assimp/cimport.h>
|
||||
#include <assimp/postprocess.h>
|
||||
#include <assimp/scene.h>
|
||||
#include <assimp/types.h>
|
||||
|
||||
#include "LineCanvas.h"
|
||||
#include "UtilsAnim.h"
|
||||
|
||||
#include <lvk/LVK.h>
|
||||
|
||||
enum MaterialType : uint32_t {
|
||||
MaterialType_Invalid = 0,
|
||||
MaterialType_Unlit = 0x80,
|
||||
MaterialType_MetallicRoughness = 0x1,
|
||||
MaterialType_SpecularGlossiness = 0x2,
|
||||
MaterialType_Sheen = 0x4,
|
||||
MaterialType_ClearCoat = 0x8,
|
||||
MaterialType_Specular = 0x10,
|
||||
MaterialType_Transmission = 0x20,
|
||||
MaterialType_Volume = 0x40,
|
||||
};
|
||||
|
||||
const uint32_t kMaxMaterials = 128;
|
||||
const uint32_t kMaxEnvironments = 4;
|
||||
const uint32_t kMaxLights = 8;
|
||||
|
||||
using glm::mat4;
|
||||
using glm::quat;
|
||||
using glm::vec2;
|
||||
using glm::vec3;
|
||||
using glm::vec4;
|
||||
|
||||
inline mat4 aiMatrix4x4ToMat4(const aiMatrix4x4& from)
|
||||
{
|
||||
mat4 to;
|
||||
|
||||
to[0][0] = (float)from.a1;
|
||||
to[0][1] = (float)from.b1;
|
||||
to[0][2] = (float)from.c1;
|
||||
to[0][3] = (float)from.d1;
|
||||
to[1][0] = (float)from.a2;
|
||||
to[1][1] = (float)from.b2;
|
||||
to[1][2] = (float)from.c2;
|
||||
to[1][3] = (float)from.d2;
|
||||
to[2][0] = (float)from.a3;
|
||||
to[2][1] = (float)from.b3;
|
||||
to[2][2] = (float)from.c3;
|
||||
to[2][3] = (float)from.d3;
|
||||
to[3][0] = (float)from.a4;
|
||||
to[3][1] = (float)from.b4;
|
||||
to[3][2] = (float)from.c4;
|
||||
to[3][3] = (float)from.d4;
|
||||
|
||||
return to;
|
||||
}
|
||||
|
||||
inline vec3 aiVector3DToVec3(const aiVector3D& from)
|
||||
{
|
||||
return vec3(from.x, from.y, from.z);
|
||||
}
|
||||
|
||||
inline glm::quat aiQuaternionToQuat(const aiQuaternion& from)
|
||||
{
|
||||
return glm::quat(from.w, from.x, from.y, from.z);
|
||||
}
|
||||
|
||||
enum LightType : uint32_t {
|
||||
LightType_Directional = 0,
|
||||
LightType_Point = 1,
|
||||
LightType_Spot = 2,
|
||||
};
|
||||
|
||||
struct EnvironmentMapDataGPU {
|
||||
uint32_t envMapTexture = 0;
|
||||
uint32_t envMapTextureSampler = 0;
|
||||
uint32_t envMapTextureIrradiance = 0;
|
||||
uint32_t envMapTextureIrradianceSampler = 0;
|
||||
|
||||
uint32_t lutBRDFTexture = 0;
|
||||
uint32_t lutBRDFTextureSampler = 0;
|
||||
|
||||
uint32_t envMapTextureCharlie = 0;
|
||||
uint32_t envMapTextureCharlieSampler = 0;
|
||||
};
|
||||
|
||||
struct LightDataGPU {
|
||||
vec3 direction = vec3(0, 0, 1);
|
||||
float range = 10000.0;
|
||||
|
||||
vec3 color = vec3(1, 1, 1);
|
||||
float intensity = 1.0;
|
||||
|
||||
vec3 position = vec3(0, 0, -5);
|
||||
|
||||
float innerConeCos = 0.0;
|
||||
float outerConeCos = 0.78;
|
||||
|
||||
LightType type = LightType_Directional;
|
||||
|
||||
int nodeId = ~0;
|
||||
int padding = 0;
|
||||
};
|
||||
|
||||
struct GLTFGlobalSamplers {
|
||||
explicit GLTFGlobalSamplers(const std::unique_ptr<lvk::IContext>& ctx)
|
||||
{
|
||||
clamp = ctx->createSampler({
|
||||
.minFilter = lvk::SamplerFilter::SamplerFilter_Linear,
|
||||
.magFilter = lvk::SamplerFilter::SamplerFilter_Linear,
|
||||
.mipMap = lvk::SamplerMip::SamplerMip_Linear,
|
||||
.wrapU = lvk::SamplerWrap::SamplerWrap_Clamp,
|
||||
.wrapV = lvk::SamplerWrap::SamplerWrap_Clamp,
|
||||
.wrapW = lvk::SamplerWrap::SamplerWrap_Clamp,
|
||||
.debugName = "Clamp Sampler",
|
||||
});
|
||||
|
||||
wrap = ctx->createSampler({
|
||||
.minFilter = lvk::SamplerFilter::SamplerFilter_Linear,
|
||||
.magFilter = lvk::SamplerFilter::SamplerFilter_Linear,
|
||||
.mipMap = lvk::SamplerMip::SamplerMip_Linear,
|
||||
.wrapU = lvk::SamplerWrap::SamplerWrap_Repeat,
|
||||
.wrapV = lvk::SamplerWrap::SamplerWrap_Repeat,
|
||||
.wrapW = lvk::SamplerWrap::SamplerWrap_Repeat,
|
||||
.debugName = "Wrap Sampler",
|
||||
});
|
||||
|
||||
mirror = ctx->createSampler({
|
||||
.minFilter = lvk::SamplerFilter::SamplerFilter_Linear,
|
||||
.magFilter = lvk::SamplerFilter::SamplerFilter_Linear,
|
||||
.mipMap = lvk::SamplerMip::SamplerMip_Linear,
|
||||
.wrapU = lvk::SamplerWrap::SamplerWrap_MirrorRepeat,
|
||||
.wrapV = lvk::SamplerWrap::SamplerWrap_MirrorRepeat,
|
||||
.debugName = "Mirror Sampler",
|
||||
});
|
||||
}
|
||||
|
||||
lvk::Holder<lvk::SamplerHandle> clamp;
|
||||
lvk::Holder<lvk::SamplerHandle> wrap;
|
||||
lvk::Holder<lvk::SamplerHandle> mirror;
|
||||
};
|
||||
|
||||
struct EnvironmentsPerFrame {
|
||||
EnvironmentMapDataGPU environments[kMaxEnvironments];
|
||||
};
|
||||
|
||||
struct Vertex {
|
||||
vec3 position;
|
||||
vec3 normal;
|
||||
vec4 color;
|
||||
vec2 uv0;
|
||||
vec2 uv1;
|
||||
float padding[2];
|
||||
};
|
||||
|
||||
struct MorphTarget {
|
||||
uint32_t meshId = ~0;
|
||||
std::vector<uint32_t> offset;
|
||||
};
|
||||
|
||||
static_assert(sizeof(Vertex) == sizeof(uint32_t) * 16);
|
||||
|
||||
struct GLTFMaterialTextures {
|
||||
// Metallic Roughness / SpecluarGlossiness
|
||||
lvk::Holder<lvk::TextureHandle> baseColorTexture;
|
||||
lvk::Holder<lvk::TextureHandle> surfacePropertiesTexture;
|
||||
|
||||
// Common properties
|
||||
lvk::Holder<lvk::TextureHandle> normalTexture;
|
||||
lvk::Holder<lvk::TextureHandle> occlusionTexture;
|
||||
lvk::Holder<lvk::TextureHandle> emissiveTexture;
|
||||
|
||||
// Sheen
|
||||
lvk::Holder<lvk::TextureHandle> sheenColorTexture;
|
||||
lvk::Holder<lvk::TextureHandle> sheenRoughnessTexture;
|
||||
|
||||
// Clearcoat
|
||||
lvk::Holder<lvk::TextureHandle> clearCoatTexture;
|
||||
lvk::Holder<lvk::TextureHandle> clearCoatRoughnessTexture;
|
||||
lvk::Holder<lvk::TextureHandle> clearCoatNormalTexture;
|
||||
|
||||
// Specular
|
||||
lvk::Holder<lvk::TextureHandle> specularTexture;
|
||||
lvk::Holder<lvk::TextureHandle> specularColorTexture;
|
||||
|
||||
// Transmission
|
||||
lvk::Holder<lvk::TextureHandle> transmissionTexture;
|
||||
|
||||
// Volumen
|
||||
lvk::Holder<lvk::TextureHandle> thicknessTexture;
|
||||
|
||||
// Iridescence
|
||||
lvk::Holder<lvk::TextureHandle> iridescenceTexture;
|
||||
lvk::Holder<lvk::TextureHandle> iridescenceThicknessTexture;
|
||||
|
||||
// Anisotropy
|
||||
lvk::Holder<lvk::TextureHandle> anisotropyTexture;
|
||||
|
||||
lvk::Holder<lvk::TextureHandle> white;
|
||||
|
||||
bool wasLoaded = false;
|
||||
};
|
||||
|
||||
struct EnvironmentMapTextures {
|
||||
explicit EnvironmentMapTextures(const std::unique_ptr<lvk::IContext>& ctx)
|
||||
: EnvironmentMapTextures(
|
||||
ctx, "data/brdfLUT.ktx", "data/piazza_bologni_1k_prefilter.ktx", "data/piazza_bologni_1k_irradiance.ktx",
|
||||
"data/piazza_bologni_1k_charlie.ktx")
|
||||
{
|
||||
}
|
||||
|
||||
EnvironmentMapTextures(
|
||||
const std::unique_ptr<lvk::IContext>& ctx, const char* brdfLUT, const char* prefilter, const char* irradiance,
|
||||
const char* prefilterCharlie = nullptr)
|
||||
{
|
||||
texBRDF_LUT = loadTexture(ctx, brdfLUT, lvk::TextureType_2D);
|
||||
if (texBRDF_LUT.empty()) {
|
||||
assert(0);
|
||||
exit(255);
|
||||
}
|
||||
|
||||
envMapTexture = loadTexture(ctx, prefilter, lvk::TextureType_Cube);
|
||||
if (envMapTexture.empty()) {
|
||||
assert(0);
|
||||
exit(255);
|
||||
}
|
||||
|
||||
envMapTextureIrradiance = loadTexture(ctx, irradiance, lvk::TextureType_Cube);
|
||||
if (envMapTextureIrradiance.empty()) {
|
||||
assert(0);
|
||||
exit(255);
|
||||
}
|
||||
|
||||
if (prefilterCharlie) {
|
||||
envMapTextureCharlie = loadTexture(ctx, prefilterCharlie, lvk::TextureType_Cube);
|
||||
if (envMapTextureCharlie.empty()) {
|
||||
assert(0);
|
||||
exit(255);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
lvk::Holder<lvk::TextureHandle> texBRDF_LUT;
|
||||
lvk::Holder<lvk::TextureHandle> envMapTexture;
|
||||
lvk::Holder<lvk::TextureHandle> envMapTextureCharlie;
|
||||
lvk::Holder<lvk::TextureHandle> envMapTextureIrradiance;
|
||||
};
|
||||
|
||||
bool assignUVandSampler(
|
||||
const GLTFGlobalSamplers& samplers, const aiMaterial* mtlDescriptor, aiTextureType textureType, uint32_t& uvIndex,
|
||||
uint32_t& textureSampler, int index = 0);
|
||||
|
||||
struct GLTFMaterialDataGPU {
|
||||
vec4 baseColorFactor = vec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
vec4 metallicRoughnessNormalOcclusion =
|
||||
vec4(1.0f, 1.0f, 1.0f, 1.0f); // Packed metallicFactor, roughnessFactor, normalScale, occlusionStrength
|
||||
vec4 specularGlossiness = vec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
vec4 sheenFactors = vec4(1.0f, 1.0f, 1.0f, 1.0f); // Sheen
|
||||
|
||||
vec4 clearcoatTransmissionThickness = vec4(1.0f, 1.0f, 1.0f, 1.0f); // Clearcoat
|
||||
vec4 specularFactors = vec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
vec4 attenuation = vec4(1.0f, 1.0f, 1.0f, 1.0f);
|
||||
|
||||
vec4 emissiveFactorAlphaCutoff = vec4(0.0f, 0.0f, 0.0f, 0.5f);
|
||||
|
||||
uint32_t occlusionTexture = 0;
|
||||
uint32_t occlusionTextureSampler = 0;
|
||||
uint32_t occlusionTextureUV = 0;
|
||||
|
||||
uint32_t emissiveTexture = 0;
|
||||
uint32_t emissiveTextureSampler = 0;
|
||||
uint32_t emissiveTextureUV = 0;
|
||||
|
||||
uint32_t baseColorTexture = 0;
|
||||
uint32_t baseColorTextureSampler = 0;
|
||||
uint32_t baseColorTextureUV = 0;
|
||||
|
||||
uint32_t surfacePropertiesTexture = 0;
|
||||
uint32_t surfacePropertiesTextureSampler = 0;
|
||||
uint32_t surfacePropertiesTextureUV = 0;
|
||||
|
||||
uint32_t normalTexture = 0;
|
||||
uint32_t normalTextureSampler = 0;
|
||||
uint32_t normalTextureUV = 0;
|
||||
|
||||
uint32_t sheenColorTexture = 0;
|
||||
uint32_t sheenColorTextureSampler = 0;
|
||||
uint32_t sheenColorTextureUV = 0;
|
||||
uint32_t sheenRoughnessTexture = 0;
|
||||
uint32_t sheenRoughnessTextureSampler = 0;
|
||||
|
||||
uint32_t sheenRoughnessTextureUV = 0;
|
||||
uint32_t clearCoatTexture = 0;
|
||||
uint32_t clearCoatTextureSampler = 0;
|
||||
uint32_t clearCoatTextureUV = 0;
|
||||
|
||||
uint32_t clearCoatRoughnessTexture = 0;
|
||||
uint32_t clearCoatRoughnessTextureSampler = 0;
|
||||
uint32_t clearCoatRoughnessTextureUV = 0;
|
||||
uint32_t clearCoatNormalTexture = 0;
|
||||
|
||||
uint32_t clearCoatNormalTextureSampler = 0;
|
||||
uint32_t clearCoatNormalTextureUV = 0;
|
||||
uint32_t specularTexture = 0;
|
||||
uint32_t specularTextureSampler = 0;
|
||||
|
||||
uint32_t specularTextureUV = 0;
|
||||
uint32_t specularColorTexture = 0;
|
||||
uint32_t specularColorTextureSampler = 0;
|
||||
uint32_t specularColorTextureUV = 0;
|
||||
|
||||
uint32_t transmissionTexture = 0;
|
||||
uint32_t transmissionTextureSampler = 0;
|
||||
uint32_t transmissionTextureUV = 0;
|
||||
uint32_t thicknessTexture = 0;
|
||||
|
||||
uint32_t thicknessTextureSampler = 0;
|
||||
uint32_t thicknessTextureUV = 0;
|
||||
uint32_t iridescenceTexture = 0;
|
||||
uint32_t iridescenceTextureSampler = 0;
|
||||
|
||||
uint32_t iridescenceTextureUV = 0;
|
||||
uint32_t iridescenceThicknessTexture = 0;
|
||||
uint32_t iridescenceThicknessTextureSampler = 0;
|
||||
uint32_t iridescenceThicknessTextureUV = 0;
|
||||
|
||||
uint32_t anisotropyTexture = 0;
|
||||
uint32_t anisotropyTextureSampler = 0;
|
||||
uint32_t anisotropyTextureUV = 0;
|
||||
uint32_t alphaMode = 0;
|
||||
|
||||
uint32_t materialTypeFlags = 0;
|
||||
float ior = 1.5f;
|
||||
uint32_t padding[2] = { 0, 0 };
|
||||
|
||||
enum AlphaMode : uint32_t {
|
||||
AlphaMode_Opaque = 0,
|
||||
AlphaMode_Mask = 1,
|
||||
AlphaMode_Blend = 2,
|
||||
};
|
||||
};
|
||||
|
||||
static_assert(sizeof(GLTFMaterialDataGPU) % 16 == 0);
|
||||
|
||||
struct GLTFDataHolder {
|
||||
std::vector<GLTFMaterialTextures> textures;
|
||||
};
|
||||
|
||||
struct MaterialsPerFrame {
|
||||
GLTFMaterialDataGPU materials[kMaxMaterials];
|
||||
};
|
||||
|
||||
using GLTFNodeRef = uint32_t;
|
||||
using GLTFMeshRef = uint32_t;
|
||||
|
||||
enum SortingType : uint32_t {
|
||||
SortingType_Opaque = 0,
|
||||
SortingType_Transmission = 1,
|
||||
SortingType_Transparent = 2,
|
||||
};
|
||||
|
||||
struct GLTFMesh {
|
||||
lvk::Topology primitive;
|
||||
uint32_t vertexOffset;
|
||||
uint32_t vertexCount;
|
||||
uint32_t indexOffset;
|
||||
uint32_t indexCount;
|
||||
uint32_t matIdx;
|
||||
SortingType sortingType;
|
||||
};
|
||||
|
||||
struct GLTFNode {
|
||||
std::string name;
|
||||
uint32_t modelMtxId;
|
||||
mat4 transform = mat4(1);
|
||||
std::vector<GLTFNodeRef> children;
|
||||
std::vector<GLTFMeshRef> meshes;
|
||||
};
|
||||
|
||||
struct GLTFFrameData {
|
||||
mat4 model;
|
||||
mat4 view;
|
||||
mat4 proj;
|
||||
vec4 cameraPos;
|
||||
};
|
||||
|
||||
struct GLTFCamera {
|
||||
std::string name;
|
||||
uint32_t nodeIdx = ~0;
|
||||
vec3 pos;
|
||||
vec3 up;
|
||||
vec3 lookAt;
|
||||
float hFOV;
|
||||
float near;
|
||||
float far;
|
||||
float aspect = 1.0f;
|
||||
float orthoWidth;
|
||||
|
||||
mat4 getProjection(float windowAspect = 1.0f) const
|
||||
{
|
||||
return orthoWidth != 0.0f
|
||||
? glm::ortho(-windowAspect / orthoWidth, windowAspect / orthoWidth, -1.0f / orthoWidth, 1.0f / orthoWidth, far, near)
|
||||
: glm::perspective(hFOV, windowAspect == 1.0f ? aspect : windowAspect, near, far);
|
||||
}
|
||||
};
|
||||
|
||||
struct GLTFTransforms {
|
||||
uint32_t modelMtxId;
|
||||
uint32_t matId;
|
||||
GLTFNodeRef nodeRef; // for CPU only
|
||||
GLTFMeshRef meshRef; // for CPU only
|
||||
uint32_t sortingType;
|
||||
};
|
||||
|
||||
// Skeleton, animation, morphing
|
||||
#define MAX_BONES_PER_VERTEX 8
|
||||
|
||||
struct VertexBoneData {
|
||||
vec4 position;
|
||||
vec4 normal;
|
||||
uint32_t boneId[MAX_BONES_PER_VERTEX] = { ~0u, ~0u, ~0u, ~0u, ~0u, ~0u, ~0u, ~0u };
|
||||
float weight[MAX_BONES_PER_VERTEX] = {};
|
||||
uint32_t meshId = ~0u;
|
||||
};
|
||||
|
||||
static_assert(sizeof(VertexBoneData) == sizeof(uint32_t) * 25);
|
||||
|
||||
struct GLTFBone {
|
||||
uint32_t boneId = ~0u;
|
||||
mat4 transform = mat4(1);
|
||||
};
|
||||
|
||||
GLTFMaterialDataGPU setupglTFMaterialData(
|
||||
const std::unique_ptr<lvk::IContext>& ctx, const GLTFGlobalSamplers& samplers, aiMaterial* const& mtlDescriptor,
|
||||
const char* assetFolder, GLTFDataHolder& glTFDataholder, bool& useVolumetric);
|
||||
|
||||
struct GLTFContext {
|
||||
explicit GLTFContext(VulkanApp& app_)
|
||||
: app(app_)
|
||||
, samplers(app_.ctx_)
|
||||
, envMapTextures(app_.ctx_)
|
||||
{
|
||||
}
|
||||
|
||||
GLTFDataHolder glTFDataholder;
|
||||
MaterialsPerFrame matPerFrame;
|
||||
GLTFGlobalSamplers samplers;
|
||||
EnvironmentMapTextures envMapTextures;
|
||||
GLTFFrameData frameData;
|
||||
std::vector<GLTFTransforms> transforms;
|
||||
std::vector<mat4> matrices;
|
||||
|
||||
std::vector<GLTFNode> nodesStorage;
|
||||
std::vector<GLTFMesh> meshesStorage;
|
||||
std::unordered_map<std::string, GLTFBone> bonesByName;
|
||||
|
||||
std::vector<MorphTarget> morphTargets;
|
||||
std::unordered_map<std::string, uint32_t> meshesRemap;
|
||||
|
||||
std::vector<Animation> animations;
|
||||
|
||||
std::vector<uint32_t> opaqueNodes;
|
||||
std::vector<uint32_t> transmissionNodes;
|
||||
std::vector<uint32_t> transparentNodes;
|
||||
|
||||
lvk::Holder<lvk::BufferHandle> envBuffer;
|
||||
lvk::Holder<lvk::BufferHandle> lightsBuffer;
|
||||
lvk::Holder<lvk::BufferHandle> perFrameBuffer;
|
||||
lvk::Holder<lvk::BufferHandle> transformBuffer;
|
||||
lvk::Holder<lvk::BufferHandle> matricesBuffer;
|
||||
lvk::Holder<lvk::BufferHandle> morphStatesBuffer;
|
||||
|
||||
lvk::Holder<lvk::RenderPipelineHandle> pipelineSolid;
|
||||
lvk::Holder<lvk::RenderPipelineHandle> pipelineTransparent;
|
||||
lvk::Holder<lvk::ComputePipelineHandle> pipelineComputeAnimations;
|
||||
|
||||
lvk::Holder<lvk::ShaderModuleHandle> vert;
|
||||
lvk::Holder<lvk::ShaderModuleHandle> frag;
|
||||
lvk::Holder<lvk::ShaderModuleHandle> animation;
|
||||
lvk::Holder<lvk::BufferHandle> vertexBuffer;
|
||||
lvk::Holder<lvk::BufferHandle> vertexSkinningBuffer;
|
||||
lvk::Holder<lvk::BufferHandle> vertexMorphingBuffer;
|
||||
lvk::Holder<lvk::BufferHandle> indexBuffer;
|
||||
lvk::Holder<lvk::BufferHandle> matBuffer;
|
||||
|
||||
lvk::Holder<lvk::TextureHandle> offscreenTex[3] = {};
|
||||
|
||||
uint32_t currentOffscreenTex = 0;
|
||||
uint32_t maxVertices = 0;
|
||||
|
||||
std::vector<MorphState> morphStates;
|
||||
std::vector<LightDataGPU> lights;
|
||||
std::vector<GLTFCamera> cameras;
|
||||
|
||||
GLTFIntrospective inspector;
|
||||
|
||||
GLTFNodeRef root;
|
||||
VulkanApp& app;
|
||||
LineCanvas3D canvas3d;
|
||||
|
||||
bool hasBones = false;
|
||||
bool isVolumetricMaterial = false;
|
||||
bool animated = false;
|
||||
bool skinning = false;
|
||||
bool morphing = false;
|
||||
bool doublesided = false;
|
||||
bool enableMorphing = true;
|
||||
|
||||
bool isScreenCopyRequired() const { return isVolumetricMaterial; }
|
||||
};
|
||||
|
||||
void loadGLTF(GLTFContext& context, const char* gltfName, const char* glTFDataPath);
|
||||
void renderGLTF(GLTFContext& context, const mat4& model, const mat4& view, const mat4& proj, bool rebuildRenderList = false);
|
||||
void animateGLTF(GLTFContext& gltf, AnimationState& anim, float dt);
|
||||
void animateBlendingGLTF(GLTFContext& gltf, AnimationState& anim1, AnimationState& anim2, float weight, float dt);
|
||||
MaterialType detectMaterialType(const aiMaterial* mtl);
|
||||
|
||||
void printPrefix(int ofs);
|
||||
void printMat4(const aiMatrix4x4& m);
|
||||
|
||||
std::vector<std::string> camerasGLTF(GLTFContext& context);
|
||||
void updateCamera(GLTFContext& gltf, const mat4& model, mat4& view, mat4& proj, float aspectRatio);
|
||||
|
||||
std::vector<std::string> animationsGLTF(GLTFContext& gltf);
|
||||
199
shared/UtilsMath.h
Normal file
199
shared/UtilsMath.h
Normal file
|
|
@ -0,0 +1,199 @@
|
|||
#pragma once
|
||||
|
||||
#define _USE_MATH_DEFINES
|
||||
#include <cmath>
|
||||
#include <glm/ext.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <vector>
|
||||
|
||||
using glm::mat4;
|
||||
using glm::vec2;
|
||||
using glm::vec3;
|
||||
using glm::vec4;
|
||||
|
||||
namespace Math
|
||||
{
|
||||
static constexpr float PI = 3.14159265359f;
|
||||
static constexpr float TWOPI = 6.28318530718f;
|
||||
} // namespace Math
|
||||
|
||||
inline vec2 clampLength(const vec2& v, float maxLength)
|
||||
{
|
||||
const float l = length(v);
|
||||
|
||||
return (l > maxLength) ? normalize(v) * maxLength : v;
|
||||
}
|
||||
|
||||
struct BoundingBox {
|
||||
vec3 min_;
|
||||
vec3 max_;
|
||||
BoundingBox() = default;
|
||||
BoundingBox(const vec3& min, const vec3& max)
|
||||
: min_(glm::min(min, max))
|
||||
, max_(glm::max(min, max))
|
||||
{
|
||||
}
|
||||
BoundingBox(const vec3* points, size_t numPoints)
|
||||
{
|
||||
vec3 vmin(std::numeric_limits<float>::max());
|
||||
vec3 vmax(std::numeric_limits<float>::lowest());
|
||||
|
||||
for (size_t i = 0; i != numPoints; i++) {
|
||||
vmin = glm::min(vmin, points[i]);
|
||||
vmax = glm::max(vmax, points[i]);
|
||||
}
|
||||
min_ = vmin;
|
||||
max_ = vmax;
|
||||
}
|
||||
vec3 getSize() const { return vec3(max_[0] - min_[0], max_[1] - min_[1], max_[2] - min_[2]); }
|
||||
vec3 getCenter() const { return 0.5f * vec3(max_[0] + min_[0], max_[1] + min_[1], max_[2] + min_[2]); }
|
||||
void transform(const glm::mat4& t)
|
||||
{
|
||||
vec3 corners[] = {
|
||||
vec3(min_.x, min_.y, min_.z), vec3(min_.x, max_.y, min_.z), vec3(min_.x, min_.y, max_.z), vec3(min_.x, max_.y, max_.z),
|
||||
vec3(max_.x, min_.y, min_.z), vec3(max_.x, max_.y, min_.z), vec3(max_.x, min_.y, max_.z), vec3(max_.x, max_.y, max_.z),
|
||||
};
|
||||
for (auto& v : corners)
|
||||
v = vec3(t * vec4(v, 1.0f));
|
||||
*this = BoundingBox(corners, 8);
|
||||
}
|
||||
BoundingBox getTransformed(const glm::mat4& t) const
|
||||
{
|
||||
BoundingBox b = *this;
|
||||
b.transform(t);
|
||||
return b;
|
||||
}
|
||||
void combinePoint(const vec3& p)
|
||||
{
|
||||
min_ = glm::min(min_, p);
|
||||
max_ = glm::max(max_, p);
|
||||
}
|
||||
};
|
||||
|
||||
template <typename T> T clamp(T v, T a, T b)
|
||||
{
|
||||
if (v < a)
|
||||
return a;
|
||||
if (v > b)
|
||||
return b;
|
||||
return v;
|
||||
}
|
||||
|
||||
inline float random01()
|
||||
{
|
||||
return (float)rand() / (float)RAND_MAX;
|
||||
}
|
||||
|
||||
inline float randomFloat(float min, float max)
|
||||
{
|
||||
return min + (max - min) * random01();
|
||||
}
|
||||
|
||||
inline vec3 randomVec(const vec3& min, const vec3& max)
|
||||
{
|
||||
return vec3(randomFloat(min.x, max.x), randomFloat(min.y, max.y), randomFloat(min.z, max.z));
|
||||
}
|
||||
|
||||
inline vec3 randVec()
|
||||
{
|
||||
return randomVec(vec3(-5, -5, -5), vec3(5, 5, 5));
|
||||
}
|
||||
|
||||
inline void getFrustumPlanes(mat4 viewProj, vec4* planes)
|
||||
{
|
||||
viewProj = glm::transpose(viewProj);
|
||||
planes[0] = vec4(viewProj[3] + viewProj[0]); // left
|
||||
planes[1] = vec4(viewProj[3] - viewProj[0]); // right
|
||||
planes[2] = vec4(viewProj[3] + viewProj[1]); // bottom
|
||||
planes[3] = vec4(viewProj[3] - viewProj[1]); // top
|
||||
planes[4] = vec4(viewProj[3] + viewProj[2]); // near
|
||||
planes[5] = vec4(viewProj[3] - viewProj[2]); // far
|
||||
}
|
||||
|
||||
inline void getFrustumCorners(mat4 viewProj, vec4* points)
|
||||
{
|
||||
const vec4 corners[] = { vec4(-1, -1, -1, 1), vec4(1, -1, -1, 1), vec4(1, 1, -1, 1), vec4(-1, 1, -1, 1),
|
||||
vec4(-1, -1, 1, 1), vec4(1, -1, 1, 1), vec4(1, 1, 1, 1), vec4(-1, 1, 1, 1) };
|
||||
|
||||
const glm::mat4 invViewProj = glm::inverse(viewProj);
|
||||
|
||||
for (int i = 0; i != 8; i++) {
|
||||
const vec4 q = invViewProj * corners[i];
|
||||
points[i] = q / q.w;
|
||||
}
|
||||
}
|
||||
|
||||
inline bool isBoxInFrustum(glm::vec4* frustumPlanes, glm::vec4* frustumCorners, const BoundingBox& box)
|
||||
{
|
||||
using glm::dot;
|
||||
|
||||
for (int i = 0; i < 6; i++) {
|
||||
int r = 0;
|
||||
r += (dot(frustumPlanes[i], vec4(box.min_.x, box.min_.y, box.min_.z, 1.0f)) < 0.0) ? 1 : 0;
|
||||
r += (dot(frustumPlanes[i], vec4(box.max_.x, box.min_.y, box.min_.z, 1.0f)) < 0.0) ? 1 : 0;
|
||||
r += (dot(frustumPlanes[i], vec4(box.min_.x, box.max_.y, box.min_.z, 1.0f)) < 0.0) ? 1 : 0;
|
||||
r += (dot(frustumPlanes[i], vec4(box.max_.x, box.max_.y, box.min_.z, 1.0f)) < 0.0) ? 1 : 0;
|
||||
r += (dot(frustumPlanes[i], vec4(box.min_.x, box.min_.y, box.max_.z, 1.0f)) < 0.0) ? 1 : 0;
|
||||
r += (dot(frustumPlanes[i], vec4(box.max_.x, box.min_.y, box.max_.z, 1.0f)) < 0.0) ? 1 : 0;
|
||||
r += (dot(frustumPlanes[i], vec4(box.min_.x, box.max_.y, box.max_.z, 1.0f)) < 0.0) ? 1 : 0;
|
||||
r += (dot(frustumPlanes[i], vec4(box.max_.x, box.max_.y, box.max_.z, 1.0f)) < 0.0) ? 1 : 0;
|
||||
if (r == 8)
|
||||
return false;
|
||||
}
|
||||
|
||||
// check frustum outside/inside box
|
||||
int r = 0;
|
||||
r = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
r += ((frustumCorners[i].x > box.max_.x) ? 1 : 0);
|
||||
if (r == 8)
|
||||
return false;
|
||||
r = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
r += ((frustumCorners[i].x < box.min_.x) ? 1 : 0);
|
||||
if (r == 8)
|
||||
return false;
|
||||
r = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
r += ((frustumCorners[i].y > box.max_.y) ? 1 : 0);
|
||||
if (r == 8)
|
||||
return false;
|
||||
r = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
r += ((frustumCorners[i].y < box.min_.y) ? 1 : 0);
|
||||
if (r == 8)
|
||||
return false;
|
||||
r = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
r += ((frustumCorners[i].z > box.max_.z) ? 1 : 0);
|
||||
if (r == 8)
|
||||
return false;
|
||||
r = 0;
|
||||
for (int i = 0; i < 8; i++)
|
||||
r += ((frustumCorners[i].z < box.min_.z) ? 1 : 0);
|
||||
if (r == 8)
|
||||
return false;
|
||||
|
||||
return true;
|
||||
}
|
||||
|
||||
inline BoundingBox combineBoxes(const std::vector<BoundingBox>& boxes)
|
||||
{
|
||||
std::vector<vec3> allPoints;
|
||||
allPoints.reserve(boxes.size() * 8);
|
||||
|
||||
for (const auto& b : boxes) {
|
||||
allPoints.emplace_back(b.min_.x, b.min_.y, b.min_.z);
|
||||
allPoints.emplace_back(b.min_.x, b.min_.y, b.max_.z);
|
||||
allPoints.emplace_back(b.min_.x, b.max_.y, b.min_.z);
|
||||
allPoints.emplace_back(b.min_.x, b.max_.y, b.max_.z);
|
||||
|
||||
allPoints.emplace_back(b.max_.x, b.min_.y, b.min_.z);
|
||||
allPoints.emplace_back(b.max_.x, b.min_.y, b.max_.z);
|
||||
allPoints.emplace_back(b.max_.x, b.max_.y, b.min_.z);
|
||||
allPoints.emplace_back(b.max_.x, b.max_.y, b.max_.z);
|
||||
}
|
||||
|
||||
return BoundingBox(allPoints.data(), allPoints.size());
|
||||
}
|
||||
412
shared/VulkanApp.cpp
Normal file
412
shared/VulkanApp.cpp
Normal file
|
|
@ -0,0 +1,412 @@
|
|||
#include "VulkanApp.h"
|
||||
|
||||
#include "UtilsGLTF.h"
|
||||
#include <unordered_map>
|
||||
|
||||
extern std::unordered_map<uint32_t, std::string> debugGLSLSourceCode;
|
||||
|
||||
static void shaderModuleCallback(lvk::IContext*, lvk::ShaderModuleHandle handle, int line, int col, const char* debugName)
|
||||
{
|
||||
const auto it = debugGLSLSourceCode.find(handle.index());
|
||||
|
||||
if (it != debugGLSLSourceCode.end()) {
|
||||
lvk::logShaderSource(it->second.c_str());
|
||||
}
|
||||
}
|
||||
|
||||
VulkanApp::VulkanApp(const VulkanAppConfig& cfg)
|
||||
: cfg_(cfg)
|
||||
{
|
||||
minilog::initialize(nullptr, { .threadNames = false });
|
||||
|
||||
int width = -95;
|
||||
int height = -90;
|
||||
|
||||
window_ = lvk::initWindow("Simple example", width, height);
|
||||
ctx_ = lvk::createVulkanContextWithSwapchain(
|
||||
window_, width, height,
|
||||
{
|
||||
.enableValidation = true,
|
||||
.shaderModuleErrorCallback = &shaderModuleCallback,
|
||||
});
|
||||
depthTexture_ = ctx_->createTexture({
|
||||
.type = lvk::TextureType_2D,
|
||||
.format = lvk::Format_Z_F32,
|
||||
.dimensions = {(uint32_t)width, (uint32_t)height},
|
||||
.usage = lvk::TextureUsageBits_Attachment,
|
||||
.debugName = "Depth buffer",
|
||||
});
|
||||
|
||||
imgui_ = std::make_unique<lvk::ImGuiRenderer>(*ctx_, "data/OpenSans-Light.ttf", 30.0f);
|
||||
implotCtx_ = ImPlot::CreateContext();
|
||||
|
||||
glfwSetWindowUserPointer(window_, this);
|
||||
|
||||
glfwSetMouseButtonCallback(window_, [](GLFWwindow* window, int button, int action, int mods) {
|
||||
VulkanApp* app = (VulkanApp*)glfwGetWindowUserPointer(window);
|
||||
if (button == GLFW_MOUSE_BUTTON_LEFT) {
|
||||
app->mouseState_.pressedLeft = action == GLFW_PRESS;
|
||||
}
|
||||
double xpos, ypos;
|
||||
glfwGetCursorPos(window, &xpos, &ypos);
|
||||
const ImGuiMouseButton_ imguiButton = (button == GLFW_MOUSE_BUTTON_LEFT)
|
||||
? ImGuiMouseButton_Left
|
||||
: (button == GLFW_MOUSE_BUTTON_RIGHT ? ImGuiMouseButton_Right : ImGuiMouseButton_Middle);
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.MousePos = ImVec2((float)xpos, (float)ypos);
|
||||
io.MouseDown[imguiButton] = action == GLFW_PRESS;
|
||||
for (auto& cb : app->callbacksMouseButton) {
|
||||
cb(window, button, action, mods);
|
||||
}
|
||||
});
|
||||
glfwSetScrollCallback(window_, [](GLFWwindow* window, double dx, double dy) {
|
||||
ImGuiIO& io = ImGui::GetIO();
|
||||
io.MouseWheelH = (float)dx;
|
||||
io.MouseWheel = (float)dy;
|
||||
});
|
||||
glfwSetCursorPosCallback(window_, [](GLFWwindow* window, double x, double y) {
|
||||
VulkanApp* app = (VulkanApp*)glfwGetWindowUserPointer(window);
|
||||
int width, height;
|
||||
glfwGetFramebufferSize(window, &width, &height);
|
||||
ImGui::GetIO().MousePos = ImVec2(x, y);
|
||||
app->mouseState_.pos.x = static_cast<float>(x / width);
|
||||
app->mouseState_.pos.y = 1.0f - static_cast<float>(y / height);
|
||||
});
|
||||
glfwSetKeyCallback(window_, [](GLFWwindow* window, int key, int scancode, int action, int mods) {
|
||||
VulkanApp* app = (VulkanApp*)glfwGetWindowUserPointer(window);
|
||||
const bool pressed = action != GLFW_RELEASE;
|
||||
if (key == GLFW_KEY_ESCAPE && pressed)
|
||||
glfwSetWindowShouldClose(window, GLFW_TRUE);
|
||||
if (key == GLFW_KEY_W)
|
||||
app->positioner_.movement_.forward_ = pressed;
|
||||
if (key == GLFW_KEY_S)
|
||||
app->positioner_.movement_.backward_ = pressed;
|
||||
if (key == GLFW_KEY_A)
|
||||
app->positioner_.movement_.left_ = pressed;
|
||||
if (key == GLFW_KEY_D)
|
||||
app->positioner_.movement_.right_ = pressed;
|
||||
if (key == GLFW_KEY_1)
|
||||
app->positioner_.movement_.up_ = pressed;
|
||||
if (key == GLFW_KEY_2)
|
||||
app->positioner_.movement_.down_ = pressed;
|
||||
|
||||
app->positioner_.movement_.fastSpeed_ = (mods & GLFW_MOD_SHIFT) != 0;
|
||||
|
||||
if (key == GLFW_KEY_SPACE) {
|
||||
app->positioner_.lookAt(app->cfg_.initialCameraPos, app->cfg_.initialCameraTarget, vec3(0.0f, 1.0f, 0.0f));
|
||||
app->positioner_.setSpeed(vec3(0));
|
||||
}
|
||||
for (auto& cb : app->callbacksKey) {
|
||||
cb(window, key, scancode, action, mods);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
VulkanApp::~VulkanApp()
|
||||
{
|
||||
ImPlot::DestroyContext(implotCtx_);
|
||||
|
||||
gridPipeline = nullptr;
|
||||
gridVert = nullptr;
|
||||
gridFrag = nullptr;
|
||||
imgui_ = nullptr;
|
||||
depthTexture_ = nullptr;
|
||||
ctx_ = nullptr;
|
||||
|
||||
glfwDestroyWindow(window_);
|
||||
glfwTerminate();
|
||||
}
|
||||
|
||||
lvk::Format VulkanApp::getDepthFormat() const
|
||||
{
|
||||
return ctx_->getFormat(depthTexture_);
|
||||
}
|
||||
|
||||
void VulkanApp::run(DrawFrameFunc drawFrame)
|
||||
{
|
||||
double timeStamp = glfwGetTime();
|
||||
float deltaSeconds = 0.0f;
|
||||
|
||||
while (!glfwWindowShouldClose(window_)) {
|
||||
fpsCounter_.tick(deltaSeconds);
|
||||
const double newTimeStamp = glfwGetTime();
|
||||
deltaSeconds = static_cast<float>(newTimeStamp - timeStamp);
|
||||
timeStamp = newTimeStamp;
|
||||
|
||||
glfwPollEvents();
|
||||
int width, height;
|
||||
#if defined(__APPLE__)
|
||||
// a hacky workaround for retina displays
|
||||
glfwGetWindowSize(window_, &width, &height);
|
||||
#else
|
||||
glfwGetFramebufferSize(window_, &width, &height);
|
||||
#endif
|
||||
if (!width || !height)
|
||||
continue;
|
||||
const float ratio = width / (float)height;
|
||||
|
||||
positioner_.update(deltaSeconds, mouseState_.pos, ImGui::GetIO().WantCaptureMouse ? false : mouseState_.pressedLeft);
|
||||
|
||||
drawFrame((uint32_t)width, (uint32_t)height, ratio, deltaSeconds);
|
||||
}
|
||||
}
|
||||
|
||||
void VulkanApp::drawMemo()
|
||||
{
|
||||
ImGui::SetNextWindowPos(ImVec2(10, 10));
|
||||
ImGui::Begin(
|
||||
"Keyboard hints:", nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoInputs | ImGuiWindowFlags_NoCollapse);
|
||||
ImGui::Text("W/S/A/D - camera movement");
|
||||
ImGui::Text("1/2 - camera up/down");
|
||||
ImGui::Text("Shift - fast movement");
|
||||
ImGui::Text("Space - reset view");
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void VulkanApp::drawGTFInspector_Animations(GLTFIntrospective& intro)
|
||||
{
|
||||
if (!intro.showAnimations)
|
||||
return;
|
||||
|
||||
if (ImGui::Begin(
|
||||
"Animations", nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
|
||||
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoCollapse)) {
|
||||
for (uint32_t a = 0; a < intro.animations.size(); ++a) {
|
||||
auto it = std::find(intro.activeAnim.begin(), intro.activeAnim.end(), a);
|
||||
bool oState = it != intro.activeAnim.end(); // && selected < anim->size();
|
||||
bool state = oState;
|
||||
ImGui::Checkbox(intro.animations[a].c_str(), &state);
|
||||
|
||||
if (state) {
|
||||
if (!oState) {
|
||||
uint32_t freeSlot = intro.activeAnim.size() - 1;
|
||||
if (auto nf = std::find(intro.activeAnim.begin(), intro.activeAnim.end(), ~0u); nf != intro.activeAnim.end()) {
|
||||
freeSlot = std::distance(intro.activeAnim.begin(), nf);
|
||||
}
|
||||
intro.activeAnim[freeSlot] = a;
|
||||
}
|
||||
} else {
|
||||
if (it != intro.activeAnim.end()) {
|
||||
*it = ~0;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (intro.showAnimationBlend) {
|
||||
ImGui::SliderFloat("Blend", &intro.blend, 0, 1.0f);
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void VulkanApp::drawGTFInspector_Materials(GLTFIntrospective& intro) {
|
||||
if (!intro.showMaterials || intro.materials.empty())
|
||||
return;
|
||||
|
||||
if (ImGui::Begin(
|
||||
"Materials", nullptr,
|
||||
ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings | ImGuiWindowFlags_NoFocusOnAppearing |
|
||||
ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoCollapse)) {
|
||||
for (uint32_t m = 0; m < intro.materials.size(); ++m) {
|
||||
GLTFMaterialIntro& mat = intro.materials[m];
|
||||
const uint32_t& currentMask = intro.materials[m].currentMaterialMask;
|
||||
|
||||
auto setMaterialMask = [&m = intro.materials[m]](uint32_t flag, bool active) {
|
||||
m.modified = true;
|
||||
if (active) {
|
||||
m.currentMaterialMask |= flag;
|
||||
} else {
|
||||
m.currentMaterialMask &= ~flag;
|
||||
}
|
||||
};
|
||||
|
||||
const bool isUnlit = (currentMask & MaterialType_Unlit) == MaterialType_Unlit;
|
||||
|
||||
bool state = false;
|
||||
|
||||
ImGui::Text("%s", mat.name.c_str());
|
||||
ImGui::PushID(m);
|
||||
state = isUnlit;
|
||||
|
||||
if (ImGui::RadioButton("Unlit", state)) {
|
||||
mat.currentMaterialMask = 0;
|
||||
setMaterialMask(MaterialType_Unlit, true);
|
||||
}
|
||||
|
||||
state = (currentMask & MaterialType_MetallicRoughness) == MaterialType_MetallicRoughness;
|
||||
if ((mat.materialMask & MaterialType_MetallicRoughness) == MaterialType_MetallicRoughness) {
|
||||
if (ImGui::RadioButton("MetallicRoughness", state)) {
|
||||
setMaterialMask(MaterialType_Unlit, false);
|
||||
setMaterialMask(MaterialType_SpecularGlossiness, false);
|
||||
setMaterialMask(MaterialType_MetallicRoughness, true);
|
||||
}
|
||||
}
|
||||
|
||||
state = (currentMask & MaterialType_SpecularGlossiness) == MaterialType_SpecularGlossiness;
|
||||
if ((mat.materialMask & MaterialType_SpecularGlossiness) == MaterialType_SpecularGlossiness) {
|
||||
if (ImGui::RadioButton("SpecularGlossiness", state)) {
|
||||
setMaterialMask(MaterialType_Unlit, false);
|
||||
setMaterialMask(MaterialType_SpecularGlossiness, true);
|
||||
setMaterialMask(MaterialType_MetallicRoughness, false);
|
||||
}
|
||||
}
|
||||
|
||||
state = (currentMask & MaterialType_Sheen) == MaterialType_Sheen;
|
||||
if ((mat.materialMask & MaterialType_Sheen) == MaterialType_Sheen) {
|
||||
ImGui::BeginDisabled(isUnlit);
|
||||
if (ImGui::Checkbox("Sheen", &state)) {
|
||||
setMaterialMask(MaterialType_Sheen, state);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
state = (mat.currentMaterialMask & MaterialType_ClearCoat) == MaterialType_ClearCoat;
|
||||
if ((mat.materialMask & MaterialType_ClearCoat) == MaterialType_ClearCoat) {
|
||||
ImGui::BeginDisabled(isUnlit);
|
||||
if (ImGui::Checkbox("ClearCoat", &state)) {
|
||||
setMaterialMask(MaterialType_ClearCoat, state);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
state = (mat.currentMaterialMask & MaterialType_Specular) == MaterialType_Specular;
|
||||
if ((mat.materialMask & MaterialType_Specular) == MaterialType_Specular) {
|
||||
ImGui::BeginDisabled(isUnlit);
|
||||
if (ImGui::Checkbox("Specular", &state)) {
|
||||
setMaterialMask(MaterialType_Specular, state);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
state = (mat.currentMaterialMask & MaterialType_Transmission) == MaterialType_Transmission;
|
||||
if ((mat.materialMask & MaterialType_Transmission) == MaterialType_Transmission) {
|
||||
ImGui::BeginDisabled(isUnlit);
|
||||
if (ImGui::Checkbox("Transmission", &state)) {
|
||||
if (!state) {
|
||||
setMaterialMask(MaterialType_Volume, false);
|
||||
}
|
||||
setMaterialMask(MaterialType_Transmission, state);
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
state = (mat.currentMaterialMask & MaterialType_Volume) == MaterialType_Volume;
|
||||
if ((mat.materialMask & MaterialType_Volume) == MaterialType_Volume) {
|
||||
ImGui::BeginDisabled(isUnlit);
|
||||
if (ImGui::Checkbox("Volume", &state)) {
|
||||
setMaterialMask(MaterialType_Volume, state);
|
||||
if (state) {
|
||||
setMaterialMask(MaterialType_Transmission, true);
|
||||
}
|
||||
}
|
||||
ImGui::EndDisabled();
|
||||
}
|
||||
|
||||
ImGui::PopID();
|
||||
}
|
||||
}
|
||||
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void VulkanApp::drawGTFInspector_Cameras(GLTFIntrospective& intro)
|
||||
{
|
||||
if (!intro.showCameras)
|
||||
return;
|
||||
|
||||
ImGui::Begin("Cameras:", nullptr, ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoCollapse);
|
||||
std::string current_item = intro.activeCamera != ~0u ? intro.cameras[intro.activeCamera] : "";
|
||||
if (ImGui::BeginCombo("##combo", current_item.c_str())) {
|
||||
for (uint32_t n = 0; n < intro.cameras.size(); n++) {
|
||||
bool is_selected = (current_item == intro.cameras[n]);
|
||||
if (ImGui::Selectable(intro.cameras[n].c_str(), is_selected)) {
|
||||
intro.activeCamera = n;
|
||||
current_item = intro.cameras[n];
|
||||
}
|
||||
if (is_selected)
|
||||
ImGui::SetItemDefaultFocus();
|
||||
}
|
||||
ImGui::EndCombo();
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void VulkanApp::drawGTFInspector(GLTFIntrospective& intro)
|
||||
{
|
||||
if (!cfg_.showGLTFInspector) {
|
||||
return;
|
||||
}
|
||||
|
||||
ImGui::SetNextWindowPos(ImVec2(10, 300));
|
||||
|
||||
drawGTFInspector_Animations(intro);
|
||||
drawGTFInspector_Materials(intro);
|
||||
drawGTFInspector_Cameras(intro);
|
||||
}
|
||||
|
||||
void VulkanApp::drawFPS()
|
||||
{
|
||||
if (const ImGuiViewport* v = ImGui::GetMainViewport()) {
|
||||
ImGui::SetNextWindowPos({ v->WorkPos.x + v->WorkSize.x - 15.0f, v->WorkPos.y + 15.0f }, ImGuiCond_Always, { 1.0f, 0.0f });
|
||||
}
|
||||
ImGui::SetNextWindowBgAlpha(0.30f);
|
||||
ImGui::SetNextWindowSize(ImVec2(ImGui::CalcTextSize("FPS : _______").x, 0));
|
||||
if (ImGui::Begin(
|
||||
"##FPS", nullptr,
|
||||
ImGuiWindowFlags_NoDecoration | ImGuiWindowFlags_AlwaysAutoResize | ImGuiWindowFlags_NoSavedSettings |
|
||||
ImGuiWindowFlags_NoFocusOnAppearing | ImGuiWindowFlags_NoNav | ImGuiWindowFlags_NoMove)) {
|
||||
ImGui::Text("FPS : %i", (int)fpsCounter_.getFPS());
|
||||
ImGui::Text("Ms : %.1f", fpsCounter_.getFPS() > 0 ? 1000.0 / fpsCounter_.getFPS() : 0);
|
||||
}
|
||||
ImGui::End();
|
||||
}
|
||||
|
||||
void VulkanApp::drawGrid(lvk::ICommandBuffer& buf, const mat4& proj, const vec3& origin, uint32_t numSamples, lvk::Format colorFormat)
|
||||
{
|
||||
drawGrid(buf, proj * camera_.getViewMatrix(), origin, camera_.getPosition(), numSamples, colorFormat);
|
||||
}
|
||||
|
||||
void VulkanApp::drawGrid(
|
||||
lvk::ICommandBuffer& buf, const mat4& mvp, const vec3& origin, const vec3& camPos, uint32_t numSamples, lvk::Format colorFormat)
|
||||
{
|
||||
if (gridPipeline.empty() || pipelineSamples != numSamples) {
|
||||
gridVert = loadShaderModule(ctx_, "data/shaders/Grid.vert");
|
||||
gridFrag = loadShaderModule(ctx_, "data/shaders/Grid.frag");
|
||||
|
||||
pipelineSamples = numSamples;
|
||||
|
||||
gridPipeline = ctx_->createRenderPipeline({
|
||||
.smVert = gridVert,
|
||||
.smFrag = gridFrag,
|
||||
.color = { {
|
||||
.format = colorFormat != lvk::Format_Invalid ? colorFormat : ctx_->getSwapchainFormat(),
|
||||
.blendEnabled = true,
|
||||
.srcRGBBlendFactor = lvk::BlendFactor_SrcAlpha,
|
||||
.dstRGBBlendFactor = lvk::BlendFactor_OneMinusSrcAlpha,
|
||||
} },
|
||||
.depthFormat = this->getDepthFormat(),
|
||||
.samplesCount = numSamples,
|
||||
.debugName = "Pipeline: drawGrid()",
|
||||
});
|
||||
}
|
||||
|
||||
const struct {
|
||||
mat4 mvp;
|
||||
vec4 camPos;
|
||||
vec4 origin;
|
||||
} pc = {
|
||||
.mvp = mvp,
|
||||
.camPos = vec4(camPos, 1.0f),
|
||||
.origin = vec4(origin, 1.0f),
|
||||
};
|
||||
|
||||
buf.cmdPushDebugGroupLabel("Grid", 0xff0000ff);
|
||||
buf.cmdBindRenderPipeline(gridPipeline);
|
||||
buf.cmdBindDepthState({ .compareOp = lvk::CompareOp_Less, .isDepthWriteEnabled = false });
|
||||
buf.cmdPushConstants(pc);
|
||||
buf.cmdDraw(6);
|
||||
buf.cmdPopDebugGroupLabel();
|
||||
}
|
||||
119
shared/VulkanApp.h
Normal file
119
shared/VulkanApp.h
Normal file
|
|
@ -0,0 +1,119 @@
|
|||
#pragma once
|
||||
|
||||
#include <lvk/HelpersImGui.h>
|
||||
#include <lvk/LVK.h>
|
||||
|
||||
#include <GLFW/glfw3.h>
|
||||
#include <implot/implot.h>
|
||||
|
||||
#include <glm/ext.hpp>
|
||||
#include <glm/glm.hpp>
|
||||
|
||||
#include <stb/stb_image.h>
|
||||
#include <stb/stb_image_write.h>
|
||||
|
||||
#include "shared/UtilsFPS.h"
|
||||
#include <shared/Bitmap.h>
|
||||
#include <shared/Camera.h>
|
||||
#include <shared/Graph.h>
|
||||
#include <shared/Utils.h>
|
||||
#include <shared/UtilsCubemap.h>
|
||||
|
||||
#include <functional>
|
||||
|
||||
using glm::mat3;
|
||||
using glm::mat4;
|
||||
using glm::vec2;
|
||||
using glm::vec3;
|
||||
using glm::vec4;
|
||||
|
||||
using DrawFrameFunc = std::function<void(uint32_t width, uint32_t height, float aspectRatio, float deltaSeconds)>;
|
||||
|
||||
struct GLTFMaterialIntro {
|
||||
std::string name;
|
||||
uint32_t materialMask;
|
||||
uint32_t currentMaterialMask;
|
||||
bool modified = false;
|
||||
};
|
||||
|
||||
struct GLTFIntrospective {
|
||||
std::vector<std::string> cameras;
|
||||
uint32_t activeCamera = ~0u;
|
||||
|
||||
std::vector<std::string> animations;
|
||||
std::vector<uint32_t> activeAnim;
|
||||
|
||||
std::vector<std::string> extensions;
|
||||
std::vector<uint32_t> activeExtension;
|
||||
|
||||
std::vector<GLTFMaterialIntro> materials;
|
||||
std::vector<bool> modifiedMaterial;
|
||||
|
||||
float blend = 0.5f;
|
||||
|
||||
bool showAnimations = false;
|
||||
bool showAnimationBlend = false;
|
||||
bool showCameras = false;
|
||||
bool showMaterials = true;
|
||||
};
|
||||
|
||||
struct VulkanAppConfig {
|
||||
vec3 initialCameraPos = vec3(0.0f, 0.0f, -2.5f);
|
||||
vec3 initialCameraTarget = vec3(0.0f, 0.0f, 0.0f);
|
||||
bool showGLTFInspector = false;
|
||||
};
|
||||
|
||||
class VulkanApp
|
||||
{
|
||||
public:
|
||||
explicit VulkanApp(const VulkanAppConfig& cfg = {});
|
||||
virtual ~VulkanApp();
|
||||
|
||||
virtual void run(DrawFrameFunc drawFrame);
|
||||
virtual void drawGrid(
|
||||
lvk::ICommandBuffer& buf, const mat4& proj, const vec3& origin = vec3(0.0f), uint32_t numSamples = 1,
|
||||
lvk::Format colorFormat = lvk::Format_Invalid);
|
||||
virtual void drawGrid(
|
||||
lvk::ICommandBuffer& buf, const mat4& mvp, const vec3& origin, const vec3& camPos, uint32_t numSamples = 1,
|
||||
lvk::Format colorFormat = lvk::Format_Invalid);
|
||||
virtual void drawFPS();
|
||||
virtual void drawMemo();
|
||||
virtual void drawGTFInspector(GLTFIntrospective& intro);
|
||||
virtual void drawGTFInspector_Animations(GLTFIntrospective& intro);
|
||||
virtual void drawGTFInspector_Materials(GLTFIntrospective& intro);
|
||||
virtual void drawGTFInspector_Cameras(GLTFIntrospective& intro);
|
||||
|
||||
lvk::Format getDepthFormat() const;
|
||||
lvk::TextureHandle getDepthTexture() const { return depthTexture_; }
|
||||
|
||||
void addMouseButtonCallback(GLFWmousebuttonfun cb) { callbacksMouseButton.push_back(cb); }
|
||||
void addKeyCallback(GLFWkeyfun cb) { callbacksKey.push_back(cb); }
|
||||
|
||||
public:
|
||||
GLFWwindow* window_ = nullptr;
|
||||
std::unique_ptr<lvk::IContext> ctx_;
|
||||
lvk::Holder<lvk::TextureHandle> depthTexture_;
|
||||
FramesPerSecondCounter fpsCounter_ = FramesPerSecondCounter(0.5f);
|
||||
std::unique_ptr<lvk::ImGuiRenderer> imgui_;
|
||||
ImPlotContext* implotCtx_ = nullptr;
|
||||
|
||||
const VulkanAppConfig cfg_ = {};
|
||||
|
||||
CameraPositioner_FirstPerson positioner_ = { cfg_.initialCameraPos, cfg_.initialCameraTarget, vec3(0.0f, 1.0f, 0.0f) };
|
||||
Camera camera_ = Camera(positioner_);
|
||||
|
||||
struct MouseState {
|
||||
vec2 pos = vec2(0.0f);
|
||||
bool pressedLeft = false;
|
||||
} mouseState_;
|
||||
|
||||
protected:
|
||||
lvk::Holder<lvk::ShaderModuleHandle> gridVert = {};
|
||||
lvk::Holder<lvk::ShaderModuleHandle> gridFrag = {};
|
||||
lvk::Holder<lvk::RenderPipelineHandle> gridPipeline = {};
|
||||
|
||||
uint32_t pipelineSamples = 1;
|
||||
|
||||
std::vector<GLFWmousebuttonfun> callbacksMouseButton;
|
||||
std::vector<GLFWkeyfun> callbacksKey;
|
||||
};
|
||||
Loading…
Add table
Add a link
Reference in a new issue