impr: Vastly improved 3D Visualizer (#1456)
Based entirely on @paxcut's amazing PR #1443 --------- Co-authored-by: paxcut <paxcut@outlook.com> Co-authored-by: paxcut <53811119+paxcut@users.noreply.github.com>
This commit is contained in:
parent
d5a40d46bc
commit
f9a9ed4846
File diff suppressed because it is too large
Load Diff
@ -10,6 +10,7 @@
|
||||
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
#include <hex/helpers/concepts.hpp>
|
||||
#include <hex/helpers/fs.hpp>
|
||||
|
||||
#include <wolv/utils/string.hpp>
|
||||
|
||||
@ -73,6 +74,7 @@ namespace ImGuiExt {
|
||||
Texture(const ImU8 *buffer, int size, int width = 0, int height = 0);
|
||||
Texture(std::span<const std::byte> bytes, int width = 0, int height = 0);
|
||||
explicit Texture(const char *path);
|
||||
explicit Texture(const std::fs::path &path);
|
||||
Texture(unsigned int texture, int width, int height);
|
||||
Texture(const Texture&) = delete;
|
||||
Texture(Texture&& other) noexcept;
|
||||
@ -86,10 +88,14 @@ namespace ImGuiExt {
|
||||
return this->m_textureId != nullptr;
|
||||
}
|
||||
|
||||
[[nodiscard]] constexpr operator ImTextureID() const noexcept {
|
||||
[[nodiscard]] operator ImTextureID() const noexcept {
|
||||
return this->m_textureId;
|
||||
}
|
||||
|
||||
[[nodiscard]] operator intptr_t() const noexcept {
|
||||
return reinterpret_cast<intptr_t>(this->m_textureId);
|
||||
}
|
||||
|
||||
[[nodiscard]] auto getSize() const noexcept {
|
||||
return ImVec2(this->m_width, this->m_height);
|
||||
}
|
||||
@ -122,7 +128,7 @@ namespace ImGuiExt {
|
||||
void Header(const char *label, bool firstEntry = false);
|
||||
void HeaderColored(const char *label, ImColor color, bool firstEntry);
|
||||
|
||||
bool InfoTooltip(const char *text = "");
|
||||
bool InfoTooltip(const char *text = "",bool = false);
|
||||
|
||||
bool TitleBarButton(const char *label, ImVec2 size_arg);
|
||||
bool ToolBarButton(const char *symbol, ImVec4 color);
|
||||
@ -258,6 +264,7 @@ namespace ImGuiExt {
|
||||
bool DimmedIconButton(const char *symbol, ImVec4 color, ImVec2 size = ImVec2(0, 0));
|
||||
bool DimmedButtonToggle(const char *icon, bool *v, ImVec2 size);
|
||||
bool DimmedIconToggle(const char *icon, bool *v);
|
||||
bool DimmedIconToggle(const char *iconOn, const char *iconOff, bool *v);
|
||||
|
||||
void TextOverlay(const char *text, ImVec2 pos);
|
||||
|
||||
@ -277,6 +284,11 @@ namespace ImGuiExt {
|
||||
if (ImGui::Button(textRight, ImVec2(width / 3, 0)))
|
||||
rightButtonCallback();
|
||||
}
|
||||
|
||||
bool VSliderAngle(const char* label, ImVec2& size, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags);
|
||||
|
||||
bool InputFilePicker(const char *label, std::fs::path &path, const std::vector<hex::fs::ItemFilter> &validExtensions);
|
||||
|
||||
template<typename T>
|
||||
constexpr ImGuiDataType getImGuiDataType() {
|
||||
if constexpr (std::same_as<T, u8>) return ImGuiDataType_U8;
|
||||
|
@ -6,9 +6,60 @@
|
||||
|
||||
#include <wolv/utils/guards.hpp>
|
||||
|
||||
#include <numbers>
|
||||
|
||||
namespace hex::gl {
|
||||
|
||||
Matrix<float,4,4> GetOrthographicMatrix( float viewWidth, float viewHeight, float nearVal,float farVal, bool actionType)
|
||||
{
|
||||
int sign =1;
|
||||
if (actionType)
|
||||
sign=-1;
|
||||
//float left = leftRight.x;
|
||||
//float right = leftRight.y;
|
||||
//float down = upDown.x;
|
||||
//float up = upDown.y;
|
||||
Matrix<float,4,4> result(0);
|
||||
//result.updateElement(0,0,sign /(right-left))
|
||||
result.updateElement(0,0,sign / viewWidth);
|
||||
//result.updateElement(0,3,sign * (right + left)/(right - left));
|
||||
//result.updateElement(1,1, sign /(up-down));
|
||||
result.updateElement(1,1, sign / viewHeight);
|
||||
//result.updateElement(1,3, sign * (up + down)/(up - down));
|
||||
|
||||
result.updateElement(2,2,-sign * 2/(farVal - nearVal));
|
||||
result.updateElement(3,2,-sign * (farVal + nearVal)/(farVal - nearVal));
|
||||
|
||||
result.updateElement(3,3,sign);
|
||||
return result;
|
||||
}
|
||||
|
||||
|
||||
Matrix<float,4,4> GetPerspectiveMatrix( float viewWidth, float viewHeight, float nearVal,float farVal, bool actionType)
|
||||
{
|
||||
int sign =1;
|
||||
if (actionType)
|
||||
sign=-1;
|
||||
//float left = leftRight.x;
|
||||
//float right = leftRight.y;
|
||||
//float down = upDown.x;
|
||||
//float up = upDown.y;
|
||||
//T aspect=(right-left)/(top-bottom);
|
||||
//T f = nearVal/top;
|
||||
Matrix<float,4,4> result(0);
|
||||
// T f = 1.0 / tan(fovy / 2.0); tan(fovy / 2.0) = top / near; fovy = 2 * atan2(top,near)
|
||||
|
||||
//result.updateElement(0,0,sign * nearVal/(right-left));
|
||||
//result.updateElement(1,1, sign * nearVal/(up-down));
|
||||
result.updateElement(0,0,sign * nearVal/viewWidth);
|
||||
result.updateElement(1,1, sign * nearVal/viewHeight);
|
||||
result.updateElement(2,2,-sign * (farVal + nearVal)/(farVal - nearVal));
|
||||
result.updateElement(3,2,-sign * 2*farVal*nearVal/(farVal - nearVal));
|
||||
result.updateElement(2,3,-sign);
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
Shader::Shader(std::string_view vertexSource, std::string_view fragmentSource) {
|
||||
auto vertexShader = glCreateShader(GL_VERTEX_SHADER);
|
||||
this->compile(vertexShader, vertexSource);
|
||||
@ -57,13 +108,14 @@ namespace hex::gl {
|
||||
glUseProgram(0);
|
||||
}
|
||||
|
||||
void Shader::setUniform(std::string_view name, const int &value) {
|
||||
glUniform1i(getUniformLocation(name), value);
|
||||
}
|
||||
|
||||
void Shader::setUniform(std::string_view name, const float &value) {
|
||||
glUniform1f(getUniformLocation(name), value);
|
||||
}
|
||||
|
||||
void Shader::setUniform(std::string_view name, const Vector<float, 3> &value) {
|
||||
glUniform3f(getUniformLocation(name), value[0], value[1], value[2]);
|
||||
}
|
||||
|
||||
GLint Shader::getUniformLocation(std::string_view name) {
|
||||
auto uniform = this->m_uniforms.find(name.data());
|
||||
@ -81,7 +133,7 @@ namespace hex::gl {
|
||||
return uniform->second;
|
||||
}
|
||||
|
||||
void Shader::compile(GLuint shader, std::string_view source) {
|
||||
void Shader::compile(GLuint shader, std::string_view source) const {
|
||||
auto sourcePtr = source.data();
|
||||
|
||||
glShaderSource(shader, 1, &sourcePtr, nullptr);
|
||||
@ -98,7 +150,7 @@ namespace hex::gl {
|
||||
|
||||
|
||||
template<typename T>
|
||||
Buffer<T>::Buffer(BufferType type, std::span<T> data) : m_size(data.size()), m_type(GLuint(type)) {
|
||||
Buffer<T>::Buffer(BufferType type, std::span<const T> data) : m_size(data.size()), m_type(GLuint(type)) {
|
||||
glGenBuffers(1, &this->m_buffer);
|
||||
glBindBuffer(this->m_type, this->m_buffer);
|
||||
glBufferData(this->m_type, data.size_bytes(), data.data(), GL_STATIC_DRAW);
|
||||
@ -143,20 +195,28 @@ namespace hex::gl {
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Buffer<T>::draw() const {
|
||||
void Buffer<T>::draw(unsigned primitive) const {
|
||||
switch (this->m_type) {
|
||||
case GL_ARRAY_BUFFER:
|
||||
glDrawArrays(GL_TRIANGLES, 0, this->m_size);
|
||||
glDrawArrays(primitive, 0, this->m_size);
|
||||
break;
|
||||
case GL_ELEMENT_ARRAY_BUFFER:
|
||||
glDrawElements(GL_TRIANGLES, this->m_size, impl::getType<T>(), nullptr);
|
||||
break;
|
||||
glDrawElements(primitive, this->m_size, impl::getType<T>(), nullptr);
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
template<typename T>
|
||||
void Buffer<T>::update(std::span<const T> data) {
|
||||
glBindBuffer(this->m_type, this->m_buffer);
|
||||
glBufferSubData(this->m_type, 0, data.size_bytes(), data.data());
|
||||
glBindBuffer(this->m_type, 0);
|
||||
}
|
||||
|
||||
template class Buffer<float>;
|
||||
template class Buffer<u32>;
|
||||
|
||||
template class Buffer<u16>;
|
||||
template class Buffer<u8>;
|
||||
|
||||
VertexArray::VertexArray() {
|
||||
glGenVertexArrays(1, &this->m_array);
|
||||
@ -244,14 +304,13 @@ namespace hex::gl {
|
||||
return copy;
|
||||
}
|
||||
|
||||
|
||||
FrameBuffer::FrameBuffer() {
|
||||
FrameBuffer::FrameBuffer(u32 width, u32 height) {
|
||||
glGenFramebuffers(1, &this->m_frameBuffer);
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, this->m_frameBuffer);
|
||||
|
||||
glGenRenderbuffers(1, &this->m_renderBuffer);
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, this->m_renderBuffer);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, 1280, 720);
|
||||
glRenderbufferStorage(GL_RENDERBUFFER, GL_DEPTH24_STENCIL8, width, height);
|
||||
glFramebufferRenderbuffer(GL_FRAMEBUFFER, GL_DEPTH_STENCIL_ATTACHMENT, GL_RENDERBUFFER, this->m_renderBuffer);
|
||||
|
||||
glBindRenderbuffer(GL_RENDERBUFFER, 0);
|
||||
@ -295,4 +354,335 @@ namespace hex::gl {
|
||||
glBindFramebuffer(GL_FRAMEBUFFER, 0);
|
||||
}
|
||||
|
||||
}
|
||||
AxesVectors::AxesVectors() {
|
||||
m_vertices.resize(36);
|
||||
m_colors.resize(48);
|
||||
m_indices.resize(18);
|
||||
|
||||
// Vertices are x,y,z. Colors are RGBA. Indices are for the ends of each segment
|
||||
// Entries with value zero are unneeded but kept to help keep track of location
|
||||
// x-axis
|
||||
//vertices[0]=0.0F; vertices[1]= 0.0F vertices[2] = 0.0F; // shaft base
|
||||
m_vertices[3] = 1.0F;//vertices[4]= 0.0F vertices[5] = 0.0F; // shaft tip
|
||||
m_vertices[6] = 0.9F; m_vertices[8] = 0.05F; // arrow base
|
||||
m_vertices[9] = 0.9F; m_vertices[11]=-0.05F; // arrow base
|
||||
// y-axis
|
||||
//vertices[12]=0.0F; vertices[13] = 0.0F; vertices[14]=0.0F;// shaft base
|
||||
m_vertices[16] = 1.0F;//vertices[17]=0.0F;// shaft tip
|
||||
m_vertices[18] = 0.05F; m_vertices[19] = 0.9F;//vertices[20]=0.0F;// arrow base
|
||||
m_vertices[21] =-0.05F; m_vertices[22] = 0.9F;//vertices[23]=0.0F;// arrow base
|
||||
// z-axis
|
||||
//vertices[24]=0.0F; vertices[25]=0.0F vertices[26] = 0.0F; // shaft base
|
||||
m_vertices[29] = 1.0F; // shaft tip
|
||||
m_vertices[30] = 0.05F; m_vertices[32] = 0.9F; // arrow base
|
||||
m_vertices[33] =-0.05F; m_vertices[35] = 0.9F; // arrow base
|
||||
// x-axis colors
|
||||
m_colors[0] = 0.7F; m_colors[3] = 1.0F;
|
||||
m_colors[4] = 0.7F; m_colors[7] = 1.0F;
|
||||
m_colors[8] = 0.7F; m_colors[11] = 1.0F;
|
||||
m_colors[12] = 0.7F; m_colors[15] = 1.0F;
|
||||
// y-axis colors
|
||||
m_colors[17] = 0.7F; m_colors[19] = 1.0F;
|
||||
m_colors[21] = 0.7F; m_colors[23] = 1.0F;
|
||||
m_colors[25] = 0.7F; m_colors[27] = 1.0F;
|
||||
m_colors[29] = 0.7F; m_colors[31] = 1.0F;
|
||||
// z-axis colors
|
||||
m_colors[34] = 0.7F; m_colors[35] = 1.0F;
|
||||
m_colors[38] = 0.7F; m_colors[39] = 1.0F;
|
||||
m_colors[42] = 0.7F; m_colors[43] = 1.0F;
|
||||
m_colors[46] = 0.7F; m_colors[47] = 1.0F;
|
||||
// indices for x
|
||||
m_indices[0] = 0; m_indices[1] = 1;
|
||||
m_indices[2] = 2; m_indices[3] = 1;
|
||||
m_indices[4] = 3; m_indices[5] = 1;
|
||||
// indices for y
|
||||
m_indices[6] = 4; m_indices[7] = 5;
|
||||
m_indices[8] = 6; m_indices[9] = 5;
|
||||
m_indices[10] = 7; m_indices[11] = 5;
|
||||
// indices for z
|
||||
m_indices[12] = 8; m_indices[13] = 9;
|
||||
m_indices[14] = 10; m_indices[15] = 9;
|
||||
m_indices[16] = 11; m_indices[17] = 9;
|
||||
}
|
||||
|
||||
AxesBuffers::AxesBuffers(const VertexArray& axesVertexArray, const AxesVectors &axesVectors) {
|
||||
m_vertices = {};
|
||||
m_colors = {};
|
||||
m_indices = {};
|
||||
|
||||
axesVertexArray.bind();
|
||||
|
||||
m_vertices = gl::Buffer<float>(gl::BufferType::Vertex, axesVectors.getVertices());
|
||||
m_colors = gl::Buffer<float>(gl::BufferType::Vertex, axesVectors.getColors());
|
||||
m_indices = gl::Buffer<u8>(gl::BufferType::Index, axesVectors.getIndices());
|
||||
|
||||
axesVertexArray.addBuffer(0, m_vertices);
|
||||
axesVertexArray.addBuffer(1, m_colors, 4);
|
||||
|
||||
m_vertices.unbind();
|
||||
m_colors.unbind();
|
||||
m_indices.unbind();
|
||||
axesVertexArray.unbind();
|
||||
}
|
||||
|
||||
|
||||
GridVectors::GridVectors(int sliceCount) {
|
||||
m_slices = sliceCount;
|
||||
m_vertices.resize((m_slices + 1) * (m_slices + 1) * 3);
|
||||
m_colors.resize((m_slices + 1) * (m_slices + 1) * 4);
|
||||
m_indices.resize(m_slices * m_slices * 6 + m_slices * 2);
|
||||
int k = 0;
|
||||
int l = 0;
|
||||
for (u32 j = 0; j <= m_slices; ++j) {
|
||||
float z = 2.0f * float(j) / float(m_slices) - 1.0f;
|
||||
for (u32 i = 0; i <= m_slices; ++i) {
|
||||
m_vertices[k ] = 2.0f * float(i) / float(m_slices) - 1.0f;
|
||||
m_vertices[k + 1] = 0.0f;
|
||||
m_vertices[k + 2] = z;
|
||||
k += 3;
|
||||
m_colors[l ] = 0.5f;
|
||||
m_colors[l + 1] = 0.5f;
|
||||
m_colors[l + 2] = 0.5f;
|
||||
m_colors[l + 3] = 0.3f;
|
||||
l += 4;
|
||||
}
|
||||
}
|
||||
k = 0;
|
||||
for (u32 j = 0; j < m_slices; ++j) {
|
||||
int row1 = j * (m_slices + 1);
|
||||
int row2 = (j + 1) * (m_slices + 1);
|
||||
|
||||
for (u32 i = 0; i < m_slices; ++i) {
|
||||
m_indices[k ] = row1 + i;
|
||||
m_indices[k + 1] = row1 + i + 1;
|
||||
m_indices[k + 2] = row1 + i + 1;
|
||||
m_indices[k + 3] = row2 + i + 1;
|
||||
m_indices[k + 4] = row2 + i + 1;
|
||||
m_indices[k + 5] = row2 + i;
|
||||
k += 6;
|
||||
|
||||
if (i == 0) {
|
||||
m_indices[k ] = row2 + i;
|
||||
m_indices[k + 1] = row1 + i;
|
||||
k += 2;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
GridBuffers::GridBuffers(const VertexArray& gridVertexArray, const GridVectors &gridVectors) {
|
||||
m_vertices = {};
|
||||
m_colors = {};
|
||||
m_indices = {};
|
||||
|
||||
gridVertexArray.bind();
|
||||
|
||||
m_vertices = gl::Buffer<float>(gl::BufferType::Vertex, gridVectors.getVertices());
|
||||
m_indices = gl::Buffer<u8>(gl::BufferType::Index, gridVectors.getIndices());
|
||||
m_colors = gl::Buffer<float>(gl::BufferType::Vertex, gridVectors.getColors());
|
||||
|
||||
gridVertexArray.addBuffer(0, m_vertices);
|
||||
gridVertexArray.addBuffer(1, m_colors,4);
|
||||
|
||||
m_vertices.unbind();
|
||||
m_colors.unbind();
|
||||
m_indices.unbind();
|
||||
gridVertexArray.unbind();
|
||||
}
|
||||
|
||||
hex::gl::LightSourceVectors::LightSourceVectors(int res) {
|
||||
m_resolution = res;
|
||||
auto res_sq = m_resolution * m_resolution;
|
||||
m_radius = 0.05f;
|
||||
m_vertices.resize((res_sq + 2) * 3);
|
||||
m_normals.resize((res_sq + 2) * 3);
|
||||
m_colors.resize((res_sq + 2) * 4);
|
||||
m_indices.resize(res_sq * 6);
|
||||
|
||||
|
||||
constexpr auto TwoPi = std::numbers::pi_v<float> * 2.0F;
|
||||
constexpr auto HalfPi = std::numbers::pi_v<float> / 2.0F;
|
||||
const auto dv = TwoPi / m_resolution;
|
||||
const auto du = std::numbers::pi_v<float> / (m_resolution + 1);
|
||||
|
||||
m_normals[0] = 0;
|
||||
m_normals[1] = 0;
|
||||
m_normals[2] = 1;
|
||||
|
||||
m_vertices[0] = 0;
|
||||
m_vertices[1] = 0;
|
||||
m_vertices[2] = m_radius;
|
||||
|
||||
m_colors[0] = 1.0;
|
||||
m_colors[1] = 1.0;
|
||||
m_colors[2] = 1.0;
|
||||
m_colors[3] = 1.0;
|
||||
|
||||
// Vertical: pi/2 to -pi/2
|
||||
for (int i = 0; i < m_resolution; i += 1) {
|
||||
float u = HalfPi - (i + 1) * du;
|
||||
float z = std::sin(u);
|
||||
float xy = std::cos(u);
|
||||
|
||||
// Horizontal: 0 to 2pi
|
||||
for (int j = 0; j < m_resolution; j += 1) {
|
||||
float v = j * dv;
|
||||
float x = xy * std::cos(v);
|
||||
float y = xy * std::sin(v);
|
||||
|
||||
i32 n = (i * m_resolution + j + 1) * 3;
|
||||
m_normals[n] = x;
|
||||
m_normals[n + 1] = y;
|
||||
m_normals[n + 2] = z;
|
||||
|
||||
m_vertices[n] = m_radius * x;
|
||||
m_vertices[n + 1] = m_radius * y;
|
||||
m_vertices[n + 2] = m_radius * z;
|
||||
|
||||
n = (i * m_resolution + j + 1) * 4;
|
||||
m_colors[n] = 1.0f;
|
||||
m_colors[n + 1] = 1.0f;
|
||||
m_colors[n + 2] = 1.0f;
|
||||
m_colors[n + 3] = 1.0f;
|
||||
}
|
||||
}
|
||||
|
||||
i32 n = ((res_sq + 1) * 3);
|
||||
m_normals[n ] = 0;
|
||||
m_normals[n + 1] = 0;
|
||||
m_normals[n + 2] = -1;
|
||||
|
||||
m_vertices[n ] = 0;
|
||||
m_vertices[n + 1] = 0;
|
||||
m_vertices[n + 2] = -m_radius;
|
||||
|
||||
n = ((res_sq + 1) * 4);
|
||||
m_colors[n ] = 1.0;
|
||||
m_colors[n + 1] = 1.0;
|
||||
m_colors[n + 2] = 1.0;
|
||||
m_colors[n + 3] = 1.0;
|
||||
|
||||
// that was the easy part, indices are a bit more complicated
|
||||
// and may need some explaining. The RxR grid slices the globe
|
||||
// into longitudes which are the vertical slices and latitudes
|
||||
// which are the horizontal slices. The latitudes are all full
|
||||
// circles except for the poles, so we don't count them as part
|
||||
// of the grid. That means that there are R+2 latitudes and R
|
||||
// longitudes.Between consecutive latitudes we have 2*R triangles.
|
||||
// Since we have R true latitudes there are R-1 spaces between them so
|
||||
// between the top and the bottom we have 2*R*(R-1) triangles.
|
||||
// the top and bottom have R triangles each, so we have a total of
|
||||
// 2*R*(R-1) + 2*R = 2*R*R triangles. Each triangle has 3 vertices,
|
||||
// so we have 6*R*R indices.
|
||||
|
||||
// The North Pole is index 0 and the South Pole is index 6*res*res -1
|
||||
// The first row of vertices is 1 to res, the second row is res+1 to 2*res etc.
|
||||
|
||||
// First, the North Pole
|
||||
for (int i = 0; i < m_resolution; i += 1) {
|
||||
m_indices[i * 3] = 0;
|
||||
m_indices[i * 3 + 1] = i + 1;
|
||||
if (i == m_resolution - 1)
|
||||
m_indices[i * 3 + 2] = 1;
|
||||
else
|
||||
m_indices[i * 3 + 2] = (i + 2);
|
||||
}
|
||||
// Now the spaces between true latitudes
|
||||
for (int i = 0; i < m_resolution - 1; i += 1) {
|
||||
// k is the index of the first vertex of the i-th latitude
|
||||
i32 k = i * m_resolution + 1;
|
||||
// When we go a full circle we need to connect the last vertex to the first, so
|
||||
// we do R-1 first because their indices can be computed easily
|
||||
for (int j = 0; j < m_resolution - 1; j += 1) {
|
||||
// We store the indices of the array where the vertices were store
|
||||
// in the triplets that make the triangles. These triplets are stored in
|
||||
// an array that has indices itself which can be confusing.
|
||||
// l keeps track of the indices of the array that stores the triplets
|
||||
// each i brings 6R and each j 6. 3R from the North Pole.
|
||||
i32 l = (i * m_resolution + j) * 6 + 3 * m_resolution;
|
||||
|
||||
m_indices[l ] = k + j;
|
||||
m_indices[l + 1] = k + j + m_resolution + 1;
|
||||
m_indices[l + 2] = k + j + 1;
|
||||
|
||||
m_indices[l + 3] = k + j;
|
||||
m_indices[l + 4] = k + j + m_resolution;
|
||||
m_indices[l + 5] = k + j + m_resolution + 1;
|
||||
}
|
||||
// Now the last vertex of the i-th latitude is connected to the first
|
||||
i32 l = (( i + 1) * m_resolution - 1) * 6 + 3 * m_resolution;
|
||||
|
||||
m_indices[l ] = k + m_resolution - 1;
|
||||
m_indices[l + 1] = k + m_resolution;
|
||||
m_indices[l + 2] = k;
|
||||
|
||||
m_indices[l + 3] = k + m_resolution - 1;
|
||||
m_indices[l + 4] = k + 2 * m_resolution - 1;
|
||||
m_indices[l + 5] = k + m_resolution;
|
||||
|
||||
}
|
||||
// Now the South Pole
|
||||
i32 k = (m_resolution-1) * m_resolution + 1;
|
||||
i32 l = 3 * m_resolution * ( 2 * m_resolution - 1);
|
||||
for (int i = 0; i < m_resolution; i += 1) {
|
||||
if (i == m_resolution -1)
|
||||
m_indices[l + i * 3] = k;
|
||||
else
|
||||
m_indices[l + i * 3] = k + i + 1;
|
||||
|
||||
m_indices[l + i * 3 + 1] = k + i;
|
||||
m_indices[l + i * 3 + 2] = k + m_resolution;
|
||||
}
|
||||
}
|
||||
|
||||
void LightSourceVectors::moveTo(const Vector<float, 3> &positionVector) {
|
||||
auto vertexCount = m_vertices.size();
|
||||
|
||||
for (unsigned k = 0; k < vertexCount; k += 3) {
|
||||
m_vertices[k ] = m_radius * m_normals[k ] + positionVector[0];
|
||||
m_vertices[k + 1] = m_radius * m_normals[k + 1] + positionVector[1];
|
||||
m_vertices[k + 2] = m_radius * m_normals[k + 2] + positionVector[2];
|
||||
}
|
||||
}
|
||||
|
||||
LightSourceBuffers::LightSourceBuffers(const VertexArray &sourceVertexArray, const LightSourceVectors &sourceVectors) {
|
||||
sourceVertexArray.bind();
|
||||
|
||||
m_vertices = gl::Buffer<float>(gl::BufferType::Vertex, sourceVectors.getVertices());
|
||||
m_indices = gl::Buffer<u16>(gl::BufferType::Index, sourceVectors.getIndices());
|
||||
m_normals = gl::Buffer<float>(gl::BufferType::Vertex, sourceVectors.getNormals());
|
||||
m_colors = gl::Buffer<float>(gl::BufferType::Vertex, sourceVectors.getColors());
|
||||
|
||||
sourceVertexArray.addBuffer(0, m_vertices);
|
||||
sourceVertexArray.addBuffer(1, m_normals);
|
||||
sourceVertexArray.addBuffer(2, m_colors, 4);
|
||||
|
||||
m_vertices.unbind();
|
||||
m_normals.unbind();
|
||||
m_colors.unbind();
|
||||
m_indices.unbind();
|
||||
sourceVertexArray.unbind();
|
||||
}
|
||||
|
||||
void LightSourceBuffers::moveVertices(const VertexArray& sourceVertexArray, const LightSourceVectors& sourceVectors) {
|
||||
sourceVertexArray.bind();
|
||||
|
||||
m_vertices.update(sourceVectors.getVertices());
|
||||
sourceVertexArray.addBuffer(0, m_vertices);
|
||||
|
||||
sourceVertexArray.unbind();
|
||||
}
|
||||
|
||||
void LightSourceBuffers::updateColors(const VertexArray& sourceVertexArray, const LightSourceVectors& sourceVectors) {
|
||||
sourceVertexArray.bind();
|
||||
|
||||
m_colors.update(sourceVectors.getColors());
|
||||
sourceVertexArray.addBuffer(2, m_colors, 4);
|
||||
|
||||
sourceVertexArray.unbind();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
|
||||
|
@ -56,6 +56,8 @@ namespace ImGuiExt {
|
||||
|
||||
Texture::Texture(std::span<const std::byte> bytes, int width, int height) : Texture(reinterpret_cast<const ImU8*>(bytes.data()), bytes.size(), width, height) { }
|
||||
|
||||
Texture::Texture(const std::fs::path &path) : Texture(reinterpret_cast<const char *>(path.u8string().c_str())) { }
|
||||
|
||||
Texture::Texture(const char *path) {
|
||||
unsigned char *imageData = stbi_load(path, &this->m_width, &this->m_height, nullptr, 4);
|
||||
if (imageData == nullptr)
|
||||
@ -382,7 +384,7 @@ namespace ImGuiExt {
|
||||
Separator();
|
||||
}
|
||||
|
||||
bool InfoTooltip(const char *text) {
|
||||
bool InfoTooltip(const char *text, bool isSeparator) {
|
||||
static double lastMoveTime;
|
||||
static ImGuiID lastHoveredID;
|
||||
|
||||
@ -393,7 +395,10 @@ namespace ImGuiExt {
|
||||
if (IsItemHovered() && (currTime - lastMoveTime) >= 0.5 && hoveredID == lastHoveredID) {
|
||||
if (!std::string_view(text).empty()) {
|
||||
BeginTooltip();
|
||||
TextUnformatted(text);
|
||||
if (isSeparator)
|
||||
SeparatorText(text);
|
||||
else
|
||||
TextUnformatted(text);
|
||||
EndTooltip();
|
||||
}
|
||||
|
||||
@ -893,6 +898,26 @@ namespace ImGuiExt {
|
||||
return toggled;
|
||||
}
|
||||
|
||||
bool DimmedIconToggle(const char *iconOn, const char *iconOff, bool *v) {
|
||||
bool pushed = false;
|
||||
bool toggled = false;
|
||||
|
||||
if (*v) {
|
||||
PushStyleColor(ImGuiCol_Border, GetStyleColorVec4(ImGuiCol_ButtonActive));
|
||||
pushed = true;
|
||||
}
|
||||
|
||||
if (DimmedIconButton(*v ? iconOn : iconOff, GetStyleColorVec4(ImGuiCol_Text))) {
|
||||
*v = !*v;
|
||||
toggled = true;
|
||||
}
|
||||
|
||||
if (pushed)
|
||||
PopStyleColor();
|
||||
|
||||
return toggled;
|
||||
}
|
||||
|
||||
void TextOverlay(const char *text, ImVec2 pos) {
|
||||
const auto textSize = CalcTextSize(text);
|
||||
const auto textPos = pos - textSize / 2;
|
||||
@ -937,6 +962,47 @@ namespace ImGuiExt {
|
||||
ImGui::EndChild();
|
||||
}
|
||||
|
||||
bool VSliderAngle(const char* label, ImVec2& size, float* v_rad, float v_degrees_min, float v_degrees_max, const char* format, ImGuiSliderFlags flags) {
|
||||
if (format == NULL)
|
||||
format = "%.0f deg";
|
||||
float v_deg = (*v_rad) * 360.0f / (2 * IM_PI);
|
||||
bool value_changed = ImGui::VSliderFloat(label, size, &v_deg, v_degrees_min, v_degrees_max, format, flags);
|
||||
*v_rad = v_deg * (2 * IM_PI) / 360.0f;
|
||||
return value_changed;
|
||||
}
|
||||
|
||||
bool InputFilePicker(const char *label, std::fs::path &path, const std::vector<hex::fs::ItemFilter> &validExtensions) {
|
||||
bool picked = false;
|
||||
|
||||
ImGui::PushID(label);
|
||||
|
||||
const auto buttonSize = ImGui::CalcTextSize(ICON_VS_FOLDER) + ImGui::GetStyle().FramePadding * 2;
|
||||
ImGui::PushItemWidth(ImGui::CalcItemWidth() - buttonSize.x - ImGui::GetStyle().FramePadding.x);
|
||||
std::string string = wolv::util::toUTF8String(path);
|
||||
if (ImGui::InputText("##pathInput", string, ImGuiInputTextFlags_AutoSelectAll)) {
|
||||
path = std::u8string(string.begin(), string.end());
|
||||
picked = true;
|
||||
}
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGui::Button(ICON_VS_FOLDER, buttonSize)) {
|
||||
hex::fs::openFileBrowser(hex::fs::DialogMode::Open, validExtensions, [&](const std::fs::path &pickedPath) {
|
||||
path = pickedPath;
|
||||
picked = true;
|
||||
});
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
ImGui::TextUnformatted(label);
|
||||
|
||||
ImGui::PopID();
|
||||
|
||||
return picked;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace ImGui {
|
||||
|
19
lib/third_party/imgui/fonts/include/fonts/blendericons_font.h
vendored
Normal file
19
lib/third_party/imgui/fonts/include/fonts/blendericons_font.h
vendored
Normal file
@ -0,0 +1,19 @@
|
||||
#pragma once
|
||||
|
||||
// Generated: 2023-11-12 08:44:48.641532
|
||||
|
||||
extern const unsigned int blendericons_compressed_size;
|
||||
extern const unsigned int blendericons_compressed_data[];
|
||||
|
||||
|
||||
#define ICON_MIN_BI 0xea00
|
||||
#define ICON_MAX_BI 0xea08
|
||||
#define ICON_BI_CUBE "\xee\xa8\x80" //< U+ea00
|
||||
#define ICON_BI_EMPTY_ARROWS "\xee\xa8\x81" //< U+ea01
|
||||
#define ICON_BI_GRID "\xee\xa8\x82" //< U+ea02
|
||||
#define ICON_BI_MESH_GRID "\xee\xa8\x83" //< U+ea03
|
||||
#define ICON_BI_MOD_SOLIDIFY "\xee\xa8\x84" //< U+ea04
|
||||
#define ICON_BI_ORIENTATION_GLOBAL "\xee\xa8\x85" //< U+ea05
|
||||
#define ICON_BI_ORIENTATION_LOCAL "\xee\xa8\x86" //< U+ea06
|
||||
#define ICON_BI_VIEW_ORTHO "\xee\xa8\x87" //< U+ea07
|
||||
#define ICON_BI_VIEW_PERSPECTIVE "\xee\xa8\x88" //< U+ea08
|
@ -1,4 +1,5 @@
|
||||
#include "init/tasks.hpp"
|
||||
#include "misc/freetype/imgui_freetype.h"
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
|
@ -80,6 +80,17 @@ add_imhex_plugin(
|
||||
source/content/tools/tcp_client_server.cpp
|
||||
source/content/tools/wiki_explainer.cpp
|
||||
|
||||
source/content/pl_visualizers/line_plot.cpp
|
||||
source/content/pl_visualizers/scatter_plot.cpp
|
||||
source/content/pl_visualizers/image.cpp
|
||||
source/content/pl_visualizers/disassembler.cpp
|
||||
source/content/pl_visualizers/3d_model.cpp
|
||||
source/content/pl_visualizers/sound.cpp
|
||||
source/content/pl_visualizers/chunk_entropy.cpp
|
||||
source/content/pl_visualizers/hex_viewer.cpp
|
||||
source/content/pl_visualizers/coordinates.cpp
|
||||
source/content/pl_visualizers/timestamp.cpp
|
||||
|
||||
source/content/views/view_hex_editor.cpp
|
||||
source/content/views/view_pattern_editor.cpp
|
||||
source/content/views/view_pattern_data.cpp
|
||||
|
@ -0,0 +1,20 @@
|
||||
#pragma once
|
||||
|
||||
#include <pl/pattern_language.hpp>
|
||||
#include <pl/patterns/pattern.hpp>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> patternToArray(pl::ptrn::Pattern *pattern){
|
||||
const auto bytes = pattern->getBytes();
|
||||
|
||||
std::vector<T> result;
|
||||
result.resize(bytes.size() / sizeof(T));
|
||||
for (size_t i = 0; i < result.size(); i++)
|
||||
std::memcpy(&result[i], &bytes[i * sizeof(T)], sizeof(T));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
BIN
plugins/builtin/romfs/fonts/blendericons.ttf
Normal file
BIN
plugins/builtin/romfs/fonts/blendericons.ttf
Normal file
Binary file not shown.
@ -455,8 +455,14 @@
|
||||
"hex.builtin.pattern_drawer.var_name": "Name",
|
||||
"hex.builtin.pattern_drawer.visualizer.unknown": "Unknown visualizer",
|
||||
"hex.builtin.pattern_drawer.visualizer.invalid_parameter_count": "Invalid parameter count",
|
||||
"hex.builtin.pl_visualizer.3d.rotation": "Rotation",
|
||||
"hex.builtin.pl_visualizer.3d.scale": "Scale",
|
||||
"hex.builtin.pl_visualizer.3d.light_position": "Light Position",
|
||||
"hex.builtin.pl_visualizer.3d.ambient_brightness": "Ambient Brightness",
|
||||
"hex.builtin.pl_visualizer.3d.diffuse_brightness": "Diffuse Brightness",
|
||||
"hex.builtin.pl_visualizer.3d.specular_brightness": "Specular Brightness",
|
||||
"hex.builtin.pl_visualizer.3d.object_reflectiveness": "Object Reflectiveness",
|
||||
"hex.builtin.pl_visualizer.3d.light_color": "Light Color",
|
||||
"hex.builtin.pl_visualizer.3d.more_settings": "More Settings",
|
||||
"hex.builtin.pl_visualizer.3d.texture_file": "Texture File Path",
|
||||
"hex.builtin.pl_visualizer.coordinates.latitude": "Latitude",
|
||||
"hex.builtin.pl_visualizer.coordinates.longitude": "Longitude",
|
||||
"hex.builtin.pl_visualizer.coordinates.query": "Find address",
|
||||
|
@ -1,12 +1,40 @@
|
||||
#version 330 core
|
||||
in vec3 normal;
|
||||
out vec4 color;
|
||||
|
||||
in VertexData {
|
||||
vec3 normal;
|
||||
vec4 fragColor;
|
||||
vec2 texCoord;
|
||||
vec3 lightPosition;
|
||||
vec3 fragPosition;
|
||||
vec4 lightBrightness;
|
||||
vec3 lightColor;
|
||||
} vertexData;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
uniform sampler2D modelTexture;
|
||||
|
||||
void main() {
|
||||
vec3 norm = normalize(normal);
|
||||
vec3 lightDir = normalize(vec3(0, 0, -1));
|
||||
float diff = max(dot(norm, lightDir), 0.0);
|
||||
vec3 diffuse = diff * vec3(1.0, 1.0, 1.0);
|
||||
vec3 ambientLightColor = vec3(1.0, 1.0, 1.0);
|
||||
|
||||
// Ambient lighting
|
||||
vec3 ambient = vertexData.lightBrightness.x * ambientLightColor;
|
||||
|
||||
// Diffuse lighting
|
||||
vec3 normalVector = normalize(vertexData.normal);
|
||||
|
||||
vec3 lightDirection = normalize(vertexData.lightPosition - vertexData.fragPosition);
|
||||
float diffuse = vertexData.lightBrightness.y * max(dot(normalVector, lightDirection), 0.0);
|
||||
|
||||
// Specular lighting
|
||||
vec3 viewDirection = normalize(-vertexData.fragPosition);
|
||||
vec3 reflectDirection = normalize(-reflect(lightDirection, normalVector));
|
||||
float reflectionIntensity = pow(max(dot(viewDirection, reflectDirection), 0.0), vertexData.lightBrightness.w);
|
||||
float specular = vertexData.lightBrightness.z * reflectionIntensity;
|
||||
|
||||
float dst = distance(vertexData.lightPosition, vertexData.fragPosition);
|
||||
float attn = 1./(1.0f + 0.1f*dst + 0.01f*dst*dst) ;
|
||||
vec3 color = ((diffuse + specular)*attn + ambient) * vertexData.lightColor;
|
||||
outColor = (texture(modelTexture, vertexData.texCoord) + vertexData.fragColor) * vec4(color, 1.0);
|
||||
}
|
||||
|
||||
color = vec4(1.0f, 0.5f, 0.2f, 1.0f) * vec4(diffuse, 1.0) + 0.1;
|
||||
}
|
22
plugins/builtin/romfs/shaders/default/lightFragment.glsl
Normal file
22
plugins/builtin/romfs/shaders/default/lightFragment.glsl
Normal file
@ -0,0 +1,22 @@
|
||||
#version 330
|
||||
|
||||
in VertexData {
|
||||
vec3 normal;
|
||||
vec4 color;
|
||||
vec3 fragPosition;
|
||||
} vertexData;
|
||||
|
||||
out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
|
||||
vec3 nLight = normalize(-vertexData.fragPosition);
|
||||
vec3 nNormal = normalize(vertexData.normal);
|
||||
|
||||
float dotLN = dot(nLight, nNormal);
|
||||
|
||||
float diffuse = dotLN * 0.5;
|
||||
|
||||
vec3 color = (diffuse+0.7)*vertexData.color.xyz;
|
||||
outColor = vec4(color, 1.0f);
|
||||
}
|
27
plugins/builtin/romfs/shaders/default/lightVertex.glsl
Normal file
27
plugins/builtin/romfs/shaders/default/lightVertex.glsl
Normal file
@ -0,0 +1,27 @@
|
||||
#version 330
|
||||
|
||||
uniform mat4 modelMatrix;
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
|
||||
layout (location = 0) in vec3 in_Position;
|
||||
layout (location = 1) in vec3 in_Normal;
|
||||
layout (location = 2) in vec4 in_Color;
|
||||
|
||||
|
||||
|
||||
out VertexData {
|
||||
vec3 normal;
|
||||
vec4 color;
|
||||
vec3 fragPosition;
|
||||
} vertexData;
|
||||
|
||||
|
||||
void main() {
|
||||
vertexData.normal = (modelMatrix * vec4(in_Normal,0)).xyz;
|
||||
//vertexData.normal = mat3(transpose(inverse(modelMatrix))) * in_Normal;
|
||||
//vertexData.fragPosition = (viewMatrix * modelMatrix * vec4(in_Position, 1.0)).xyz;
|
||||
vertexData.fragPosition = (modelMatrix * vec4(in_Position, 1.0)).xyz;
|
||||
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(in_Position, 1.0);
|
||||
vertexData.color = in_Color;
|
||||
}
|
8
plugins/builtin/romfs/shaders/default/lineFragment.glsl
Normal file
8
plugins/builtin/romfs/shaders/default/lineFragment.glsl
Normal file
@ -0,0 +1,8 @@
|
||||
#version 330 core
|
||||
|
||||
in vec4 fragColor;
|
||||
out vec4 outColor;
|
||||
|
||||
void main() {
|
||||
outColor = fragColor;
|
||||
}
|
15
plugins/builtin/romfs/shaders/default/lineVertex.glsl
Normal file
15
plugins/builtin/romfs/shaders/default/lineVertex.glsl
Normal file
@ -0,0 +1,15 @@
|
||||
#version 330 core
|
||||
|
||||
layout (location = 0) in vec3 in_Position;
|
||||
layout (location = 1) in vec4 in_Color;
|
||||
|
||||
uniform mat4 modelMatrix;
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
|
||||
out vec4 fragColor;
|
||||
|
||||
void main() {
|
||||
gl_Position = projectionMatrix * viewMatrix * modelMatrix * vec4(in_Position, 1.0);
|
||||
fragColor = in_Color;
|
||||
}
|
@ -1,36 +1,39 @@
|
||||
#version 330 core
|
||||
layout (location = 0) in vec3 in_Position;
|
||||
layout (location = 1) in vec3 in_Normal;
|
||||
layout (location = 1) in vec4 in_Color;
|
||||
layout (location = 2) in vec3 in_Normal;
|
||||
layout (location = 3) in vec2 in_TexCoord;
|
||||
|
||||
/*uniform float time;*/
|
||||
uniform float scale;
|
||||
uniform vec3 rotation;
|
||||
uniform vec3 translation;
|
||||
uniform mat4 modelScale;
|
||||
|
||||
out vec3 normal;
|
||||
uniform mat4 modelMatrix;
|
||||
uniform mat4 viewMatrix;
|
||||
uniform mat4 projectionMatrix;
|
||||
|
||||
mat4 rotationMatrix(vec3 axis, float angle) {
|
||||
axis = normalize(axis);
|
||||
float s = sin(angle);
|
||||
float c = cos(angle);
|
||||
float oc = 1.0 - c;
|
||||
uniform vec3 lightPosition;
|
||||
uniform vec4 lightBrightness;
|
||||
uniform vec3 lightColor;
|
||||
|
||||
return mat4(oc * axis.x * axis.x + c, oc * axis.x * axis.y - axis.z * s, oc * axis.z * axis.x + axis.y * s, 0.0,
|
||||
oc * axis.x * axis.y + axis.z * s, oc * axis.y * axis.y + c, oc * axis.y * axis.z - axis.x * s, 0.0,
|
||||
oc * axis.z * axis.x - axis.y * s, oc * axis.y * axis.z + axis.x * s, oc * axis.z * axis.z + c, 0.0,
|
||||
0.0, 0.0, 0.0, 1.0);
|
||||
}
|
||||
|
||||
mat4 viewMatrix(vec3 rotation) {
|
||||
mat4 rotationX = rotationMatrix(vec3(1, 0, 0), rotation.x);
|
||||
mat4 rotationY = rotationMatrix(vec3(0, 1, 0), rotation.y);
|
||||
mat4 rotationZ = rotationMatrix(vec3(0, 0, 1), rotation.z);
|
||||
|
||||
return rotationX * rotationY * rotationZ;
|
||||
}
|
||||
out VertexData {
|
||||
vec3 normal;
|
||||
vec4 fragColor;
|
||||
vec2 texCoord;
|
||||
vec3 lightPosition;
|
||||
vec3 fragPosition;
|
||||
vec4 lightBrightness;
|
||||
vec3 lightColor;
|
||||
} vertexData;
|
||||
|
||||
void main() {
|
||||
mat4 view = viewMatrix(rotation);
|
||||
normal = (vec4(in_Normal, 1.0) * view).xyz * -1;
|
||||
gl_Position = vec4((in_Position + translation) * -scale, 1.0) * view;
|
||||
gl_Position = projectionMatrix * viewMatrix * modelScale * vec4(in_Position, 1.0);
|
||||
|
||||
vertexData.normal = mat3(transpose(inverse(modelScale))) * in_Normal;
|
||||
vertexData.fragPosition = vec3(viewMatrix * modelScale * vec4(in_Position, 1.0));
|
||||
vertexData.fragColor = in_Color;
|
||||
vertexData.texCoord = in_TexCoord;
|
||||
vertexData.lightBrightness = lightBrightness;
|
||||
vertexData.lightColor = lightColor;
|
||||
|
||||
// Transform world-space light position to view-space light position
|
||||
vertexData.lightPosition = vec3(viewMatrix * modelMatrix * vec4(lightPosition, 1.0));
|
||||
}
|
@ -7,6 +7,7 @@
|
||||
|
||||
#include <fonts/fontawesome_font.h>
|
||||
#include <fonts/codicons_font.h>
|
||||
#include <fonts/blendericons_font.h>
|
||||
|
||||
#include <imgui_freetype.h>
|
||||
|
||||
@ -21,6 +22,7 @@ namespace hex::plugin::builtin {
|
||||
* efficient when packing the glyphs into the font atlas and therefor make the atlas much smaller.
|
||||
*/
|
||||
|
||||
ImHexApi::Fonts::loadFont("Blender Icons", romfs::get("fonts/blendericons.ttf").span<u8>(),{ { ICON_MIN_BI, ICON_MAX_BI } }, { 0, -3_scaled });
|
||||
ImHexApi::Fonts::loadFont("Font Awesome 5", romfs::get("fonts/fontawesome.otf").span<u8>(),
|
||||
{
|
||||
{ glyph(ICON_FA_BACKSPACE), glyph(ICON_FA_INFINITY), glyph(ICON_FA_TACHOMETER_ALT), glyph(ICON_FA_MICROCHIP), glyph(ICON_FA_CODE_BRANCH) }
|
||||
|
@ -209,12 +209,14 @@ namespace hex::plugin::builtin {
|
||||
if (fontFile.empty())
|
||||
fonts->Clear();
|
||||
|
||||
if (ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_bold", false))
|
||||
cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Bold;
|
||||
if (ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_italic", false))
|
||||
cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Oblique;
|
||||
if (!ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_antialias", false))
|
||||
cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Monochrome | ImGuiFreeTypeBuilderFlags_MonoHinting;
|
||||
if (ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.custom_font_enable", false).get<bool>()) {
|
||||
if (ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_bold", false))
|
||||
cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Bold;
|
||||
if (ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_italic", false))
|
||||
cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Oblique;
|
||||
if (!ContentRegistry::Settings::read("hex.builtin.setting.font", "hex.builtin.setting.font.font_antialias", false))
|
||||
cfg.FontBuilderFlags |= ImGuiFreeTypeBuilderFlags_Monochrome | ImGuiFreeTypeBuilderFlags_MonoHinting;
|
||||
}
|
||||
|
||||
auto loadDefaultFont = [&](const char *fontName, u32 flags = 0) {
|
||||
ImFontConfig defaultConfig = cfg;
|
||||
@ -266,7 +268,7 @@ namespace hex::plugin::builtin {
|
||||
|
||||
ranges.push_back(fontRange);
|
||||
|
||||
cfg.FontBuilderFlags = startFlags | font.flags;
|
||||
cfg.FontBuilderFlags = font.flags;
|
||||
|
||||
float descent = [&] {
|
||||
ImFontAtlas atlas;
|
||||
@ -297,6 +299,7 @@ namespace hex::plugin::builtin {
|
||||
cfg.GlyphOffset = { font.offset.x, font.offset.y - defaultFont->Descent + descent };
|
||||
fonts->AddFontFromMemoryTTF(font.fontData.data(), int(font.fontData.size()), 0, &cfg, ranges.back().Data);
|
||||
}
|
||||
cfg.FontBuilderFlags = startFlags;
|
||||
|
||||
// Create bold and italic font
|
||||
cfg.MergeMode = false;
|
||||
|
@ -1,717 +1,21 @@
|
||||
#include <hex/api/content_registry.hpp>
|
||||
#include <hex/api/localization_manager.hpp>
|
||||
#include <hex/api/task_manager.hpp>
|
||||
|
||||
#include <hex/helpers/http_requests.hpp>
|
||||
#include <hex/helpers/disassembler.hpp>
|
||||
#include <hex/helpers/utils.hpp>
|
||||
#include <hex/helpers/opengl.hpp>
|
||||
|
||||
#include <hex/helpers/fmt.hpp>
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <implot.h>
|
||||
|
||||
#include <opengl_support.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
#include <fonts/codicons_font.h>
|
||||
|
||||
#include <pl/patterns/pattern.hpp>
|
||||
|
||||
#include <miniaudio.h>
|
||||
|
||||
#include <romfs/romfs.hpp>
|
||||
|
||||
#include <numeric>
|
||||
#include <numbers>
|
||||
|
||||
#include <content/helpers/diagrams.hpp>
|
||||
#include <ui/hex_editor.hpp>
|
||||
|
||||
#include <content/providers/memory_file_provider.hpp>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
namespace {
|
||||
|
||||
template<typename T>
|
||||
std::vector<T> patternToArray(pl::ptrn::Pattern *pattern){
|
||||
const auto bytes = pattern->getBytes();
|
||||
|
||||
std::vector<T> result;
|
||||
result.resize(bytes.size() / sizeof(T));
|
||||
for (size_t i = 0; i < result.size(); i++)
|
||||
std::memcpy(&result[i], &bytes[i * sizeof(T)], sizeof(T));
|
||||
|
||||
return result;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
namespace {
|
||||
|
||||
void drawLinePlotVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
static std::vector<float> values;
|
||||
auto dataPattern = arguments[0].toPattern();
|
||||
|
||||
if (ImPlot::BeginPlot("##plot", ImVec2(400, 250), ImPlotFlags_CanvasOnly)) {
|
||||
ImPlot::SetupAxes("X", "Y", ImPlotAxisFlags_AutoFit, ImPlotAxisFlags_AutoFit);
|
||||
|
||||
if (shouldReset) {
|
||||
values.clear();
|
||||
values = sampleData(patternToArray<float>(dataPattern.get()), ImPlot::GetPlotSize().x * 4);
|
||||
}
|
||||
|
||||
ImPlot::PlotLine("##line", values.data(), values.size());
|
||||
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
}
|
||||
|
||||
void drawScatterPlotVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
static std::vector<float> xValues, yValues;
|
||||
|
||||
auto xPattern = arguments[0].toPattern();
|
||||
auto yPattern = arguments[1].toPattern();
|
||||
|
||||
if (ImPlot::BeginPlot("##plot", ImVec2(400, 250), ImPlotFlags_CanvasOnly)) {
|
||||
ImPlot::SetupAxes("X", "Y", ImPlotAxisFlags_AutoFit, ImPlotAxisFlags_AutoFit);
|
||||
|
||||
if (shouldReset) {
|
||||
xValues.clear(); yValues.clear();
|
||||
xValues = sampleData(patternToArray<float>(xPattern.get()), ImPlot::GetPlotSize().x * 4);
|
||||
yValues = sampleData(patternToArray<float>(yPattern.get()), ImPlot::GetPlotSize().x * 4);
|
||||
}
|
||||
|
||||
ImPlot::PlotScatter("##scatter", xValues.data(), yValues.data(), xValues.size());
|
||||
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
}
|
||||
|
||||
void drawImageVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
static ImGuiExt::Texture texture;
|
||||
static float scale = 1.0F;
|
||||
|
||||
if (shouldReset) {
|
||||
auto pattern = arguments[0].toPattern();
|
||||
|
||||
auto data = pattern->getBytes();
|
||||
texture = ImGuiExt::Texture(data.data(), data.size());
|
||||
scale = 200_scaled / texture.getSize().x;
|
||||
}
|
||||
|
||||
if (texture.isValid())
|
||||
ImGui::Image(texture, texture.getSize() * scale);
|
||||
|
||||
if (ImGui::IsWindowHovered()) {
|
||||
auto scrollDelta = ImGui::GetIO().MouseWheel;
|
||||
if (scrollDelta != 0.0F) {
|
||||
scale += scrollDelta * 0.1F;
|
||||
scale = std::clamp(scale, 0.1F, 10.0F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void drawBitmapVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
static ImGuiExt::Texture texture;
|
||||
static float scale = 1.0F;
|
||||
|
||||
if (shouldReset) {
|
||||
auto pattern = arguments[0].toPattern();
|
||||
auto width = arguments[1].toUnsigned();
|
||||
auto height = arguments[2].toUnsigned();
|
||||
|
||||
auto data = pattern->getBytes();
|
||||
texture = ImGuiExt::Texture(data.data(), data.size(), width, height);
|
||||
}
|
||||
|
||||
if (texture.isValid())
|
||||
ImGui::Image(texture, texture.getSize() * scale);
|
||||
|
||||
if (ImGui::IsWindowHovered()) {
|
||||
auto scrollDelta = ImGui::GetIO().MouseWheel;
|
||||
if (scrollDelta != 0.0F) {
|
||||
scale += scrollDelta * 0.1F;
|
||||
scale = std::clamp(scale, 0.1F, 10.0F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void drawDisassemblyVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
struct Disassembly {
|
||||
u64 address;
|
||||
std::vector<u8> bytes;
|
||||
std::string instruction;
|
||||
};
|
||||
|
||||
static std::vector<Disassembly> disassembly;
|
||||
if (shouldReset) {
|
||||
auto pattern = arguments[0].toPattern();
|
||||
auto baseAddress = arguments[1].toUnsigned();
|
||||
auto architecture = arguments[2].toUnsigned();
|
||||
auto mode = arguments[3].toUnsigned();
|
||||
|
||||
disassembly.clear();
|
||||
|
||||
csh capstone;
|
||||
if (cs_open(static_cast<cs_arch>(architecture), static_cast<cs_mode>(mode), &capstone) == CS_ERR_OK) {
|
||||
cs_option(capstone, CS_OPT_SKIPDATA, CS_OPT_ON);
|
||||
|
||||
auto data = pattern->getBytes();
|
||||
cs_insn *instructions = nullptr;
|
||||
|
||||
size_t instructionCount = cs_disasm(capstone, data.data(), data.size(), baseAddress, 0, &instructions);
|
||||
for (size_t i = 0; i < instructionCount; i++) {
|
||||
disassembly.push_back({ instructions[i].address, { instructions[i].bytes, instructions[i].bytes + instructions[i].size }, hex::format("{} {}", instructions[i].mnemonic, instructions[i].op_str) });
|
||||
}
|
||||
cs_free(instructions, instructionCount);
|
||||
cs_close(&capstone);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::BeginTable("##disassembly", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY, scaled(ImVec2(0, 300)))) {
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
ImGui::TableSetupColumn("hex.builtin.common.address"_lang);
|
||||
ImGui::TableSetupColumn("hex.builtin.common.bytes"_lang);
|
||||
ImGui::TableSetupColumn("hex.builtin.common.instruction"_lang);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
for (auto &entry : disassembly) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGuiExt::TextFormatted("0x{0:08X}", entry.address);
|
||||
ImGui::TableNextColumn();
|
||||
std::string bytes;
|
||||
for (auto byte : entry.bytes)
|
||||
bytes += hex::format("{0:02X} ", byte);
|
||||
ImGui::TextUnformatted(bytes.c_str());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(entry.instruction.c_str());
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
void draw3DVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
auto verticesPattern = arguments[0].toPattern();
|
||||
auto indicesPattern = arguments[1].toPattern();
|
||||
|
||||
static ImGuiExt::Texture texture;
|
||||
|
||||
static gl::Vector<float, 3> translation;
|
||||
static gl::Vector<float, 3> rotation = { { 1.0F, -1.0F, 0.0F } };
|
||||
static float scaling = 0.1F;
|
||||
|
||||
static std::vector<float> vertices, normals;
|
||||
static std::vector<u32> indices;
|
||||
|
||||
static gl::Shader shader;
|
||||
static gl::VertexArray vertexArray;
|
||||
static gl::Buffer<float> vertexBuffer, normalBuffer;
|
||||
static gl::Buffer<u32> indexBuffer;
|
||||
|
||||
{
|
||||
auto dragDelta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Middle);
|
||||
rotation[0] += -dragDelta.y * 0.0075F;
|
||||
rotation[1] += -dragDelta.x * 0.0075F;
|
||||
ImGui::ResetMouseDragDelta(ImGuiMouseButton_Middle);
|
||||
|
||||
dragDelta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right);
|
||||
translation[0] += -dragDelta.x * 0.1F;
|
||||
translation[1] += -dragDelta.y * 0.1F;
|
||||
ImGui::ResetMouseDragDelta(ImGuiMouseButton_Right);
|
||||
|
||||
auto scrollDelta = ImGui::GetIO().MouseWheel;
|
||||
scaling += scrollDelta * 0.01F;
|
||||
|
||||
if (scaling < 0.01F)
|
||||
scaling = 0.01F;
|
||||
}
|
||||
|
||||
if (shouldReset) {
|
||||
vertices = patternToArray<float>(verticesPattern.get());
|
||||
indices = patternToArray<u32>(indicesPattern.get());
|
||||
|
||||
normals.clear();
|
||||
normals.resize(vertices.size());
|
||||
|
||||
for (u32 i = 0; i < normals.size(); i += 9) {
|
||||
|
||||
auto v1 = gl::Vector<float, 3>({ vertices[i] , vertices[i + 1], vertices[i + 2] });
|
||||
auto v2 = gl::Vector<float, 3>({ vertices[i + 3], vertices[i + 4], vertices[i + 5] });
|
||||
auto v3 = gl::Vector<float, 3>({ vertices[i + 6], vertices[i + 7], vertices[i + 8] });
|
||||
|
||||
auto normal = ((v2 - v1).cross(v3 - v1)).normalize();
|
||||
normals[i] = normal[0];
|
||||
normals[i + 1] = normal[1];
|
||||
normals[i + 2] = normal[2];
|
||||
normals[i + 3] = normal[0];
|
||||
normals[i + 4] = normal[1];
|
||||
normals[i + 5] = normal[2];
|
||||
normals[i + 6] = normal[0];
|
||||
normals[i + 7] = normal[1];
|
||||
normals[i + 8] = normal[2];
|
||||
}
|
||||
|
||||
shader = gl::Shader(romfs::get("shaders/default/vertex.glsl").string(), romfs::get("shaders/default/fragment.glsl").string());
|
||||
|
||||
vertexArray = gl::VertexArray();
|
||||
|
||||
vertexBuffer = {};
|
||||
normalBuffer = {};
|
||||
indexBuffer = {};
|
||||
|
||||
vertexArray.bind();
|
||||
|
||||
vertexBuffer = gl::Buffer<float>(gl::BufferType::Vertex, vertices);
|
||||
normalBuffer = gl::Buffer<float>(gl::BufferType::Vertex, normals);
|
||||
indexBuffer = gl::Buffer<u32>(gl::BufferType::Index, indices);
|
||||
|
||||
vertexArray.addBuffer(0, vertexBuffer);
|
||||
vertexArray.addBuffer(1, normalBuffer);
|
||||
|
||||
if (!indices.empty())
|
||||
vertexArray.addBuffer(2, indexBuffer);
|
||||
|
||||
vertexBuffer.unbind();
|
||||
normalBuffer.unbind();
|
||||
indexBuffer.unbind();
|
||||
vertexArray.unbind();
|
||||
}
|
||||
|
||||
{
|
||||
gl::FrameBuffer frameBuffer;
|
||||
|
||||
gl::Texture renderTexture(400_scaled, 400_scaled);
|
||||
frameBuffer.attachTexture(renderTexture);
|
||||
|
||||
frameBuffer.bind();
|
||||
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
|
||||
shader.bind();
|
||||
shader.setUniform("scale", scaling);
|
||||
shader.setUniform("rotation", rotation);
|
||||
shader.setUniform("translation", translation);
|
||||
|
||||
vertexArray.bind();
|
||||
|
||||
glViewport(0, 0, renderTexture.getWidth(), renderTexture.getHeight());
|
||||
glClearColor(0.00F, 0.00F, 0.00F, 0.00f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
|
||||
if (indices.empty())
|
||||
vertexBuffer.draw();
|
||||
else
|
||||
indexBuffer.draw();
|
||||
|
||||
vertexArray.unbind();
|
||||
shader.unbind();
|
||||
frameBuffer.unbind();
|
||||
|
||||
texture = ImGuiExt::Texture(renderTexture.release(), renderTexture.getWidth(), renderTexture.getHeight());
|
||||
}
|
||||
|
||||
auto textureSize = texture.getSize();
|
||||
|
||||
if (ImGui::BeginTable("##3DVisualizer", 2, ImGuiTableFlags_SizingFixedFit)) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
if (ImGui::BeginChild("##image", textureSize, true, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
|
||||
ImGui::Image(texture, textureSize, ImVec2(0, 1), ImVec2(1, 0));
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted("hex.builtin.pl_visualizer.3d.rotation"_lang);
|
||||
ImGui::VSliderFloat("##X", ImVec2(18_scaled, textureSize.y), &rotation.data()[0], 0, std::numbers::pi * 2, "", ImGuiSliderFlags_AlwaysClamp);
|
||||
ImGui::SameLine();
|
||||
ImGui::VSliderFloat("##Y", ImVec2(18_scaled, textureSize.y), &rotation.data()[1], 0, std::numbers::pi * 2, "", ImGuiSliderFlags_AlwaysClamp);
|
||||
ImGui::SameLine();
|
||||
ImGui::VSliderFloat("##Z", ImVec2(18_scaled, textureSize.y), &rotation.data()[2], 0, std::numbers::pi * 2, "", ImGuiSliderFlags_AlwaysClamp);
|
||||
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
ImGui::TextUnformatted("hex.builtin.pl_visualizer.3d.scale"_lang);
|
||||
ImGui::SameLine();
|
||||
ImGui::PushItemWidth(ImGui::GetContentRegionAvail().x);
|
||||
ImGui::SliderFloat("##Scale", &scaling, 0.0001F, 0.2F, "");
|
||||
ImGui::PopItemWidth();
|
||||
|
||||
for (u8 i = 0; i < 3; i++) {
|
||||
while (rotation.data()[i] > std::numbers::pi * 2)
|
||||
rotation.data()[i] -= std::numbers::pi * 2;
|
||||
while (rotation.data()[i] < 0)
|
||||
rotation.data()[i] += std::numbers::pi * 2;
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
if (ImGui::Button("hex.builtin.common.reset"_lang, ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
|
||||
translation = gl::Vector<float, 3>({ 0.0F, 0.0F, 0.0F });
|
||||
rotation = gl::Vector<float, 3>({ 0.0F, 0.0F, 0.0F });
|
||||
scaling = 0.1F;
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
void drawSoundVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
auto wavePattern = arguments[0].toPattern();
|
||||
auto channels = arguments[1].toUnsigned();
|
||||
auto sampleRate = arguments[2].toUnsigned();
|
||||
|
||||
static std::vector<i16> waveData, sampledData;
|
||||
static ma_device audioDevice;
|
||||
static ma_device_config deviceConfig;
|
||||
static bool shouldStop = false;
|
||||
static u64 index = 0;
|
||||
static TaskHolder resetTask;
|
||||
|
||||
if (sampleRate == 0)
|
||||
throw std::logic_error(hex::format("Invalid sample rate: {}", sampleRate));
|
||||
else if (channels == 0)
|
||||
throw std::logic_error(hex::format("Invalid channel count: {}", channels));
|
||||
|
||||
if (shouldReset) {
|
||||
waveData.clear();
|
||||
|
||||
resetTask = TaskManager::createTask("Visualizing...", TaskManager::NoProgress, [=](Task &) {
|
||||
ma_device_stop(&audioDevice);
|
||||
waveData = patternToArray<i16>(wavePattern.get());
|
||||
sampledData = sampleData(waveData, 300_scaled * 4);
|
||||
index = 0;
|
||||
|
||||
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
deviceConfig.playback.format = ma_format_s16;
|
||||
deviceConfig.playback.channels = channels;
|
||||
deviceConfig.sampleRate = sampleRate;
|
||||
deviceConfig.pUserData = &waveData;
|
||||
deviceConfig.dataCallback = [](ma_device *device, void *pOutput, const void *, ma_uint32 frameCount) {
|
||||
if (index >= waveData.size()) {
|
||||
index = 0;
|
||||
shouldStop = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ma_copy_pcm_frames(pOutput, waveData.data() + index, frameCount, device->playback.format, device->playback.channels);
|
||||
index += frameCount;
|
||||
};
|
||||
|
||||
ma_device_init(nullptr, &deviceConfig, &audioDevice);
|
||||
});
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(resetTask.isRunning());
|
||||
|
||||
ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, ImVec2(0, 0));
|
||||
if (ImPlot::BeginPlot("##amplitude_plot", scaled(ImVec2(300, 80)), ImPlotFlags_CanvasOnly | ImPlotFlags_NoFrame | ImPlotFlags_NoInputs)) {
|
||||
ImPlot::SetupAxes("##time", "##amplitude", ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_NoMenus, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_NoMenus);
|
||||
ImPlot::SetupAxesLimits(0, waveData.size(), std::numeric_limits<i16>::min(), std::numeric_limits<i16>::max(), ImGuiCond_Always);
|
||||
|
||||
double dragPos = index;
|
||||
if (ImPlot::DragLineX(1, &dragPos, ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
||||
if (dragPos < 0) dragPos = 0;
|
||||
if (dragPos >= waveData.size()) dragPos = waveData.size() - 1;
|
||||
|
||||
index = dragPos;
|
||||
}
|
||||
|
||||
ImPlot::PlotLine("##audio", sampledData.data(), sampledData.size());
|
||||
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
ImPlot::PopStyleVar();
|
||||
|
||||
{
|
||||
const u64 min = 0, max = waveData.size();
|
||||
ImGui::PushItemWidth(300_scaled);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||
ImGui::SliderScalar("##index", ImGuiDataType_U64, &index, &min, &max, "");
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
if (shouldStop) {
|
||||
shouldStop = false;
|
||||
ma_device_stop(&audioDevice);
|
||||
}
|
||||
|
||||
bool playing = ma_device_is_started(&audioDevice);
|
||||
|
||||
if (ImGuiExt::IconButton(playing ? ICON_VS_DEBUG_PAUSE : ICON_VS_PLAY, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarGreen))) {
|
||||
if (playing)
|
||||
ma_device_stop(&audioDevice);
|
||||
else
|
||||
ma_device_start(&audioDevice);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGuiExt::IconButton(ICON_VS_DEBUG_STOP, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarRed))) {
|
||||
index = 0;
|
||||
ma_device_stop(&audioDevice);
|
||||
}
|
||||
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (resetTask.isRunning())
|
||||
ImGuiExt::TextSpinner("");
|
||||
else
|
||||
ImGuiExt::TextFormatted("{:02d}:{:02d} / {:02d}:{:02d}",
|
||||
(index / sampleRate) / 60, (index / sampleRate) % 60,
|
||||
(waveData.size() / sampleRate) / 60, (waveData.size() / sampleRate) % 60);
|
||||
}
|
||||
|
||||
void drawChunkBasedEntropyVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
// Variable used to store the result to avoid having to recalculate the result at each frame
|
||||
static DiagramChunkBasedEntropyAnalysis analyzer;
|
||||
|
||||
// Compute data
|
||||
if (shouldReset) {
|
||||
auto pattern = arguments[0].toPattern();
|
||||
auto chunkSize = arguments[1].toUnsigned();
|
||||
analyzer.process(pattern->getBytes(), chunkSize);
|
||||
}
|
||||
|
||||
// Show results
|
||||
analyzer.draw(ImVec2(400, 250), ImPlotFlags_CanvasOnly);
|
||||
}
|
||||
|
||||
void drawHexVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
static ui::HexEditor editor;
|
||||
static std::unique_ptr<MemoryFileProvider> dataProvider;
|
||||
|
||||
if (shouldReset) {
|
||||
auto pattern = arguments[0].toPattern();
|
||||
std::vector<u8> data;
|
||||
|
||||
dataProvider = std::make_unique<MemoryFileProvider>();
|
||||
try {
|
||||
data = pattern->getBytes();
|
||||
} catch (const std::exception &) {
|
||||
dataProvider->resize(0);
|
||||
throw;
|
||||
}
|
||||
|
||||
dataProvider->resize(data.size());
|
||||
dataProvider->writeRaw(0x00, data.data(), data.size());
|
||||
dataProvider->setReadOnly(true);
|
||||
|
||||
editor.setProvider(dataProvider.get());
|
||||
}
|
||||
|
||||
if (ImGui::BeginChild("##editor", scaled(ImVec2(600, 400)), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
|
||||
editor.draw();
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
void drawCoordinateVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
static ImVec2 coordinate;
|
||||
static double latitude, longitude;
|
||||
static std::string address;
|
||||
static std::mutex addressMutex;
|
||||
static TaskHolder addressTask;
|
||||
|
||||
static auto mapTexture = ImGuiExt::Texture(romfs::get("assets/common/map.jpg").span());
|
||||
static ImVec2 mapSize = scaled(ImVec2(500, 500 / mapTexture.getAspectRatio()));
|
||||
|
||||
if (shouldReset) {
|
||||
std::scoped_lock lock(addressMutex);
|
||||
|
||||
address.clear();
|
||||
latitude = arguments[0].toFloatingPoint();
|
||||
longitude = arguments[1].toFloatingPoint();
|
||||
|
||||
// Convert latitude and longitude to X/Y coordinates on the image
|
||||
coordinate.x = float((longitude + 180) / 360 * mapSize.x);
|
||||
coordinate.y = float((-latitude + 90) / 180 * mapSize.y);
|
||||
}
|
||||
|
||||
const auto startPos = ImGui::GetWindowPos() + ImGui::GetCursorPos();
|
||||
|
||||
// Draw background image
|
||||
ImGui::Image(mapTexture, mapSize);
|
||||
|
||||
// Draw Longitude / Latitude text below image
|
||||
ImGui::PushTextWrapPos(startPos.x + mapSize.x);
|
||||
ImGuiExt::TextFormattedWrapped("{}: {:.0f}° {:.0f}' {:.4f}\" {} | {}: {:.0f}° {:.0f}' {:.4f}\" {}",
|
||||
"hex.builtin.pl_visualizer.coordinates.latitude"_lang,
|
||||
std::floor(std::abs(latitude)),
|
||||
std::floor(std::abs(latitude - std::floor(latitude)) * 60),
|
||||
(std::abs(latitude - std::floor(latitude)) * 60 - std::floor(std::abs(latitude - std::floor(latitude)) * 60)) * 60,
|
||||
latitude >= 0 ? "N" : "S",
|
||||
"hex.builtin.pl_visualizer.coordinates.longitude"_lang,
|
||||
std::floor(std::abs(longitude)),
|
||||
std::floor(std::abs(longitude - std::floor(longitude)) * 60),
|
||||
(std::abs(longitude - std::floor(longitude)) * 60 - std::floor(std::abs(longitude - std::floor(longitude)) * 60)) * 60,
|
||||
longitude >= 0 ? "E" : "W"
|
||||
);
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
if (addressTask.isRunning()) {
|
||||
ImGuiExt::TextSpinner("hex.builtin.pl_visualizer.coordinates.querying"_lang);
|
||||
} else if (address.empty()) {
|
||||
if (ImGuiExt::DimmedButton("hex.builtin.pl_visualizer.coordinates.query"_lang)) {
|
||||
addressTask = TaskManager::createBackgroundTask("hex.builtin.pl_visualizer.coordinates.querying"_lang, [lat = latitude, lon = longitude](auto &) {
|
||||
constexpr static auto ApiURL = "https://geocode.maps.co/reverse?lat={}&lon={}&format=jsonv2";
|
||||
|
||||
HttpRequest request("GET", hex::format(ApiURL, lat, lon));
|
||||
auto response = request.execute().get();
|
||||
|
||||
if (!response.isSuccess())
|
||||
return;
|
||||
|
||||
try {
|
||||
|
||||
auto json = nlohmann::json::parse(response.getData());
|
||||
auto jsonAddr = json["address"];
|
||||
|
||||
std::scoped_lock lock(addressMutex);
|
||||
if (jsonAddr.contains("village")) {
|
||||
address = hex::format("{} {}, {} {}",
|
||||
jsonAddr["village"].get<std::string>(),
|
||||
jsonAddr["county"].get<std::string>(),
|
||||
jsonAddr["state"].get<std::string>(),
|
||||
jsonAddr["country"].get<std::string>());
|
||||
} else if (jsonAddr.contains("city")) {
|
||||
address = hex::format("{}, {} {}, {} {}",
|
||||
jsonAddr["road"].get<std::string>(),
|
||||
jsonAddr["quarter"].get<std::string>(),
|
||||
jsonAddr["city"].get<std::string>(),
|
||||
jsonAddr["state"].get<std::string>(),
|
||||
jsonAddr["country"].get<std::string>());
|
||||
}
|
||||
} catch (std::exception &) {
|
||||
address = std::string("hex.builtin.pl_visualizer.coordinates.querying_no_address"_lang);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
ImGui::PushTextWrapPos(startPos.x + mapSize.x);
|
||||
ImGuiExt::TextFormattedWrapped("{}", address);
|
||||
ImGui::PopTextWrapPos();
|
||||
}
|
||||
|
||||
// Draw crosshair pointing to the coordinates
|
||||
{
|
||||
constexpr static u32 CrossHairColor = 0xFF00D0D0;
|
||||
constexpr static u32 BorderColor = 0xFF000000;
|
||||
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
drawList->AddLine(startPos + ImVec2(coordinate.x, 0), startPos + ImVec2(coordinate.x, mapSize.y), CrossHairColor, 2_scaled);
|
||||
drawList->AddLine(startPos + ImVec2(0, coordinate.y), startPos + ImVec2(mapSize.x, coordinate.y), CrossHairColor, 2_scaled);
|
||||
drawList->AddCircleFilled(startPos + coordinate, 5, CrossHairColor);
|
||||
drawList->AddCircle(startPos + coordinate, 5, BorderColor);
|
||||
}
|
||||
}
|
||||
|
||||
void drawTimestampVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool, std::span<const pl::core::Token::Literal> arguments) {
|
||||
time_t timestamp = arguments[0].toUnsigned();
|
||||
auto tm = fmt::gmtime(timestamp);
|
||||
auto date = std::chrono::year_month_day(std::chrono::year(tm.tm_year + 1900), std::chrono::month(tm.tm_mon + 1), std::chrono::day(tm.tm_mday));
|
||||
|
||||
auto lastMonthDay = std::chrono::year_month_day_last(date.year(), date.month() / std::chrono::last);
|
||||
auto firstWeekDay = std::chrono::weekday(std::chrono::year_month_day(date.year(), date.month(), std::chrono::day(1)));
|
||||
|
||||
const auto scale = 1_scaled * (ImHexApi::Fonts::getFontSize() / ImHexApi::Fonts::DefaultFontSize);
|
||||
|
||||
// Draw calendar
|
||||
if (ImGui::BeginTable("##month_table", 2)) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
// Draw centered month name and year
|
||||
ImGuiExt::TextFormattedCenteredHorizontal("{:%B %Y}", tm);
|
||||
|
||||
if (ImGui::BeginTable("##days_table", 7, ImGuiTableFlags_Borders | ImGuiTableFlags_NoHostExtendX, ImVec2(160, 120) * scale)) {
|
||||
constexpr static auto ColumnFlags = ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoReorder | ImGuiTableColumnFlags_NoHide;
|
||||
ImGui::TableSetupColumn("M", ColumnFlags);
|
||||
ImGui::TableSetupColumn("T", ColumnFlags);
|
||||
ImGui::TableSetupColumn("W", ColumnFlags);
|
||||
ImGui::TableSetupColumn("T", ColumnFlags);
|
||||
ImGui::TableSetupColumn("F", ColumnFlags);
|
||||
ImGui::TableSetupColumn("S", ColumnFlags);
|
||||
ImGui::TableSetupColumn("S", ColumnFlags);
|
||||
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
ImGui::TableNextRow();
|
||||
|
||||
// Skip days before the first day of the month
|
||||
for (u8 i = 0; i < firstWeekDay.c_encoding() - 1; ++i)
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
// Draw days
|
||||
for (u8 i = 1; i <= u32(lastMonthDay.day()); ++i) {
|
||||
ImGui::TableNextColumn();
|
||||
ImGuiExt::TextFormatted("{:02}", i);
|
||||
|
||||
if (std::chrono::day(i) == date.day())
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ImGuiExt::GetCustomColorU32(ImGuiCustomCol_ToolbarRed));
|
||||
|
||||
if (std::chrono::weekday(std::chrono::year_month_day(date.year(), date.month(), std::chrono::day(i))) == std::chrono::Sunday)
|
||||
ImGui::TableNextRow();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
// Draw analog clock
|
||||
const auto size = ImVec2(120, 120) * scale;
|
||||
if (ImGui::BeginChild("##clock", size + ImVec2(0, ImGui::GetTextLineHeightWithSpacing()))) {
|
||||
// Draw centered digital hour, minute and seconds
|
||||
ImGuiExt::TextFormattedCenteredHorizontal("{:%H:%M:%S}", tm);
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
const auto center = ImGui::GetWindowPos() + ImVec2(0, ImGui::GetTextLineHeightWithSpacing()) + size / 2;
|
||||
|
||||
// Draw clock face
|
||||
drawList->AddCircle(center, size.x / 2, ImGui::GetColorU32(ImGuiCol_TextDisabled), 0);
|
||||
|
||||
auto sectionPos = [](float i) {
|
||||
return ImVec2(std::sin(-i * 30.0F * std::numbers::pi / 180.0F + std::numbers::pi / 2), std::cos(-i * 30.0F * std::numbers::pi / 180.0F + std::numbers::pi / 2));
|
||||
};
|
||||
|
||||
// Draw clock sections and numbers
|
||||
for (u8 i = 0; i < 12; ++i) {
|
||||
auto text = hex::format("{}", (((i + 2) % 12) + 1));
|
||||
drawList->AddLine(center + sectionPos(i) * size / 2.2, center + sectionPos(i) * size / 2, ImGui::GetColorU32(ImGuiCol_TextDisabled), 1_scaled);
|
||||
drawList->AddText(center + sectionPos(i) * size / 3 - ImGui::CalcTextSize(text.c_str()) / 2, ImGui::GetColorU32(ImGuiCol_Text), text.c_str());
|
||||
}
|
||||
|
||||
// Draw hour hand
|
||||
drawList->AddLine(center, center + sectionPos((tm.tm_hour + 9) % 12 + float(tm.tm_min) / 60.0) * size / 3.5, ImGui::GetColorU32(ImGuiCol_TextDisabled), 3_scaled);
|
||||
|
||||
// Draw minute hand
|
||||
drawList->AddLine(center, center + sectionPos((float(tm.tm_min) / 5.0F) - 3) * size / 2.5, ImGui::GetColorU32(ImGuiCol_TextDisabled), 3_scaled);
|
||||
|
||||
// Draw second hand
|
||||
drawList->AddLine(center, center + sectionPos((float(tm.tm_sec) / 5.0F) - 3) * size / 2.5, ImGuiExt::GetCustomColorU32(ImGuiCustomCol_ToolbarRed), 2_scaled);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
void drawLinePlotVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool, std::span<const pl::core::Token::Literal> arguments);
|
||||
void drawScatterPlotVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool, std::span<const pl::core::Token::Literal> arguments);
|
||||
void drawImageVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool, std::span<const pl::core::Token::Literal> arguments);
|
||||
void drawBitmapVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool, std::span<const pl::core::Token::Literal> arguments);
|
||||
void drawDisassemblyVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool, std::span<const pl::core::Token::Literal> arguments);
|
||||
void draw3DVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool, std::span<const pl::core::Token::Literal> arguments);
|
||||
void drawSoundVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool, std::span<const pl::core::Token::Literal> arguments);
|
||||
void drawChunkBasedEntropyVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool, std::span<const pl::core::Token::Literal> arguments);
|
||||
void drawHexVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool, std::span<const pl::core::Token::Literal> arguments);
|
||||
void drawCoordinateVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool, std::span<const pl::core::Token::Literal> arguments);
|
||||
void drawTimestampVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool, std::span<const pl::core::Token::Literal> arguments);
|
||||
|
||||
void registerPatternLanguageVisualizers() {
|
||||
using ParamCount = pl::api::FunctionParameterCount;
|
||||
@ -721,7 +25,7 @@ namespace hex::plugin::builtin {
|
||||
ContentRegistry::PatternLanguage::addVisualizer("image", drawImageVisualizer, ParamCount::exactly(1));
|
||||
ContentRegistry::PatternLanguage::addVisualizer("bitmap", drawBitmapVisualizer, ParamCount::exactly(3));
|
||||
ContentRegistry::PatternLanguage::addVisualizer("disassembler", drawDisassemblyVisualizer, ParamCount::exactly(4));
|
||||
ContentRegistry::PatternLanguage::addVisualizer("3d", draw3DVisualizer, ParamCount::exactly(2));
|
||||
ContentRegistry::PatternLanguage::addVisualizer("3d", draw3DVisualizer, ParamCount::between(2, 6));
|
||||
ContentRegistry::PatternLanguage::addVisualizer("sound", drawSoundVisualizer, ParamCount::exactly(3));
|
||||
ContentRegistry::PatternLanguage::addVisualizer("chunk_entropy", drawChunkBasedEntropyVisualizer, ParamCount::exactly(2));
|
||||
ContentRegistry::PatternLanguage::addVisualizer("hex_viewer", drawHexVisualizer, ParamCount::exactly(1));
|
||||
|
908
plugins/builtin/source/content/pl_visualizers/3d_model.cpp
Normal file
908
plugins/builtin/source/content/pl_visualizers/3d_model.cpp
Normal file
@ -0,0 +1,908 @@
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
#include <content/pl_visualizers/visualizer_helpers.hpp>
|
||||
|
||||
#include <fonts/codicons_font.h>
|
||||
#include <fonts/blendericons_font.h>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <imgui_internal.h>
|
||||
#include <hex/helpers/opengl.hpp>
|
||||
#include <opengl_support.h>
|
||||
#include <GLFW/glfw3.h>
|
||||
|
||||
#include <numbers>
|
||||
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
#include <hex/api/imhex_api.hpp>
|
||||
#include <hex/api/localization_manager.hpp>
|
||||
|
||||
#include <romfs/romfs.hpp>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
namespace {
|
||||
|
||||
enum class IndexType {
|
||||
U8,
|
||||
U16,
|
||||
U32,
|
||||
Invalid,
|
||||
};
|
||||
|
||||
struct Vectors {
|
||||
std::vector<float> vertices;
|
||||
std::vector<float> normals;
|
||||
std::vector<float> colors;
|
||||
std::vector<float> uv1;
|
||||
std::vector<u8> indices8;
|
||||
std::vector<u16> indices16;
|
||||
std::vector<u32> indices32;
|
||||
};
|
||||
|
||||
struct LineVectors {
|
||||
std::vector<float> vertices;
|
||||
std::vector<float> colors;
|
||||
std::vector<u8> indices8;
|
||||
std::vector<u16> indices16;
|
||||
std::vector<u32> indices32;
|
||||
};
|
||||
|
||||
struct Buffers {
|
||||
gl::Buffer<float> vertices;
|
||||
gl::Buffer<float> normals;
|
||||
gl::Buffer<float> colors;
|
||||
gl::Buffer<float> uv1;
|
||||
gl::Buffer<u8> indices8;
|
||||
gl::Buffer<u16> indices16;
|
||||
gl::Buffer<u32> indices32;
|
||||
};
|
||||
|
||||
struct LineBuffers {
|
||||
gl::Buffer<float> vertices;
|
||||
gl::Buffer<float> colors;
|
||||
gl::Buffer<u8> indices8;
|
||||
gl::Buffer<u16> indices16;
|
||||
gl::Buffer<u32> indices32;
|
||||
};
|
||||
|
||||
ImVec2 s_renderingWindowSize;
|
||||
|
||||
int s_drawMode = GL_TRIANGLES;
|
||||
float s_nearLimit = 0.9F;
|
||||
float s_farLimit = 100.0F;
|
||||
float s_scaling = 1.0F;
|
||||
float s_max;
|
||||
|
||||
bool s_isPerspective = true;
|
||||
bool s_drawAxes = true;
|
||||
bool s_drawGrid = true;
|
||||
bool s_drawLightSource = true;
|
||||
bool s_drawTexture = false;
|
||||
bool s_shouldReset = false;
|
||||
|
||||
bool s_shouldUpdateLightSource = true;
|
||||
bool s_shouldUpdateTexture = false;
|
||||
|
||||
IndexType s_indexType;
|
||||
|
||||
ImGuiExt::Texture s_modelTexture;
|
||||
|
||||
gl::Vector<float, 3> s_translation = { { 0.0F, 0.0F, -3.0F } };
|
||||
gl::Vector<float, 3> s_rotation = { { 0.0F, 0.0F, 0.0F } };
|
||||
gl::Vector<float, 3> s_lightPosition = { { -0.7F, 0.0F, 0.0F } };
|
||||
gl::Vector<float, 4> s_lightBrightness = { { 0.5F, 0.5F, 0.5F, 32.0F } };
|
||||
gl::Vector<float, 3> s_lightColor = { { 1.0F, 1.0F, 1.0f } };
|
||||
gl::Matrix<float, 4, 4> s_rotate = gl::Matrix<float, 4, 4>::identity();
|
||||
|
||||
ImGuiExt::Texture s_texture;
|
||||
std::fs::path s_texturePath;
|
||||
|
||||
template<typename T>
|
||||
void indicesForLines(std::vector<T> &vertexIndices) {
|
||||
std::vector<u32> indices;
|
||||
|
||||
u32 vertexCount = vertexIndices.size() / 3;
|
||||
indices.resize(vertexCount * 6);
|
||||
|
||||
for (u32 i = 0; i < vertexCount; ++i) {
|
||||
indices[i * 6] = vertexIndices[3 * i];
|
||||
indices[i * 6 + 1] = vertexIndices[3 * i + 1];
|
||||
|
||||
indices[i * 6 + 2] = vertexIndices[3 * i + 1];
|
||||
indices[i * 6 + 3] = vertexIndices[3 * i + 2];
|
||||
|
||||
indices[i * 6 + 4] = vertexIndices[3 * i + 2];
|
||||
indices[i * 6 + 5] = vertexIndices[3 * i];
|
||||
}
|
||||
|
||||
vertexIndices.resize(indices.size());
|
||||
for (u32 i = 0; i < indices.size(); ++i)
|
||||
vertexIndices[i] = indices[i];
|
||||
}
|
||||
|
||||
|
||||
float getBoundingBox(const std::vector<float> &vertices) {
|
||||
gl::Vector<float, 4> minWorld(std::numeric_limits<float>::infinity()), maxWorld(-std::numeric_limits<float>::infinity());
|
||||
for (u32 i = 0; i < vertices.size(); i += 3) {
|
||||
if (vertices[i] < minWorld[0]) minWorld[0] = vertices[i];
|
||||
if (vertices[i + 1] < minWorld[1]) minWorld[1] = vertices[i + 1];
|
||||
if (vertices[i + 2] < minWorld[2]) minWorld[2] = vertices[i + 2];
|
||||
|
||||
if (vertices[i] > maxWorld[0]) maxWorld[0] = vertices[i];
|
||||
if (vertices[i + 1] > maxWorld[1]) maxWorld[1] = vertices[i + 1];
|
||||
if (vertices[i + 2] > maxWorld[2]) maxWorld[2] = vertices[i + 2];
|
||||
}
|
||||
|
||||
minWorld[3] = 1;
|
||||
maxWorld[3] = 1;
|
||||
|
||||
gl::Vector<float, 4> minCamera = minWorld, maxCamera = maxWorld;
|
||||
|
||||
if (maxCamera[3] != 0)
|
||||
maxCamera = maxCamera * (1.0f / maxCamera[3]);
|
||||
|
||||
if (minCamera[3] != 0)
|
||||
minCamera = minCamera * (1.0f / minCamera[3]);
|
||||
|
||||
float maxx = std::max(std::fabs(minCamera[0]), std::fabs(maxCamera[0]));
|
||||
float maxy = std::max(std::fabs(minCamera[1]), std::fabs(maxCamera[1]));
|
||||
|
||||
return std::max(maxx, maxy);
|
||||
}
|
||||
|
||||
void setDefaultColors(std::vector<float> &colors, float size, u32 color) {
|
||||
colors.resize(size / 3 * 4);
|
||||
|
||||
float red = float((color >> 0) & 0xFF) / 255.0F;
|
||||
float green = float((color >> 8) & 0xFF) / 255.0F;
|
||||
float blue = float((color >> 16) & 0xFF) / 255.0F;
|
||||
float alpha = float((color >> 24) & 0xFF) / 255.0F;
|
||||
|
||||
for (u32 i = 0; i < colors.size(); i += 4) {
|
||||
colors[i] = red;
|
||||
colors[i + 1] = green;
|
||||
colors[i + 2] = blue;
|
||||
colors[i + 3] = alpha;
|
||||
}
|
||||
}
|
||||
|
||||
void setNormals(const std::vector<float> &vertices, std::vector<float> &normals) {
|
||||
for (u32 i = 0; i < normals.size(); i += 9) {
|
||||
|
||||
auto v1 = gl::Vector<float, 3>({vertices[i], vertices[i + 1], vertices[i + 2]});
|
||||
auto v2 = gl::Vector<float, 3>({vertices[i + 3], vertices[i + 4], vertices[i + 5]});
|
||||
auto v3 = gl::Vector<float, 3>({vertices[i + 6], vertices[i + 7], vertices[i + 8]});
|
||||
|
||||
auto normal = ((v2 - v1).cross(v3 - v1));
|
||||
normals[i] += normal[0];
|
||||
normals[i + 1] += normal[1];
|
||||
normals[i + 2] += normal[2];
|
||||
normals[i + 3] += normal[0];
|
||||
normals[i + 4] += normal[1];
|
||||
normals[i + 5] += normal[2];
|
||||
normals[i + 6] += normal[0];
|
||||
normals[i + 7] += normal[1];
|
||||
normals[i + 8] += normal[2];
|
||||
}
|
||||
for (u32 i = 0; i < normals.size(); i += 3) {
|
||||
auto normal = gl::Vector<float, 3>({normals[i], normals[i + 1], normals[i + 2]});
|
||||
normal.normalize();
|
||||
normals[i] = normal[0];
|
||||
normals[i + 1] = normal[1];
|
||||
normals[i + 2] = normal[2];
|
||||
}
|
||||
}
|
||||
|
||||
void setNormalsWithIndices(const std::vector<float> &vertices, std::vector<float> &normals, const std::vector<u32> &indices) {
|
||||
for (u32 i = 0; i < indices.size(); i += 3) {
|
||||
auto idx = indices[i];
|
||||
auto idx1 = indices[i + 1];
|
||||
auto idx2 = indices[i + 2];
|
||||
|
||||
auto v1 = gl::Vector<float, 3>({vertices[3 * idx], vertices[3 * idx + 1], vertices[3 * idx + 2]});
|
||||
auto v2 = gl::Vector<float, 3>(
|
||||
{vertices[3 * idx1], vertices[3 * idx1 + 1], vertices[3 * idx1 + 2]});
|
||||
auto v3 = gl::Vector<float, 3>(
|
||||
{vertices[3 * idx2], vertices[3 * idx2 + 1], vertices[3 * idx2 + 2]});
|
||||
|
||||
auto weighted = ((v2 - v1).cross(v3 - v1));
|
||||
|
||||
normals[3 * idx] += weighted[0];
|
||||
normals[3 * idx + 1] += weighted[1];
|
||||
normals[3 * idx + 2] += weighted[2];
|
||||
normals[3 * idx1] += weighted[0];
|
||||
normals[3 * idx1 + 1] += weighted[1];
|
||||
normals[3 * idx1 + 2] += weighted[2];
|
||||
normals[3 * idx2] += weighted[0];
|
||||
normals[3 * idx2 + 1] += weighted[1];
|
||||
normals[3 * idx2 + 2] += weighted[2];
|
||||
}
|
||||
for (u32 i = 0; i < normals.size(); i += 3) {
|
||||
|
||||
auto normal = gl::Vector<float, 3>({normals[i], normals[i + 1], normals[i + 2]});
|
||||
auto mag = normal.magnitude();
|
||||
if (mag > 0.001F) {
|
||||
normals[i] = normal[0] / mag;
|
||||
normals[i + 1] = normal[1] / mag;
|
||||
normals[i + 2] = normal[2] / mag;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadVectors(Vectors &vectors, IndexType indexType) {
|
||||
s_max = getBoundingBox(vectors.vertices);
|
||||
|
||||
if (s_drawTexture)
|
||||
setDefaultColors(vectors.colors, vectors.vertices.size(), 0x00000000);
|
||||
else if (vectors.colors.empty())
|
||||
setDefaultColors(vectors.colors, vectors.vertices.size(), 0xFF337FFF);
|
||||
|
||||
if (vectors.normals.empty()) {
|
||||
vectors.normals.resize(vectors.vertices.size());
|
||||
|
||||
if ((indexType == IndexType::U8 && vectors.indices8.empty()) || (indexType == IndexType::Invalid) ||
|
||||
(indexType == IndexType::U16 && vectors.indices16.empty()) ||
|
||||
(indexType == IndexType::U32 && vectors.indices32.empty())) {
|
||||
|
||||
setNormals(vectors.vertices, vectors.normals);
|
||||
|
||||
} else {
|
||||
std::vector<u32> indices;
|
||||
|
||||
if (indexType == IndexType::U16) {
|
||||
indices.resize(vectors.indices16.size());
|
||||
for (u32 i = 0; i < vectors.indices16.size(); ++i)
|
||||
indices[i] = vectors.indices16[i];
|
||||
|
||||
} else if (indexType == IndexType::U8) {
|
||||
indices.resize(vectors.indices8.size());
|
||||
for (u32 i = 0; i < vectors.indices8.size(); ++i)
|
||||
indices[i] = vectors.indices8[i];
|
||||
|
||||
} else {
|
||||
indices.resize(vectors.indices32.size());
|
||||
for (u32 i = 0; i < vectors.indices32.size(); ++i)
|
||||
indices[i] = vectors.indices32[i];
|
||||
}
|
||||
setNormalsWithIndices(vectors.vertices, vectors.normals, indices);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void loadLineVectors(LineVectors &lineVectors, IndexType indexType) {
|
||||
s_max = getBoundingBox(lineVectors.vertices);
|
||||
|
||||
if (lineVectors.colors.empty())
|
||||
setDefaultColors(lineVectors.colors, lineVectors.vertices.size(), 0xFF337FFF);
|
||||
|
||||
std::vector<u32> indices;
|
||||
if (indexType == IndexType::U8)
|
||||
indicesForLines(lineVectors.indices8);
|
||||
else if (indexType == IndexType::U16)
|
||||
indicesForLines(lineVectors.indices16);
|
||||
else
|
||||
indicesForLines(lineVectors.indices32);
|
||||
}
|
||||
|
||||
void processKeyEvent(ImGuiKey key, float &variable, float incr, float accel) {
|
||||
if (ImGui::IsKeyPressed(ImGui::GetKeyIndex(key))) {
|
||||
auto temp = variable + incr * accel;
|
||||
if (variable * temp < 0.0F)
|
||||
variable = 0.0F;
|
||||
else
|
||||
variable = temp;
|
||||
}
|
||||
}
|
||||
|
||||
void processInputEvents(gl::Vector<float, 3> &rotation, gl::Vector<float, 3> &translation, float &scaling, float &nearLimit, float &farLimit) {
|
||||
auto accel = 1.0F;
|
||||
if (ImGui::IsKeyDown(ImGui::GetKeyIndex(ImGuiKey_LeftShift)) ||
|
||||
ImGui::IsKeyDown(ImGui::GetKeyIndex(ImGuiKey_RightShift)))
|
||||
accel = 10.0F;
|
||||
|
||||
auto dragDelta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Middle);
|
||||
if (dragDelta.x != 0) {
|
||||
rotation[1] += dragDelta.x * 0.0075F * accel;
|
||||
}
|
||||
|
||||
if (dragDelta.y != 0) {
|
||||
rotation[0] += dragDelta.y * 0.0075F * accel;
|
||||
}
|
||||
|
||||
ImGui::ResetMouseDragDelta(ImGuiMouseButton_Middle);
|
||||
|
||||
dragDelta = ImGui::GetMouseDragDelta(ImGuiMouseButton_Right);
|
||||
translation[0] += dragDelta.x * 0.0075F * accel;
|
||||
translation[1] -= dragDelta.y * 0.0075F * accel;
|
||||
ImGui::ResetMouseDragDelta(ImGuiMouseButton_Right);
|
||||
|
||||
auto scrollDelta = ImGui::GetIO().MouseWheel;
|
||||
scaling += scrollDelta * 0.1F * accel;
|
||||
|
||||
if (scaling < 0.01F)
|
||||
scaling = 0.01F;
|
||||
|
||||
processKeyEvent(ImGuiKey_Keypad4, translation[0], -0.1F, accel);
|
||||
processKeyEvent(ImGuiKey_Keypad6, translation[0], 0.1F, accel);
|
||||
processKeyEvent(ImGuiKey_Keypad8, translation[1], 0.1F, accel);
|
||||
processKeyEvent(ImGuiKey_Keypad2, translation[1], -0.1F, accel);
|
||||
processKeyEvent(ImGuiKey_Keypad1, translation[2], 0.1F, accel);
|
||||
processKeyEvent(ImGuiKey_Keypad7, translation[2], -0.1F, accel);
|
||||
processKeyEvent(ImGuiKey_Keypad9, nearLimit, -0.01F, accel);
|
||||
processKeyEvent(ImGuiKey_Keypad3, nearLimit, 0.01F, accel);
|
||||
|
||||
if (ImHexApi::System::isDebugBuild()) {
|
||||
processKeyEvent(ImGuiKey_KeypadDivide, farLimit, -1.0F, accel);
|
||||
processKeyEvent(ImGuiKey_KeypadMultiply, farLimit, 1.0F, accel);
|
||||
}
|
||||
|
||||
processKeyEvent(ImGuiKey_KeypadAdd, rotation[2], -0.075F, accel);
|
||||
processKeyEvent(ImGuiKey_KeypadSubtract, rotation[2], 0.075F, accel);
|
||||
rotation[2] = std::fmod(rotation[2], 2 * std::numbers::pi);
|
||||
}
|
||||
|
||||
|
||||
void bindBuffers(Buffers &buffers, const gl::VertexArray &vertexArray, Vectors vectors, IndexType indexType) {
|
||||
buffers.vertices = {};
|
||||
buffers.normals = {};
|
||||
buffers.colors = {};
|
||||
buffers.uv1 = {};
|
||||
buffers.indices8 = {};
|
||||
buffers.indices16 = {};
|
||||
buffers.indices32 = {};
|
||||
|
||||
vertexArray.bind();
|
||||
buffers.vertices = gl::Buffer<float>(gl::BufferType::Vertex, vectors.vertices);
|
||||
buffers.colors = gl::Buffer<float>(gl::BufferType::Vertex, vectors.colors);
|
||||
buffers.normals = gl::Buffer<float>(gl::BufferType::Vertex, vectors.normals);
|
||||
|
||||
if (indexType == IndexType::U8)
|
||||
buffers.indices8 = gl::Buffer<u8>(gl::BufferType::Index, vectors.indices8);
|
||||
else if (indexType == IndexType::U16)
|
||||
buffers.indices16 = gl::Buffer<u16>(gl::BufferType::Index, vectors.indices16);
|
||||
else
|
||||
buffers.indices32 = gl::Buffer<u32>(gl::BufferType::Index, vectors.indices32);
|
||||
|
||||
if (!vectors.uv1.empty())
|
||||
buffers.uv1 = gl::Buffer<float>(gl::BufferType::Vertex, vectors.uv1);
|
||||
|
||||
vertexArray.addBuffer(0, buffers.vertices);
|
||||
vertexArray.addBuffer(1, buffers.colors, 4);
|
||||
vertexArray.addBuffer(2, buffers.normals);
|
||||
|
||||
if (!vectors.uv1.empty())
|
||||
vertexArray.addBuffer(3, buffers.uv1, 2);
|
||||
|
||||
buffers.vertices.unbind();
|
||||
buffers.colors.unbind();
|
||||
buffers.normals.unbind();
|
||||
|
||||
if (!vectors.uv1.empty())
|
||||
buffers.uv1.unbind();
|
||||
|
||||
if (indexType == IndexType::U8)
|
||||
buffers.indices8.unbind();
|
||||
|
||||
else if (indexType == IndexType::U16)
|
||||
buffers.indices16.unbind();
|
||||
|
||||
else if (indexType == IndexType::U32)
|
||||
buffers.indices32.unbind();
|
||||
|
||||
vertexArray.unbind();
|
||||
|
||||
}
|
||||
|
||||
void bindLineBuffers(LineBuffers &lineBuffers, const gl::VertexArray &vertexArray, const LineVectors &lineVectors, IndexType indexType) {
|
||||
lineBuffers.vertices = {};
|
||||
lineBuffers.colors = {};
|
||||
lineBuffers.indices8 = {};
|
||||
lineBuffers.indices16 = {};
|
||||
lineBuffers.indices32 = {};
|
||||
|
||||
vertexArray.bind();
|
||||
lineBuffers.vertices = gl::Buffer<float>(gl::BufferType::Vertex, lineVectors.vertices);
|
||||
lineBuffers.colors = gl::Buffer<float>(gl::BufferType::Vertex, lineVectors.colors);
|
||||
|
||||
if (indexType == IndexType::U8)
|
||||
lineBuffers.indices8 = gl::Buffer<u8>(gl::BufferType::Index, lineVectors.indices8);
|
||||
else if (indexType == IndexType::U16)
|
||||
lineBuffers.indices16 = gl::Buffer<u16>(gl::BufferType::Index, lineVectors.indices16);
|
||||
else
|
||||
lineBuffers.indices32 = gl::Buffer<u32>(gl::BufferType::Index, lineVectors.indices32);
|
||||
|
||||
vertexArray.addBuffer(0, lineBuffers.vertices);
|
||||
vertexArray.addBuffer(1, lineBuffers.colors, 4);
|
||||
|
||||
lineBuffers.vertices.unbind();
|
||||
lineBuffers.colors.unbind();
|
||||
|
||||
if (indexType == IndexType::U8)
|
||||
lineBuffers.indices8.unbind();
|
||||
else if (indexType == IndexType::U16)
|
||||
lineBuffers.indices16.unbind();
|
||||
else if (indexType == IndexType::U32)
|
||||
lineBuffers.indices32.unbind();
|
||||
|
||||
vertexArray.unbind();
|
||||
|
||||
}
|
||||
|
||||
void drawWindow(const ImGuiExt::Texture &texture, ImVec2 &renderingWindowSize, const gl::Matrix<float, 4, 4> &mvp) {
|
||||
auto textureSize = texture.getSize();
|
||||
auto textureWidth = textureSize.x;
|
||||
auto textureHeight = textureSize.y;
|
||||
|
||||
ImVec2 screenPos = ImGui::GetCursorScreenPos();
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_WindowPadding, ImVec2(0, 0));
|
||||
|
||||
ImGui::SetNextWindowSizeConstraints(scaled({ 350, 350 }), ImVec2(FLT_MAX, FLT_MAX));
|
||||
if (ImGui::BeginChild("##image", textureSize, ImGuiChildFlags_ResizeX | ImGuiChildFlags_ResizeY | ImGuiChildFlags_Border, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
|
||||
renderingWindowSize = ImGui::GetContentRegionAvail();
|
||||
|
||||
ImGui::Image(texture, textureSize, ImVec2(0, 1), ImVec2(1, 0));
|
||||
|
||||
if (s_drawAxes) {
|
||||
gl::Matrix<float, 4, 4> axes = gl::Matrix<float, 4, 4>::identity();
|
||||
axes(0, 3) = 1.0f;
|
||||
axes(1, 3) = 1.0f;
|
||||
axes(2, 3) = 1.0f;
|
||||
|
||||
axes = axes * mvp;
|
||||
bool showX = axes(0, 3) > 0.0f;
|
||||
bool showY = axes(1, 3) > 0.0f;
|
||||
bool showZ = axes(2, 3) > 0.0f;
|
||||
|
||||
axes.updateRow(0, axes.getRow(0) * (1.0f / axes(0, 3)));
|
||||
axes.updateRow(1, axes.getRow(1) * (1.0f / axes(1, 3)));
|
||||
axes.updateRow(2, axes.getRow(2) * (1.0f / axes(2, 3)));
|
||||
|
||||
auto axesPosx = (axes.getColumn(0) + 1.0f) * (textureWidth / 2.0f);
|
||||
auto axesPosy = (axes.getColumn(1) + 1.0f) * (-textureHeight / 2.0f) + textureHeight;
|
||||
|
||||
ImDrawList *drawList = ImGui::GetWindowDrawList();
|
||||
|
||||
if (showX)
|
||||
drawList->AddText(ImVec2(axesPosx[0], axesPosy[0]) + screenPos, IM_COL32(255, 0, 0, 255), "X");
|
||||
if (showY)
|
||||
drawList->AddText(ImVec2(axesPosx[1], axesPosy[1]) + screenPos, IM_COL32(0, 255, 0, 255), "Y");
|
||||
if (showZ)
|
||||
drawList->AddText(ImVec2(axesPosx[2], axesPosy[2]) + screenPos, IM_COL32(0, 0, 255, 255), "Z");
|
||||
}
|
||||
|
||||
if (ImHexApi::System::isDebugBuild()) {
|
||||
auto mousePos = ImClamp(ImGui::GetMousePos() - screenPos, { 0, 0 }, textureSize);
|
||||
ImDrawList *drawList = ImGui::GetWindowDrawList();
|
||||
drawList->AddText(
|
||||
screenPos + scaled({ 5, 5 }),
|
||||
ImGui::GetColorU32(ImGuiCol_Text),
|
||||
hex::format("X: {:.5}\nY: {:.5}", mousePos.x, mousePos.y).c_str());
|
||||
}
|
||||
|
||||
}
|
||||
ImGui::EndChild();
|
||||
ImGui::PopStyleVar();
|
||||
|
||||
// Draw axis arrows toggle
|
||||
{
|
||||
ImGui::PushID(1);
|
||||
if (ImGuiExt::DimmedIconToggle(ICON_BI_EMPTY_ARROWS, &s_drawAxes))
|
||||
s_shouldReset = true;
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Draw grid toggle
|
||||
{
|
||||
ImGui::PushID(2);
|
||||
if (ImGuiExt::DimmedIconToggle(s_isPerspective ? ICON_BI_GRID : ICON_VS_SYMBOL_NUMBER, &s_drawGrid))
|
||||
s_shouldReset = true;
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Draw light source toggle
|
||||
{
|
||||
ImGui::PushID(3);
|
||||
if (ImGuiExt::DimmedIconToggle(ICON_VS_LIGHTBULB, &s_drawLightSource))
|
||||
s_shouldReset = true;
|
||||
|
||||
if (ImGui::IsItemClicked(ImGuiMouseButton_Right)) {
|
||||
ImGui::OpenPopup("LightSettings");
|
||||
}
|
||||
|
||||
if (ImGui::BeginPopup("LightSettings")) {
|
||||
if (ImGui::DragFloat3("hex.builtin.pl_visualizer.3d.light_position"_lang, s_lightPosition.data(), 0.05F)) {
|
||||
s_shouldUpdateLightSource = true;
|
||||
}
|
||||
|
||||
ImGui::SliderFloat("hex.builtin.pl_visualizer.3d.ambient_brightness"_lang, &s_lightBrightness.data()[0], 0, 2);
|
||||
ImGui::SliderFloat("hex.builtin.pl_visualizer.3d.diffuse_brightness"_lang, &s_lightBrightness.data()[1], 0, 2);
|
||||
ImGui::SliderFloat("hex.builtin.pl_visualizer.3d.specular_brightness"_lang, &s_lightBrightness.data()[2], 0, 2);
|
||||
ImGui::SliderFloat("hex.builtin.pl_visualizer.3d.object_reflectiveness"_lang, &s_lightBrightness.data()[3], 0, 64);
|
||||
if (ImGui::ColorEdit3("hex.builtin.pl_visualizer.3d.light_color"_lang, s_lightColor.data()))
|
||||
s_shouldUpdateLightSource = true;
|
||||
|
||||
ImGui::EndPopup();
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
|
||||
ImGui::SameLine();
|
||||
|
||||
// Draw projection toggle
|
||||
{
|
||||
ImGui::PushID(4);
|
||||
if (ImGuiExt::DimmedIconToggle(ICON_BI_VIEW_PERSPECTIVE, ICON_BI_VIEW_ORTHO, &s_isPerspective)) {
|
||||
s_shouldReset = true;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
// Draw solid / line mode toggle
|
||||
{
|
||||
ImGui::PushID(4);
|
||||
bool isSolid = s_drawMode == GL_TRIANGLES;
|
||||
if (ImGuiExt::DimmedIconToggle(ICON_BI_MOD_SOLIDIFY, ICON_BI_CUBE , &isSolid)) {
|
||||
s_shouldReset = true;
|
||||
|
||||
s_drawMode = isSolid ? GL_TRIANGLES : GL_LINES;
|
||||
}
|
||||
ImGui::PopID();
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
ImGui::SeparatorEx(ImGuiSeparatorFlags_Vertical);
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGuiExt::DimmedButton("hex.builtin.common.reset"_lang, ImVec2(ImGui::GetContentRegionAvail().x, 0))) {
|
||||
s_translation = { { 0.0F, 0.0F, -3.0F } };
|
||||
s_rotation = { { 0.0F, 0.0F, 0.0F } };
|
||||
s_scaling = 1.0F;
|
||||
}
|
||||
|
||||
// Draw more settings
|
||||
if (ImGui::CollapsingHeader("hex.builtin.pl_visualizer.3d.more_settings"_lang)) {
|
||||
if (ImGuiExt::InputFilePicker("hex.builtin.pl_visualizer.3d.texture_file"_lang, s_texturePath, {}))
|
||||
s_shouldUpdateTexture = true;
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
void draw3DVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
static gl::LightSourceVectors sourceVectors(20);
|
||||
static gl::VertexArray sourceVertexArray = gl::VertexArray();
|
||||
static gl::LightSourceBuffers sourceBuffers(sourceVertexArray, sourceVectors);
|
||||
|
||||
static gl::VertexArray gridVertexArray = gl::VertexArray();
|
||||
static gl::GridVectors gridVectors(9);
|
||||
static gl::GridBuffers gridBuffers(gridVertexArray, gridVectors);
|
||||
|
||||
static gl::VertexArray axesVertexArray = gl::VertexArray();
|
||||
static gl::AxesVectors axesVectors;
|
||||
static gl::AxesBuffers axesBuffers(axesVertexArray, axesVectors);
|
||||
|
||||
static gl::VertexArray vertexArray = gl::VertexArray();
|
||||
static Buffers buffers;
|
||||
static LineBuffers lineBuffers;
|
||||
|
||||
std::shared_ptr<pl::ptrn::Pattern> verticesPattern = arguments[0].toPattern();
|
||||
std::shared_ptr<pl::ptrn::Pattern> indicesPattern = arguments[1].toPattern();
|
||||
std::shared_ptr<pl::ptrn::Pattern> normalsPattern = nullptr;
|
||||
std::shared_ptr<pl::ptrn::Pattern> colorsPattern = nullptr;
|
||||
std::shared_ptr<pl::ptrn::Pattern> uvPattern1 = nullptr;
|
||||
|
||||
std::string textureFile;
|
||||
if (arguments.size() > 2) {
|
||||
normalsPattern = arguments[2].toPattern();
|
||||
if (arguments.size() > 3) {
|
||||
colorsPattern = arguments[3].toPattern();
|
||||
if (arguments.size() > 4) {
|
||||
uvPattern1 = arguments[4].toPattern();
|
||||
if (arguments.size() > 5)
|
||||
textureFile = arguments[5].toString();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (shouldReset)
|
||||
s_shouldReset = true;
|
||||
|
||||
const auto fontSize = ImGui::GetFontSize();
|
||||
const auto framePad = ImGui::GetStyle().FramePadding;
|
||||
float minSize = fontSize * 8_scaled + framePad.x * 20_scaled;
|
||||
minSize = minSize > 200_scaled ? minSize : 200_scaled;
|
||||
|
||||
if (s_renderingWindowSize.x <= 0 || s_renderingWindowSize.y <= 0)
|
||||
s_renderingWindowSize = { minSize, minSize };
|
||||
|
||||
if (!textureFile.empty())
|
||||
s_texturePath = textureFile;
|
||||
else
|
||||
s_drawTexture = false;
|
||||
|
||||
if (s_renderingWindowSize.x < minSize)
|
||||
s_renderingWindowSize.x = minSize;
|
||||
if (s_renderingWindowSize.y < minSize)
|
||||
s_renderingWindowSize.y = minSize;
|
||||
|
||||
gl::Matrix<float, 4, 4> mvp(0);
|
||||
|
||||
processInputEvents(s_rotation, s_translation, s_scaling, s_nearLimit, s_farLimit);
|
||||
|
||||
if (s_shouldReset) {
|
||||
s_shouldReset = false;
|
||||
|
||||
auto *iterable = dynamic_cast<pl::ptrn::IIterable*>(indicesPattern.get());
|
||||
if (iterable != nullptr && iterable->getEntryCount() > 0) {
|
||||
const auto &content = iterable->getEntry(0);
|
||||
if (content->getSize() == 1) {
|
||||
s_indexType = IndexType::U8;
|
||||
} else if (content->getSize() == 2) {
|
||||
s_indexType = IndexType::U16;
|
||||
} else if (content->getSize() == 4) {
|
||||
s_indexType = IndexType::U32;
|
||||
} else {
|
||||
s_indexType = IndexType::Invalid;
|
||||
}
|
||||
}
|
||||
|
||||
if (s_drawMode == GL_TRIANGLES) {
|
||||
Vectors vectors;
|
||||
|
||||
vectors.vertices = patternToArray<float>(verticesPattern.get());
|
||||
if (s_indexType == IndexType::U16)
|
||||
vectors.indices16 = patternToArray<u16>(indicesPattern.get());
|
||||
else if (s_indexType == IndexType::U32)
|
||||
vectors.indices32 = patternToArray<u32>(indicesPattern.get());
|
||||
else if (s_indexType == IndexType::U8)
|
||||
vectors.indices8 = patternToArray<u8>(indicesPattern.get());
|
||||
|
||||
if (colorsPattern != nullptr)
|
||||
vectors.colors = patternToArray<float>(colorsPattern.get());
|
||||
if (normalsPattern != nullptr)
|
||||
vectors.normals = patternToArray<float>(normalsPattern.get());
|
||||
if (uvPattern1 != nullptr)
|
||||
vectors.uv1 = patternToArray<float>(uvPattern1.get());
|
||||
|
||||
loadVectors(vectors, s_indexType);
|
||||
|
||||
bindBuffers(buffers, vertexArray, vectors, s_indexType);
|
||||
} else {
|
||||
LineVectors lineVectors;
|
||||
|
||||
lineVectors.vertices = patternToArray<float>(verticesPattern.get());
|
||||
if (s_indexType == IndexType::U16)
|
||||
lineVectors.indices16 = patternToArray<u16>(indicesPattern.get());
|
||||
|
||||
else if (s_indexType == IndexType::U32)
|
||||
lineVectors.indices32 = patternToArray<u32>(indicesPattern.get());
|
||||
|
||||
else if (s_indexType == IndexType::U8)
|
||||
lineVectors.indices8 = patternToArray<u8>(indicesPattern.get());
|
||||
|
||||
if (colorsPattern != nullptr)
|
||||
lineVectors.colors = patternToArray<float>(colorsPattern.get());
|
||||
|
||||
loadLineVectors(lineVectors, s_indexType);
|
||||
|
||||
bindLineBuffers(lineBuffers, vertexArray, lineVectors, s_indexType);
|
||||
}
|
||||
}
|
||||
|
||||
if (s_shouldUpdateLightSource) {
|
||||
s_shouldUpdateLightSource = false;
|
||||
sourceVectors.moveTo(s_lightPosition);
|
||||
sourceVectors.setColor(s_lightColor[0], s_lightColor[1], s_lightColor[2]);
|
||||
sourceBuffers.moveVertices(sourceVertexArray, sourceVectors);
|
||||
sourceBuffers.updateColors(sourceVertexArray, sourceVectors);
|
||||
}
|
||||
|
||||
{
|
||||
gl::Matrix<float, 4, 4> model(0);
|
||||
gl::Matrix<float, 4, 4> scaledModel(0);
|
||||
gl::Matrix<float, 4, 4> view(0);
|
||||
gl::Matrix<float, 4, 4> projection(0);
|
||||
|
||||
unsigned width = std::floor(s_renderingWindowSize.x);
|
||||
unsigned height = std::floor(s_renderingWindowSize.y);
|
||||
|
||||
gl::FrameBuffer frameBuffer(width, height);
|
||||
gl::Texture renderTexture(width, height);
|
||||
frameBuffer.attachTexture(renderTexture);
|
||||
frameBuffer.bind();
|
||||
|
||||
s_rotate = gl::getRotationMatrix<float>(s_rotation, true, gl::RotationSequence::ZYX);
|
||||
|
||||
gl::Matrix<float, 4, 4> scale = gl::Matrix<float, 4, 4>::identity();
|
||||
gl::Matrix<float, 4, 4> scaleForVertices = gl::Matrix<float, 4, 4>::identity();
|
||||
gl::Matrix<float, 4, 4> translate = gl::Matrix<float, 4, 4>::identity();
|
||||
|
||||
float totalScale;
|
||||
float viewWidth = s_renderingWindowSize.x / 500.0f;
|
||||
float viewHeight = s_renderingWindowSize.y / 500.0f;
|
||||
glViewport(0,0 , GLsizei(renderTexture.getWidth()), GLsizei(renderTexture.getHeight()));
|
||||
glDepthRangef(s_nearLimit, s_farLimit);
|
||||
glClearColor(0.00F, 0.00F, 0.00F, 0.00f);
|
||||
glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT);
|
||||
glEnable(GL_DEPTH_TEST);
|
||||
glEnable(GL_CULL_FACE);
|
||||
|
||||
if (s_isPerspective == 0) {
|
||||
projection = gl::GetOrthographicMatrix(viewWidth, viewHeight, s_nearLimit, s_farLimit, false);
|
||||
totalScale = s_scaling / (std::fabs(s_translation[2]));
|
||||
scale(0, 0) = totalScale;
|
||||
scale(1, 1) = totalScale;
|
||||
scale(2, 2) = totalScale;
|
||||
|
||||
translate(3, 0) = s_translation[0] / std::fabs(s_translation[2]);
|
||||
translate(3, 1) = s_translation[1] / std::fabs(s_translation[2]);
|
||||
translate(3, 2) = s_translation[2];
|
||||
} else {
|
||||
projection = gl::GetPerspectiveMatrix(viewWidth, viewHeight, s_nearLimit, s_farLimit, false);
|
||||
totalScale = s_scaling;
|
||||
scale(0, 0) = totalScale;
|
||||
scale(1, 1) = totalScale;
|
||||
scale(2, 2) = totalScale;
|
||||
|
||||
translate(3, 0) = s_translation[0];
|
||||
translate(3, 1) = s_translation[1];
|
||||
translate(3, 2) = s_translation[2];
|
||||
}
|
||||
totalScale /= (3.0f * s_max);
|
||||
scaleForVertices(0, 0) = totalScale;
|
||||
scaleForVertices(1, 1) = totalScale;
|
||||
scaleForVertices(2, 2) = totalScale;
|
||||
|
||||
model = s_rotate * scale;
|
||||
scaledModel = s_rotate * scaleForVertices;
|
||||
view = translate;
|
||||
mvp = model * view * projection;
|
||||
|
||||
if (s_drawMode == GL_TRIANGLES) {
|
||||
static gl::Shader shader = gl::Shader(romfs::get("shaders/default/vertex.glsl").string(),
|
||||
romfs::get("shaders/default/fragment.glsl").string());
|
||||
shader.bind();
|
||||
|
||||
shader.setUniform("modelScale", scaledModel);
|
||||
shader.setUniform("modelMatrix", model);
|
||||
shader.setUniform("viewMatrix", view);
|
||||
shader.setUniform("projectionMatrix",projection);
|
||||
shader.setUniform("lightPosition", s_lightPosition);
|
||||
shader.setUniform("lightBrightness", s_lightBrightness);
|
||||
shader.setUniform("lightColor", s_lightColor);
|
||||
|
||||
vertexArray.bind();
|
||||
if (s_shouldUpdateTexture) {
|
||||
s_shouldUpdateTexture = false;
|
||||
s_modelTexture = ImGuiExt::Texture(s_texturePath);
|
||||
}
|
||||
|
||||
if (s_drawTexture)
|
||||
glBindTexture(GL_TEXTURE_2D, s_modelTexture);
|
||||
|
||||
if (s_indexType == IndexType::U8) {
|
||||
|
||||
buffers.indices8.bind();
|
||||
if (buffers.indices8.getSize() == 0)
|
||||
buffers.vertices.draw(s_drawMode);
|
||||
else
|
||||
buffers.indices8.draw(s_drawMode);
|
||||
buffers.indices8.unbind();
|
||||
|
||||
} else if (s_indexType == IndexType::U16) {
|
||||
|
||||
buffers.indices16.bind();
|
||||
if (buffers.indices16.getSize() == 0)
|
||||
buffers.vertices.draw(s_drawMode);
|
||||
else
|
||||
buffers.indices16.draw(s_drawMode);
|
||||
buffers.indices16.unbind();
|
||||
} else {
|
||||
|
||||
buffers.indices32.bind();
|
||||
if (buffers.indices32.getSize() == 0)
|
||||
buffers.vertices.draw(s_drawMode);
|
||||
else
|
||||
buffers.indices32.draw(s_drawMode);
|
||||
buffers.indices32.unbind();
|
||||
|
||||
|
||||
}
|
||||
} else {
|
||||
static gl::Shader lineShader = gl::Shader(
|
||||
romfs::get("shaders/default/lineVertex.glsl").string(),
|
||||
romfs::get("shaders/default/lineFragment.glsl").string());
|
||||
lineShader.bind();
|
||||
lineShader.setUniform("modelMatrix", scaledModel);
|
||||
lineShader.setUniform("viewMatrix", view);
|
||||
lineShader.setUniform("projectionMatrix", projection);
|
||||
vertexArray.bind();
|
||||
if (s_indexType == IndexType::U8) {
|
||||
lineBuffers.indices8.bind();
|
||||
if (lineBuffers.indices8.getSize() == 0)
|
||||
lineBuffers.vertices.draw(s_drawMode);
|
||||
else
|
||||
lineBuffers.indices8.draw(s_drawMode);
|
||||
lineBuffers.indices8.unbind();
|
||||
|
||||
} else if (s_indexType == IndexType::U16) {
|
||||
lineBuffers.indices16.bind();
|
||||
if (lineBuffers.indices16.getSize() == 0)
|
||||
lineBuffers.vertices.draw(s_drawMode);
|
||||
else
|
||||
lineBuffers.indices16.draw(s_drawMode);
|
||||
lineBuffers.indices16.unbind();
|
||||
} else {
|
||||
lineBuffers.indices32.bind();
|
||||
if (lineBuffers.indices32.getSize() == 0)
|
||||
lineBuffers.vertices.draw(s_drawMode);
|
||||
else
|
||||
lineBuffers.indices32.draw(s_drawMode);
|
||||
lineBuffers.indices32.unbind();
|
||||
}
|
||||
}
|
||||
|
||||
if (s_drawGrid || s_drawAxes) {
|
||||
static auto gridAxesShader = gl::Shader(
|
||||
romfs::get("shaders/default/lineVertex.glsl").string(),
|
||||
romfs::get("shaders/default/lineFragment.glsl").string());
|
||||
gridAxesShader.bind();
|
||||
|
||||
gridAxesShader.setUniform("modelMatrix", model);
|
||||
gridAxesShader.setUniform("viewMatrix", view);
|
||||
gridAxesShader.setUniform("projectionMatrix", projection);
|
||||
|
||||
if (s_drawGrid) {
|
||||
gridVertexArray.bind();
|
||||
gridBuffers.getIndices().bind();
|
||||
gridBuffers.getIndices().draw(GL_LINES);
|
||||
gridBuffers.getIndices().unbind();
|
||||
gridVertexArray.unbind();
|
||||
}
|
||||
|
||||
if (s_drawAxes) {
|
||||
axesVertexArray.bind();
|
||||
axesBuffers.getIndices().bind();
|
||||
axesBuffers.getIndices().draw(GL_LINES);
|
||||
axesBuffers.getIndices().unbind();
|
||||
axesVertexArray.unbind();
|
||||
}
|
||||
gridAxesShader.unbind();
|
||||
}
|
||||
if (s_drawLightSource) {
|
||||
static auto sourceShader = gl::Shader(
|
||||
romfs::get("shaders/default/lightVertex.glsl").string(),
|
||||
romfs::get("shaders/default/lightFragment.glsl").string());
|
||||
sourceShader.bind();
|
||||
|
||||
sourceShader.setUniform("modelMatrix", model);
|
||||
sourceShader.setUniform("viewMatrix", view);
|
||||
sourceShader.setUniform("projectionMatrix", projection);
|
||||
|
||||
sourceVertexArray.bind();
|
||||
sourceBuffers.getIndices().bind();
|
||||
sourceBuffers.getIndices().draw(GL_TRIANGLES);
|
||||
sourceBuffers.getIndices().unbind();
|
||||
sourceVertexArray.unbind();
|
||||
sourceShader.unbind();
|
||||
}
|
||||
|
||||
vertexArray.unbind();
|
||||
frameBuffer.unbind();
|
||||
|
||||
s_texture = ImGuiExt::Texture(renderTexture.release(), GLsizei(renderTexture.getWidth()), GLsizei(renderTexture.getHeight()));
|
||||
|
||||
drawWindow(s_texture, s_renderingWindowSize, mvp);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,28 @@
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
#include <content/pl_visualizers/visualizer_helpers.hpp>
|
||||
|
||||
#include <implot.h>
|
||||
#include <imgui.h>
|
||||
#include <content/helpers/diagrams.hpp>
|
||||
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
void drawChunkBasedEntropyVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
// Variable used to store the result to avoid having to recalculate the result at each frame
|
||||
static DiagramChunkBasedEntropyAnalysis analyzer;
|
||||
|
||||
// Compute data
|
||||
if (shouldReset) {
|
||||
auto pattern = arguments[0].toPattern();
|
||||
auto chunkSize = arguments[1].toUnsigned();
|
||||
analyzer.process(pattern->getBytes(), chunkSize);
|
||||
}
|
||||
|
||||
// Show results
|
||||
analyzer.draw(ImVec2(400, 250), ImPlotFlags_CanvasOnly);
|
||||
}
|
||||
|
||||
}
|
116
plugins/builtin/source/content/pl_visualizers/coordinates.cpp
Normal file
116
plugins/builtin/source/content/pl_visualizers/coordinates.cpp
Normal file
@ -0,0 +1,116 @@
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
#include <content/pl_visualizers/visualizer_helpers.hpp>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <hex/api/task_manager.hpp>
|
||||
#include <hex/api/localization_manager.hpp>
|
||||
#include <hex/helpers/http_requests.hpp>
|
||||
#include <nlohmann/json.hpp>
|
||||
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
#include <romfs/romfs.hpp>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
void drawCoordinateVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
static ImVec2 coordinate;
|
||||
static double latitude, longitude;
|
||||
static std::string address;
|
||||
static std::mutex addressMutex;
|
||||
static TaskHolder addressTask;
|
||||
|
||||
static auto mapTexture = ImGuiExt::Texture(romfs::get("assets/common/map.jpg").span());
|
||||
static ImVec2 mapSize = scaled(ImVec2(500, 500 / mapTexture.getAspectRatio()));
|
||||
|
||||
if (shouldReset) {
|
||||
std::scoped_lock lock(addressMutex);
|
||||
|
||||
address.clear();
|
||||
latitude = arguments[0].toFloatingPoint();
|
||||
longitude = arguments[1].toFloatingPoint();
|
||||
|
||||
// Convert latitude and longitude to X/Y coordinates on the image
|
||||
coordinate.x = float((longitude + 180) / 360 * mapSize.x);
|
||||
coordinate.y = float((-latitude + 90) / 180 * mapSize.y);
|
||||
}
|
||||
|
||||
const auto startPos = ImGui::GetWindowPos() + ImGui::GetCursorPos();
|
||||
|
||||
// Draw background image
|
||||
ImGui::Image(mapTexture, mapSize);
|
||||
|
||||
// Draw Longitude / Latitude text below image
|
||||
ImGui::PushTextWrapPos(startPos.x + mapSize.x);
|
||||
ImGuiExt::TextFormattedWrapped("{}: {:.0f}° {:.0f}' {:.4f}\" {} | {}: {:.0f}° {:.0f}' {:.4f}\" {}",
|
||||
"hex.builtin.pl_visualizer.coordinates.latitude"_lang,
|
||||
std::floor(std::abs(latitude)),
|
||||
std::floor(std::abs(latitude - std::floor(latitude)) * 60),
|
||||
(std::abs(latitude - std::floor(latitude)) * 60 - std::floor(std::abs(latitude - std::floor(latitude)) * 60)) * 60,
|
||||
latitude >= 0 ? "N" : "S",
|
||||
"hex.builtin.pl_visualizer.coordinates.longitude"_lang,
|
||||
std::floor(std::abs(longitude)),
|
||||
std::floor(std::abs(longitude - std::floor(longitude)) * 60),
|
||||
(std::abs(longitude - std::floor(longitude)) * 60 - std::floor(std::abs(longitude - std::floor(longitude)) * 60)) * 60,
|
||||
longitude >= 0 ? "E" : "W"
|
||||
);
|
||||
ImGui::PopTextWrapPos();
|
||||
|
||||
if (addressTask.isRunning()) {
|
||||
ImGuiExt::TextSpinner("hex.builtin.pl_visualizer.coordinates.querying"_lang);
|
||||
} else if (address.empty()) {
|
||||
if (ImGuiExt::DimmedButton("hex.builtin.pl_visualizer.coordinates.query"_lang)) {
|
||||
addressTask = TaskManager::createBackgroundTask("hex.builtin.pl_visualizer.coordinates.querying"_lang, [lat = latitude, lon = longitude](auto &) {
|
||||
constexpr static auto ApiURL = "https://geocode.maps.co/reverse?lat={}&lon={}&format=jsonv2";
|
||||
|
||||
HttpRequest request("GET", hex::format(ApiURL, lat, lon));
|
||||
auto response = request.execute().get();
|
||||
|
||||
if (!response.isSuccess())
|
||||
return;
|
||||
|
||||
try {
|
||||
|
||||
auto json = nlohmann::json::parse(response.getData());
|
||||
auto jsonAddr = json["address"];
|
||||
|
||||
std::scoped_lock lock(addressMutex);
|
||||
if (jsonAddr.contains("village")) {
|
||||
address = hex::format("{} {}, {} {}",
|
||||
jsonAddr["village"].get<std::string>(),
|
||||
jsonAddr["county"].get<std::string>(),
|
||||
jsonAddr["state"].get<std::string>(),
|
||||
jsonAddr["country"].get<std::string>());
|
||||
} else if (jsonAddr.contains("city")) {
|
||||
address = hex::format("{}, {} {}, {} {}",
|
||||
jsonAddr["road"].get<std::string>(),
|
||||
jsonAddr["quarter"].get<std::string>(),
|
||||
jsonAddr["city"].get<std::string>(),
|
||||
jsonAddr["state"].get<std::string>(),
|
||||
jsonAddr["country"].get<std::string>());
|
||||
}
|
||||
} catch (std::exception &) {
|
||||
address = std::string("hex.builtin.pl_visualizer.coordinates.querying_no_address"_lang);
|
||||
}
|
||||
});
|
||||
}
|
||||
} else {
|
||||
ImGui::PushTextWrapPos(startPos.x + mapSize.x);
|
||||
ImGuiExt::TextFormattedWrapped("{}", address);
|
||||
ImGui::PopTextWrapPos();
|
||||
}
|
||||
|
||||
// Draw crosshair pointing to the coordinates
|
||||
{
|
||||
constexpr static u32 CrossHairColor = 0xFF00D0D0;
|
||||
constexpr static u32 BorderColor = 0xFF000000;
|
||||
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
drawList->AddLine(startPos + ImVec2(coordinate.x, 0), startPos + ImVec2(coordinate.x, mapSize.y), CrossHairColor, 2_scaled);
|
||||
drawList->AddLine(startPos + ImVec2(0, coordinate.y), startPos + ImVec2(mapSize.x, coordinate.y), CrossHairColor, 2_scaled);
|
||||
drawList->AddCircleFilled(startPos + coordinate, 5, CrossHairColor);
|
||||
drawList->AddCircle(startPos + coordinate, 5, BorderColor);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,70 @@
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
#include <content/pl_visualizers/visualizer_helpers.hpp>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <capstone/capstone.h>
|
||||
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
#include <hex/api/localization_manager.hpp>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
void drawDisassemblyVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
struct Disassembly {
|
||||
u64 address;
|
||||
std::vector<u8> bytes;
|
||||
std::string instruction;
|
||||
};
|
||||
|
||||
static std::vector<Disassembly> disassembly;
|
||||
if (shouldReset) {
|
||||
auto pattern = arguments[0].toPattern();
|
||||
auto baseAddress = arguments[1].toUnsigned();
|
||||
auto architecture = arguments[2].toUnsigned();
|
||||
auto mode = arguments[3].toUnsigned();
|
||||
|
||||
disassembly.clear();
|
||||
|
||||
csh capstone;
|
||||
if (cs_open(static_cast<cs_arch>(architecture), static_cast<cs_mode>(mode), &capstone) == CS_ERR_OK) {
|
||||
cs_option(capstone, CS_OPT_SKIPDATA, CS_OPT_ON);
|
||||
|
||||
auto data = pattern->getBytes();
|
||||
cs_insn *instructions = nullptr;
|
||||
|
||||
size_t instructionCount = cs_disasm(capstone, data.data(), data.size(), baseAddress, 0, &instructions);
|
||||
for (size_t i = 0; i < instructionCount; i++) {
|
||||
disassembly.push_back({ instructions[i].address, { instructions[i].bytes, instructions[i].bytes + instructions[i].size }, hex::format("{} {}", instructions[i].mnemonic, instructions[i].op_str) });
|
||||
}
|
||||
cs_free(instructions, instructionCount);
|
||||
cs_close(&capstone);
|
||||
}
|
||||
}
|
||||
|
||||
if (ImGui::BeginTable("##disassembly", 3, ImGuiTableFlags_Borders | ImGuiTableFlags_RowBg | ImGuiTableFlags_Resizable | ImGuiTableFlags_Reorderable | ImGuiTableFlags_SizingFixedFit | ImGuiTableFlags_ScrollY, scaled(ImVec2(0, 300)))) {
|
||||
ImGui::TableSetupScrollFreeze(0, 1);
|
||||
ImGui::TableSetupColumn("hex.builtin.common.address"_lang);
|
||||
ImGui::TableSetupColumn("hex.builtin.common.bytes"_lang);
|
||||
ImGui::TableSetupColumn("hex.builtin.common.instruction"_lang);
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
for (auto &entry : disassembly) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
ImGuiExt::TextFormatted("0x{0:08X}", entry.address);
|
||||
ImGui::TableNextColumn();
|
||||
std::string bytes;
|
||||
for (auto byte : entry.bytes)
|
||||
bytes += hex::format("{0:02X} ", byte);
|
||||
ImGui::TextUnformatted(bytes.c_str());
|
||||
ImGui::TableNextColumn();
|
||||
ImGui::TextUnformatted(entry.instruction.c_str());
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
43
plugins/builtin/source/content/pl_visualizers/hex_viewer.cpp
Normal file
43
plugins/builtin/source/content/pl_visualizers/hex_viewer.cpp
Normal file
@ -0,0 +1,43 @@
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
#include <content/pl_visualizers/visualizer_helpers.hpp>
|
||||
|
||||
#include <imgui.h>
|
||||
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
#include <ui/hex_editor.hpp>
|
||||
#include <content/providers/memory_file_provider.hpp>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
void drawHexVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
static ui::HexEditor editor;
|
||||
static std::unique_ptr<MemoryFileProvider> dataProvider;
|
||||
|
||||
if (shouldReset) {
|
||||
auto pattern = arguments[0].toPattern();
|
||||
std::vector<u8> data;
|
||||
|
||||
dataProvider = std::make_unique<MemoryFileProvider>();
|
||||
try {
|
||||
data = pattern->getBytes();
|
||||
} catch (const std::exception &) {
|
||||
dataProvider->resize(0);
|
||||
throw;
|
||||
}
|
||||
|
||||
dataProvider->resize(data.size());
|
||||
dataProvider->writeRaw(0x00, data.data(), data.size());
|
||||
dataProvider->setReadOnly(true);
|
||||
|
||||
editor.setProvider(dataProvider.get());
|
||||
}
|
||||
|
||||
if (ImGui::BeginChild("##editor", scaled(ImVec2(600, 400)), false, ImGuiWindowFlags_NoScrollbar | ImGuiWindowFlags_NoScrollWithMouse)) {
|
||||
editor.draw();
|
||||
|
||||
ImGui::EndChild();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
61
plugins/builtin/source/content/pl_visualizers/image.cpp
Normal file
61
plugins/builtin/source/content/pl_visualizers/image.cpp
Normal file
@ -0,0 +1,61 @@
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
#include <content/pl_visualizers/visualizer_helpers.hpp>
|
||||
|
||||
#include <implot.h>
|
||||
#include <imgui.h>
|
||||
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
void drawImageVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
static ImGuiExt::Texture texture;
|
||||
static float scale = 1.0F;
|
||||
|
||||
if (shouldReset) {
|
||||
auto pattern = arguments[0].toPattern();
|
||||
|
||||
auto data = pattern->getBytes();
|
||||
texture = ImGuiExt::Texture(data.data(), data.size());
|
||||
scale = 200_scaled / texture.getSize().x;
|
||||
}
|
||||
|
||||
if (texture.isValid())
|
||||
ImGui::Image(texture, texture.getSize() * scale);
|
||||
|
||||
if (ImGui::IsWindowHovered()) {
|
||||
auto scrollDelta = ImGui::GetIO().MouseWheel;
|
||||
if (scrollDelta != 0.0F) {
|
||||
scale += scrollDelta * 0.1F;
|
||||
scale = std::clamp(scale, 0.1F, 10.0F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
void drawBitmapVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
static ImGuiExt::Texture texture;
|
||||
static float scale = 1.0F;
|
||||
|
||||
if (shouldReset) {
|
||||
auto pattern = arguments[0].toPattern();
|
||||
auto width = arguments[1].toUnsigned();
|
||||
auto height = arguments[2].toUnsigned();
|
||||
|
||||
auto data = pattern->getBytes();
|
||||
texture = ImGuiExt::Texture(data.data(), data.size(), width, height);
|
||||
}
|
||||
|
||||
if (texture.isValid())
|
||||
ImGui::Image(texture, texture.getSize() * scale);
|
||||
|
||||
if (ImGui::IsWindowHovered()) {
|
||||
auto scrollDelta = ImGui::GetIO().MouseWheel;
|
||||
if (scrollDelta != 0.0F) {
|
||||
scale += scrollDelta * 0.1F;
|
||||
scale = std::clamp(scale, 0.1F, 10.0F);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
}
|
30
plugins/builtin/source/content/pl_visualizers/line_plot.cpp
Normal file
30
plugins/builtin/source/content/pl_visualizers/line_plot.cpp
Normal file
@ -0,0 +1,30 @@
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
#include <content/pl_visualizers/visualizer_helpers.hpp>
|
||||
|
||||
#include <implot.h>
|
||||
#include <imgui.h>
|
||||
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
void drawLinePlotVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
static std::vector<float> values;
|
||||
auto dataPattern = arguments[0].toPattern();
|
||||
|
||||
if (ImPlot::BeginPlot("##plot", ImVec2(400, 250), ImPlotFlags_CanvasOnly)) {
|
||||
ImPlot::SetupAxes("X", "Y", ImPlotAxisFlags_AutoFit, ImPlotAxisFlags_AutoFit);
|
||||
|
||||
if (shouldReset) {
|
||||
values.clear();
|
||||
values = sampleData(patternToArray<float>(dataPattern.get()), ImPlot::GetPlotSize().x * 4);
|
||||
}
|
||||
|
||||
ImPlot::PlotLine("##line", values.data(), values.size());
|
||||
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,33 @@
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
#include <content/pl_visualizers/visualizer_helpers.hpp>
|
||||
|
||||
#include <implot.h>
|
||||
#include <imgui.h>
|
||||
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
void drawScatterPlotVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
static std::vector<float> xValues, yValues;
|
||||
|
||||
auto xPattern = arguments[0].toPattern();
|
||||
auto yPattern = arguments[1].toPattern();
|
||||
|
||||
if (ImPlot::BeginPlot("##plot", ImVec2(400, 250), ImPlotFlags_CanvasOnly)) {
|
||||
ImPlot::SetupAxes("X", "Y", ImPlotAxisFlags_AutoFit, ImPlotAxisFlags_AutoFit);
|
||||
|
||||
if (shouldReset) {
|
||||
xValues.clear(); yValues.clear();
|
||||
xValues = sampleData(patternToArray<float>(xPattern.get()), ImPlot::GetPlotSize().x * 4);
|
||||
yValues = sampleData(patternToArray<float>(yPattern.get()), ImPlot::GetPlotSize().x * 4);
|
||||
}
|
||||
|
||||
ImPlot::PlotScatter("##scatter", xValues.data(), yValues.data(), xValues.size());
|
||||
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
124
plugins/builtin/source/content/pl_visualizers/sound.cpp
Normal file
124
plugins/builtin/source/content/pl_visualizers/sound.cpp
Normal file
@ -0,0 +1,124 @@
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
#include <content/pl_visualizers/visualizer_helpers.hpp>
|
||||
|
||||
#include <implot.h>
|
||||
#include <imgui.h>
|
||||
#include <miniaudio.h>
|
||||
#include <fonts/codicons_font.h>
|
||||
#include <hex/api/task_manager.hpp>
|
||||
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
void drawSoundVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool shouldReset, std::span<const pl::core::Token::Literal> arguments) {
|
||||
auto wavePattern = arguments[0].toPattern();
|
||||
auto channels = arguments[1].toUnsigned();
|
||||
auto sampleRate = arguments[2].toUnsigned();
|
||||
|
||||
static std::vector<i16> waveData, sampledData;
|
||||
static ma_device audioDevice;
|
||||
static ma_device_config deviceConfig;
|
||||
static bool shouldStop = false;
|
||||
static u64 index = 0;
|
||||
static TaskHolder resetTask;
|
||||
|
||||
if (sampleRate == 0)
|
||||
throw std::logic_error(hex::format("Invalid sample rate: {}", sampleRate));
|
||||
else if (channels == 0)
|
||||
throw std::logic_error(hex::format("Invalid channel count: {}", channels));
|
||||
|
||||
if (shouldReset) {
|
||||
waveData.clear();
|
||||
|
||||
resetTask = TaskManager::createTask("Visualizing...", TaskManager::NoProgress, [=](Task &) {
|
||||
ma_device_stop(&audioDevice);
|
||||
waveData = patternToArray<i16>(wavePattern.get());
|
||||
sampledData = sampleData(waveData, 300_scaled * 4);
|
||||
index = 0;
|
||||
|
||||
deviceConfig = ma_device_config_init(ma_device_type_playback);
|
||||
deviceConfig.playback.format = ma_format_s16;
|
||||
deviceConfig.playback.channels = channels;
|
||||
deviceConfig.sampleRate = sampleRate;
|
||||
deviceConfig.pUserData = &waveData;
|
||||
deviceConfig.dataCallback = [](ma_device *device, void *pOutput, const void *, ma_uint32 frameCount) {
|
||||
if (index >= waveData.size()) {
|
||||
index = 0;
|
||||
shouldStop = true;
|
||||
return;
|
||||
}
|
||||
|
||||
ma_copy_pcm_frames(pOutput, waveData.data() + index, frameCount, device->playback.format, device->playback.channels);
|
||||
index += frameCount;
|
||||
};
|
||||
|
||||
ma_device_init(nullptr, &deviceConfig, &audioDevice);
|
||||
});
|
||||
}
|
||||
|
||||
ImGui::BeginDisabled(resetTask.isRunning());
|
||||
|
||||
ImPlot::PushStyleVar(ImPlotStyleVar_PlotPadding, ImVec2(0, 0));
|
||||
if (ImPlot::BeginPlot("##amplitude_plot", scaled(ImVec2(300, 80)), ImPlotFlags_CanvasOnly | ImPlotFlags_NoFrame | ImPlotFlags_NoInputs)) {
|
||||
ImPlot::SetupAxes("##time", "##amplitude", ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_NoMenus, ImPlotAxisFlags_NoDecorations | ImPlotAxisFlags_NoMenus);
|
||||
ImPlot::SetupAxesLimits(0, waveData.size(), std::numeric_limits<i16>::min(), std::numeric_limits<i16>::max(), ImGuiCond_Always);
|
||||
|
||||
double dragPos = index;
|
||||
if (ImPlot::DragLineX(1, &dragPos, ImGui::GetStyleColorVec4(ImGuiCol_Text))) {
|
||||
if (dragPos < 0) dragPos = 0;
|
||||
if (dragPos >= waveData.size()) dragPos = waveData.size() - 1;
|
||||
|
||||
index = dragPos;
|
||||
}
|
||||
|
||||
ImPlot::PlotLine("##audio", sampledData.data(), sampledData.size());
|
||||
|
||||
ImPlot::EndPlot();
|
||||
}
|
||||
ImPlot::PopStyleVar();
|
||||
|
||||
{
|
||||
const u64 min = 0, max = waveData.size();
|
||||
ImGui::PushItemWidth(300_scaled);
|
||||
ImGui::PushStyleVar(ImGuiStyleVar_FramePadding, ImVec2(0, 0));
|
||||
ImGui::SliderScalar("##index", ImGuiDataType_U64, &index, &min, &max, "");
|
||||
ImGui::PopStyleVar();
|
||||
ImGui::PopItemWidth();
|
||||
}
|
||||
|
||||
if (shouldStop) {
|
||||
shouldStop = false;
|
||||
ma_device_stop(&audioDevice);
|
||||
}
|
||||
|
||||
bool playing = ma_device_is_started(&audioDevice);
|
||||
|
||||
if (ImGuiExt::IconButton(playing ? ICON_VS_DEBUG_PAUSE : ICON_VS_PLAY, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarGreen))) {
|
||||
if (playing)
|
||||
ma_device_stop(&audioDevice);
|
||||
else
|
||||
ma_device_start(&audioDevice);
|
||||
}
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (ImGuiExt::IconButton(ICON_VS_DEBUG_STOP, ImGuiExt::GetCustomColorVec4(ImGuiCustomCol_ToolbarRed))) {
|
||||
index = 0;
|
||||
ma_device_stop(&audioDevice);
|
||||
}
|
||||
|
||||
ImGui::EndDisabled();
|
||||
|
||||
ImGui::SameLine();
|
||||
|
||||
if (resetTask.isRunning())
|
||||
ImGuiExt::TextSpinner("");
|
||||
else
|
||||
ImGuiExt::TextFormatted("{:02d}:{:02d} / {:02d}:{:02d}",
|
||||
(index / sampleRate) / 60, (index / sampleRate) % 60,
|
||||
(waveData.size() / sampleRate) / 60, (waveData.size() / sampleRate) % 60);
|
||||
}
|
||||
|
||||
}
|
107
plugins/builtin/source/content/pl_visualizers/timestamp.cpp
Normal file
107
plugins/builtin/source/content/pl_visualizers/timestamp.cpp
Normal file
@ -0,0 +1,107 @@
|
||||
#include <hex/helpers/utils.hpp>
|
||||
|
||||
#include <content/pl_visualizers/visualizer_helpers.hpp>
|
||||
|
||||
#include <numbers>
|
||||
|
||||
#include <imgui.h>
|
||||
#include <hex/api/imhex_api.hpp>
|
||||
|
||||
#include <hex/ui/imgui_imhex_extensions.h>
|
||||
|
||||
#include <chrono>
|
||||
#include <fmt/chrono.h>
|
||||
|
||||
namespace hex::plugin::builtin {
|
||||
|
||||
void drawTimestampVisualizer(pl::ptrn::Pattern &, pl::ptrn::IIterable &, bool, std::span<const pl::core::Token::Literal> arguments) {
|
||||
time_t timestamp = arguments[0].toUnsigned();
|
||||
auto tm = fmt::gmtime(timestamp);
|
||||
auto date = std::chrono::year_month_day(std::chrono::year(tm.tm_year + 1900), std::chrono::month(tm.tm_mon + 1), std::chrono::day(tm.tm_mday));
|
||||
|
||||
auto lastMonthDay = std::chrono::year_month_day_last(date.year(), date.month() / std::chrono::last);
|
||||
auto firstWeekDay = std::chrono::weekday(std::chrono::year_month_day(date.year(), date.month(), std::chrono::day(1)));
|
||||
|
||||
const auto scale = 1_scaled * (ImHexApi::Fonts::getFontSize() / ImHexApi::Fonts::DefaultFontSize);
|
||||
|
||||
// Draw calendar
|
||||
if (ImGui::BeginTable("##month_table", 2)) {
|
||||
ImGui::TableNextRow();
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
// Draw centered month name and year
|
||||
ImGuiExt::TextFormattedCenteredHorizontal("{:%B %Y}", tm);
|
||||
|
||||
if (ImGui::BeginTable("##days_table", 7, ImGuiTableFlags_Borders | ImGuiTableFlags_NoHostExtendX, ImVec2(160, 120) * scale)) {
|
||||
constexpr static auto ColumnFlags = ImGuiTableColumnFlags_WidthFixed | ImGuiTableColumnFlags_NoResize | ImGuiTableColumnFlags_NoReorder | ImGuiTableColumnFlags_NoHide;
|
||||
ImGui::TableSetupColumn("M", ColumnFlags);
|
||||
ImGui::TableSetupColumn("T", ColumnFlags);
|
||||
ImGui::TableSetupColumn("W", ColumnFlags);
|
||||
ImGui::TableSetupColumn("T", ColumnFlags);
|
||||
ImGui::TableSetupColumn("F", ColumnFlags);
|
||||
ImGui::TableSetupColumn("S", ColumnFlags);
|
||||
ImGui::TableSetupColumn("S", ColumnFlags);
|
||||
|
||||
ImGui::TableHeadersRow();
|
||||
|
||||
ImGui::TableNextRow();
|
||||
|
||||
// Skip days before the first day of the month
|
||||
for (u8 i = 0; i < firstWeekDay.c_encoding() - 1; ++i)
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
// Draw days
|
||||
for (u8 i = 1; i <= u32(lastMonthDay.day()); ++i) {
|
||||
ImGui::TableNextColumn();
|
||||
ImGuiExt::TextFormatted("{:02}", i);
|
||||
|
||||
if (std::chrono::day(i) == date.day())
|
||||
ImGui::TableSetBgColor(ImGuiTableBgTarget_CellBg, ImGuiExt::GetCustomColorU32(ImGuiCustomCol_ToolbarRed));
|
||||
|
||||
if (std::chrono::weekday(std::chrono::year_month_day(date.year(), date.month(), std::chrono::day(i))) == std::chrono::Sunday)
|
||||
ImGui::TableNextRow();
|
||||
}
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
|
||||
ImGui::TableNextColumn();
|
||||
|
||||
// Draw analog clock
|
||||
const auto size = ImVec2(120, 120) * scale;
|
||||
if (ImGui::BeginChild("##clock", size + ImVec2(0, ImGui::GetTextLineHeightWithSpacing()))) {
|
||||
// Draw centered digital hour, minute and seconds
|
||||
ImGuiExt::TextFormattedCenteredHorizontal("{:%H:%M:%S}", tm);
|
||||
auto drawList = ImGui::GetWindowDrawList();
|
||||
const auto center = ImGui::GetWindowPos() + ImVec2(0, ImGui::GetTextLineHeightWithSpacing()) + size / 2;
|
||||
|
||||
// Draw clock face
|
||||
drawList->AddCircle(center, size.x / 2, ImGui::GetColorU32(ImGuiCol_TextDisabled), 0);
|
||||
|
||||
auto sectionPos = [](float i) {
|
||||
return ImVec2(std::sin(-i * 30.0F * std::numbers::pi / 180.0F + std::numbers::pi / 2), std::cos(-i * 30.0F * std::numbers::pi / 180.0F + std::numbers::pi / 2));
|
||||
};
|
||||
|
||||
// Draw clock sections and numbers
|
||||
for (u8 i = 0; i < 12; ++i) {
|
||||
auto text = hex::format("{}", (((i + 2) % 12) + 1));
|
||||
drawList->AddLine(center + sectionPos(i) * size / 2.2, center + sectionPos(i) * size / 2, ImGui::GetColorU32(ImGuiCol_TextDisabled), 1_scaled);
|
||||
drawList->AddText(center + sectionPos(i) * size / 3 - ImGui::CalcTextSize(text.c_str()) / 2, ImGui::GetColorU32(ImGuiCol_Text), text.c_str());
|
||||
}
|
||||
|
||||
// Draw hour hand
|
||||
drawList->AddLine(center, center + sectionPos((tm.tm_hour + 9) % 12 + float(tm.tm_min) / 60.0) * size / 3.5, ImGui::GetColorU32(ImGuiCol_TextDisabled), 3_scaled);
|
||||
|
||||
// Draw minute hand
|
||||
drawList->AddLine(center, center + sectionPos((float(tm.tm_min) / 5.0F) - 3) * size / 2.5, ImGui::GetColorU32(ImGuiCol_TextDisabled), 3_scaled);
|
||||
|
||||
// Draw second hand
|
||||
drawList->AddLine(center, center + sectionPos((float(tm.tm_sec) / 5.0F) - 3) * size / 2.5, ImGuiExt::GetCustomColorU32(ImGuiCustomCol_ToolbarRed), 2_scaled);
|
||||
}
|
||||
ImGui::EndChild();
|
||||
|
||||
ImGui::EndTable();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
Loading…
x
Reference in New Issue
Block a user