Skip to content

Commit cf02c99

Browse files
committed
ozz_animation_viewer setup for windows, add vulkan sdk and glfw
1 parent 6326630 commit cf02c99

17 files changed

+1227
-83
lines changed

.gitmodules

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -34,3 +34,6 @@
3434
[submodule "Externals/entt"]
3535
path = Externals/entt
3636
url = https://github.com/skypjack/entt.git
37+
[submodule "Externals/glfw"]
38+
path = Externals/glfw
39+
url = https://github.com/glfw/glfw.git

Externals/CMakeLists.txt

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -27,6 +27,13 @@ endif()
2727

2828
add_subdirectory(ozz-animation)
2929

30+
# GLFW - Window and input library for Vulkan/OpenGL
31+
set(GLFW_BUILD_EXAMPLES OFF CACHE BOOL "Build GLFW example programs" FORCE)
32+
set(GLFW_BUILD_TESTS OFF CACHE BOOL "Build GLFW test programs" FORCE)
33+
set(GLFW_BUILD_DOCS OFF CACHE BOOL "Build GLFW documentation" FORCE)
34+
set(GLFW_INSTALL OFF CACHE BOOL "Generate GLFW installation target" FORCE)
35+
add_subdirectory(glfw)
36+
3037
# EnTT - Entity Component System library (header-only)
3138
add_subdirectory(entt)
3239

Externals/glfw

Submodule glfw added at 8e15281

src/xrAnimation/tools/CMakeLists.txt

Lines changed: 8 additions & 5 deletions
Original file line numberDiff line numberDiff line change
@@ -1,10 +1,16 @@
11
set(_xrAnimation_tool_targets "")
22

33
# Find Vulkan SDK (required for ozz_animation_viewer Vulkan renderer)
4-
find_package(Vulkan)
4+
# Vulkan SDK is a system dependency - install from:
5+
# Windows: https://vulkan.lunarg.com/
6+
# Linux: sudo apt-get install libvulkan-dev vulkan-tools
7+
find_package(Vulkan COMPONENTS glslc)
58
if(Vulkan_FOUND)
69
message(STATUS "Vulkan SDK found: ${Vulkan_INCLUDE_DIRS}")
710
message(STATUS "Vulkan library: ${Vulkan_LIBRARIES}")
11+
if(Vulkan_glslc_FOUND)
12+
message(STATUS "Vulkan glslc shader compiler found")
13+
endif()
814

915
# Check for shader compiler (glslc preferred, glslangValidator as fallback)
1016
find_program(GLSLC glslc HINTS ${Vulkan_INCLUDE_DIRS}/../bin ENV VULKAN_SDK)
@@ -94,11 +100,8 @@ if(EXISTS ${_converter_src})
94100
endif()
95101

96102
# ozz animation viewer tool (using ozz playback sample)
103+
# Now supported on all platforms including Visual Studio with GLFW submodule
97104
set(_ozz_animation_viewer_supported ON)
98-
if(CMAKE_GENERATOR MATCHES "Visual Studio")
99-
set(_ozz_animation_viewer_supported OFF)
100-
message(STATUS "Skipping ozz_animation_viewer for Visual Studio generators")
101-
endif()
102105

103106
if(_ozz_animation_viewer_supported AND Vulkan_FOUND)
104107
# Renderer source files
Lines changed: 233 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,233 @@
1+
#include "SimpleObjLoader.h"
2+
#include "framework/mesh.h"
3+
4+
#include <fstream>
5+
#include <sstream>
6+
#include <cstdio>
7+
#include <unordered_map>
8+
#include <cstring>
9+
10+
#define Msg(...) printf(__VA_ARGS__), printf("\n")
11+
12+
namespace xray {
13+
namespace animation {
14+
namespace tools {
15+
16+
// Hash function for vertex deduplication
17+
struct VertexKey {
18+
int v_idx, vt_idx, vn_idx;
19+
20+
bool operator==(const VertexKey& other) const {
21+
return v_idx == other.v_idx && vt_idx == other.vt_idx && vn_idx == other.vn_idx;
22+
}
23+
};
24+
25+
} // namespace tools
26+
} // namespace animation
27+
} // namespace xray
28+
29+
namespace std {
30+
template <>
31+
struct hash<xray::animation::tools::VertexKey> {
32+
size_t operator()(const xray::animation::tools::VertexKey& k) const {
33+
return ((hash<int>()(k.v_idx) ^ (hash<int>()(k.vt_idx) << 1)) >> 1) ^ (hash<int>()(k.vn_idx) << 1);
34+
}
35+
};
36+
}
37+
38+
namespace xray {
39+
namespace animation {
40+
namespace tools {
41+
42+
bool SimpleObjLoader::LoadObjFile(const std::string& file_path, ozz::sample::Mesh& out_mesh) {
43+
std::ifstream file(file_path);
44+
if (!file.is_open()) {
45+
Msg("! Failed to open OBJ file: %s", file_path.c_str());
46+
return false;
47+
}
48+
49+
// Temporary storage for OBJ data (1-indexed, so we add dummy at index 0)
50+
std::vector<float> positions; // x,y,z
51+
std::vector<float> texcoords; // u,v
52+
std::vector<float> normals; // x,y,z
53+
54+
// Add dummy elements at index 0
55+
positions.push_back(0); positions.push_back(0); positions.push_back(0);
56+
texcoords.push_back(0); texcoords.push_back(0);
57+
normals.push_back(0); normals.push_back(0); normals.push_back(0);
58+
59+
// Final mesh data
60+
std::vector<float> mesh_positions;
61+
std::vector<float> mesh_normals;
62+
std::vector<float> mesh_uvs;
63+
std::vector<uint32_t> mesh_indices;
64+
65+
// Vertex deduplication
66+
std::unordered_map<VertexKey, uint32_t> vertex_map;
67+
68+
std::string line;
69+
int line_number = 0;
70+
71+
while (std::getline(file, line)) {
72+
line_number++;
73+
74+
if (line.empty() || line[0] == '#') {
75+
continue;
76+
}
77+
78+
std::istringstream iss(line);
79+
std::string prefix;
80+
iss >> prefix;
81+
82+
if (prefix == "v") {
83+
// Vertex position
84+
float x, y, z;
85+
iss >> x >> y >> z;
86+
positions.push_back(x);
87+
positions.push_back(y);
88+
positions.push_back(z);
89+
}
90+
else if (prefix == "vt") {
91+
// Texture coordinate
92+
float u, v;
93+
iss >> u >> v;
94+
texcoords.push_back(u);
95+
texcoords.push_back(v);
96+
}
97+
else if (prefix == "vn") {
98+
// Normal
99+
float nx, ny, nz;
100+
iss >> nx >> ny >> nz;
101+
normals.push_back(nx);
102+
normals.push_back(ny);
103+
normals.push_back(nz);
104+
}
105+
else if (prefix == "f") {
106+
// Face (triangle)
107+
std::vector<VertexKey> face_vertices;
108+
std::string vertex_str;
109+
110+
while (iss >> vertex_str) {
111+
VertexKey key{};
112+
113+
// Parse vertex format: v/vt/vn or v//vn or v/vt or v
114+
int v_idx = 0, vt_idx = 0, vn_idx = 0;
115+
116+
size_t pos1 = vertex_str.find('/');
117+
if (pos1 == std::string::npos) {
118+
// Format: v
119+
v_idx = std::stoi(vertex_str);
120+
} else {
121+
v_idx = std::stoi(vertex_str.substr(0, pos1));
122+
123+
size_t pos2 = vertex_str.find('/', pos1 + 1);
124+
if (pos2 == std::string::npos) {
125+
// Format: v/vt
126+
vt_idx = std::stoi(vertex_str.substr(pos1 + 1));
127+
} else {
128+
// Format: v/vt/vn or v//vn
129+
if (pos2 > pos1 + 1) {
130+
vt_idx = std::stoi(vertex_str.substr(pos1 + 1, pos2 - pos1 - 1));
131+
}
132+
vn_idx = std::stoi(vertex_str.substr(pos2 + 1));
133+
}
134+
}
135+
136+
// Convert negative indices to positive
137+
if (v_idx < 0) v_idx = static_cast<int>(positions.size() / 3) + v_idx;
138+
if (vt_idx < 0) vt_idx = static_cast<int>(texcoords.size() / 2) + vt_idx;
139+
if (vn_idx < 0) vn_idx = static_cast<int>(normals.size() / 3) + vn_idx;
140+
141+
key.v_idx = v_idx;
142+
key.vt_idx = vt_idx;
143+
key.vn_idx = vn_idx;
144+
145+
face_vertices.push_back(key);
146+
}
147+
148+
// Triangulate if needed (fan triangulation for n>3)
149+
for (size_t i = 1; i + 1 < face_vertices.size(); ++i) {
150+
const size_t triangle_indices[3] = {0, i, i + 1};
151+
for (size_t idx = 0; idx < 3; ++idx) {
152+
const size_t j = triangle_indices[idx];
153+
const VertexKey& key = face_vertices[j];
154+
155+
auto it = vertex_map.find(key);
156+
if (it != vertex_map.end()) {
157+
// Reuse existing vertex
158+
mesh_indices.push_back(it->second);
159+
} else {
160+
// Add new vertex
161+
uint32_t new_index = static_cast<uint32_t>(mesh_positions.size() / 3);
162+
vertex_map[key] = new_index;
163+
mesh_indices.push_back(new_index);
164+
165+
// Add position
166+
if (key.v_idx > 0 && key.v_idx * 3 < static_cast<int>(positions.size())) {
167+
mesh_positions.push_back(positions[key.v_idx * 3 + 0]);
168+
mesh_positions.push_back(positions[key.v_idx * 3 + 1]);
169+
mesh_positions.push_back(positions[key.v_idx * 3 + 2]);
170+
} else {
171+
mesh_positions.push_back(0); mesh_positions.push_back(0); mesh_positions.push_back(0);
172+
}
173+
174+
// Add UV
175+
if (key.vt_idx > 0 && key.vt_idx * 2 < static_cast<int>(texcoords.size())) {
176+
mesh_uvs.push_back(texcoords[key.vt_idx * 2 + 0]);
177+
mesh_uvs.push_back(texcoords[key.vt_idx * 2 + 1]);
178+
} else {
179+
mesh_uvs.push_back(0); mesh_uvs.push_back(0);
180+
}
181+
182+
// Add normal
183+
if (key.vn_idx > 0 && key.vn_idx * 3 < static_cast<int>(normals.size())) {
184+
mesh_normals.push_back(normals[key.vn_idx * 3 + 0]);
185+
mesh_normals.push_back(normals[key.vn_idx * 3 + 1]);
186+
mesh_normals.push_back(normals[key.vn_idx * 3 + 2]);
187+
} else {
188+
mesh_normals.push_back(0); mesh_normals.push_back(0); mesh_normals.push_back(1);
189+
}
190+
}
191+
}
192+
}
193+
}
194+
}
195+
196+
file.close();
197+
198+
if (mesh_positions.empty() || mesh_indices.empty()) {
199+
Msg("! OBJ file contained no geometry: %s", file_path.c_str());
200+
return false;
201+
}
202+
203+
// Convert uint32_t indices to uint16_t for ozz
204+
out_mesh.triangle_indices.clear();
205+
out_mesh.triangle_indices.reserve(mesh_indices.size());
206+
for (uint32_t idx : mesh_indices) {
207+
if (idx > 65535) {
208+
Msg("! Warning: OBJ has too many vertices for uint16_t indices");
209+
}
210+
out_mesh.triangle_indices.push_back(static_cast<uint16_t>(idx));
211+
}
212+
213+
// Create a single part with all vertices (no skinning)
214+
out_mesh.parts.resize(1);
215+
ozz::sample::Mesh::Part& part = out_mesh.parts[0];
216+
217+
part.positions.assign(mesh_positions.begin(), mesh_positions.end());
218+
part.normals.assign(mesh_normals.begin(), mesh_normals.end());
219+
part.uvs.assign(mesh_uvs.begin(), mesh_uvs.end());
220+
221+
// No skinning data
222+
out_mesh.joint_remaps.clear();
223+
out_mesh.inverse_bind_poses.clear();
224+
225+
Msg("* Loaded OBJ: %zu vertices, %zu triangles",
226+
mesh_positions.size() / 3, mesh_indices.size() / 3);
227+
228+
return true;
229+
}
230+
231+
} // namespace tools
232+
} // namespace animation
233+
} // namespace xray
Lines changed: 35 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,35 @@
1+
#pragma once
2+
3+
#include <string>
4+
#include <vector>
5+
#include <ozz/base/containers/vector.h>
6+
#include <ozz/base/maths/simd_math.h>
7+
8+
namespace ozz {
9+
namespace sample {
10+
struct Mesh;
11+
}
12+
}
13+
14+
namespace xray {
15+
namespace animation {
16+
namespace tools {
17+
18+
// Simple OBJ loader that creates a non-skinned ozz::sample::Mesh
19+
class SimpleObjLoader {
20+
public:
21+
// Load an OBJ file into an ozz::sample::Mesh
22+
// Returns true on success
23+
static bool LoadObjFile(const std::string& file_path, ozz::sample::Mesh& out_mesh);
24+
25+
private:
26+
struct TempVertex {
27+
float position[3];
28+
float normal[3];
29+
float uv[2];
30+
};
31+
};
32+
33+
} // namespace tools
34+
} // namespace animation
35+
} // namespace xray

src/xrAnimation/tools/ozz_animation_viewer.cpp

Lines changed: 57 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -1102,6 +1102,63 @@ void DrawRenderingPanel(VulkanRenderer& renderer) {
11021102
renderer.SetShowVikingRoom(show_viking_room);
11031103
}
11041104

1105+
// Mesh debug mode controls
1106+
if (renderer.GetMeshRenderer().IsInitialized() && renderer.GetMeshRenderer().HasMesh() && show_mesh) {
1107+
ImGui::Separator();
1108+
ImGui::Text("Mesh Debug Visualization:");
1109+
1110+
const char* debug_modes[] = {
1111+
"Normal Lighting",
1112+
"Face Orientation (gl_FrontFacing)",
1113+
"Normal Direction Visualization",
1114+
"Depth Visualization",
1115+
"UV Coordinates",
1116+
"Normal Z Component",
1117+
"Checkerboard Front/Back",
1118+
"Two-Sided Visualization",
1119+
"World Position Gradient"
1120+
};
1121+
1122+
uint32_t current_mode = renderer.GetMeshRenderer().GetDebugMode();
1123+
int mode_index = static_cast<int>(current_mode);
1124+
if (ImGui::Combo("Debug Mode", &mode_index, debug_modes, IM_ARRAYSIZE(debug_modes))) {
1125+
renderer.GetMeshRenderer().SetDebugMode(static_cast<uint32_t>(mode_index));
1126+
}
1127+
1128+
if (current_mode != 0) {
1129+
ImGui::TextColored(ImVec4(0.7f, 0.7f, 1.0f, 1.0f), "Debug Info:");
1130+
switch(current_mode) {
1131+
case 1:
1132+
ImGui::TextWrapped("Green = front faces, Red = back faces");
1133+
ImGui::TextWrapped("If you see red, faces are incorrectly oriented");
1134+
break;
1135+
case 2:
1136+
ImGui::TextWrapped("Normal vectors mapped to RGB colors");
1137+
ImGui::TextWrapped("Shows surface orientation");
1138+
break;
1139+
case 3:
1140+
ImGui::TextWrapped("Blue = near depth, Red = far depth");
1141+
break;
1142+
case 4:
1143+
ImGui::TextWrapped("Red = U coordinate, Green = V coordinate");
1144+
break;
1145+
case 5:
1146+
ImGui::TextWrapped("White = facing camera, Black = facing away");
1147+
break;
1148+
case 6:
1149+
ImGui::TextWrapped("Checkerboard: Green=front, Red=back");
1150+
break;
1151+
case 7:
1152+
ImGui::TextWrapped("Normal shading for front faces");
1153+
ImGui::TextWrapped("Pink = back faces (shouldn't see)");
1154+
break;
1155+
case 8:
1156+
ImGui::TextWrapped("Position gradients for debugging");
1157+
break;
1158+
}
1159+
}
1160+
}
1161+
11051162
ImGui::Separator();
11061163

11071164
float clear_r = 0.0f;

0 commit comments

Comments
 (0)